Three.js 框架中的 `Uniform` 使用详解

person 少陵野老    watch_later 2024-10-20 13:55:11
visibility 89    class Uniform    bookmark 专栏

Uniform 在 Three.js 中用于在自定义着色器中传递全局变量。通过 Uniform,我们可以将 JavaScript 层的数据传递给 WebGL 着色器(Shader),并在着色器程序的运行过程中保持这些数据的一致性。在复杂的三维场景渲染中,Uniform 对于处理材质效果、动画、光照和交互等操作非常关键。

本文将详细介绍 Uniform 的使用,包括属性、方法、示例代码,以及如何结合其他组件使用 Uniform,帮助你掌握在 Three.js 中使用 Uniform 的技巧。

1. 什么是 Uniform

Uniform 是 WebGL 中用于在顶点和片段着色器中传递一致数据的变量。与 attributevarying 不同,Uniform 是全局变量,每次渲染时,它们的值对所有顶点或片段都保持一致。这意味着 Uniform 的值是每一帧在 JavaScript 层面更新后传递到 GPU 的,在着色器中,它们不会随着顶点或片段的变化而变化。

在 Three.js 中,Uniform 可以用于控制诸如光照强度、材质属性、时间变量等。

2. Uniform 的基本使用

Three.js 提供了一个 Uniform 类,并且自定义着色器材质(如 ShaderMaterial)中的 uniforms 属性就是通过它来定义的。以下是定义和使用 Uniform 的基本步骤。

2.1 定义 Uniform

当我们使用 ShaderMaterial 时,可以在 uniforms 对象中定义所有需要传递给着色器的全局变量。每个 uniform 都需要指定类型和值。

const uniforms = {
    time: { value: 1.0 },
    resolution: { value: new THREE.Vector2() },
    color: { value: new THREE.Color(0xff0000) }
};
  • time:这是一个浮点数类型的 uniform,它的值随着时间变化。
  • resolution:这是一个 Vector2 类型的 uniform,表示屏幕的分辨率。
  • color:这是一个 Color 类型的 uniform,表示一个颜色值。

2.2 在 ShaderMaterial 中使用 uniforms

在创建 ShaderMaterial 时,我们可以将 uniforms 传递给它,并在顶点和片段着色器中使用这些 uniform 变量。

const material = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: vertexShaderCode,
    fragmentShader: fragmentShaderCode
});

vertexShaderCodefragmentShaderCode 分别表示顶点着色器和片段着色器的代码。

2.3 使用 Uniform 传递数据

在 Three.js 的渲染循环中,我们可以通过修改 uniforms 对象中的值来控制着色器的行为。例如,我们可以根据时间动态更新 time uniform。

function animate() {
    requestAnimationFrame(animate);

    // 动态更新 time uniform
    uniforms.time.value += 0.05;

    renderer.render(scene, camera);
}

animate();

在每一帧中,uniforms.time.value 的值都会增加,这样我们就可以在着色器中根据时间来控制动画效果。

3. Uniform 的数据类型

在 WebGL 中,Uniform 支持多种数据类型,每种类型对应着不同的值结构。以下是 Three.js 中常用的 Uniform 数据类型及其对应的 JavaScript 类型。

  • float:对应 THREE.Uniform({ value: 0.0 }),表示一个浮点数。
  • vec2:对应 THREE.Uniform({ value: new THREE.Vector2(x, y) }),表示一个二维向量。
  • vec3:对应 THREE.Uniform({ value: new THREE.Vector3(x, y, z) }),表示一个三维向量。
  • vec4:对应 THREE.Uniform({ value: new THREE.Vector4(x, y, z, w) }),表示一个四维向量。
  • mat3:对应 THREE.Uniform({ value: new THREE.Matrix3() }),表示一个 3x3 矩阵。
  • mat4:对应 THREE.Uniform({ value: new THREE.Matrix4() }),表示一个 4x4 矩阵。
  • sampler2D:对应 THREE.Uniform({ value: texture }),表示一个二维纹理。

示例代码

const uniforms = {
    time: { value: 0.0 },
    color: { value: new THREE.Color(0x00ff00) },
    texture: { value: new THREE.TextureLoader().load('path/to/texture.jpg') }
};

在这个例子中,我们定义了三个 uniforms:一个时间变量 time、一个颜色变量 color 和一个纹理变量 texture

4. Uniform 的属性和方法

Three.js 中的 Uniform 主要有两个属性:valueneedsUpdate

4.1 value

valueUniform 的核心属性,它表示传递给着色器的值。你可以随时更新 value,它的变化会直接影响着色器中的变量。

uniforms.time.value = performance.now() / 1000; // 根据时间更新 time

4.2 needsUpdate

needsUpdate 是一个布尔值,用于告诉渲染器当前 Uniform 的值是否需要更新。在大多数情况下,Three.js 会自动检测 Uniform 是否需要更新,但在一些特殊情况下,你可以手动设置 needsUpdate = true 来强制更新。

uniforms.texture.needsUpdate = true; // 强制更新纹理

5. Uniform 使用的完整示例

下面是一个完整的示例,展示了如何在 Three.js 中使用 Uniform 动态控制着色器中的动画效果。

示例:使用 Uniform 控制时间动画

// 创建场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 定义 uniforms
const uniforms = {
    time: { value: 0.0 },
    resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
};

// 定义着色器
const vertexShader = `
    void main() {
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
`;

const fragmentShader = `
    uniform float time;
    uniform vec2 resolution;

    void main() {
        vec2 uv = gl_FragCoord.xy / resolution;
        vec3 color = vec3(0.5 + 0.5 * cos(time + uv.xyx + vec3(0, 2, 4)));
        gl_FragColor = vec4(color, 1.0);
    }
`;

// 创建材质
const material = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: vertexShader,
    fragmentShader: fragmentShader
});

// 创建几何体和网格对象
const geometry = new THREE.PlaneGeometry(2, 2);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

// 设置相机位置
camera.position.z = 1;

// 渲染循环
function animate() {
    requestAnimationFrame(animate);

    // 更新时间 uniform
    uniforms.time.value += 0.05;

    renderer.render(scene, camera);
}

animate();

在这个示例中,我们创建了一个简单的片段着色器,通过 time uniform 实现了颜色随时间变化的动画效果。resolution uniform 则确保了片段着色器根据屏幕分辨率正确绘制。

6. 结合其他组件使用 Uniform

6.1 与 TextureLoader 一起使用

Uniform 经常用于传递纹理数据到着色器中。例如,我们可以使用 THREE.TextureLoader 来加载纹理,并将其传递给着色器。

const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('path/to/texture.jpg');

const uniforms = {
    texture: { value: texture }
};

在着色器中,我们可以通过 sampler2D 类型的 uniform 来访问这个纹理,并根据纹理的颜色信息进行渲染。

6.2 与 Clock 一起使用

Three.js 中的 Clock 对象非常适合与 Uniform 结合使用来实现基于时间的动画。通过 Clock.getElapsedTime()

我们可以获取程序运行的总时间,并将其传递给 time uniform。

const clock = new THREE.Clock();

function animate() {
    requestAnimationFrame(animate);

    // 使用 Clock 获取经过的时间
    uniforms.time.value = clock.getElapsedTime();

    renderer.render(scene, camera);
}

animate();

7. 总结

Uniform 是 Three.js 中用于在 JavaScript 和着色器之间传递全局数据的关键机制。通过 Uniform,我们可以动态控制渲染效果、动画、光照等场景中的重要元素。本文详细介绍了 Uniform 的基本概念、使用方法、属性,以及如何与其他组件结合使用的示例。

希望通过本文的学习,你能够熟练掌握 Uniform 在 Three.js 中的使用,并在项目中创造更加丰富的视觉效果。如果你在项目中需要自定义着色器或动态控制场景元素,Uniform 将是不可或缺的工具。

评论区
评论列表
menu