Three.js 自定义着色器(Custom Shaders)

一、引言

在 Three.js 中,自定义着色器(Custom Shaders)是一项强大且灵活的功能,它允许开发者完全掌控图形渲染过程,实现诸如炫酷的特效、独特的材质外观等复杂视觉效果。通过自定义着色器,你可以突破 Three.js 内置材质的限制,按照自己的需求精确控制顶点和片元的处理逻辑,为场景增添独一无二的视觉体验。接下来,我们将逐步深入了解如何在 Three.js 中创建和使用自定义着色器。

二、准备工作

在开始编写自定义着色器之前,确保你已经引入了 Three.js 库。可以通过在 HTML 文件中添加以下代码引入:

xml 复制代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

同时,创建一个基本的 Three.js 场景结构,包括场景、相机和渲染器。以下是一个简单的示例代码:

ini 复制代码
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

三、自定义着色器基础概念

3.1 着色器类型

在 Three.js 中,自定义着色器主要涉及两种类型的着色器:顶点着色器(Vertex Shader)和片元着色器(Fragment Shader)。顶点着色器负责处理几何体的顶点数据,例如变换顶点的位置、计算顶点的颜色等;片元着色器则用于计算每个像素(片元)的最终颜色,包括处理光照、纹理等效果。

3.2 着色器语言

Three.js 中的自定义着色器使用 GLSL(OpenGL Shading Language)编写。GLSL 是一种专门用于图形渲染的编程语言,语法与 C 语言类似。顶点着色器和片元着色器分别在不同的 GLSL 文件中编写,或者在 JavaScript 代码中以字符串的形式定义。

四、创建自定义材质

在 Three.js 中,要使用自定义着色器,需要创建一个ShaderMaterial对象。ShaderMaterial允许我们将顶点着色器和片元着色器与材质属性相结合。以下是创建一个简单ShaderMaterial的步骤:

4.1 定义顶点着色器

顶点着色器的主要任务是对顶点进行变换和传递数据到片元着色器。以下是一个简单的顶点着色器示例,它只是将顶点的位置进行了简单的传递:

ini 复制代码
const vertexShader = `
    attribute vec3 position;
    void main() {
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
`;

在这段代码中,attribute声明了一个名为position的顶点属性,用于存储顶点的三维位置。在main函数中,gl_Position是一个内置变量,用于指定顶点在裁剪空间中的最终位置。通过将顶点位置乘以projectionMatrix(投影矩阵)和modelViewMatrix(模型视图矩阵),我们将顶点从局部空间变换到裁剪空间。

4.2 定义片元着色器

片元着色器负责计算每个片元的最终颜色。以下是一个简单的片元着色器示例,它将所有片元设置为红色:

ini 复制代码
const fragmentShader = `
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
`;

在这段代码中,gl_FragColor是一个内置变量,用于指定片元的最终颜色。vec4(1.0, 0.0, 0.0, 1.0)表示红色(RGBA 格式,其中 R 为 1.0,G 为 0.0,B 为 0.0,A 为 1.0 即不透明)。

4.3 创建 ShaderMaterial

有了顶点着色器和片元着色器后,我们可以创建ShaderMaterial对象,并将它们应用到几何体上。以下是完整的代码示例:

ini 复制代码
// 定义顶点着色器
const vertexShader = `
    attribute vec3 position;
    void main() {
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
`;
// 定义片元着色器
const fragmentShader = `
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
`;
// 创建ShaderMaterial
const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,
    fragmentShader: fragmentShader
});
// 创建几何体
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 创建Mesh
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 渲染循环
function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}
animate();

在上述代码中,我们首先定义了顶点着色器和片元着色器,然后使用它们创建了一个ShaderMaterial对象。接着,创建了一个立方体几何体,并将ShaderMaterial应用到几何体上,最后将立方体添加到场景中并开始渲染循环。

五、传递自定义属性

在实际应用中,我们常常需要向着色器传递自定义的属性或参数,以实现更丰富的效果。以下是如何向自定义着色器传递一个简单的颜色属性的示例:

5.1 修改顶点着色器

我们在顶点着色器中添加一个新的uniform变量来接收颜色值:

ini 复制代码
const vertexShader = `
    attribute vec3 position;
    uniform vec3 customColor;
    void main() {
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
`;

这里的uniform变量customColor用于存储从 JavaScript 代码传递过来的颜色值。uniform变量在同一帧内对于所有顶点和片元都是相同的。

5.2 修改片元着色器

在片元着色器中使用这个uniform变量来设置片元颜色:

ini 复制代码
const fragmentShader = `
    uniform vec3 customColor;
    void main() {
        gl_FragColor = vec4(customColor, 1.0);
    }
`;

5.3 在 JavaScript 中传递属性值

在创建ShaderMaterial时,通过uniforms属性传递颜色值:

ini 复制代码
// 定义顶点着色器
const vertexShader = `
    attribute vec3 position;
    uniform vec3 customColor;
    void main() {
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
`;
// 定义片元着色器
const fragmentShader = `
    uniform vec3 customColor;
    void main() {
        gl_FragColor = vec4(customColor, 1.0);
    }
`;
// 创建ShaderMaterial并传递颜色属性
const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    uniforms: {
        customColor: { value: new THREE.Vector3(0.0, 1.0, 0.0) }
    }
});
// 创建几何体
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 创建Mesh
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 渲染循环
function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}
animate();

在上述代码中,我们在ShaderMaterial的uniforms属性中定义了customColor变量,并设置其初始值为绿色(0.0, 1.0, 0.0)。这样,立方体就会显示为绿色。

六、处理纹理

除了颜色属性,我们还可以在自定义着色器中使用纹理来实现更复杂的材质效果。以下是一个在自定义着色器中使用纹理的示例:

6.1 加载纹理

首先,我们需要加载一个纹理图片。在 Three.js 中,可以使用TextureLoader来加载纹理:

ini 复制代码
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('texture.jpg');

这里假设texture.jpg是你要加载的纹理图片,你需要将其替换为实际的图片路径。

6.2 修改顶点着色器

在顶点着色器中,我们需要传递纹理坐标(uv)到片元着色器。通常,几何体已经包含了纹理坐标属性,我们只需要将其传递下去:

ini 复制代码
const vertexShader = `
    attribute vec3 position;
    attribute vec2 uv;
    varying vec2 vUv;
    void main() {
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        vUv = uv;
    }
`;

这里使用varying变量vUv来传递纹理坐标。varying变量会在顶点之间进行插值,并传递到片元着色器。

6.3 修改片元着色器

在片元着色器中,使用传递过来的纹理坐标对纹理进行采样,以获取片元的颜色:

ini 复制代码
const fragmentShader = `
    uniform sampler2D texture;
    varying vec2 vUv;
    void main() {
        gl_FragColor = texture2D(texture, vUv);
    }
`;

这里的sampler2D是 GLSL 中用于表示二维纹理的类型。texture2D函数用于对纹理进行采样,根据纹理坐标vUv获取纹理上对应位置的颜色值,并将其设置为片元的最终颜色。

6.4 创建 ShaderMaterial

在创建ShaderMaterial时,将纹理传递给uniforms属性:

ini 复制代码
// 定义顶点着色器
const vertexShader = `
    attribute vec3 position;
    attribute vec2 uv;
    varying vec2 vUv;
    void main() {
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        vUv = uv;
    }
`;
// 定义片元着色器
const fragmentShader = `
    uniform sampler2D texture;
    varying vec2 vUv;
    void main() {
        gl_FragColor = texture2D(texture, vUv);
    }
`;
// 加载纹理
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('texture.jpg');
// 创建ShaderMaterial
const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    uniforms: {
        texture: { value: texture }
    }
});
// 创建几何体
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 创建Mesh
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 渲染循环
function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}
animate();

通过以上步骤,我们就可以在自定义着色器中使用纹理,为几何体赋予更丰富的外观。

七、结语

通过本文的学习,你已经了解了 Three.js 中自定义着色器的基本概念、创建方法以及如何传递自定义属性和使用纹理。自定义着色器是一个非常强大的工具,它为我们在 Three.js 中实现各种复杂的视觉效果提供了无限可能。随着学习的深入,你可以尝试结合数学运算、光照模型等知识,创作出更加绚丽的特效和独特的材质。在实践过程中,不断探索和尝试,相信你会在 Three.js 的图形渲染世界中创造出令人惊叹的作品。

上述内容从基础到进阶展示了 Three.js 自定义着色器用法。若你想深入学习特定效果实现或优化,欢迎随时和我说说需求。

相关推荐
罚时大师月色1 天前
Vue+ts 如何实现父组件和子组件通信
javascript·vue.js·ecmascript
漂流瓶jz1 天前
快速定位源码问题:SourceMap的生成/使用/文件格式与历史
前端·javascript·前端工程化
samroom1 天前
iframe实战:跨域通信与安全隔离
前端·安全
fury_1231 天前
vue3:数组的.includes方法怎么使用
前端·javascript·vue.js
weixin_405023371 天前
包资源管理器NPM 使用
前端·npm·node.js
宁&沉沦1 天前
Cursor 科技感的登录页面提示词
前端·javascript·vue.js
Dragonir1 天前
React+Three.js 实现 Apple 2025 热成像 logo
前端·javascript·html·three.js·页面特效
古一|1 天前
Vue3中ref与reactive实战指南:使用场景与代码示例
开发语言·javascript·ecmascript
peachSoda71 天前
封装一个不同跳转方式的通用方法(跳转外部链接,跳转其他小程序,跳转半屏小程序)
前端·javascript·微信小程序·小程序