在现代前端开发中,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 绘图和交互功能。