在现代前端开发中,HTML5 的画布元素(<canvas>)为我们提供了强大的绘图能力,不仅可以进行 2D 绘图,借助 WebGL 还能实现 3D 绘图。而 3D 场景的交互则能让用户与 3D 世界进行更自然、更有趣的互动,提升用户体验。本文将深入探讨如何在 HTML5 画布上实现 3D 绘图,并添加交互功能。
在 HTML5 中,我们主要使用 WebGL 来进行 3D 绘图。WebGL 是一种基于 OpenGL ES 2.0 的 JavaScript API,它允许我们在网页上创建和操作 3D 图形。下面是一个简单的创建 3D 立方体的示例代码:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>3D Cube</title><style>canvas {display: block;width: 100%;height: 100%;}</style></head><body><canvas id="glCanvas"></canvas><script>// 获取 canvas 元素const canvas = document.getElementById('glCanvas');// 获取 WebGL 上下文const gl = canvas.getContext('webgl');if (!gl) {alert('Unable to initialize WebGL. Your browser or machine may not support it.');return;}// 顶点着色器代码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);}`;// 创建着色器函数function createShader(gl, type, source) {const shader = gl.createShader(type);gl.shaderSource(shader, source);gl.compileShader(shader);const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);if (success) {return shader;}console.log(gl.getShaderInfoLog(shader));gl.deleteShader(shader);}// 创建着色器程序函数function createProgram(gl, vertexShader, fragmentShader) {const program = gl.createProgram();gl.attachShader(program, vertexShader);gl.attachShader(program, fragmentShader);gl.linkProgram(program);const success = gl.getProgramParameter(program, gl.LINK_STATUS);if (success) {return program;}console.log(gl.getProgramInfoLog(program));gl.deleteProgram(program);}// 创建顶点和片段着色器const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);// 创建着色器程序const program = createProgram(gl, vertexShader, fragmentShader);// 获取属性位置const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');// 创建缓冲区const positionBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);// 立方体顶点数据const positions = [-0.5, -0.5, 0.5,0.5, -0.5, 0.5,0.5, 0.5, 0.5,-0.5, 0.5, 0.5,-0.5, -0.5, -0.5,0.5, -0.5, -0.5,0.5, 0.5, -0.5,-0.5, 0.5, -0.5];gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);// 调整画布大小function resizeCanvasToDisplaySize(canvas) {const displayWidth = canvas.clientWidth;const displayHeight = canvas.clientHeight;if (canvas.width!== displayWidth || canvas.height!== displayHeight) {canvas.width = displayWidth;canvas.height = displayHeight;}}resizeCanvasToDisplaySize(canvas);gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);// 启用属性gl.enableVertexAttribArray(positionAttributeLocation);const size = 3;const type = gl.FLOAT;const normalize = false;const stride = 0;const offset = 0;gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);// 使用着色器程序gl.useProgram(program);// 绘制立方体const primitiveType = gl.TRIANGLES;const offsetDraw = 0;const count = 36;gl.drawArrays(primitiveType, offsetDraw, count);</script></body></html>
canvas.getContext('webgl') 获取 WebGL 上下文,用于后续的 3D 绘图操作。gl.createBuffer() 创建缓冲区,并使用 gl.bufferData() 将顶点数据存储到缓冲区中。gl.drawArrays() 方法绘制图形。实现 3D 场景的交互通常涉及到鼠标和键盘事件的处理。下面是一个添加鼠标交互功能的示例代码,允许用户通过鼠标拖动来旋转立方体:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>3D Cube Interaction</title><style>canvas {display: block;width: 100%;height: 100%;}</style></head><body><canvas id="glCanvas"></canvas><script>const canvas = document.getElementById('glCanvas');const gl = canvas.getContext('webgl');if (!gl) {alert('Unable to initialize WebGL. Your browser or machine may not support it.');return;}// 顶点着色器代码const vertexShaderSource = `attribute vec4 a_position;uniform mat4 u_modelViewMatrix;uniform mat4 u_projectionMatrix;void main() {gl_Position = u_projectionMatrix * u_modelViewMatrix * a_position;}`;// 片段着色器代码const fragmentShaderSource = `precision mediump float;void main() {gl_FragColor = vec4(1, 0, 0, 1);}`;// 创建着色器和着色器程序function createShader(gl, type, source) {const shader = gl.createShader(type);gl.shaderSource(shader, source);gl.compileShader(shader);const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);if (success) {return shader;}console.log(gl.getShaderInfoLog(shader));gl.deleteShader(shader);}function createProgram(gl, vertexShader, fragmentShader) {const program = gl.createProgram();gl.attachShader(program, vertexShader);gl.attachShader(program, fragmentShader);gl.linkProgram(program);const success = gl.getProgramParameter(program, gl.LINK_STATUS);if (success) {return program;}console.log(gl.getProgramInfoLog(program));gl.deleteProgram(program);}const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);const program = createProgram(gl, vertexShader, fragmentShader);// 获取属性和统一变量位置const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');const modelViewMatrixUniformLocation = gl.getUniformLocation(program, 'u_modelViewMatrix');const projectionMatrixUniformLocation = gl.getUniformLocation(program, 'u_projectionMatrix');// 创建缓冲区const positionBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);// 立方体顶点数据const positions = [-0.5, -0.5, 0.5,0.5, -0.5, 0.5,0.5, 0.5, 0.5,-0.5, 0.5, 0.5,-0.5, -0.5, -0.5,0.5, -0.5, -0.5,0.5, 0.5, -0.5,-0.5, 0.5, -0.5];gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);// 调整画布大小function resizeCanvasToDisplaySize(canvas) {const displayWidth = canvas.clientWidth;const displayHeight = canvas.clientHeight;if (canvas.width!== displayWidth || canvas.height!== displayHeight) {canvas.width = displayWidth;canvas.height = displayHeight;}}resizeCanvasToDisplaySize(canvas);gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);// 启用属性gl.enableVertexAttribArray(positionAttributeLocation);const size = 3;const type = gl.FLOAT;const normalize = false;const stride = 0;const offset = 0;gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);// 创建投影矩阵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 isDragging = false;let lastX = 0;let lastY = 0;let rotationX = 0;let rotationY = 0;// 鼠标事件处理函数canvas.addEventListener('mousedown', (event) => {isDragging = true;lastX = event.clientX;lastY = event.clientY;});canvas.addEventListener('mousemove', (event) => {if (isDragging) {const dx = (event.clientX - lastX) * 0.01;const dy = (event.clientY - lastY) * 0.01;rotationX += dy;rotationY += dx;lastX = event.clientX;lastY = event.clientY;}});canvas.addEventListener('mouseup', () => {isDragging = false;});// 渲染函数function render() {// 清除画布gl.clearColor(0.0, 0.0, 0.0, 1.0);gl.clearDepth(1.0);gl.enable(gl.DEPTH_TEST);gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);// 更新模型视图矩阵mat4.identity(modelViewMatrix);mat4.translate(modelViewMatrix, modelViewMatrix, [0.0, 0.0, -6.0]);mat4.rotateX(modelViewMatrix, modelViewMatrix, rotationX);mat4.rotateY(modelViewMatrix, modelViewMatrix, rotationY);// 设置统一变量gl.useProgram(program);gl.uniformMatrix4fv(projectionMatrixUniformLocation, false, projectionMatrix);gl.uniformMatrix4fv(modelViewMatrixUniformLocation, false, modelViewMatrix);// 绘制立方体const primitiveType = gl.TRIANGLES;const offsetDraw = 0;const count = 36;gl.drawArrays(primitiveType, offsetDraw, count);// 请求下一帧动画requestAnimationFrame(render);}// 开始渲染render();</script><script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/3.3.0/gl-matrix-min.js"></script></body></html>
u_modelViewMatrix 和 u_projectionMatrix 统一变量,用于处理模型视图变换和投影变换。mousedown、mousemove 和 mouseup 事件,实现鼠标拖动旋转立方体的功能。通过本文的介绍,我们了解了如何在 HTML5 画布上使用 WebGL 进行 3D 绘图,并添加交互功能。以下是一个简单的总结表格:
| 功能 | 描述 |
|---|---|
| 3D 绘图 | 使用 WebGL 创建 3D 图形,包括顶点着色器、片段着色器、缓冲区等 |
| 3D 场景交互 | 通过监听鼠标和键盘事件,实现 3D 场景的交互,如旋转、缩放等 |
希望本文能帮助你更好地理解和实现 HTML5 画布上的 3D 绘图和交互功能。