在使用 Babylon.js 进行 GLSL Shader 编程时,attribute
、varying
和uniform
这三种类型的变量是构建强大且灵活的着色器的基础。理解它们各自的功能和区别,对于实现复杂的图形效果至关重要。本文将详细介绍这三种变量的功能差异,并通过实例进行说明,最后给出一个较为复杂的基于 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 案例,通过结合 attribute
、varying
和 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);
设置其初始值。
通过这个复杂案例,我们可以看到如何灵活运用 attribute
、varying
和 uniform
变量来实现复杂的图形效果。希望本文能帮助你更好地理解和运用这些变量,在 Babylon.js 的 Shader 编程中创造出更加精彩的视觉效果。