GLSL:使用片段着色器进行对象转换

GLSL : Object translation with fragment shader

如下图所示,我试图通过将对象多绘制两次来表达轮廓:左右移动1个像素。

但是,我不知道这应该是 运行 在顶点着色器还是片段着色器中。

是否可以在片段着色器中移动顶点(像素)?

如果不是,我是否应该在每一帧计算屏幕 space 顶点坐标?

不,一旦进入片段着色器,输出位置就已经由光栅化过程固定。

由于像素的位置已经由您在片段着色器中的时间确定,所以这不是一个选项。顶点着色器也帮不了你,因为它只能为每个传入顶点推送一个输出顶点。

然而,几何着色器阶段可以为每个传入的顶点发射多个顶点。这可以让您克隆两个额外的顶点,每个顶点在原始顶点的左侧或右侧都有一个平移。

此资源有一些详细的现代示例:https://open.gl/geometry

对于传统的片段着色器输出,答案是明确而响亮的。片段着色器无法决定要渲染哪个像素。顶点着色器和片段着色器之间的固定功能步骤(光栅化)决定了哪些片段被基元覆盖。然后为每个片段调用片段着色器。它决定在这个片段位置写入输出缓冲区的值(颜色等),或者它可以决定根本不写任何东西(discard)。但它不会改变位置。

以下是我想到的一些选项。

图片

OpenGL 4.2 及更高版本中有一项功能在这方面添加了新选项:图像。您可以将纹理绑定为图像,然后使用内置 imageStore() 函数在着色器代码中写入它们。此函数将坐标和值作为参数,因此您可以将值写入图像中的任意位置。

使用它,您可以使用图像作为输出而不是传统的片段着色器输出,并向其写入多个值。或者使用混合,您仍然使用片段着色器输出进行主要渲染,将阴影部分写入图像,然后将两者与额外的渲染通道组合。

多个绘制调用

使用更传统的功能,您通常必须多次渲染几何体,使用深度或模板测试将主要渲染与阴影效果结合起来。例如,使用深度测试,您可以将形状以其原始颜色渲染一次,然后再渲染两次,稍微 left/right 偏移,并稍微增加深度,这样阴影就在原来的后面形状。

几何着色器

我相信您可以使用几何着色器生成每个图元的 3 个实例。因此每个图元仍会渲染 3 次,但您实际上不必进行 3 次不同的绘制调用。

图像Post-处理

为了达到您想要的效果,您可以将没有阴影的整个对象渲染到 FBO 中,从而在纹理中生成帧。然后你制作另一个绘制通道,在其中绘制一个 window 大小的四边形,并从包含你的框架的纹理中采样。您对纹理采样 3 次,然后组合 3 次结果以产生阴影效果。

只是为了勾画这个(代码完全未经测试)。如果您使用带有 alpha 分量的纹理作为渲染目标,您可以检查 alpha 值以查看在渲染过程中是否命中给定像素。

// Texture produced as output from original render pass.
uniform texture2D Tex;
// Offset to add one pixel to texture coordinates, should be
// 1.0 / width of render target.
uniform float PixelOffset;
// Incoming texture coordinate from rendering window sized quad.
in vec2 TexCoord;
// Output.
out vec4 FragColor;

void main() {
    vec4 centerColor = texture(Tex, TexCoord);
    vec4 leftColor = texture(Tex, vec2(TexCoord.s, TexCoord.t - PixelOffset));
    vec4 rightColor = texture(Tex, vec2(TexCoord.s, TexCoord.t + PixelOffset));

    if (centerColor.a > 0.0) {
        // Fragment was rendered, use its color as output.
        FragColor = centerColor;
    } else if (leftColor.a + rightColor.a > 0.0) {
        // Fragment is within 1 pixel left/right of rendered fragment,
        // color it black.
        FragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // Neither rendered nor in shadow. Set output to background color,
        // or discard it. This would be for white background.
        FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}

Conclusion/Recommendation

直觉上,我自己喜欢图像 Post- 处理方法。我可能会先尝试一下。我认为下一个最优雅的解决方案是使用几何着色器复制图元。