Bootstrap

深入理解 GLSL 中的 uniform 变量及在 Babylonjs 中的应用

        在计算机图形学的领域中,Shader(着色器)是实现各种炫酷视觉效果的核心技术。而在 GLSL(OpenGL Shading Language)进行 Shader 编程时,uniform 变量起着举足轻重的作用。本文将深入探讨 uniform 变量的含义、作用,并基于 Babylonjs 使用 Shader 的方式列举一些使用案例,帮助大家更好地理解和运用这一概念。

一、uniform 变量是什么

(一)定义与性质

  1. 全局常量:uniform 变量本质上是 GLSL shader 中的全局常量。这意味着它可以在顶点着色器(vertex shader)、几何着色器(geometry shader)和片段着色器(fragment shader)中被随意访问,只要在这些 shader 中进行了相同的声明,它们就代表同一个变量。
  1. 外部传入:uniform 变量是由外部程序传递给 shader 的变量。在实际应用中,通常是由 CPU 一侧的应用程序通过相关函数赋值给 GPU 中的 shader,例如在 OpenGL 中,我们会使用 glUniform**() 系列函数来完成这一操作。
  1. 不可修改:在 shader 程序内部,uniform 变量不能被修改,就如同 C 语言里的常量(const)。如果尝试在 shader 中修改 uniform 变量,将会导致编译错误。

(二)作用与用途

  1. 传递变换信息:在图形渲染中,常常需要对物体进行各种变换,如投影、旋转、缩放等。uniform 变量常用来表示变换矩阵,像投影矩阵、视图矩阵等。通过设置这些 uniform 变量,shader 可以对顶点或片段进行相应的坐标变换,从而实现我们期望的场景效果。
  1. 设置材质与光照:现实世界中,不同物体具有不同的材质属性,同时光照条件也会影响物体的外观。在 Shader 编程中,uniform 变量可以用于传递材质属性和光照参数,例如材质的颜色、光泽度,以及光源的位置、强度、颜色等信息。这些信息对于计算物体表面的光照效果至关重要,能够让 shader 根据不同的材质和光照条件来准确计算每个像素的颜色。
  1. 控制特效参数:在实现各种特效时,uniform 变量发挥着关键作用。例如,在制作雾效、阴影效果、粒子系统等特效时,可以通过 uniform 变量来传递雾的浓度、阴影的强度、粒子的发射速度等参数,从而实现对特效的灵活控制,为用户带来更加丰富和逼真的视觉体验。
  1. 纹理采样相关:纹理映射是为物体表面添加细节的常用技术。在进行纹理采样时,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 变量的作用,创造出更加精彩的图形效果。

;