• 主页

  • 投资

  • IT

    🔥
  • 设计

  • 销售

  • 共240篇

    前端 - HTML5

关闭

返回栏目

关闭

返回前端 - HTML5栏目

117 - 画布元素 - 3D 绘图 - 3D 场景的交互

作者:

贺及楼

成为作者

更新日期:2025-02-27 12:27:43

前端 - HTML5 《画布元素 - 3D 绘图 - 3D 场景的交互》

引言

在现代前端开发中,HTML5 的画布元素(<canvas>)为我们提供了强大的绘图能力,不仅可以进行 2D 绘图,借助 WebGL 还能实现 3D 绘图。而 3D 场景的交互则能让用户与 3D 世界进行更自然、更有趣的互动,提升用户体验。本文将深入探讨如何在 HTML5 画布上实现 3D 绘图,并添加交互功能。

3D 绘图基础

在 HTML5 中,我们主要使用 WebGL 来进行 3D 绘图。WebGL 是一种基于 OpenGL ES 2.0 的 JavaScript API,它允许我们在网页上创建和操作 3D 图形。下面是一个简单的创建 3D 立方体的示例代码:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>3D Cube</title>
  7. <style>
  8. canvas {
  9. display: block;
  10. width: 100%;
  11. height: 100%;
  12. }
  13. </style>
  14. </head>
  15. <body>
  16. <canvas id="glCanvas"></canvas>
  17. <script>
  18. // 获取 canvas 元素
  19. const canvas = document.getElementById('glCanvas');
  20. // 获取 WebGL 上下文
  21. const gl = canvas.getContext('webgl');
  22. if (!gl) {
  23. alert('Unable to initialize WebGL. Your browser or machine may not support it.');
  24. return;
  25. }
  26. // 顶点着色器代码
  27. const vertexShaderSource = `
  28. attribute vec4 a_position;
  29. void main() {
  30. gl_Position = a_position;
  31. }
  32. `;
  33. // 片段着色器代码
  34. const fragmentShaderSource = `
  35. precision mediump float;
  36. void main() {
  37. gl_FragColor = vec4(1, 0, 0, 1);
  38. }
  39. `;
  40. // 创建着色器函数
  41. function createShader(gl, type, source) {
  42. const shader = gl.createShader(type);
  43. gl.shaderSource(shader, source);
  44. gl.compileShader(shader);
  45. const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  46. if (success) {
  47. return shader;
  48. }
  49. console.log(gl.getShaderInfoLog(shader));
  50. gl.deleteShader(shader);
  51. }
  52. // 创建着色器程序函数
  53. function createProgram(gl, vertexShader, fragmentShader) {
  54. const program = gl.createProgram();
  55. gl.attachShader(program, vertexShader);
  56. gl.attachShader(program, fragmentShader);
  57. gl.linkProgram(program);
  58. const success = gl.getProgramParameter(program, gl.LINK_STATUS);
  59. if (success) {
  60. return program;
  61. }
  62. console.log(gl.getProgramInfoLog(program));
  63. gl.deleteProgram(program);
  64. }
  65. // 创建顶点和片段着色器
  66. const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
  67. const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
  68. // 创建着色器程序
  69. const program = createProgram(gl, vertexShader, fragmentShader);
  70. // 获取属性位置
  71. const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
  72. // 创建缓冲区
  73. const positionBuffer = gl.createBuffer();
  74. gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  75. // 立方体顶点数据
  76. const positions = [
  77. -0.5, -0.5, 0.5,
  78. 0.5, -0.5, 0.5,
  79. 0.5, 0.5, 0.5,
  80. -0.5, 0.5, 0.5,
  81. -0.5, -0.5, -0.5,
  82. 0.5, -0.5, -0.5,
  83. 0.5, 0.5, -0.5,
  84. -0.5, 0.5, -0.5
  85. ];
  86. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
  87. // 调整画布大小
  88. function resizeCanvasToDisplaySize(canvas) {
  89. const displayWidth = canvas.clientWidth;
  90. const displayHeight = canvas.clientHeight;
  91. if (canvas.width!== displayWidth || canvas.height!== displayHeight) {
  92. canvas.width = displayWidth;
  93. canvas.height = displayHeight;
  94. }
  95. }
  96. resizeCanvasToDisplaySize(canvas);
  97. gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  98. // 启用属性
  99. gl.enableVertexAttribArray(positionAttributeLocation);
  100. const size = 3;
  101. const type = gl.FLOAT;
  102. const normalize = false;
  103. const stride = 0;
  104. const offset = 0;
  105. gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);
  106. // 使用着色器程序
  107. gl.useProgram(program);
  108. // 绘制立方体
  109. const primitiveType = gl.TRIANGLES;
  110. const offsetDraw = 0;
  111. const count = 36;
  112. gl.drawArrays(primitiveType, offsetDraw, count);
  113. </script>
  114. </body>
  115. </html>

代码解释

  1. 获取 WebGL 上下文:通过 canvas.getContext('webgl') 获取 WebGL 上下文,用于后续的 3D 绘图操作。
  2. 创建着色器和着色器程序:WebGL 使用顶点着色器和片段着色器来处理图形的顶点和颜色。顶点着色器负责处理顶点的位置,片段着色器负责处理每个像素的颜色。
  3. 创建缓冲区:缓冲区用于存储顶点数据,通过 gl.createBuffer() 创建缓冲区,并使用 gl.bufferData() 将顶点数据存储到缓冲区中。
  4. 绘制图形:使用 gl.drawArrays() 方法绘制图形。

3D 场景的交互

实现 3D 场景的交互通常涉及到鼠标和键盘事件的处理。下面是一个添加鼠标交互功能的示例代码,允许用户通过鼠标拖动来旋转立方体:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>3D Cube Interaction</title>
  7. <style>
  8. canvas {
  9. display: block;
  10. width: 100%;
  11. height: 100%;
  12. }
  13. </style>
  14. </head>
  15. <body>
  16. <canvas id="glCanvas"></canvas>
  17. <script>
  18. const canvas = document.getElementById('glCanvas');
  19. const gl = canvas.getContext('webgl');
  20. if (!gl) {
  21. alert('Unable to initialize WebGL. Your browser or machine may not support it.');
  22. return;
  23. }
  24. // 顶点着色器代码
  25. const vertexShaderSource = `
  26. attribute vec4 a_position;
  27. uniform mat4 u_modelViewMatrix;
  28. uniform mat4 u_projectionMatrix;
  29. void main() {
  30. gl_Position = u_projectionMatrix * u_modelViewMatrix * a_position;
  31. }
  32. `;
  33. // 片段着色器代码
  34. const fragmentShaderSource = `
  35. precision mediump float;
  36. void main() {
  37. gl_FragColor = vec4(1, 0, 0, 1);
  38. }
  39. `;
  40. // 创建着色器和着色器程序
  41. function createShader(gl, type, source) {
  42. const shader = gl.createShader(type);
  43. gl.shaderSource(shader, source);
  44. gl.compileShader(shader);
  45. const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  46. if (success) {
  47. return shader;
  48. }
  49. console.log(gl.getShaderInfoLog(shader));
  50. gl.deleteShader(shader);
  51. }
  52. function createProgram(gl, vertexShader, fragmentShader) {
  53. const program = gl.createProgram();
  54. gl.attachShader(program, vertexShader);
  55. gl.attachShader(program, fragmentShader);
  56. gl.linkProgram(program);
  57. const success = gl.getProgramParameter(program, gl.LINK_STATUS);
  58. if (success) {
  59. return program;
  60. }
  61. console.log(gl.getProgramInfoLog(program));
  62. gl.deleteProgram(program);
  63. }
  64. const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
  65. const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
  66. const program = createProgram(gl, vertexShader, fragmentShader);
  67. // 获取属性和统一变量位置
  68. const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
  69. const modelViewMatrixUniformLocation = gl.getUniformLocation(program, 'u_modelViewMatrix');
  70. const projectionMatrixUniformLocation = gl.getUniformLocation(program, 'u_projectionMatrix');
  71. // 创建缓冲区
  72. const positionBuffer = gl.createBuffer();
  73. gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  74. // 立方体顶点数据
  75. const positions = [
  76. -0.5, -0.5, 0.5,
  77. 0.5, -0.5, 0.5,
  78. 0.5, 0.5, 0.5,
  79. -0.5, 0.5, 0.5,
  80. -0.5, -0.5, -0.5,
  81. 0.5, -0.5, -0.5,
  82. 0.5, 0.5, -0.5,
  83. -0.5, 0.5, -0.5
  84. ];
  85. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
  86. // 调整画布大小
  87. function resizeCanvasToDisplaySize(canvas) {
  88. const displayWidth = canvas.clientWidth;
  89. const displayHeight = canvas.clientHeight;
  90. if (canvas.width!== displayWidth || canvas.height!== displayHeight) {
  91. canvas.width = displayWidth;
  92. canvas.height = displayHeight;
  93. }
  94. }
  95. resizeCanvasToDisplaySize(canvas);
  96. gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  97. // 启用属性
  98. gl.enableVertexAttribArray(positionAttributeLocation);
  99. const size = 3;
  100. const type = gl.FLOAT;
  101. const normalize = false;
  102. const stride = 0;
  103. const offset = 0;
  104. gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);
  105. // 创建投影矩阵
  106. const fieldOfView = 45 * Math.PI / 180;
  107. const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  108. const zNear = 0.1;
  109. const zFar = 100.0;
  110. const projectionMatrix = mat4.create();
  111. mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
  112. // 创建模型视图矩阵
  113. const modelViewMatrix = mat4.create();
  114. mat4.translate(modelViewMatrix, modelViewMatrix, [0.0, 0.0, -6.0]);
  115. // 鼠标交互变量
  116. let isDragging = false;
  117. let lastX = 0;
  118. let lastY = 0;
  119. let rotationX = 0;
  120. let rotationY = 0;
  121. // 鼠标事件处理函数
  122. canvas.addEventListener('mousedown', (event) => {
  123. isDragging = true;
  124. lastX = event.clientX;
  125. lastY = event.clientY;
  126. });
  127. canvas.addEventListener('mousemove', (event) => {
  128. if (isDragging) {
  129. const dx = (event.clientX - lastX) * 0.01;
  130. const dy = (event.clientY - lastY) * 0.01;
  131. rotationX += dy;
  132. rotationY += dx;
  133. lastX = event.clientX;
  134. lastY = event.clientY;
  135. }
  136. });
  137. canvas.addEventListener('mouseup', () => {
  138. isDragging = false;
  139. });
  140. // 渲染函数
  141. function render() {
  142. // 清除画布
  143. gl.clearColor(0.0, 0.0, 0.0, 1.0);
  144. gl.clearDepth(1.0);
  145. gl.enable(gl.DEPTH_TEST);
  146. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  147. // 更新模型视图矩阵
  148. mat4.identity(modelViewMatrix);
  149. mat4.translate(modelViewMatrix, modelViewMatrix, [0.0, 0.0, -6.0]);
  150. mat4.rotateX(modelViewMatrix, modelViewMatrix, rotationX);
  151. mat4.rotateY(modelViewMatrix, modelViewMatrix, rotationY);
  152. // 设置统一变量
  153. gl.useProgram(program);
  154. gl.uniformMatrix4fv(projectionMatrixUniformLocation, false, projectionMatrix);
  155. gl.uniformMatrix4fv(modelViewMatrixUniformLocation, false, modelViewMatrix);
  156. // 绘制立方体
  157. const primitiveType = gl.TRIANGLES;
  158. const offsetDraw = 0;
  159. const count = 36;
  160. gl.drawArrays(primitiveType, offsetDraw, count);
  161. // 请求下一帧动画
  162. requestAnimationFrame(render);
  163. }
  164. // 开始渲染
  165. render();
  166. </script>
  167. <script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/3.3.0/gl-matrix-min.js"></script>
  168. </body>
  169. </html>

代码解释

  1. 添加统一变量:在顶点着色器中添加 u_modelViewMatrixu_projectionMatrix 统一变量,用于处理模型视图变换和投影变换。
  2. 鼠标事件处理:通过监听 mousedownmousemovemouseup 事件,实现鼠标拖动旋转立方体的功能。
  3. 更新模型视图矩阵:在渲染函数中,根据鼠标拖动的偏移量更新模型视图矩阵,实现立方体的旋转效果。

总结

通过本文的介绍,我们了解了如何在 HTML5 画布上使用 WebGL 进行 3D 绘图,并添加交互功能。以下是一个简单的总结表格:

功能 描述
3D 绘图 使用 WebGL 创建 3D 图形,包括顶点着色器、片段着色器、缓冲区等
3D 场景交互 通过监听鼠标和键盘事件,实现 3D 场景的交互,如旋转、缩放等

希望本文能帮助你更好地理解和实现 HTML5 画布上的 3D 绘图和交互功能。