OpenGL颜色插值

10

我目前正在使用C++和OpenGL开发一个小项目,并尝试实现类似于Photoshop的颜色选择工具,如下所示。

enter image description here

然而,我在大正方形的插值上遇到了问题。在我的桌面电脑上使用8800 GTS时,结果相似,但混合效果不是很平滑。

这是我正在使用的代码:

GLfloat swatch[] = { 0,0,0, 1,1,1, mR,mG,mB, 0,0,0 };
GLint swatchVert[] = { 400,700, 400,500, 600,500, 600,700 };

glVertexPointer(2, GL_INT, 0, swatchVert);
glColorPointer(3, GL_FLOAT, 0, swatch);
glDrawArrays(GL_QUADS, 0, 4);

转到使用Intel Graphics HD 3000的笔记本电脑上,即使没有更改代码,结果甚至更糟。

http://i.imgur.com/wSJI2.png

我以为是OpenGL将四边形分成了两个三角形,所以我尝试使用三角形进行渲染,并在正方形中间插值颜色,但结果仍然不太符合我的期望。

enter image description here

3个回答

14

OpenGL使用重心插值将来自顶点着色器的值转换为片段着色器的每个片段输入值。你所看到的是将四边形分割成三角形后产生的效果,正如你已经正确猜测的那样。

现在看看它:这里有右上角的三角形,其中有红色,因此会很好地插值出一定量的红色。然后有只有灰色的左下角三角形。当然,右上角三角形的红色不会对左下方的灰色产生影响。

问题是,插值发生在RGB空间中,但对于您想要的结果,必须将其放置在HSV或HSL空间中,其中H(色相)保持恒定。

然而,这不仅仅是插值问题。还有一个问题是,颜色不会线性插值;大多数显示器都应用了非线性函数,也称为“伽马”(实际上,伽马是应用于输入值的幂的指数)。

您的OpenGL上下文可能在色彩校正的颜色空间中,也可能不在。您需要进行测试。然后,知道帧缓冲区位于哪个颜色空间后,您必须使用片段着色器将重心坐标(使用顶点着色器评估)的每个片段变换为每个片段值。


11

我已经快速制作了一个顶点/片元着色器,成功地插值颜色:

#ifdef GL_ES
precision highp float;
#endif

uniform vec2 resolution;

void main(void)
{
    vec2 p = gl_FragCoord.xy / resolution.xy;
    float gray = 1.0 - p.x;
    float red = p.y;
    gl_FragColor = vec4(red, gray*red, gray*red, 1.0);
}

这里是结果:enter image description here

现在将其应用于四边形,可以得到正确的结果,因为插值真正地使用了 xy 坐标覆盖整个表面。有关此工作原理的详细解释,请参见@datenwolf的说明。

编辑1 为了在功能性颜色选择器中获得完整的颜色范围,可以交互地修改色调(请参阅https://dev59.com/RGox5IYBdhLWcg3wYzfX#9234854)。

在线实时演示:http://goo.gl/Ivirl

#ifdef GL_ES
precision highp float;
#endif

uniform float time;
uniform vec2 resolution;

const vec4  kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);
const vec4  kRGBToI     = vec4 (0.596, -0.275, -0.321, 0.0);
const vec4  kRGBToQ     = vec4 (0.212, -0.523, 0.311, 0.0);

const vec4  kYIQToR   = vec4 (1.0, 0.956, 0.621, 0.0);
const vec4  kYIQToG   = vec4 (1.0, -0.272, -0.647, 0.0);
const vec4  kYIQToB   = vec4 (1.0, -1.107, 1.704, 0.0);

const float PI = 3.14159265358979323846264;

void adjustHue(inout vec4 color, float hueAdjust) {
    // Convert to YIQ
    float   YPrime  = dot (color, kRGBToYPrime);
    float   I      = dot (color, kRGBToI);
    float   Q      = dot (color, kRGBToQ);

    // Calculate the hue and chroma
    float   hue     = atan (Q, I);
    float   chroma  = sqrt (I * I + Q * Q);

    // Make the user's adjustments
    hue += hueAdjust;

    // Convert back to YIQ
    Q = chroma * sin (hue);
    I = chroma * cos (hue);

    // Convert back to RGB
    vec4 yIQ   = vec4 (YPrime, I, Q, 0.0);
    color.r = dot (yIQ, kYIQToR);
    color.g = dot (yIQ, kYIQToG);
    color.b = dot (yIQ, kYIQToB);
}

void main(void)
{
    vec2 p = gl_FragCoord.xy / resolution.xy;
    float gray = 1.0 - p.x;
    float red = p.y;
    vec4 color = vec4(red, gray*red, gray*red, 1.0);
    adjustHue(color, mod(time, 2.0*PI));
    gl_FragColor = color;
}

编辑2:如果需要,用于使用纹理坐标的着色器(可应用于具有从0到1的纹理坐标的四边形)应该类似于此。 未经测试。

片段着色器:

void main(void)
{
    vec2 p = gl_TexCoord[0].st;
    float gray = 1.0 - p.x;
    float red = p.y;
    gl_FragColor = vec4(red, gray*red, gray*red, 1.0);
}

一个透传顶点着色器:

void main()
{
    gl_TexCoord[0]=gl_MultiTexCoord0; 
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

谢谢您的回答,我对GLSL的经验非常少,尽管我似乎记得读过一些关于如果想使用着色器,就必须实现所有内容,因为它完全替代了OpenGL管线的文章?是否有一种简单的方法可以使用您提供的着色器,并仅将其应用于一个glDrawArrays()调用? - Will-of-fortune
@Will-of-fortune:只要你坚持使用OpenGL-2.1及以下版本,就可以有选择地使用着色器。也就是说,对于大多数事情使用固定功能管线,但仅在某些情况下使用着色器。只有在OpenGL-3核心及更高版本中,必须使用着色器。但老实说:使用着色器会让生活变得更简单,所以我强烈建议在任何地方都使用它们。 - datenwolf
@Will-of-fortune 我通常使用框架或图形引擎,使着色器的管理更加容易。不过,我建议你参考这篇文章:http://nehe.gamedev.net/article/glsl_an_introduction/25007/ 跳到“GLSL API 如何在你的OpenGL应用中使用GLSL”一节。这一节将展示如何比我在评论中做得更好地整合glsl着色器。 - num3ric

4

我不太喜欢最受欢迎的答案,因为它过于字面化,只关注了简单答案中的红色,因此我想提供一个基于顶点颜色的简单替代方案:

#version 330 core

smooth in vec4 color;
smooth in vec2 uv;

out vec4 colorResult;

void main(){
    float uvX = 1.0 - uv.st.x;
    float uvY = uv.st.y;

    vec4 white = vec4(1, 1, 1, 1);
    vec4 black = vec4(0, 0, 0, 1);

    colorResult = mix(mix(color, white, uvX), black, uvY);
}

这很容易解释和理解。mix是线性插值,或在GLSL中称为lerp。因此,我在反向x轴上在顶点颜色和白色之间进行混合,这使我在左上角得到白色,在右上角得到纯色。根据该结果,我沿y轴混合到黑色,从而在底部得到黑色。
希望这可以帮助你。我看到了这个答案,发现它对于除了硬编码的红色、绿色或蓝色调色板以外的任何内容都没有什么用处,而且adjustHue部分根本就不是我想要的。
基本上,要使其工作,您需要将所有顶点颜色设置为所需的颜色,并确保设置了UV坐标:
TL: UV(0, 0), COLOR(YOUR DESIRED COLOR)
BL: UV(0, 1), COLOR(YOUR DESIRED COLOR)
BR: UV(1, 1), COLOR(YOUR DESIRED COLOR)
TR: UV(1, 0), COLOR(YOUR DESIRED COLOR)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接