如何在OpenGL中实现灰度渲染?

Posted

技术标签:

【中文标题】如何在OpenGL中实现灰度渲染?【英文标题】:how to implement grayscale rendering in OpenGL? 【发布时间】:2010-10-26 10:37:18 【问题描述】:

在渲染带纹理的多边形场景时,我希望能够在原始颜色渲染和“灰度”模式之间切换。我一直在尝试使用混合和颜色矩阵操作来实现这一点;没有一个起作用(通过混合,我找不到一个 glBlendFunc() 可以实现与我想要的东西很相似的东西,以及颜色矩阵操作...are discussed here)。

想到的一个解决方案(但也相当昂贵)是每帧捕获屏幕并将生成的纹理转换为灰度纹理并显示它...(我说灰度实际上是指任何带有低饱和度,但我猜大多数可能的解决方案与灰度没有太大区别。

我还有什么其他选择?

【问题讨论】:

【参考方案1】:

默认的 OpenGL 帧缓冲区使用 RGB 颜色空间,它不存储显式饱和度。您需要一种方法来提取饱和度、对其进行修改并再次将其改回。

我之前的建议只是使用 RGB 向量长度来表示亮度为 0,这是不正确的,因为它没有考虑缩放,我很抱歉。

新的短 sn-p 归功于 FreeNode/IRC 上的 ##opengl 和 ##opengl3 的普通用户“RTFM_FTW”,它允许您直接修改饱和度,而无需计算代价高昂的 RGB->HSV-> RGB 转换,这正是您想要的。尽管 HSV 代码在您的问题方面较差,但我让它留下来。

void main( void ) 
 
    vec3 R0 = texture2DRect( S, gl_TexCoord[0].st ).rgb;
    gl_FragColor = vec4( mix( vec3( dot( R0, vec3( 0.2125, 0.7154, 0.0721 ) ) ),
        R0, T ), gl_Color.a ); 

如果您想要更多的控制而不仅仅是饱和度,您需要转换为 HSL 或 HSV 色彩空间。如下所示,使用 GLSL 片段着色器。

阅读http://www.opengl.org/registry 上提供的 OpenGL 3.0 和 GLSL 1.30 规范,了解如何使用 GLSL v1.30 功能。

#version 130
#define RED 0
#define GREEN 1
#define BLUE 2

in vec4 vertexIn;
in vec4 colorIn;
in vec2 tcoordIn;
out vec4 pixel;
Sampler2D tex;
vec4 texel;
const float epsilon = 1e-6;

vec3 RGBtoHSV(vec3 color)

    /* hue, saturation and value are all in the range [0,1> here, as opposed to their
       normal ranges of: hue: [0,360>, sat: [0, 100] and value: [0, 256> */
    int sortindex[3] = RED,GREEN,BLUE;
    float rgbArr[3] = float[3](color.r, color.g, color.b);

    float hue, saturation, value, diff;
    float minCol, maxCol;
    int minIndex, maxIndex;

    if(color.g < color.r)
        swap(sortindex[0], sortindex[1]);
    if(color.b < color.g)
        swap(sortindex[1], sortindex[2]);
    if(color.r < color.b)
        swap(sortindex[2], sortindex[0]);

    minIndex = sortindex[0];
    maxIndex = sortindex[2];
    minCol = rgbArr[minIndex];
    maxCol = rgbArr[maxIndex];

    diff = maxCol - minCol;

    /* Hue */
    if( diff < epsilon)
        hue = 0.0;
    
    else if(maxIndex == RED)
        hue = ((1.0/6.0) * ( (color.g - color.b) / diff )) + 1.0;
        hue = fract(hue);
    
    else if(maxIndex == GREEN)
        hue = ((1.0/6.0) * ( (color.b - color.r) / diff )) + (1.0/3.0);
    
    else if(maxIndex == BLUE)
        hue = ((1.0/6.0) * ( (color.r - color.g) / diff )) + (2.0/3.0);        
    

    /* Saturation */
    if(maxCol < epsilon)
        saturation = 0;
    else
        saturation = (maxCol - minCol) / maxCol;

    /* Value */
    value = maxCol;

    return vec3(hue, saturation, value);

vec3 HSVtoRGB(vec3 color)

    float f,p,q,t, hueRound;
    int hueIndex;
    float hue, saturation, value;
    vec3 result;

    /* just for clarity */
    hue = color.r;
    saturation = color.g;
    value = color.b;

    hueRound = floor(hue * 6.0);
    hueIndex = int(hueRound) % 6;
    f = (hue * 6.0) - hueRound;
    p = value * (1.0 - saturation);
    q = value * (1.0 - f*saturation);
    t = value * (1.0 - (1.0 - f)*saturation);

    switch(hueIndex)
    
        case 0:
            result = vec3(value,t,p);
        break;
        case 1:
            result = vec3(q,value,p);
        break;
        case 2:
            result = vec3(p,value,t);
        break;
        case 3:
            result = vec3(p,q,value);
        break;
        case 4:
            result = vec3(t,p,value);
        break;
        default:
            result = vec3(value,p,q);
        break;
    
    return result;

void main(void)

    vec4 srcColor;
    vec3 hsvColor;
    vec3 rgbColor;
    texel = Texture2D(tex, tcoordIn);
    srcColor = texel*colorIn;
    hsvColor = RGBtoHSV(srcColor.rgb);
    /* You can do further changes here, if you want. */
    hsvColor.g = 0; /* Set saturation to zero */
    rgbColor = HSVtoRGB(hsvColor);
    pixel = vec4(rgbColor.r, rgbColor.g, rgbColor.b, srcColor.a);

【讨论】:

"v" 未定义。查看 hsv2rgb 的其他算法,似乎这实际上应该是“值”此外,最终的 case 语句(案例 5)也应该是默认案例。 有没有办法在openGL中将灰度效果转换为草图效果?【参考方案2】:

如果您正在使用足够现代的 OpenGL,我想说pixel shaders 是一个非常合适的解决方案。通过在渲染时挂钩每个多边形的阴影,或者通过在第二次传递中执行单个全屏四边形,仅读取每个像素,转换为灰度,然后将其写回。除非您的分辨率、图形硬件和目标帧速率在某种程度上是“极限”,否则这些天在大多数情况下应该是可行的。

【讨论】:

【参考方案3】:

对于大多数桌面来说,Render-To-Texture 不再那么昂贵,所有 compiz、aero 等以及最近游戏中出现的绽放或景深等效果都依赖于它。

实际上,您不会将屏幕纹理本身转换为灰度,您可能希望使用纹理和片段着色器绘制一个屏幕大小的四边形,将值转换为灰度。

另一种选择是为您的三角形设置两组片段着色器,一组只是像固定函数 pieline 那样复制 gl_FrontColor 属性,另一组将灰度值写入屏幕缓冲区。

如果您将 uüp 设置为灰度调色板,则第三个选项可能是索引颜色模式,但该模式现在可能已被弃用且支持不佳;如果我没记错的话,你会失去很多功能,比如混合。

【讨论】:

是的,没有与 colorindex 模式混合。 “所有的 compiz、aero 等”是什么意思? 他的意思是桌面窗口渲染器现在经常使用渲染到纹理(他可能是一个窗口管理器程序员)。

以上是关于如何在OpenGL中实现灰度渲染?的主要内容,如果未能解决你的问题,请参考以下文章

在 OpenGL ES 2 中实现 VBO 以渲染精灵

OpenGL ES - 如何在 Blender Cycles Render 中实现光泽着色器?

CSharpGL(26)在opengl中实现控件布局/渲染文字

如何在matlab中实现灰度形态检测灰度图像上的圆形物体?

如何在matlab程序中实现二值图像转化成灰度图像?

如何在OpenGL中实现形状的原位旋转和旋转状态下的移动?