【问题标题】:OpenGL Colour InterpolationOpenGL 颜色插值
【发布时间】:2012-11-03 16:14:55
【问题描述】:

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

但是,我在插值大正方形时遇到了麻烦。 在我的 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 的笔记本电脑上,这个结果更糟,代码没有变化。

我以为是 OpenGL 将四边形分成两个三角形,所以我尝试使用三角形进行渲染并自己在正方形中间插入颜色,但它仍然与我希望的结果不太匹配。

【问题讨论】:

    标签: opengl interpolation


    【解决方案1】:

    OpenGL 使用从顶点着色器发出的值到片段着色器的每个片段输入值的重心插值。正如您已经猜对的那样,您看到的是将四边形分成三角形的效果。

    现在看一下:右上角的三角形里面有红色,因此很好地向下插值,里面有一个红色的量。然后是左下角的三角形,里面只有灰色。当然,右上三角的红色不会影响左下三角的灰色。

    问题是,插值发生在 RGB 空间中,但为了获得您想要的结果,必须将其放置在 HSV 或 HSL 空间中,其中 H(色调)将保持不变。

    然而,这不仅仅是插值的问题。同样阻碍你的是,颜色不是线性插值的。大多数显示器都应用了非线性函数,也称为“gamma”(实际上 gamma 是施加到输入值的功率的指数)。

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

    【讨论】:

      【解决方案2】:

      我很快就制作了一个成功插入颜色的顶点/片段着色器:

      #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);
      }
      

      结果如下:

      现在将其应用于四边形会产生正确的结果,因为插值实际上是使用xy 坐标在整个曲面上完成的。请参阅 @datenwolf 的详细解释,了解为什么会这样。

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

      现场在线演示: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:只要您坚持使用 OpenGL-2.1 及以下版本,您就可以有选择地使用着色器。 IE。大多数事情使用固定功能管道,但只有一些事情使用着色器。仅在 OpenGL-3 核心和更高版本中必须使用着色器。但老实说:使用着色器让生活变得如此简单,所以我强烈建议在任何地方都使用它们。
      • @Will-of-fortune 我通常使用框架或图形引擎来简化着色器的管理。但是,我建议您参考:nehe.gamedev.net/article/glsl_an_introduction/25007 跳至“GLSL API 如何在您的 OpenGL 应用程序中使用 GLSL”部分。本节将向您展示如何在这些 cmets 中比我更好地结合 glsl 着色器。
      【解决方案3】:

      我不喜欢投票最多的答案,因为它是如此字面意思,并且在简单的答案中只真正关注红色,所以我想提出一个基于顶点颜色的简单替代方案:

      #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)
      

      【讨论】:

        猜你喜欢
        • 2021-08-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多