Bootstrap

深入理解GLSL Shader 的三种变量:attribute、varying 和 uniform

        在使用 Babylon.js 进行 GLSL Shader 编程时,attributevaryinguniform这三种类型的变量是构建强大且灵活的着色器的基础。理解它们各自的功能和区别,对于实现复杂的图形效果至关重要。本文将详细介绍这三种变量的功能差异,并通过实例进行说明,最后给出一个较为复杂的基于 Babylon.js 的 Shader 使用案例。

三种变量的功能区别

1. attribute 变量

        attribute 变量的主要功能是从顶点缓冲区读取每个顶点的数据。这些数据通常包含顶点的位置、法线、纹理坐标等信息。它是专门为顶点着色器设计的,因为它存储的是每个顶点特有的数据。在顶点着色器执行过程中,会逐个处理每个顶点的 attribute 数据。

2. varying 变量

        varying 变量充当了顶点着色器和片段着色器之间的数据桥梁。顶点着色器会为每个顶点计算 varying 变量的值,在光栅化阶段,这些值会在三角形的各个片段之间进行插值。片段着色器会接收这些插值后的值,并以此进行后续的计算。简而言之,varying 变量将顶点级别的数据传递到了片段级别。

3. uniform 变量

        uniform 变量是全局常量,在整个渲染过程中保持不变。它的作用是从 CPU 侧的应用程序向 GPU 中的着色器程序传递数据,例如变换矩阵、光照参数、时间等。uniform 变量可以同时在顶点着色器和片段着色器中使用,并且一旦设置,在一次渲染调用中,所有顶点和片段的 uniform 变量值都是相同的,且在着色器内部不能被修改。

简单示例展示变量使用

        以下是一个简单的 Babylon.js 示例,帮助你直观地理解这三种变量的使用:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Babylon.js Shader Example</title>
    <style>
        canvas {
            width: 100%;
            height: 100%;
        }
    </style>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
</head>

<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        // 创建场景和引擎
        const canvas = document.getElementById('renderCanvas');
        const engine = new BABYLON.Engine(canvas, true);
        const scene = new BABYLON.Scene(engine);

        // 创建相机
        const camera = new BABYLON.ArcRotateCamera("camera", Math.PI / 2, Math.PI / 2, 5, BABYLON.Vector3.Zero(), scene);
        camera.attachControl(canvas, true);

        // 创建顶点着色器
        const vertexShader = `
            precision highp float;
            // attribute 变量:顶点位置
            attribute vec3 position;
            // varying 变量:传递顶点颜色到片段着色器
            varying vec3 vColor;
            // uniform 变量:变换矩阵
            uniform mat4 worldViewProjection;

            void main() {
                // 将顶点位置进行变换
                gl_Position = worldViewProjection * vec4(position, 1.0);
                // 简单地根据顶点位置计算颜色
                vColor = position * 0.5 + 0.5;
            }
        `;

        // 创建片段着色器
        const fragmentShader = `
            precision highp float;
            // 接收从顶点着色器传递过来的颜色
            varying vec3 vColor;
            // uniform 变量:全局颜色偏移
            uniform vec3 colorOffset;

            void main() {
                // 应用颜色偏移
                gl_FragColor = vec4(vColor + colorOffset, 1.0);
            }
        `;

        // 创建 ShaderMaterial
        const shaderMaterial = new BABYLON.ShaderMaterial("shaderMaterial", scene, {
            vertexSource: vertexShader,
            fragmentSource: fragmentShader
        }, {
            attributes: ["position"],
            uniforms: ["worldViewProjection", "colorOffset"]
        });

        // 创建一个球体
        const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2 }, scene);
        sphere.material = shaderMaterial;

        // 设置 uniform 变量的值
        const colorOffset = new BABYLON.Vector3(0.1, 0.1, 0.1);
        shaderMaterial.setVector3("colorOffset", colorOffset);

        // 渲染循环
        engine.runRenderLoop(() => {
            scene.render();
        });

        // 窗口大小改变时调整引擎大小
        window.addEventListener("resize", () => {
            engine.resize();
        });
    </script>
</body>

</html>

代码解释

  • attribute 变量:在顶点着色器中,attribute vec3 position; 接收每个顶点的位置信息,这些信息从顶点缓冲区读取,每个顶点都有自己的位置值。
  • varying 变量:顶点着色器中的 varying vec3 vColor; 计算每个顶点的颜色并传递给片段着色器。在 main 函数中,vColor = position * 0.5 + 0.5; 完成颜色计算。片段着色器通过 varying vec3 vColor; 接收插值后的颜色值进行最终颜色计算。
  • uniform 变量:顶点着色器中的 uniform mat4 worldViewProjection; 用于顶点位置的空间转换,片段着色器中的 uniform vec3 colorOffset; 用于全局颜色偏移。在 JavaScript 代码中,通过 shaderMaterial.setVector3("colorOffset", colorOffset); 设置 colorOffset 的值。

复杂的基于 Babylon.js 的 Shader 使用案例:实现动态纹理扭曲效果

        下面我们将实现一个更为复杂的 Shader 案例,通过结合 attributevarying 和 uniform 变量,实现动态纹理扭曲效果。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Babylon.js Complex Shader Example</title>
    <style>
        canvas {
            width: 100%;
            height: 100%;
        }
    </style>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
</head>

<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        // 创建场景和引擎
        const canvas = document.getElementById('renderCanvas');
        const engine = new BABYLON.Engine(canvas, true);
        const scene = new BABYLON.Scene(engine);

        // 创建相机
        const camera = new BABYLON.ArcRotateCamera("camera", Math.PI / 2, Math.PI / 2, 5, BABYLON.Vector3.Zero(), scene);
        camera.attachControl(canvas, true);

        // 创建顶点着色器
        const vertexShader = `
            precision highp float;
            // attribute 变量:顶点位置和纹理坐标
            attribute vec3 position;
            attribute vec2 uv;
            // varying 变量:传递纹理坐标到片段着色器
            varying vec2 vUv;
            // uniform 变量:变换矩阵
            uniform mat4 worldViewProjection;

            void main() {
                gl_Position = worldViewProjection * vec4(position, 1.0);
                vUv = uv;
            }
        `;

        // 创建片段着色器
        const fragmentShader = `
            precision highp float;
            // 接收从顶点着色器传递过来的纹理坐标
            varying vec2 vUv;
            // uniform 变量:纹理采样器、时间和扭曲强度
            uniform sampler2D textureSampler;
            uniform float time;
            uniform float distortionStrength;

            void main() {
                // 计算扭曲偏移
                vec2 distortion = vec2(
                    sin(vUv.x * 10.0 + time) * distortionStrength,
                    cos(vUv.y * 10.0 + time) * distortionStrength
                );
                // 应用扭曲偏移到纹理坐标
                vec2 distortedUv = vUv + distortion;
                // 从纹理中采样颜色
                vec4 color = texture2D(textureSampler, distortedUv);
                gl_FragColor = color;
            }
        `;

        // 创建 ShaderMaterial
        const shaderMaterial = new BABYLON.ShaderMaterial("shaderMaterial", scene, {
            vertexSource: vertexShader,
            fragmentSource: fragmentShader
        }, {
            attributes: ["position", "uv"],
            uniforms: ["worldViewProjection", "textureSampler", "time", "distortionStrength"]
        });

        // 创建一个平面
        const plane = BABYLON.MeshBuilder.CreatePlane("plane", { size: 5 }, scene);
        plane.material = shaderMaterial;

        // 加载纹理
        const texture = new BABYLON.Texture("https://picsum.photos/500/500", scene);
        shaderMaterial.setTexture("textureSampler", texture);

        // 设置初始扭曲强度
        const distortionStrength = 0.05;
        shaderMaterial.setFloat("distortionStrength", distortionStrength);

        // 动态更新时间 uniform 变量
        let time = 0;
        engine.runRenderLoop(() => {
            time += 0.01;
            shaderMaterial.setFloat("time", time);
            scene.render();
        });

        // 窗口大小改变时调整引擎大小
        window.addEventListener("resize", () => {
            engine.resize();
        });
    </script>
</body>

</html>

复杂案例代码解释

  • attribute 变量:在顶点着色器中,attribute vec3 position; 接收顶点位置,attribute vec2 uv; 接收顶点的纹理坐标,每个顶点都有其独特的位置和纹理坐标信息。
  • varying 变量varying vec2 vUv; 在顶点着色器中接收顶点的纹理坐标,并将其传递给片段着色器。在光栅化阶段,纹理坐标会在三角形的各个片段之间进行插值。
  • uniform 变量
    • uniform mat4 worldViewProjection; 用于将顶点位置从模型空间转换到裁剪空间。
    • uniform sampler2D textureSampler; 用于采样纹理,通过 shaderMaterial.setTexture("textureSampler", texture); 设置纹理。
    • uniform float time; 是一个动态的时间变量,通过不断更新其值,实现动态的纹理扭曲效果。
    • uniform float distortionStrength; 控制纹理扭曲的强度,通过 shaderMaterial.setFloat("distortionStrength", distortionStrength); 设置其初始值。

        通过这个复杂案例,我们可以看到如何灵活运用 attributevarying 和 uniform 变量来实现复杂的图形效果。希望本文能帮助你更好地理解和运用这些变量,在 Babylon.js 的 Shader 编程中创造出更加精彩的视觉效果。

;