创建彩色立方体
我们将使用WebGL来生成一个简单的彩色立方体。立方体由6个面组成,每个面都有不同的颜色。
1. HTML结构
首先,创建一个基本的HTML页面,并添加一个<canvas>元素来显示渲染的3D图形。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/gl-matrix@2.4.0/dist/gl-matrix.js"></script>
<title>WebGL 彩色立方体</title>
<style>
body {
margin: 0;
overflow: hidden;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="webgl-canvas"></canvas>
<script type="module" src="app.js"></script>
</body>
</html>
2. WebGL初始化
在JavaScript中,我们首先要初始化WebGL上下文并设置一些基础的WebGL参数。
// 获取canvas元素
const canvas = document.getElementById('webgl-canvas');
// 初始化WebGL上下文
const gl = canvas.getContext('webgl');
// 如果浏览器不支持WebGL,给出提示
if (!gl) {
alert("WebGL初始化失败,您的浏览器不支持WebGL!");
}
// 设置canvas的尺寸
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
3. 顶点和颜色数据
为了绘制立方体,我们需要定义其顶点坐标和每个面对应的颜色数据。立方体有6个面,每个面由4个顶点构成(两个三角形组成一个面),立方体的每个顶点都需要配有颜色信息。
// 立方体顶点数据 (x, y, z, r, g, b)
const vertices = [
// 前面
-1, -1, 1, 1, 0, 0, // 0
1, -1, 1, 1, 0, 0, // 1
1, 1, 1, 1, 0, 0, // 2
-1, 1, 1, 1, 0, 0, // 3
// 后面
-1, -1, -1, 0, 1, 0, // 4
1, -1, -1, 0, 1, 0, // 5
1, 1, -1, 0, 1, 0, // 6
-1, 1, -1, 0, 1, 0, // 7
// 左面
-1, -1, -1, 0, 0, 1, // 8
-1, -1, 1, 0, 0, 1, // 9
-1, 1, 1, 0, 0, 1, // 10
-1, 1, -1, 0, 0, 1, // 11
// 右面
1, -1, 1, 1, 1, 0, // 12
1, -1, -1, 1, 1, 0, // 13
1, 1, -1, 1, 1, 0, // 14
1, 1, 1, 1, 1, 0, // 15
// 上面
-1, 1, 1, 0, 1, 1, // 16
1, 1, 1, 0, 1, 1, // 17
1, 1, -1, 0, 1, 1, // 18
-1, 1, -1, 0, 1, 1, // 19
// 下面
-1, -1, -1, 1, 0, 1, // 20
1, -1, -1, 1, 0, 1, // 21
1, -1, 1, 1, 0, 1, // 22
-1, -1, 1, 1, 0, 1, // 23
];
// 每个顶点的颜色
const colors = new Float32Array([
// 前面
1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0,
1.0, 1.0, 0.0, 1.0,
// 后面
0.0, 1.0, 1.0, 1.0,
1.0, 0.0, 1.0, 1.0,
1.0, 0.5, 0.0, 1.0,
0.5, 0.0, 0.5, 1.0
]);
// 每个面由2个三角形构成,每个三角形有3个顶点
const indices = [
0, 1, 2, 0, 2, 3, // 前面
4, 5, 6, 4, 6, 7, // 后面
8, 9, 10, 8, 10, 11, // 左面
12, 13, 14, 12, 14, 15, // 右面
16, 17, 18, 16, 18, 19, // 上面
20, 21, 22, 20, 22, 23 // 下面
];
4. 创建着色器
WebGL渲染图形需要着色器程序,分为顶点着色器(Vertex Shader)和片段着色器(Fragment Shader)。
// 顶点着色器代码
const vertexShaderSource = `
attribute vec4 a_position;
attribute vec4 a_color;
uniform mat4 u_matrix;
varying vec4 v_color;
void main() {
gl_Position = u_matrix * 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;
}
// 创建程序
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 vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexShader, fragmentShader);
// 使用着色器程序
gl.useProgram(program);
// 获取属性和 uniform 变量的位置
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
const colorAttributeLocation = gl.getAttribLocation(program, 'a_color');
const matrixUniformLocation = gl.getUniformLocation(program, 'u_matrix');
5. 设置缓冲区并传递数据
接下来,我们需要将顶点、颜色和索引数据传递到GPU。
// 创建顶点缓冲区
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// 创建索引缓冲区
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
// 启用顶点属性
gl.enableVertexAttribArray(positionAttributeLocation);
gl.enableVertexAttribArray(colorAttributeLocation);
// 设置顶点属性指针
const stride = 6 * Float32Array.BYTES_PER_ELEMENT;
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, stride, 0);
gl.vertexAttribPointer(colorAttributeLocation, 3, gl.FLOAT, false, stride, 3 * Float32Array.BYTES_PER_ELEMENT);
6. 设置投影和视图矩阵
为了实现3D效果,我们需要设置一个合适的投影和视图矩阵,确保立方体可以在屏幕中正确显示。
// 创建透视投影矩阵
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix, Math.PI / 4, canvas.width / canvas.height, 0.1, 100);
// 创建模型视图矩阵
const modelViewMatrix = mat4.create();
mat4.translate(modelViewMatrix, modelViewMatrix, [0.0, 0.0, -6.0]); // 将立方体向远离视角的方向移动
// 传递矩阵到着色器
const uProjectionMatrix = gl.getUniformLocation(program, 'u_projectionMatrix');
const uModelViewMatrix = gl.getUniformLocation(program, 'u_modelViewMatrix');
gl.uniformMatrix4fv(uProjectionMatrix, false, projectionMatrix);
gl.uniformMatrix4fv(uModelViewMatrix, false, modelViewMatrix);
7. 渲染循环
最后,编写渲染循环来不断更新画面。
// 设置视口大小
gl.viewport(0, 0, canvas.width, canvas.height);
// 清空画布
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 启用深度测试
gl.enable(gl.DEPTH_TEST);
// 创建旋转矩阵
let angle = 0;
function render() {
angle += 0.01;
const rotationMatrix = mat4.create();
mat4.rotate(rotationMatrix, rotationMatrix, angle, [1, 1, 0]);
// 合并模型视图矩阵和旋转矩阵
const combinedMatrix = mat4.create();
mat4.multiply(combinedMatrix, modelViewMatrix, rotationMatrix);
// 合并投影矩阵和模型视图矩阵
const finalMatrix = mat4.create();
mat4.multiply(finalMatrix, projectionMatrix, combinedMatrix);
// 传递矩阵到着色器
gl.uniformMatrix4fv(matrixUniformLocation, false, finalMatrix);
// 绘制立方体
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
render();