OpenGL 屏幕后处理效果

OpenGL screen postprocessing effects

我在 Java 中使用 OpenGL 构建了一个不错的音乐可视化工具。它看起来已经很整洁了,但我考虑过向它添加一些 post 处理。当时,它看起来像这样:

已经有一个用于记录输出的帧缓冲区,所以我有可用的纹理。现在我想知道是否有人对某些效果有想法。当前的片段着色器如下所示:

#version 440

in vec3 position_FS_in;
in vec2 texCoords_FS_in;


out vec4 out_Color;

//the texture of the last Frame by now exactly the same as the output
uniform sampler2D textureSampler;

//available data:
//the average height of the lines seen in the screenshot, ranging from 0 to 1
uniform float mean;
//the array of heights of the lines seen in the screenshot
uniform float music[512];

void main()
{
    vec4 texColor = texture(textureSampler, texCoords_FS_in);
    //insert post processing here
    out_Color = texColor;
}

大多数 post 处理效果随时间变化,因此随时间变化的制服很常见。例如,可以通过使用 sin(elapsedSec * wavyRadsPerSec + (PI * gl_FragCoord.y * 0.5 + 0.5) * wavyCyclesInFrame).

偏移纹理坐标来创建 "wavy" 效果

一些 "postprocessing" 效果可以非常简单地完成,例如,您可以在整个屏幕上混合一个近乎黑色的透明四边形,而不是使用 glClear 清除后台缓冲区。这将创建一个持久性效果,过去的帧在当前帧后面逐渐变黑。

可以通过在距每个点的不同距离处获取多个样本,并对较近的样本进行更大的加权并求和来实现定向模糊。如果你跟踪一个点相对于相机位置和方向的运动,它可以做成运动模糊实现。

颜色变换也非常简单,只需将RGB 视为向量的XYZ,然后对其进行有趣的变换即可。棕褐色和 "psychedelic" 颜色可以通过这种方式产生。

您可能会发现将颜色转换为类似 HSV 的颜色、对该表示进行转换并将其转换回 RGB 以用于帧缓冲区写入很有帮助。可以影响色相、饱和度,比如淡化成黑白,或者平滑地加强颜色饱和度。

可以通过将帧缓冲区混合到帧缓冲区上来实现 "smearing into the distance" 效果,方法是从 gl_FragCoord 稍微放大的 texcoord 中读取,例如 texture(textureSampler, (gl_FragCoord * 1.01).xy).

关于这一点,您不需要那些纹理坐标属性,您可以使用 gl_FragCoord 找出您在屏幕上的位置,并将其(调整后的副本)用于您的 texture打电话。

查看 GLSLSandbox 上的一些着色器以获得灵感。

我在 GLSLSandbox 上完成了 a simple emulation of the trail effect。在真实的循环中,循环不存在,它会从一个小的偏移量中获取一个样本。 "loop" 效果会自行发生,因为它的输入包括最后一帧的输出。为了模拟最后一帧的纹理,我简单地做了它,这样我就可以计算出另一个像素是什么。在做轨迹效果时,你会读取最后一帧纹理而不是调用像 pixelAt 这样的东西。

你可以用波形代替我伪造的正弦波。使用 uv.x 到 select 的索引,适当缩放。

GLSL

#ifdef GL_ES
precision mediump float;
#endif

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
const float PI = 3.14159265358979323;// lol ya right, but hey, I memorized it

vec4 pixelAt(vec2 uv)
{
    vec4 result;
    float thickness = 0.05;
    float movementSpeed = 0.4;
    float wavesInFrame = 5.0;
    float waveHeight = 0.3;
    float point = (sin(time * movementSpeed + 
               uv.x * wavesInFrame * 2.0 * PI) *
               waveHeight);
    const float sharpness = 1.40;
    float dist = 1.0 - abs(clamp((point - uv.y) / thickness, -1.0, 1.0));
    float val;
    float brightness = 0.8;

    // All of the threads go the same way so this if is easy
    if (sharpness != 1.0)
        dist = pow(dist, sharpness);

    dist *= brightness;

    result = vec4(vec3(0.3, 0.6, 0.3) * dist, 1.0);

    return result;
}

void main( void ) {

    vec2 fc = gl_FragCoord.xy;
    vec2 uv     = fc / resolution - 0.5;
    vec4 pixel;

    pixel = pixelAt(uv);

    // I can't really do postprocessing in this shader, so instead of
    // doing the texturelookup, I restructured it to be able to compute
    // what the other pixel might be. The real code would lookup a texel
    // and there would be one sample at a small offset, the feedback
    // replaces the loop.
    const float e = 64.0, s = 1.0 / e;
    for (float i = 0.0; i < e; ++i) {
        pixel += pixelAt(uv + (uv * (i*s))) * (0.3-i*s*0.325);
    }
    pixel /= 1.0;

    gl_FragColor = pixel;
}