从零学习GLSL之实现一个时空穿梭效果

205 阅读4分钟

写在开头

最近依然在学习glsl,发现其实很多酷炫得效果以为难如登天,但是学习掌握之后发现也不过如此!今天就给大家分享一个时空穿梭效果~

效果参考Xor Hyperspace 2

glsl入门事项

基础SDF

void main() {
    vec2 fragCoord = gl_FragCoord.xy;

    // 坐标变换
    vec2 uv = (fragCoord - u_resolution * 0.5) / min(u_resolution.x, u_resolution.y);
    uv *= 2.0;

    // 简单的板条SDF
    float y = uv.y;

    float signedDistance = 0.3 - abs(y);

    vec3 color;
    if (signedDistance > 0.0) {
        color = vec3(0.8, 0.2, 0.2); // 红色内部
    } else {
        color = vec3(0.1, 0.1, 0.3); // 蓝色外部
    }

    // 边界高亮
    if (abs(signedDistance) < 0.02) {
        color = vec3(1.0);
    }

    gl_FragColor = vec4(color, 1.0);
}

从代码中我们可以看到我们显示归一化了uv,然后float signedDistance = 0.3 - abs(y);就是生成一个了一个距离范围[-0.7,0,-0.7]然后大于0.0我们填充为红色,小于0.0得我们填充为蓝色,我们最终得时空穿梭效果就是由这个超级简单例子拓展一步一步实现得!

光线步进

void main() {
    vec2 fragCoord = gl_FragCoord.xy;
    vec4 fragColor = vec4(0.0);

    float signedDistance = 0.0;

    // 简单的光线步进(3步)
    for (int step = 0; step < 3; step++) {
        vec3 resolution = vec3(u_resolution, 1.0);
        vec3 centeredCoord = vec3(fragCoord * 2.0, 1.0) - resolution;


        float rayDepth = float(step) * 0.5 + 0.1;
        vec3 samplePoint = rayDepth * normalize(centeredCoord);

        if (u_animate) samplePoint.y += sin(u_time + rayDepth) * 0.1;

        signedDistance = 0.3 - abs(samplePoint.y);


        if (signedDistance > 0.0) {
            float intensity = 1.0 / (1.0 + rayDepth);
            fragColor.rgb += vec3(0.3, 0.1, 0.1) * intensity;
        }
    }

    gl_FragColor = fragColor;
}

本篇不对光线步进(raymarching)技术进行长篇大论,可以先简单理解光线步进的本质是:

沿着一条光线 一步一步向前走,每一步都检查当前走到的位置是不是接触到了物体表面,如果碰到了就停下,否则继续前进。

和“射一条光线直接求交点”的光线追踪(Ray Tracing)不同,光线步进是按小步迭代去逼近交点。

回到代码中,我们代入一个点来理解就非常容易理解了,如果step = 0时,rayDepth=0.1,因为rayDepth=0.1会导致samplePoint变得很小,也就会导致后续的signedDistance都会大于0随着step的增大rayDepth也会增大,同理signedDistance就会变得有一些像素点不 > 0.0,这也就产生了分层的效果

geogebra-export.png

颜色映射

void main() {
    vec2 fragCoord = gl_FragCoord.xy;
    vec4 fragColor = vec4(0.0);

    // 光线步进(5步)
    for (int step = 0; step < 5; step++) {
        vec3 resolution = vec3(u_resolution, 1.0);
        vec3 centeredCoord = vec3(fragCoord * 2.0, 0.0) - resolution;

        float rayDepth = float(step) * 0.3 + 0.1;
        vec3 samplePoint = rayDepth * normalize(centeredCoord);

        samplePoint.y += sin(u_time + rayDepth * 2.0) * 0.15;

        float signedDistance = 0.3 - abs(samplePoint.y);
        float unsignedDistance = abs(signedDistance);

        if (signedDistance > 0.0) {
            // 基于距离的颜色映射
            float colorPhase = signedDistance * 10.0 + u_time;
            vec3 color = vec3(
            0.5 + 0.5 * cos(colorPhase),
            0.5 + 0.5 * cos(colorPhase + 2.0),
            0.5 + 0.5 * cos(colorPhase + 4.0)
            );

            float intensity = 1.0 / (1.0 + rayDepth + unsignedDistance);
            fragColor.rgb += color * intensity * 0.3;
        }
    }

    gl_FragColor = fragColor;
}

这一步就是在上一步得基础上加上了颜色映射!并且动态改变samplePoint.y使其有一个类似扫光得效果!

多层湍流

void main() {

    vec2 fragCoord = gl_FragCoord.xy;
    vec4 fragColor = vec4(0.0);

    // 光线步进(10步)
    for (int step = 0; step < 10; step++) {
        vec3 resolution = vec3(u_resolution, 1.0);
        vec3 centeredCoord = vec3(fragCoord * 2.0, 0.0) - resolution;

        float rayDepth = float(step) * 0.15 + 0.1;
        vec3 samplePoint = rayDepth * normalize(centeredCoord);

        // 多层湍流(3层)
        float time = u_animate ? u_time : 0.0;
        vec3 rotated = vec3(samplePoint.y, samplePoint.z, samplePoint.x);

        samplePoint += sin(rotated * 6.0 - time) / 6.0;
        rotated = vec3(samplePoint.y, samplePoint.z, samplePoint.x);
        samplePoint += sin(rotated * 12.0 - time) / 12.0;
        rotated = vec3(samplePoint.y, samplePoint.z, samplePoint.x);
        samplePoint += sin(rotated * 24.0 - time) / 24.0;

        float signedDistance = 0.3 - abs(samplePoint.y);

        if (signedDistance > 0.0) {
            float colorPhase = signedDistance * 20.0 + samplePoint.x * 8.0 + time;
            vec3 color = vec3(
            0.5 + 0.5 * cos(colorPhase + 0.0),
            0.5 + 0.5 * cos(colorPhase + 2.1),
            0.5 + 0.5 * cos(colorPhase + 4.2)
            );

            float intensity = 1.0 / (1.0 + rayDepth * 1.5);
            fragColor.rgb += color * intensity * 0.15;
        }
    }

    gl_FragColor = fragColor;

}

在上一步我们已经通过改变samplePoint.y来实现一个扫光得效果,这一步我们叠加更多得不规则湍流使其效果变得更随机

色调映射/径向渐变

void main() {

    vec2 fragCoord = gl_FragCoord.xy;
    vec4 fragColor = vec4(0.0);

    float time = u_animate ? u_time : 0.0;

    // 完整光线步进(10步)
    for (int step = 0; step < 10; step++) {
        vec3 resolution = vec3(u_resolution, 1.0);
        vec3 centeredCoord = vec3(fragCoord * 2.0, 0.0) - resolution;

        float rayDepth = float(step) * 0.1 + 0.05;
        vec3 samplePoint = rayDepth * normalize(centeredCoord);
        

        // 完整湍流(6层)
        vec3 rotated = vec3(samplePoint.y, samplePoint.z, samplePoint.x);
        samplePoint += sin(rotated * 6.0 - time) / 6.0;
        rotated = vec3(samplePoint.y, samplePoint.z, samplePoint.x);
        samplePoint += sin(rotated * 12.0 - time) / 12.0;
        rotated = vec3(samplePoint.y, samplePoint.z, samplePoint.x);
        samplePoint += sin(rotated * 24.0 - time) / 24.0;
        rotated = vec3(samplePoint.y, samplePoint.z, samplePoint.x);
        samplePoint += sin(rotated * 48.0 - time) / 48.0;
        rotated = vec3(samplePoint.y, samplePoint.z, samplePoint.x);
        samplePoint += sin(rotated * 96.0 - time) / 96.0;
        rotated = vec3(samplePoint.y, samplePoint.z, samplePoint.x);
        samplePoint += sin(rotated * 192.0 - time) / 192.0;

        float signedDistance = 0.3 - abs(samplePoint.y);

        
        // 径向渐变
        float radialGradient = tanh(length(centeredCoord) * 2.0 / resolution.y);

        // 彩色条纹
        float stripePhase = signedDistance * 10.0 + samplePoint.x * 5.0 + time;
        vec4 stripeColor = cos(stripePhase - vec4(6.0, 1.0, 2.0, 3.0) - 3.0) + 1.5;

        // 自适应步长
        float stepSize = 0.005 + abs(signedDistance) / 4.0;

        // 累加颜色
        fragColor += radialGradient * stripeColor / (stepSize * 200.0);
    }

    // 色调映射
    fragColor = tanh(fragColor * fragColor / 10.0);

    gl_FragColor = fragColor;

}

length(centeredCoord) * 2.0 / resolution.y本身是一个圆形得SDF

03.png

关于tanh可以查看我的上一篇文章什么是色调映射/以及tanh的使用,两者结合radialGradient其实就是一个径向得范围区间,配合后续得color计算产生了不一样得各种颜色,再配合上我们得六层湍流,就产生了一种时空穿梭得效果!

结语

glsl虽然很难,但是冷静分析得话还是可以慢慢得掌握!