Skip to content

WebGL - 交互式立方体

在上一示例的基础上,本章演示了如何使 3D 立方体具有交互性,允许根据鼠标移动进行旋转。

我们将为 canvas 添加事件监听器,以跟踪鼠标按下、移动和松开事件。当鼠标按钮按下并移动时,我们将计算 X 和 Y 位置的变化,并用此来更新立方体的旋转角度。

以下代码在旋转立方体的示例基础上添加了鼠标控制功能。

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>WebGL Interactive Cube</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/3.3.0/gl-matrix-min.js"></script>
<style>canvas { border: 1px solid black; cursor: grab; } canvas:active { cursor: grabbing; }</style>
</head>
<body>
<canvas width="570" height="570" id="my_Canvas"></canvas>
<script>
const { mat4, vec3 } = glMatrix;
/*============= 创建 canvas ======================*/
const canvas = document.getElementById('my_Canvas');
const gl = canvas.getContext('webgl');
if (!gl) {
alert('WebGL not supported!');
throw new Error('WebGL not supported');
}
/*========== 定义并存储几何体 ==========*/
// (几何体和颜色定义 - 与旋转立方体示例相同)
const vertices = [
// 前面
-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,
// 顶面
-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, -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 faceColors = [
[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0],
[1.0, 1.0, 0.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0]
];
let colors = [];
faceColors.forEach(c => { colors = colors.concat(c, c, c, c); });
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
];
// --- 创建缓冲区(与旋转立方体示例相同) ---
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), 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);
/*=================== 着色器 =================== */
// (着色器源码、编译、链接 - 与旋转立方体示例相同)
const vsSource = ` attribute vec4 aVertexPosition; attribute vec3 aVertexColor; uniform mat4 uModelViewMatrix; uniform mat4 uProjectionMatrix; varying lowp vec3 vColor; void main(void) { gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; vColor = aVertexColor; }`;
const fsSource = ` varying lowp vec3 vColor; void main(void) { gl_FragColor = vec4(vColor, 1.0); }`;
function loadShader(gl, type, source) { /* ... (同前) ... */
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('Shader compile error:', gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null;
} return shader;
}
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('Shader link error:', gl.getProgramInfoLog(shaderProgram)); throw new Error('Shader link failed');
}
const programInfo = {
program: shaderProgram,
attribLocations: { /* ... (同前) ... */
vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'),
},
uniformLocations: { /* ... (同前) ... */
projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
},
};
/*======== 将 attribute 与缓冲区关联 =====*/
// (attribute 设置 - 与旋转立方体示例相同)
{
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(programInfo.attribLocations.vertexPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
}
{
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.vertexAttribPointer(programInfo.attribLocations.vertexColor, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(programInfo.attribLocations.vertexColor);
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.useProgram(programInfo.program);
/*==================== 矩阵设置 ====================== */
// (投影矩阵设置 - 与旋转立方体示例相同)
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 modelViewMatrix = mat4.create();
mat4.translate(modelViewMatrix, modelViewMatrix, [0.0, 0.0, -6.0]); // 将摄像机向后移动
// 用于跟踪基于鼠标拖动旋转的变量
let currentRotation = {
x: 0,
y: 0
};
/*================= 鼠标事件 ======================*/
let drag = false;
let lastMouseX = -1, lastMouseY = -1;
function mouseDown(event) {
drag = true;
lastMouseX = event.clientX;
lastMouseY = event.clientY;
event.preventDefault(); // 阻止文本选择
}
function mouseUp(event) {
drag = false;
}
function mouseMove(event) {
if (!drag) return;
const dx = event.clientX - lastMouseX;
const dy = event.clientY - lastMouseY;
// Update rotation angles (adjust sensitivity as needed)
currentRotation.y += dx * 0.01;
currentRotation.x += dy * 0.01;
// Clamp X rotation to avoid flipping upside down
currentRotation.x = Math.max(Math.min(currentRotation.x, Math.PI / 2), -Math.PI / 2);
lastMouseX = event.clientX;
lastMouseY = event.clientY;
}
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mouseout', mouseUp, false); // 如果鼠标离开 canvas 则停止拖动
canvas.addEventListener('mousemove', mouseMove, false);
/*=================== 绘制 =================== */
function render() {
gl.clearColor(0.5, 0.5, 0.5, 0.9);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 每帧将 modelViewMatrix 重置为基础平移
mat4.identity(modelViewMatrix);
mat4.translate(modelViewMatrix, modelViewMatrix, [0.0, 0.0, -6.0]);
// 应用鼠标控制的旋转
mat4.rotate(modelViewMatrix, modelViewMatrix, currentRotation.x, [1, 0, 0]); // 绕 X 轴旋转
mat4.rotate(modelViewMatrix, modelViewMatrix, currentRotation.y, [0, 1, 0]); // 绕 Y 轴旋转
// 设置着色器 uniform
gl.uniformMatrix4fv(programInfo.uniformLocations.projectionMatrix, false, projectionMatrix);
gl.uniformMatrix4fv(programInfo.uniformLocations.modelViewMatrix, false, modelViewMatrix);
// 绘制立方体
{
const vertexCount = 36;
const type = gl.UNSIGNED_SHORT;
const offset = 0;
gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
</script>
</body>
</html>

预期结果:

这段代码将绘制一个彩色立方体。在 canvas 上点击并拖动鼠标将根据鼠标移动旋转立方体。