WebGL - 交互式立方体
WebGL - 交互式立方体示例
Section titled “WebGL - 交互式立方体示例”在上一示例的基础上,本章演示了如何使 3D 立方体具有交互性,允许根据鼠标移动进行旋转。
我们将为 canvas 添加事件监听器,以跟踪鼠标按下、移动和松开事件。当鼠标按钮按下并移动时,我们将计算 X 和 Y 位置的变化,并用此来更新立方体的旋转角度。
示例 – 绘制一个交互式立方体
Section titled “示例 – 绘制一个交互式立方体”以下代码在旋转立方体的示例基础上添加了鼠标控制功能。
<!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 上点击并拖动鼠标将根据鼠标移动旋转立方体。