目录
- WebGL基础回顾
- GLSL着色
- WebGL渲染管线
- Three.js与WebGL直接交互
- 自定义着色器与Three.js材质的结合
- 利用WebGL2的新特性
- WebGPU与Three.js的未来
- WebGPU简介与展望
- 使用Three.js实验性支持WebGPU
WebGL基础回顾
上下文创建
首先,需要在HTML5 canvas元素上初始化WebGL上下文。
<canvas id="glCanvas" width="640" height="480"></canvas>
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.log('WebGL not supported, falling back on experimental-webgl');
gl = canvas.getContext('experimental-webgl');
}
if (!gl) {
alert('Your browser does not support WebGL');
}
顶点和片段着色器
顶点着色器和片段着色器用GLSL编写,分别处理几何体的属性和颜色。
顶点着色器:
const vertexShaderSource = `
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
`;
片段着色器:
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1); // Red color
}
`;
缓冲区和顶点数组
存储几何数据,如位置、颜色和纹理坐标。
创建和绑定缓冲区:
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
const vertices = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
-0.5, 0.5,
0.5, 0.5
]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
着色器程序
组合顶点和片段着色器,执行图形渲染。
编译着色器:
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
创建着色器程序:
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
const shaderProgram = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(shaderProgram);
统一值(Uniforms)和属性(Attributes)
传递到着色器的数据,统一值全局有效,属性按顶点传递。
设置顶点属性:
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position');
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
绘制场景:
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebGL 基础示例</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="glCanvas" width="640" height="480"></canvas>
<script>
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.log('WebGL not supported, falling back on experimental-webgl');
gl = canvas.getContext('experimental-webgl');
}
if (!gl) {
alert('Your browser does not support WebGL');
}
const vertexShaderSource = `
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
`;
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1); // Red color
}
`;
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
const shaderProgram = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(shaderProgram);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
const vertices = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
-0.5, 0.5,
0.5, 0.5
]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position');
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
</script>
</body>
</html>
GLSL着色
语法
GLSL(OpenGL Shading Language)是一种类似C的语言,用于编写GPU着色器程序。着色器程序分为两种主要类型:顶点着色器和片段着色器。
类型
GLSL 支持多种数据类型,包括:
- 标量:float, int, bool
- 向量:vec2, vec3, vec4(浮点数向量), ivec2, ivec3, ivec4(整数向量)
- 矩阵:mat2, mat3, mat4
- 纹理采样器:sampler2D, samplerCube
输入输出
- attribute:用于从CPU传递数据到顶点着色器,每个顶点一个值。只能在顶点着色器中使用。
- uniform:用于从CPU传递数据到着色器程序,全局有效的变量。
- varying:用于从顶点着色器传递数据到片段着色器。
控制流
GLSL 支持条件语句和循环控制流。
条件语句:if, else 循环:for, while, do-while
数学运算
GLSL 支持多种数学运算,特别是向量和矩阵运算。
示例代码
顶点着色器 顶点着色器负责计算每个顶点的位置。
attribute vec4 a_position; // 输入顶点位置
attribute vec4 a_color; // 输入顶点颜色
uniform mat4 u_modelViewMatrix; // 模型视图矩阵
uniform mat4 u_projectionMatrix; // 投影矩阵
varying vec4 v_color; // 传递给片段着色器的顶点颜色
void main() {
gl_Position = u_projectionMatrix * u_modelViewMatrix * a_position; // 计算顶点位置
v_color = a_color; // 将顶点颜色传递给片段着色器
}
片段着色器 片段着色器负责计算每个像素的颜色。
precision mediump float;
varying vec4 v_color; // 接收顶点着色器传递的颜色
void main() {
gl_FragColor = v_color; // 设置像素颜色
}
基本使用示例
<canvas id="glCanvas" width="640" height="480"></canvas>
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.log('WebGL not supported, falling back on experimental-webgl');
gl = canvas.getContext('experimental-webgl');
}
if (!gl) {
alert('Your browser does not support WebGL');
}
const vertexShaderSource = `
attribute vec4 a_position;
attribute vec4 a_color;
uniform mat4 u_modelViewMatrix;
uniform mat4 u_projectionMatrix;
varying vec4 v_color;
void main() {
gl_Position = u_projectionMatrix * u_modelViewMatrix * a_position;
v_color = a_color;
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`;
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
const shaderProgram = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(shaderProgram);
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
-0.5, 0.5,
0.5, 0.5
]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
const colors = new Float32Array([
1, 0, 0, 1, // Red
0, 1, 0, 1, // Green
0, 0, 1, 1, // Blue
1, 1, 0, 1 // Yellow
]);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position');
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
const colorAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_color');
gl.enableVertexAttribArray(colorAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.vertexAttribPointer(colorAttributeLocation, 4, gl.FLOAT, false, 0, 0);
const modelViewMatrix = mat4.create();
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix, 45 * Math.PI / 180, canvas.width / canvas.height, 0.1, 100.0);
mat4.translate(modelViewMatrix, modelViewMatrix, [-0.0, 0.0, -6.0]);
const uModelViewMatrix = gl.getUniformLocation(shaderProgram, 'u_modelViewMatrix');
gl.uniformMatrix4fv(uModelViewMatrix, false, modelViewMatrix);
const uProjectionMatrix = gl.getUniformLocation(shaderProgram, 'u_projectionMatrix');
gl.uniformMatrix4fv(uProjectionMatrix, false, projectionMatrix);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
WebGL渲染管线
WebGL渲染管线由多个步骤组成,每个步骤都处理特定的图形渲染任务。
模型
模型包含几何数据(顶点、面)和材质信息。几何数据通常存储在缓冲区中,材质信息包含表面属性,如颜色、纹理等。
创建一个立方体模型:
javascript
const vertices = new Float32Array([
-1.0, -1.0, 1.0, // 顶点0
1.0, -1.0, 1.0, // 顶点1
-1.0, 1.0, 1.0, // 顶点2
1.0, 1.0, 1.0, // 顶点3
-1.0, -1.0, -1.0, // 顶点4
1.0, -1.0, -1.0, // 顶点5
-1.0, 1.0, -1.0, // 顶点6
1.0, 1.0, -1.0 // 顶点7
]);
const indices = new Uint16Array([
0, 1, 2, 1, 3, 2, // 前面
4, 5, 6, 5, 7, 6, // 后面
4, 5, 0, 5, 1, 0, // 底面
6, 7, 2, 7, 3, 2, // 顶面
4, 6, 0, 6, 2, 0, // 左面
1, 5, 3, 5, 7, 3 // 右面
]);
// 创建缓冲区并上传数据
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
视图
视图定义了相机的位置和方向。
设置相机视图矩阵:
const cameraPosition = [0, 0, 5];
const target = [0, 0, 0];
const up = [0, 1, 0];
const viewMatrix = mat4.create();
mat4.lookAt(viewMatrix, cameraPosition, target, up);
const uViewMatrix = gl.getUniformLocation(shaderProgram, 'u_viewMatrix');
gl.uniformMatrix4fv(uViewMatrix, false, viewMatrix);
投影
投影将3D空间映射到2D屏幕。
设置投影矩阵:
const fieldOfView = 45 * Math.PI / 180; // 弧度
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.1;
const zFar = 100.0;
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
const uProjectionMatrix = gl.getUniformLocation(shaderProgram, 'u_projectionMatrix');
gl.uniformMatrix4fv(uProjectionMatrix, false, projectionMatrix);
裁剪
裁剪剔除超出视口的几何体。
启用裁剪:
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
归一化设备坐标
将裁剪后的坐标转换为[-1, 1]范围。这个步骤在顶点着色器中完成。
顶点着色器代码:
attribute vec4 a_position;
uniform mat4 u_modelMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;
void main() {
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_position;
}
片段生成
片段着色器处理每个像素的颜色。
片段着色器代码:
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
颜色混合
应用透明度和混合规则。
启用颜色混合:
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
完整代码示例
<canvas id="glCanvas" width="640" height="480"></canvas>
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.log('WebGL not supported, falling back on experimental-webgl');
gl = canvas.getContext('experimental-webgl');
}
if (!gl) {
alert('Your browser does not support WebGL');
}
const vertexShaderSource = `
attribute vec4 a_position;
uniform mat4 u_modelMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;
void main() {
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_position;
}
`;
const fragmentShaderSource = `
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
`;
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
const shaderProgram = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(shaderProgram);
const vertices = new Float32Array([
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, 1.0, -1.0
]);
const indices = new Uint16Array([
0, 1, 2, 1, 3, 2,
4, 5, 6, 5, 7, 6,
4, 5, 0, 5, 1, 0,
6, 7, 2, 7, 3, 2,
4, 6, 0, 6, 2, 0,
1, 5, 3, 5, 7, 3
]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position');
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
const modelMatrix = mat4.create();
const viewMatrix = mat4.create();
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix, 45 * Math.PI / 180, canvas.width / canvas.height, 0.1, 100.0);
mat4.lookAt(viewMatrix, [0, 0, 5], [0, 0, 0], [0, 1, 0]);
const uModelMatrix = gl.getUniformLocation(shaderProgram, '
Three.js与WebGL直接交互
Three.js 是一个基于 WebGL 的 JavaScript 库,它封装了许多复杂的 WebGL 操作,提供了简洁的高级接口,简化了 3D 图形的创建和管理。
封装
Three.js 提供了高级接口,简化了 WebGL 的使用。例如,创建一个基本的 3D 场景,包括渲染器、场景、相机和物体。
创建一个基本的 Three.js 场景:
// 创建场景
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);
// 创建一个立方体
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 渲染函数
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
材质系统
Three.js 提供了多种预定义的材质类型,如 MeshBasicMaterial、MeshPhongMaterial 等,用于定义物体的外观。
使用不同材质:
// MeshBasicMaterial
const basicMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const basicCube = new THREE.Mesh(new THREE.BoxGeometry(), basicMaterial);
scene.add(basicCube);
// MeshPhongMaterial
const phongMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00, shininess: 100 });
const phongCube = new THREE.Mesh(new THREE.BoxGeometry(), phongMaterial);
scene.add(phongCube);
// 添加光源
const light = new THREE.PointLight(0xffffff);
light.position.set(10, 10, 10);
scene.add(light);
几何体
Three.js 提供了多种内置几何形状,如 BoxGeometry、SphereGeometry 等,用于快速创建常见的 3D 形状。
创建不同的几何体:
// BoxGeometry
const box = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial({ color: 0x00ff00 }));
scene.add(box);
// SphereGeometry
const sphere = new THREE.Mesh(new THREE.SphereGeometry(1, 32, 32), new THREE.MeshBasicMaterial({ color: 0x0000ff }));
scene.add(sphere);
// CylinderGeometry
const cylinder = new THREE.Mesh(new THREE.CylinderGeometry(1, 1, 2, 32), new THREE.MeshBasicMaterial({ color: 0xff00ff }));
scene.add(cylinder);
加载器
Three.js 提供了多种加载器用于加载模型和纹理,如 GLTFLoader、TextureLoader 等。
加载纹理:
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('path/to/texture.jpg');
const texturedMaterial = new THREE.MeshBasicMaterial({ map: texture });
const texturedCube = new THREE.Mesh(new THREE.BoxGeometry(), texturedMaterial);
scene.add(texturedCube);
加载 GLTF 模型:
const loader = new THREE.GLTFLoader();
loader.load('path/to/model.glb', function(gltf) {
scene.add(gltf.scene);
}, undefined, function(error) {
console.error(error);
});
动画系统
Three.js 提供了一个强大的动画系统,用于管理对象的运动和动画。
简单的动画:
const mixer = new THREE.AnimationMixer(scene);
const action = mixer.clipAction(gltf.animations[0]);
action.play();
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mixer.update(delta);
renderer.render(scene, camera);
}
animate();
完整代码
以下是一个完整的 Three.js 应用,展示了如何使用以上功能创建和渲染一个带有动画和纹理的 3D 场景。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Three.js Example</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
<script>
// 创建场景
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);
// 创建一个立方体
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 添加光源
const light = new THREE.PointLight(0xffffff);
light.position.set(10, 10, 10);
scene.add(light);
// 纹理加载器
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('path/to/texture.jpg');
// 纹理材质
const texturedMaterial = new THREE.MeshBasicMaterial({ map: texture });
const texturedCube = new THREE.Mesh(new THREE.BoxGeometry(), texturedMaterial);
scene.add(texturedCube);
// GLTF 模型加载
const loader = new THREE.GLTFLoader();
loader.load('path/to/model.glb', function(gltf) {
scene.add(gltf.scene);
const mixer = new THREE.AnimationMixer(gltf.scene);
const action = mixer.clipAction(gltf.animations[0]);
action.play();
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mixer.update(delta);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
}, undefined, function(error) {
console.error(error);
});
</script>
</body>
</html>
自定义着色器与Three.js材质的结合
Three.js 提供了 ShaderMaterial 类,用于创建和使用自定义着色器材质。通过 uniforms 和 attributes,可以将数据传递给着色器。
顶点着色器和片段着色器
顶点着色器:
const vertexShader = `
varying vec3 vPosition;
void main() {
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
片段着色器:
const fragmentShader = `
precision mediump float;
varying vec3 vPosition;
uniform vec3 uColor;
void main() {
gl_FragColor = vec4(vPosition * uColor, 1.0);
}
`;
创建自定义 ShaderMaterial
const uniforms = {
uColor: { value: new THREE.Color(0x00ff00) }
};
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
uniforms: uniforms
});
实例化几何体并应用材质
const geometry = new THREE.BoxGeometry();
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
完整代码
以下是一个完整的 Three.js 示例,展示了如何使用自定义着色器创建一个具有动态颜色的立方体。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Three.js Custom Shader Example</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// 创建场景
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);
// 自定义顶点着色器
const vertexShader = `
varying vec3 vPosition;
void main() {
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
// 自定义片段着色器
const fragmentShader = `
precision mediump float;
varying vec3 vPosition;
uniform vec3 uColor;
void main() {
gl_FragColor = vec4(vPosition * uColor, 1.0);
}
`;
// 定义 uniforms
const uniforms = {
uColor: { value: new THREE.Color(0x00ff00) }
};
// 创建 ShaderMaterial
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
uniforms: uniforms
});
// 创建几何体并应用材质
const geometry = new THREE.BoxGeometry();
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 渲染函数
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
利用WebGL2的新特性
WebGL2 新特性包括多边形偏移、浮点纹理、多个输出以及顶点数组对象的使用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGL2 Features</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
</head>
<body>
<canvas id="webgl-canvas" width="600" height="400"></canvas>
<script>
const glCanvas = document.getElementById("webgl-canvas");
const gl = glCanvas.getContext("webgl2");
if (!gl) {
console.error("WebGL2 not supported");
}
// 顶点着色器代码
const vertexShaderSource = `
#version 300 es
in vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`;
// 片段着色器代码
const fragmentShaderSource = `
#version 300 es
precision mediump float;
out vec4 fragColor1;
out vec4 fragColor2;
void main() {
fragColor1 = vec4(1.0, 0.0, 0.0, 1.0); // 红色
fragColor2 = vec4(0.0, 0.0, 1.0, 1.0); // 蓝色
}
`;
// 创建顶点着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
// 创建片段着色器
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// 创建着色器程序
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
// 创建并绑定顶点数组对象
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// 创建并绑定顶点缓冲区
const vertices = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
0.0, 0.5
]);
const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 设置顶点属性指针
const positionAttribLocation = gl.getAttribLocation(shaderProgram, "position");
gl.enableVertexAttribArray(positionAttribLocation);
gl.vertexAttribPointer(positionAttribLocation, 2, gl.FLOAT, false, 0, 0);
// 清空画布
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
</script>
</body>
</html>
-
多边形偏移:在这个简单的示例中,我们没有直接使用多边形偏移。多边形偏移主要用于解决 Z-缓冲冲突,避免多个面重叠时产生渲染错误。
-
浮点纹理:尽管在这个简单的示例中没有使用纹理,但是 WebGL2 支持将浮点数值存储在纹理中,这在一些科学计算和渲染中很有用。
-
多个输出:我们在片段着色器中定义了两个颜色输出 fragColor1 和 fragColor2。这样的片段着色器可以输出多个颜色,每个颜色对应一个输出。
-
顶点数组对象(VAO):我们使用 gl.createVertexArray() 创建了一个顶点数组对象,并使用 gl.bindVertexArray(vao) 绑定了它。VAO 可以保存顶点属性配置、缓冲区绑定状态等,可以提高渲染效率和代码可维护性。
WebGPU与Three.js的未来
WebGPU 是一个旨在提供低级、高性能 GPU 访问的 Web API,它允许开发者更直接地控制硬件资源,从而实现更高的性能和灵活性。虽然目前 WebGPU 还处于发展阶段,但其对 Three.js 未来的影响已经显现。
WebGPU API 的基本使用
WebGPU 通过 GPUDevice、GPUQueue、GPUBuffer、GPUShaderModule 等核心概念,提供了与 GPU 交互的接口。开发者需要手动管理内存、编排命令、创建管道等,这与 WebGL 自动化的渲染管线相比更为底层。
Three.js 适配 WebGPU
为了使 Three.js 能够利用 WebGPU,Three.js 社区和开发团队需要做的是:
- 开发适配层:构建一个抽象层,将现有的 Three.js API 映射到 WebGPU API,确保用户可以在不大幅修改现有代码的情况下尝试使用 WebGPU。
- 重写渲染逻辑:由于 WebGPU 的工作方式与 WebGL 大相径庭,Three.js 的内部渲染逻辑需要重新设计,以适应新的管道和资源管理方式。
- 支持新特性:WebGPU 提供了如计算着色器、纹理视图、可变速率着色等新功能,Three.js 应该考虑如何暴露这些功能给开发者。
性能提升
WebGPU 的设计目标之一就是提供比 WebGL 更高的性能,主要得益于:
- 更低的抽象层级:更直接地控制硬件,减少中间层的开销。
- 并行计算能力:通过计算着色器支持,可以在 GPU 上执行通用计算任务。
- 更好的资源管理:更细粒度的控制内存分配和数据传输,减少带宽占用。
未来趋势
- 逐步迁移:考虑到兼容性和现有内容,Three.js 可能会采取逐步过渡的方式,同时支持 WebGL 和 WebGPU,直到 WebGPU 成为主流。
- 社区驱动:随着 WebGPU 标准的成熟和浏览器支持的增加,Three.js 社区将扮演关键角色,通过反馈和贡献推动库的演进。
- 教育与示例:随着技术的推进,会有越来越多的教程、示例代码和最佳实践出现,帮助开发者理解如何在 Three.js 中有效利用 WebGPU。
WebGPU简介与展望
WebGPU 是一种新兴的 Web API,旨在为 Web 开发者提供低级别的图形和计算访问,以充分利用现代 GPU 的能力。它借鉴了桌面平台上的 Direct3D 12、Vulkan 和 Metal 等现代图形API,目的是在 Web 上实现与原生应用相似的性能和功能。
性能增强:
低级接口:WebGPU 提供更接近硬件的接口,允许开发者直接控制 GPU 资源,减少性能开销。 多线程支持:支持并发执行,允许多个独立的计算或图形任务并行运行。 高效资源管理:更好的内存管理和数据传输,减少带宽浪费。
安全性和稳定性:
沙箱环境:在浏览器的安全环境中运行,限制可能导致系统崩溃或恶意攻击的操作。 验证和错误检查:在编译和执行阶段进行严格验证,确保安全的GPU编程。
跨平台兼容性:
标准化:WebGPU 正在成为 W3C 标准,旨在跨浏览器实现一致性。 硬件支持:目标是覆盖广泛的硬件配置,包括移动设备和桌面系统。
高级图形功能:
可变速率着色:根据需要调整不同区域的渲染质量,优化性能。 计算着色器:支持通用GPU计算,可用于物理模拟、图像处理等。 纹理采样器:提供多种采样模式,支持更复杂的纹理操作。
Web 开发者工具:
调试和分析工具:随着 WebGPU 的发展,开发者工具也将跟进,帮助调试和优化 GPU 代码。
渐进增强:
向后兼容:Three.js 和其他框架可能会提供与 WebGL 兼容的层,以便在 WebGPU 不可用时回退。
未来应用:
增强现实和虚拟现实:WebGPU 的高性能可以支持更复杂的3D场景和交互。 游戏和图形密集型应用:提供类似原生应用的体验。 科学可视化:高效地处理大量数据,创建复杂的科学和工程可视化。
使用Three.js实验性支持WebGPU
Three.js 正在实验性地探索对 WebGPU 的支持,但请注意,此功能可能尚未稳定,且API和实现细节可能会随时间变化。以下是一个简化的示例,展示如何在Three.js中初步尝试使用WebGPU作为渲染后端。请注意,此代码示例假设你已安装了支持WebGPU的Three.js版本,并且你的浏览器支持WebGPU API。
import * as THREE from 'three';
import { WebGPURenderer } from 'three/examples/jsm/renderers/webgpu/WebGPURenderer.js';
// 场景设置
const scene = new THREE.Scene();
// 相机设置
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// 几何体和材质
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 网格
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 渲染器设置 - 使用WebGPU
const renderer = new WebGPURenderer(); // 实验性WebGPU渲染器
renderer.setClearColor(0x000000);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 渲染循环
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
上面展示了如何初始化一个Three.js场景,设置一个相机、立方体网格,并使用WebGPU作为渲染器。请注意以下几点:
- 你需要从Three.js的examples/jsm/renderers/webgpu目录引入WebGPURenderer.js,这是Three.js实验性支持WebGPU的渲染器实现。
- WebGPURenderer类替代了传统的WebGLRenderer,用于与WebGPU API交互。
- 由于WebGPU和WebGL的差异,某些Three.js特性可能在WebGPU渲染器中不可用或表现不同,特别是那些依赖于WebGL特定功能的部分。
- 在实际项目中使用前,请确保检查Three.js的最新文档和示例,因为WebGPU支持的状态和API可能会有更新。