3

我想在我的片段着色器中的像素之间创建一个锐利的过渡效果,但我不确定我该如何做到这一点。

在我的顶点着色器中,我有一个varying float x;,在我的片段着色器中,我使用这个值来设置颜色的不透明度。我量化当前值以产生分层效果。我想做的是在一个非常小的效果水平上产生一个独特的边框(完全不同的颜色)。例如,如果x>0.1并且对于任何相邻像素x<0.1,则生成的颜色应该是黑色。

它在 GLSL 中看不到任何访问相邻像素的方法(我可能是错的)。我怎么能达到这样的效果。我仅限于 OpenGL-ES2.0(尽管如果在这个版本上根本不可能,那么任何解决方案都会有所帮助)。

4

3 回答 3

5

您是正确的,您无法访问相邻像素,这是因为无法保证写入像素的顺序,它们都是并行绘制的。如果您可以访问帧缓冲区中的相邻像素,您将得到不一致的结果。

但是,如果需要,您可以在后期处理中执行此操作。将整个场景绘制到帧缓冲区纹理中,然后使用过滤着色器将该纹理绘制到屏幕上。

从着色器中的纹理绘制时,您可以随意采样相邻的纹素,因此您可以轻松比较两个相邻纹素之间的增量。

于 2012-08-19T21:25:08.967 回答
3

如果您的 OpenGL ES 实现支持OES_standard_derivatives扩展,您可以通过与 2×2 四边形中的相邻像素进行前向/后向差分来获得变量的变化率:

float outline(float t, float threshold, float width)
{
    return clamp(width - abs(threshold - t) / fwidth(t), 0.0, 1.0);
}

此函数返回指定宽度的线的覆盖范围,其中tthresholdfwidth用于确定它与截止点的距离。请注意,fwidth(t)它等效于abs(dFdx(t)) + abs(dFdy(t))并计算曼哈顿距离中的宽度,这可能会使对角线过胖。如果您更喜欢欧几里得距离

float outline(float t, float threshold, float width)
{
    float dx = dFdx(t);
    float dy = dFdy(t);
    float ewidth = sqrt(dx * dx + dy * dy);
    return clamp(width - abs(threshold - t) / ewidth, 0.0, 1.0);
}
于 2012-08-20T00:48:03.607 回答
1

除了基于导数的 Pivot 实现之外,您还可以使用基于源像素尺寸的偏移量从源图像中抓取相邻像素。以像素为单位的宽度或高度的倒数是您需要在此处使用的当前纹理坐标的偏移量。

例如,这是一个顶点着色器,我用来计算围绕中心像素的八个像素的这些偏移量:

 attribute vec4 position;
 attribute vec4 inputTextureCoordinate;

 uniform highp float texelWidth; 
 uniform highp float texelHeight; 

 varying vec2 textureCoordinate;
 varying vec2 leftTextureCoordinate;
 varying vec2 rightTextureCoordinate;

 varying vec2 topTextureCoordinate;
 varying vec2 topLeftTextureCoordinate;
 varying vec2 topRightTextureCoordinate;

 varying vec2 bottomTextureCoordinate;
 varying vec2 bottomLeftTextureCoordinate;
 varying vec2 bottomRightTextureCoordinate;

 void main()
 {
     gl_Position = position;

     vec2 widthStep = vec2(texelWidth, 0.0);
     vec2 heightStep = vec2(0.0, texelHeight);
     vec2 widthHeightStep = vec2(texelWidth, texelHeight);
     vec2 widthNegativeHeightStep = vec2(texelWidth, -texelHeight);

     textureCoordinate = inputTextureCoordinate.xy;
     leftTextureCoordinate = inputTextureCoordinate.xy - widthStep;
     rightTextureCoordinate = inputTextureCoordinate.xy + widthStep;

     topTextureCoordinate = inputTextureCoordinate.xy - heightStep;
     topLeftTextureCoordinate = inputTextureCoordinate.xy - widthHeightStep;
     topRightTextureCoordinate = inputTextureCoordinate.xy + widthNegativeHeightStep;

     bottomTextureCoordinate = inputTextureCoordinate.xy + heightStep;
     bottomLeftTextureCoordinate = inputTextureCoordinate.xy - widthNegativeHeightStep;
     bottomRightTextureCoordinate = inputTextureCoordinate.xy + widthHeightStep;
 }

这是一个片段着色器,它使用它来执行 Sobel 边缘检测:

 precision mediump float;

 varying vec2 textureCoordinate;
 varying vec2 leftTextureCoordinate;
 varying vec2 rightTextureCoordinate;

 varying vec2 topTextureCoordinate;
 varying vec2 topLeftTextureCoordinate;
 varying vec2 topRightTextureCoordinate;

 varying vec2 bottomTextureCoordinate;
 varying vec2 bottomLeftTextureCoordinate;
 varying vec2 bottomRightTextureCoordinate;

 uniform sampler2D inputImageTexture;

 void main()
 {
    float bottomLeftIntensity = texture2D(inputImageTexture, bottomLeftTextureCoordinate).r;
    float topRightIntensity = texture2D(inputImageTexture, topRightTextureCoordinate).r;
    float topLeftIntensity = texture2D(inputImageTexture, topLeftTextureCoordinate).r;
    float bottomRightIntensity = texture2D(inputImageTexture, bottomRightTextureCoordinate).r;
    float leftIntensity = texture2D(inputImageTexture, leftTextureCoordinate).r;
    float rightIntensity = texture2D(inputImageTexture, rightTextureCoordinate).r;
    float bottomIntensity = texture2D(inputImageTexture, bottomTextureCoordinate).r;
    float topIntensity = texture2D(inputImageTexture, topTextureCoordinate).r;
    float h = -topLeftIntensity - 2.0 * topIntensity - topRightIntensity + bottomLeftIntensity + 2.0 * bottomIntensity + bottomRightIntensity;
    float v = -bottomLeftIntensity - 2.0 * leftIntensity - topLeftIntensity + bottomRightIntensity + 2.0 * rightIntensity + topRightIntensity;

    float mag = length(vec2(h, v));

    gl_FragColor = vec4(vec3(mag), 1.0);
 }

我传入texelWidthtexelHeight制服,它们分别是图像的 1/宽度和 1/高度。这确实需要您跟踪输入图像的宽度和高度,但它应该适用于所有 OpenGL ES 设备,而不仅仅是那些具有衍生扩展的设备。

我在顶点着色器中进行纹理偏移计算有两个原因:因此偏移计算只需要每个顶点执行一次,而不是每个片段一次,更重要的是因为一些基于图块的延迟渲染器对依赖纹理的反应很差读取在片段着色器中计算纹理偏移的位置。对于在这些设备上删除这些依赖纹理读取的着色器程序,性能可以提高 20 倍。

于 2012-08-20T17:37:20.693 回答