在计算机图形学的领域中,Shader(着色器)是实现各种炫酷视觉效果的核心技术。而在 GLSL(OpenGL Shading Language)进行 Shader 编程时,uniform 变量起着举足轻重的作用。本文将深入探讨 uniform 变量的含义、作用,并基于 Babylonjs 使用 Shader 的方式列举一些使用案例,帮助大家更好地理解和运用这一概念。
一、uniform 变量是什么
(一)定义与性质
- 全局常量:uniform 变量本质上是 GLSL shader 中的全局常量。这意味着它可以在顶点着色器(vertex shader)、几何着色器(geometry shader)和片段着色器(fragment shader)中被随意访问,只要在这些 shader 中进行了相同的声明,它们就代表同一个变量。
- 外部传入:uniform 变量是由外部程序传递给 shader 的变量。在实际应用中,通常是由 CPU 一侧的应用程序通过相关函数赋值给 GPU 中的 shader,例如在 OpenGL 中,我们会使用 glUniform**() 系列函数来完成这一操作。
- 不可修改:在 shader 程序内部,uniform 变量不能被修改,就如同 C 语言里的常量(const)。如果尝试在 shader 中修改 uniform 变量,将会导致编译错误。
(二)作用与用途
- 传递变换信息:在图形渲染中,常常需要对物体进行各种变换,如投影、旋转、缩放等。uniform 变量常用来表示变换矩阵,像投影矩阵、视图矩阵等。通过设置这些 uniform 变量,shader 可以对顶点或片段进行相应的坐标变换,从而实现我们期望的场景效果。
- 设置材质与光照:现实世界中,不同物体具有不同的材质属性,同时光照条件也会影响物体的外观。在 Shader 编程中,uniform 变量可以用于传递材质属性和光照参数,例如材质的颜色、光泽度,以及光源的位置、强度、颜色等信息。这些信息对于计算物体表面的光照效果至关重要,能够让 shader 根据不同的材质和光照条件来准确计算每个像素的颜色。
- 控制特效参数:在实现各种特效时,uniform 变量发挥着关键作用。例如,在制作雾效、阴影效果、粒子系统等特效时,可以通过 uniform 变量来传递雾的浓度、阴影的强度、粒子的发射速度等参数,从而实现对特效的灵活控制,为用户带来更加丰富和逼真的视觉体验。
- 纹理采样相关:纹理映射是为物体表面添加细节的常用技术。在进行纹理采样时,uniform 变量可用于指定纹理单元、纹理采样器等信息,帮助 shader 从正确的纹理中获取纹理数据,并进行相应的采样操作,以实现纹理映射、凹凸映射等效果,使物体表面看起来更加真实和生动。
二、在 Babylonjs 中使用 Shader 及 uniform 变量的案例
(一)创建一个简单的 Shader 材质
在 Babylonjs 中,我们可以通过 ShaderMaterial 来创建自定义的 Shader 材质。下面是一个简单的示例,展示如何创建一个红色的球体,使用自定义的 Shader 来控制其颜色。
// 引入Babylonjs库
<script src="https://cdn.babylonjs.com/babylon.js"></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, 10, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
// 编写顶点着色器
const vertexShader = `
precision highp float;
attribute vec3 position;
uniform mat4 worldViewProjection;
void main() {
gl_Position = worldViewProjection * vec4(position, 1.0);
}
`;
// 编写片元着色器
const fragmentShader = `
precision highp float;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色
}
`;
// 创建ShaderMaterial
const shaderMaterial = new BABYLON.ShaderMaterial('shader', scene, {
vertexSource: vertexShader,
fragmentSource: fragmentShader
}, {
attributes: ['position'],
uniforms: ['worldViewProjection']
});
// 创建球体并应用ShaderMaterial
const sphere = BABYLON.MeshBuilder.CreateSphere('sphere', { diameter: 2 }, scene);
sphere.material = shaderMaterial;
// 渲染场景
engine.runRenderLoop(() => {
scene.render();
});
window.addEventListener('resize', () => {
engine.resize();
});
在这个例子中,我们定义了一个简单的顶点着色器和片元着色器。顶点着色器通过 uniform 变量worldViewProjection将顶点从模型空间转换到裁剪空间,片元着色器则直接将片元颜色设置为红色。
(二)通过 uniform 变量实现动态效果
接下来,我们通过传递 uniform 变量来实现一个动态的效果,例如让物体的颜色随着时间变化。
// 引入Babylonjs库
<script src="https://cdn.babylonjs.com/babylon.js"></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, 10, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
// 编写顶点着色器
const vertexShader = `
precision highp float;
attribute vec3 position;
uniform mat4 worldViewProjection;
void main() {
gl_Position = worldViewProjection * vec4(position, 1.0);
}
`;
// 编写片元着色器,通过uniform变量time来控制颜色
const fragmentShader = `
precision highp float;
uniform float time;
void main() {
float r = sin(time) * 0.5 + 0.5;
float g = cos(time) * 0.5 + 0.5;
float b = 0.5;
gl_FragColor = vec4(r, g, b, 1.0);
}
`;
// 创建ShaderMaterial
const shaderMaterial = new BABYLON.ShaderMaterial('shader', scene, {
vertexSource: vertexShader,
fragmentSource: fragmentShader
}, {
attributes: ['position'],
uniforms: ['worldViewProjection', 'time']
});
// 创建球体并应用ShaderMaterial
const sphere = BABYLON.MeshBuilder.CreateSphere('sphere', { diameter: 2 }, scene);
sphere.material = shaderMaterial;
// 动态更新uniform变量time
const updateShader = () => {
const time = performance.now() / 1000;
shaderMaterial.setFloat('time', time);
requestAnimationFrame(updateShader);
};
updateShader();
// 渲染场景
engine.runRenderLoop(() => {
scene.render();
});
window.addEventListener('resize', () => {
engine.resize();
});
在这个案例中,我们在片元着色器中定义了一个 uniform 变量time,并通过 JavaScript 代码不断更新它的值。片元着色器根据time的值计算出不同的颜色分量,从而实现物体颜色随时间动态变化的效果。
(三)使用 uniform 变量实现菲涅尔效果
菲涅尔效果是一种常见的光学现象,在物体表面的边缘处,反射光会增强,而在物体表面的中心部分,反射光会减弱。下面是使用 Babylonjs 和 Shader 实现简单菲涅尔效果的示例。
// 创建球体
const sphere = BABYLON.MeshBuilder.CreateSphere('sphere', { segments: 128, diameter: 2 });
// 创建shader材质
const shaderMaterial = new BABYLON.ShaderMaterial(
'sphereMat',
scene,
{
vertex: 'custom',
fragment: 'custom'
},
{
attributes: ['position', 'normal', 'uv'],
uniforms: ['world', 'worldView', 'worldViewProjection', 'view', 'projection', 'time', 'cameraPosition']
}
);
// 赋予材质
sphere.material = shaderMaterial;
// 顶点着色器
BABYLON.Effect.ShadersStore('customVertexShader') = `
#ifdef GL_ES
precision mediump float;
#endif
attribute vec3 position;
attribute vec2 uv;
attribute vec3 normal;
uniform mat4 worldViewProjection;
uniform mat4 world;
uniform vec3 cameraPosition;
varying float vIntensity;
void main(void) {
vec3 worldNormal = normalize((world * vec4(normal,1.0))).xyz;
vec3 dirToCamera = normalize(cameraPosition - worldNormal.xyz);
float intensity = 1.0 - dot(worldNormal, dirToCamera);
intensity = pow(intensity, 5.0);
gl_Position = worldViewProjection * vec4(position, 1.0);
vIntensity = intensity;
}
`;
// 片元着色器
BABYLON.Effect.ShadersStore('customFragmentShader') = `
#ifdef GL_ES
precision mediump float;
#endif
varying float vIntensity;
void main(void) {
gl_FragColor = vec4(vec3(vIntensity), 1.0);
}
`;
在这个示例中,顶点着色器通过 uniform 变量world、worldViewProjection和cameraPosition来计算顶点到相机的方向向量以及光照强度,然后将计算结果通过vIntensity传递给片元着色器。片元着色器根据接收到的vIntensity值来设置片元的颜色,从而实现菲涅尔效果。
三、总结
通过以上内容,我们对 GLSL 中的 uniform 变量有了更深入的理解,并且了解了在 Babylonjs 中如何使用 Shader 和 uniform 变量来实现各种有趣的效果。uniform 变量作为连接外部应用程序和 Shader 的桥梁,为我们在图形渲染中提供了极大的灵活性和控制力。希望大家通过学习和实践,能够在自己的项目中充分发挥 uniform 变量的作用,创造出更加精彩的图形效果。