4.4 WebGL 性能优化与最佳实践


文档摘要

4.4 WebGL 性能优化与最佳实践 (Performance Optimization and Best Practices) 4.4 WebGL 性能优化与最佳实践 (Performance Optimization and Best Practices) 4.4.1 理解 WebGL 渲染管线与性能瓶颈 在深入优化技巧之前,理解 WebGL 的渲染管线至关重要。渲染管线描述了从 3D 模型数据到最终像素在屏幕上显示所经历的一系列处理步骤。理解管线有助于我们识别性能瓶颈并针对性地进行优化。 渲染管线主要阶段: CPU 端 - 应用逻辑与数据准备: 应用逻辑 (Application Logic): JavaScript 代码运行,处理用户输入、动画更新、场景管理等。

4.4 WebGL 性能优化与最佳实践 (Performance Optimization and Best Practices)

4.4 WebGL 性能优化与最佳实践 (Performance Optimization and Best Practices)

4.4.1 理解 WebGL 渲染管线与性能瓶颈

在深入优化技巧之前,理解 WebGL 的渲染管线至关重要。渲染管线描述了从 3D 模型数据到最终像素在屏幕上显示所经历的一系列处理步骤。理解管线有助于我们识别性能瓶颈并针对性地进行优化。

渲染管线主要阶段:

  1. CPU 端 - 应用逻辑与数据准备:

    • 应用逻辑 (Application Logic): JavaScript 代码运行,处理用户输入、动画更新、场景管理等。

    • 数据准备 (Data Preparation): 将顶点数据、纹理数据、uniform 变量等上传到 GPU。

  2. GPU 端 - 顶点着色器 (Vertex Shader):

    • 处理顶点数据,进行模型变换、视图变换、投影变换等,计算顶点位置。
  3. GPU 端 - 图元装配 (Primitive Assembly):

    • 将顶点着色器输出的顶点组装成图元 (点、线、三角形)。
  4. GPU 端 - 光栅化 (Rasterization):

    • 将图元转换为片元 (Fragment),即像素的候选者,并进行插值计算 (颜色、纹理坐标等)。
  5. GPU 端 - 片元着色器 (Fragment Shader):

    • 为每个片元计算最终颜色,考虑光照、纹理、材质等因素。
  6. GPU 端 - 测试与混合 (Tests and Blending):

    • 执行深度测试、模板测试、Alpha 混合等,决定片元是否写入帧缓冲区。
  7. 帧缓冲区 (Framebuffer):

    • 存储最终渲染结果,显示在屏幕上。

性能瓶颈分析:

WebGL 应用的性能瓶颈可能出现在管线的任何阶段,常见的瓶颈包括:

  • CPU 瓶颈: 复杂的 JavaScript 计算、大量的对象创建和销毁、低效的数据结构和算法等。

  • 顶点着色器瓶颈: 复杂的顶点计算、大量的顶点数据处理。

  • 片元着色器瓶颈: 复杂的像素计算、大量的纹理采样、复杂的光照模型。

  • 带宽瓶颈: CPU 到 GPU 的数据传输带宽限制、纹理带宽限制、帧缓冲区带宽限制。

  • 状态切换瓶颈: 频繁的 WebGL 状态切换 (例如,切换着色器程序、纹理、缓冲区等) 会带来性能开销。

  • 绘制调用瓶颈: 过多的绘制调用 (draw calls) 会增加 CPU 和 GPU 的开销。

4.4.2 优化绘制调用 (Draw Call Optimization)

绘制调用 (Draw Call) 是 CPU 向 GPU 发送渲染指令的过程。每次绘制调用都有一定的开销,包括状态切换、数据准备等。减少绘制调用是 WebGL 性能优化的关键策略之一。

4.4.2.1 批处理 (Batching)

批处理是将多个具有相同渲染状态 (例如,相同的着色器程序、纹理、材质等) 的几何体合并到一个绘制调用中。这样可以显著减少绘制调用的次数。

代码实践 - 批处理:

假设我们有多个立方体需要渲染,它们都使用相同的材质和着色器。我们可以将它们的顶点数据合并到一个缓冲区中进行绘制。

// 假设 cubesData 是一个包含多个立方体顶点数据的数组 const cubesData = [/* ... 多个立方体的顶点数据 ... */]; // 合并所有立方体的顶点数据到一个数组 let combinedVertices = []; for (const cubeData of cubesData) { combinedVertices = combinedVertices.concat(cubeData.vertices); } // 创建顶点缓冲区 const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(combinedVertices), gl.STATIC_DRAW); // 设置顶点属性指针 (假设每个立方体顶点数据格式相同) const positionAttributeLocation = gl.getAttribLocation(program, 'a_position'); gl.enableVertexAttribArray(positionAttributeLocation); gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0); // 设置 uniform 变量 (例如,模型矩阵,可以使用 uniform 数组或纹理传递) // ... // 绘制合并后的几何体 gl.drawArrays(gl.TRIANGLES, 0, combinedVertices.length / 3);

内容详解:

  • 合并顶点数据: 将多个几何体的顶点数据合并到一个大的顶点缓冲区中。

  • 单次绘制调用: 使用 gl.drawArraysgl.drawElements 进行一次绘制调用,渲染所有合并的几何体。

  • Uniform 变量管理: 对于每个几何体可能不同的 uniform 变量 (例如,模型矩阵),可以使用 uniform 数组或纹理来传递,避免为每个几何体设置 uniform。

  • 适用场景: 适用于渲染大量具有相似渲染状态的静态几何体。

4.4.2.2 实例化 (Instancing)

实例化是一种更高级的批处理技术,它允许使用一次绘制调用渲染同一几何体的多个实例,每个实例可以有不同的变换、颜色等属性。实例化可以大幅减少绘制调用,尤其适用于渲染大量重复的几何体,例如树木、草地、粒子等。

代码实践 - 实例化:

// 顶点数据 (单个立方体) const cubeVertices = [/* ... 单个立方体的顶点数据 ... */]; // 实例变换矩阵数据 (假设 instancesTransforms 是一个包含多个变换矩阵的数组) const instancesTransforms = [/* ... 多个实例的变换矩阵 ... */]; // 创建顶点缓冲区 (单个立方体) const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubeVertices), gl.STATIC_DRAW); // 创建实例变换矩阵缓冲区 const transformBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, transformBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(instancesTransforms.flat()), gl.STATIC_DRAW); // 设置顶点属性指针 const positionAttributeLocation = gl.getAttribLocation(program, 'a_position'); gl.enableVertexAttribArray(positionAttributeLocation); gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0); // 设置实例变换矩阵属性指针 (使用顶点属性除法器扩展 ANGLE_instanced_arrays) const matrixAttributeLocation = gl.getAttribLocation(program, 'a_modelMatrix'); for (let i = 0; i < 4; ++i) { // 4x4 矩阵需要 4 个属性 const attribLocation = matrixAttributeLocation + i; gl.enableVertexAttribArray(attribLocation); gl.bindBuffer(gl.ARRAY_BUFFER, transformBuffer); const offset = i * 4 * Float32Array.BYTES_PER_ELEMENT; // 每个矩阵 4 行,每行 4 个 float gl.vertexAttribPointer(attribLocation, 4, gl.FLOAT, false, 16 * Float32Array.BYTES_PER_ELEMENT, offset); gl.vertexAttribDivisor(attribLocation, 1); // 每个实例更新一次属性 } // 绘制实例化几何体 (使用 ANGLE_instanced_arrays 扩展) const ext = gl.getExtension('ANGLE_instanced_arrays'); ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, cubeVertices.length / 3, instancesTransforms.length);

内容详解:

  • 实例数据缓冲区: 为每个实例的属性 (例如,变换矩阵) 创建单独的缓冲区。

  • 顶点属性除法器: 使用 vertexAttribDivisor 扩展 (ANGLE_instanced_arrays) 设置顶点属性的更新频率。divisor 为 1 表示每个实例更新一次属性。

  • 实例化绘制调用: 使用 drawArraysInstancedANGLEdrawElementsInstancedANGLE 进行实例化绘制调用,指定实例数量。

  • 适用场景: 适用于渲染大量相同几何体的实例,例如植被、粒子系统、人群模拟等。

Mermaid 图 - 实例化流程:

4.4.3 几何数据优化 (Geometry Optimization)

几何数据的复杂程度直接影响顶点着色器的性能和数据传输带宽。优化几何数据可以提高渲染效率。

4.4.3.1 减少顶点数量

  • 模型简化 (Model Simplification): 对于远处或细节要求不高的物体,可以使用低模版本,减少顶点数量。

  • 网格优化 (Mesh Optimization): 使用网格优化算法 (例如,边折叠、顶点聚类) 减少网格的顶点和面片数量,同时保持模型外观。

  • 曲面细分控制 (Tessellation Control): 动态调整模型的曲面细分级别,根据距离或细节需求减少顶点数量。

4.4.3.2 使用索引缓冲区 (Index Buffer)

索引缓冲区可以复用顶点数据,减少顶点缓冲区的大小。对于共享顶点的网格模型,使用索引缓冲区可以显著减少顶点数量,提高渲染效率和降低内存占用。

代码实践 - 索引缓冲区:

// 顶点数据 (包含重复顶点) const vertices = [ // 三角形 1 -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.5, 0.5, 0.0, // 三角形 2 (复用顶点) -0.5, -0.5, 0.0, // 复用顶点 0 0.5, 0.5, 0.0, // 复用顶点 2 -0.5, 0.5, 0.0 // 新顶点 ]; // 索引数据 (指定顶点索引顺序) const indices = [ 0, 1, 2, // 三角形 1 0, 2, 5 // 三角形 2 ]; // 创建顶点缓冲区 const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), 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 positionAttributeLocation = gl.getAttribLocation(program, 'a_position'); gl.enableVertexAttribArray(positionAttributeLocation); gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0); // 使用索引缓冲区进行绘制 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); // 绑定索引缓冲区 gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0); // 使用 drawElements

内容详解:

  • 顶点数据复用: 顶点缓冲区可以包含重复的顶点数据。

  • 索引数据: 索引缓冲区存储顶点索引,指定顶点在顶点缓冲区中的顺序,用于组成三角形或其他图元。

  • gl.drawElements: 使用 gl.drawElements 进行绘制,使用索引缓冲区指定顶点顺序。

  • 优势: 减少顶点缓冲区大小,降低内存占用和数据传输带宽,提高渲染效率。

4.4.3.3 视锥体裁剪 (Frustum Culling) 和 遮挡剔除 (Occlusion Culling)

  • 视锥体裁剪: 剔除位于相机视锥体之外的物体,不进行渲染。

  • 遮挡剔除: 剔除被其他物体遮挡而不可见的物体,不进行渲染。

这些剔除技术可以减少不必要的渲染操作,提高渲染效率。视锥体裁剪通常在 CPU 端进行,遮挡剔除可以在 CPU 或 GPU 端进行 (例如,使用 WebGL 扩展 EXT_occlusion_query_boolean)。

4.4.4 纹理优化 (Texture Optimization)

纹理是 WebGL 应用中重要的资源,纹理的加载、上传和采样都会影响性能。优化纹理使用可以提高渲染效率。

4.4.4.1 纹理压缩 (Texture Compression)

使用纹理压缩格式 (例如,ASTC, ETC, DXT) 可以显著减小纹理文件大小和 GPU 显存占用,降低纹理带宽,提高纹理采样效率。

代码实践 - 纹理压缩 (使用 ASTC 格式和 WEBGL_compressed_texture_astc 扩展):

const ext = gl.getExtension('WEBGL_compressed_texture_astc'); if (ext) { // 加载 ASTC 纹理数据 (假设 astcTextureData 是加载的压缩纹理数据) const astcTextureData = loadASTCTextureData(); // 创建纹理对象 const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); // 上传压缩纹理数据 (使用压缩纹理格式常量) gl.compressedTexImage2D( gl.TEXTURE_2D, 0, ext.COMPRESSED_RGBA_ASTC_4x4_KHR, // ASTC 4x4 压缩格式 width, height, 0, astcTextureData ); // 设置纹理参数 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); gl.generateMipmap(gl.TEXTURE_2D); } else { console.warn('ASTC texture compression not supported.'); // 使用未压缩纹理加载流程 }

内容详解:

  • 压缩纹理扩展: 检查浏览器是否支持压缩纹理扩展 (例如,WEBGL_compressed_texture_astc, WEBGL_compressed_texture_etc, WEBGL_compressed_texture_s3tc)。

  • 压缩纹理格式: 使用扩展提供的压缩纹理格式常量 (例如,ext.COMPRESSED_RGBA_ASTC_4x4_KHR)。

  • gl.compressedTexImage2D: 使用 gl.compressedTexImage2D 上传压缩纹理数据。

  • 优势: 减小纹理文件大小,加速下载,降低 GPU 显存占用,降低纹理带宽,提高纹理采样效率。

4.4.4.2 Mipmapping

Mipmapping 是一种多级纹理技术,为每个纹理创建多个分辨率的版本 (mipmap levels)。根据物体与相机的距离,WebGL 会自动选择合适的 mipmap level 进行纹理采样,避免远处物体纹理采样失真和性能浪费。

代码实践 - Mipmapping (默认开启):

// 创建纹理对象 const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); // 上传纹理数据 (loadImageTexture 是加载图像并上传纹理数据的函数) loadImageTexture('texture.png', texture, gl); // 设置纹理参数 (开启 mipmapping) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); // 使用 mipmapping gl.generateMipmap(gl.TEXTURE_2D); // 生成 mipmap levels

内容详解:

  • gl.generateMipmap: 调用 gl.generateMipmap 生成 mipmap levels。

  • TEXTURE_MIN_FILTER: 设置 TEXTURE_MIN_FILTERgl.LINEAR_MIPMAP_LINEARgl.NEAREST_MIPMAP_NEAREST 等 mipmapping 采样模式。

  • 优势: 提高远处物体纹理采样质量,减少远处物体纹理采样的性能开销。

4.4.4.3 纹理图集 (Texture Atlas)

纹理图集是将多个小纹理合并到一个大的纹理中。使用纹理图集可以减少纹理切换次数,提高渲染效率。

代码实践 - 纹理图集:

// 加载纹理图集 (假设 textureAtlasImage 是加载的纹理图集图像) const textureAtlasImage = loadImage('texture_atlas.png'); // 创建纹理对象 const textureAtlas = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, textureAtlas); // 上传纹理图集图像数据 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureAtlasImage); // 设置纹理参数 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); gl.generateMipmap(gl.TEXTURE_2D); // 在着色器中使用纹理坐标偏移来访问纹理图集中的不同区域 // uniform vec4 u_textureRects[NUM_TEXTURES]; // 纹理区域矩形信息 (x, y, width, height) // 片元着色器代码示例: /* void main() { vec2 atlasUV = v_texCoord; vec4 textureRect = u_textureRects[textureIndex]; // 获取纹理区域矩形 atlasUV = textureRect.xy + atlasUV * textureRect.zw; // 计算纹理图集 UV 坐标 gl_FragColor = texture2D(u_textureAtlas, atlasUV); } */

内容详解:

  • 合并纹理: 将多个小纹理合并到一个大的纹理图集中。

  • 纹理坐标偏移: 在着色器中使用纹理坐标偏移和缩放来访问纹理图集中的不同区域。

  • u_textureRects: 可以使用 uniform 数组或其他方式传递每个小纹理在纹理图集中的矩形区域信息。

  • 优势: 减少纹理切换次数,提高渲染效率,适用于渲染大量使用不同小纹理的物体,例如 UI 元素、字体、图标等。

4.4.5 着色器优化 (Shader Optimization)

着色器代码运行在 GPU 上,着色器的性能直接影响渲染效率。优化着色器代码可以提高渲染性能。

4.4.5.1 减少计算复杂度

  • 简化算法: 使用更简单的数学运算和算法,避免复杂的计算。

  • 避免不必要的计算: 只计算必要的变量和操作,移除冗余代码。

  • 预计算: 将一些可以在 CPU 端预先计算的值传递给着色器,减少 GPU 端的计算量。

4.4.5.2 减少纹理采样

  • 减少纹理采样次数: 尽量减少片元着色器中的纹理采样次数,尤其是昂贵的纹理采样操作。

  • 使用低分辨率纹理: 对于远处或细节要求不高的物体,可以使用低分辨率纹理。

  • 纹理缓存 (Texture Cache): 如果多个片元需要采样相同的纹理区域,可以考虑使用纹理缓存技术,避免重复采样。

4.4.5.3 优化控制流

  • 减少分支 (Branching): GPU 对分支语句的执行效率较低,尽量减少着色器中的分支语句 (例如,if, else, for 循环)。可以使用 step, smoothstep, mix 等内置函数代替分支语句。

  • 避免动态循环 (Dynamic Loops): 动态循环的性能开销较大,尽量使用静态循环或展开循环。

4.4.5.4 数据类型选择

  • 使用低精度数据类型: 如果精度要求不高,可以使用低精度数据类型 (例如,lowp, mediump),可以提高计算速度和降低内存带宽。

  • 避免不必要的类型转换: 减少数据类型转换操作。

代码实践 - 着色器优化示例 (简化计算):

优化前 - 复杂计算:

// 片元着色器 precision mediump float; varying vec3 v_normal; varying vec3 v_surfacePosition; uniform vec3 u_lightDirection; uniform vec3 u_ambientColor; uniform vec3 u_diffuseColor; uniform vec3 u_specularColor; uniform float u_shininess; void main() { vec3 normal = normalize(v_normal); vec3 lightDir = normalize(u_lightDirection); vec3 viewDir = normalize(-v_surfacePosition); // 假设相机位置在原点 vec3 reflectDir = reflect(-lightDir, normal); // 环境光 vec3 ambient = u_ambientColor; // 漫反射光 float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = u_diffuseColor * diff; // 镜面反射光 float spec = pow(max(dot(viewDir, reflectDir), 0.0), u_shininess); vec3 specular = u_specularColor * spec; gl_FragColor = vec4(ambient + diffuse + specular, 1.0); }

优化后 - 简化计算 (使用半兰伯特光照模型简化漫反射计算):

// 片元着色器 precision mediump float; varying vec3 v_normal; uniform vec3 u_lightDirection; uniform vec3 u_ambientColor; uniform vec3 u_diffuseColor; void main() { vec3 normal = normalize(v_normal); vec3 lightDir = normalize(u_lightDirection); // 环境光 vec3 ambient = u_ambientColor; // 半兰伯特漫反射光 (简化计算) float diff = dot(normal, lightDir) * 0.5 + 0.5; // 半兰伯特公式 vec3 diffuse = u_diffuseColor * diff; gl_FragColor = vec4(ambient + diffuse, 1.0); // 移除镜面反射 }

内容详解:

  • 简化光照模型: 使用更简单的光照模型 (例如,半兰伯特光照模型) 代替复杂的光照模型 (例如,Phong 光照模型)。

  • 移除不必要的计算: 移除镜面反射计算,减少着色器计算量。

  • 性能提升: 简化计算可以显著提高片元着色器的性能,尤其是在片元数量较多的场景中。

4.4.6 状态切换优化 (State Change Optimization)

WebGL 渲染需要设置各种状态 (例如,着色器程序、纹理、缓冲区、uniform 变量等)。状态切换有一定的开销,频繁的状态切换会降低渲染效率。

4.4.6.1 减少状态切换次数

  • 状态排序 (State Sorting): 对渲染对象进行排序,按照渲染状态进行分组,尽量在同一组对象中使用相同的渲染状态,减少状态切换次数。

  • 批量渲染 (Batch Rendering): 如前所述的批处理和实例化技术,可以将多个具有相同渲染状态的对象合并到一个绘制调用中,减少状态切换次数。

  • 纹理图集和 Uniform 缓冲区: 使用纹理图集和 Uniform 缓冲区可以减少纹理和 Uniform 变量的切换次数。

4.4.6.2 缓存状态 (State Caching)

在应用中缓存当前 WebGL 状态,在设置新的状态之前,先检查是否与缓存状态相同,如果相同则避免重复设置。

代码实践 - 状态缓存示例 (简化示例):

let cachedProgram = null; let cachedTexture = null; function renderObject(program, texture, geometry) { // 着色器程序切换 if (program !== cachedProgram) { gl.useProgram(program); cachedProgram = program; } // 纹理切换 if (texture !== cachedTexture) { gl.bindTexture(gl.TEXTURE_2D, texture); cachedTexture = texture; } // 设置几何数据和 uniform 变量 ... setGeometry(geometry); setUniforms(program); // 绘制调用 gl.drawArrays(gl.TRIANGLES, 0, geometry.vertexCount); }

内容详解:

  • 缓存变量: 使用变量 (例如,cachedProgram, cachedTexture) 缓存当前 WebGL 状态。

  • 状态检查: 在设置新的状态之前,先检查新的状态是否与缓存状态相同。

  • 避免重复设置: 只有当新的状态与缓存状态不同时,才进行 WebGL 状态设置操作。

  • 优势: 减少不必要的状态切换,提高渲染效率。

4.4.7 CPU-GPU 数据传输优化

CPU 和 GPU 之间的数据传输是相对较慢的操作,优化数据传输可以提高渲染效率。

4.4.7.1 减少数据传输量

  • 几何数据优化: 如前所述,减少顶点数量、使用索引缓冲区等可以减少几何数据传输量.

  • 纹理压缩: 使用纹理压缩可以减小纹理数据传输量。

  • 只传输必要数据: 只传输渲染所需的必要数据,避免传输冗余数据。

4.4.7.2 批量数据上传

  • bufferSubData: 使用 gl.bufferSubData 更新缓冲区的部分数据,而不是重新上传整个缓冲区。

  • 纹理子区域更新: 使用 gl.texSubImage2D 更新纹理的子区域,而不是重新上传整个纹理。

代码实践 - bufferSubData 示例 (动态更新顶点位置):

// 创建动态顶点缓冲区 (gl.DYNAMIC_DRAW) const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertexDataSize, gl.DYNAMIC_DRAW); // 预先分配缓冲区空间 function updateVertexPositions(newPositions) { // 更新顶点缓冲区的部分数据 (使用 bufferSubData) gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(newPositions)); // 从偏移量 0 开始更新 }

内容详解:

  • gl.DYNAMIC_DRAW: 创建缓冲区时使用 gl.DYNAMIC_DRAW 提示 GPU 缓冲区数据会频繁更新。

  • gl.bufferSubData: 使用 gl.bufferSubData 更新缓冲区的部分数据,第一个参数是目标缓冲区类型,第二个参数是更新数据的偏移量 (字节为单位),第三个参数是要更新的数据。


发布者: 作者: 转发
评论区 (0)
U