仅使用无依赖库的WebGL,如何在场景地面创建无限网格?
纯WebGL实现3D场景无限地面网格
核心思路
不用生成海量顶点,而是通过片元着色器计算实现无限网格:利用相机位置动态偏移网格坐标,只在视野范围内绘制网格线,同时根据透视距离调整线宽,模拟无限延伸的效果。
步骤1:准备全屏四边形(顶点数据)
我们不需要复杂的3D模型,用一个覆盖整个视口的全屏四边形(2个三角形)即可,所有网格逻辑都在片元着色器里处理:
// 全屏四边形顶点数据(裁剪空间坐标) const quadVertices = new Float32Array([ -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, ]);
步骤2:编写顶点着色器
顶点着色器的作用是把裁剪空间坐标传递给片元着色器,同时提供坐标转换的基础数据:
attribute vec2 a_position; varying vec2 v_clipPos; void main() { v_clipPos = a_position; gl_Position = vec4(a_position, 0.0, 1.0); }
步骤3:编写片元着色器(核心逻辑)
这部分是实现无限网格的关键,主要完成坐标转换、网格计算和颜色混合:
precision mediump float; varying vec2 v_clipPos; uniform mat4 u_invViewProj; // 视图投影矩阵的逆矩阵 uniform vec3 u_cameraPos; // 相机世界位置 uniform vec2 u_gridSize; // 网格尺寸(主网格/次网格) uniform vec3 u_gridColor; // 次网格颜色 uniform vec3 u_thickColor; // 主网格线颜色 void main() { // 把裁剪空间坐标转为世界空间坐标 vec4 worldPos = u_invViewProj * vec4(v_clipPos, 0.0, 1.0); worldPos /= worldPos.w; // 固定在y=0的地面层,同时根据相机位置偏移网格,实现随动效果 vec2 gridPos = worldPos.xz - mod(u_cameraPos.xz, u_gridSize.y); // 计算次网格线的像素距离,根据透视调整线宽 vec2 gridLine = abs(fract(gridPos / u_gridSize.y) - 0.5) * u_gridSize.y; float lineWidth = 0.02 * length(worldPos.xyz - u_cameraPos); float line = min(gridLine.x, gridLine.y); // 计算主网格线 vec2 thickGridLine = abs(fract(gridPos / u_gridSize.x) - 0.5) * u_gridSize.x; float thickLine = min(thickGridLine.x, thickGridLine.y); // 混合主/次网格颜色 vec3 color = mix(u_gridColor, u_thickColor, step(thickLine, lineWidth)); color = mix(vec3(1.0), color, step(line, lineWidth)); // 远处网格渐隐,增强真实感 float alpha = 1.0 - smoothstep(50.0, 100.0, length(worldPos.xyz - u_cameraPos)); gl_FragColor = vec4(color, alpha); }
步骤4:渲染逻辑
每次渲染时更新相机相关参数,绘制全屏四边形即可:
// 初始化时获取uniform变量位置 const invViewProjLoc = gl.getUniformLocation(program, 'u_invViewProj'); const cameraPosLoc = gl.getUniformLocation(program, 'u_cameraPos'); const gridSizeLoc = gl.getUniformLocation(program, 'u_gridSize'); const gridColorLoc = gl.getUniformLocation(program, 'u_gridColor'); const thickColorLoc = gl.getUniformLocation(program, 'u_thickColor'); // 帧渲染函数 function render() { // 计算视图投影矩阵的逆矩阵(需自行实现矩阵逆运算,可参考标准3D矩阵公式) const viewProj = calculateViewProjectionMatrix(camera); const invViewProj = invertMatrix(viewProj); // 传入uniform参数 gl.uniformMatrix4fv(invViewProjLoc, false, invViewProj); gl.uniform3f(cameraPosLoc, camera.pos.x, camera.pos.y, camera.pos.z); gl.uniform2f(gridSizeLoc, 10.0, 1.0); // 主网格10单位,次网格1单位 gl.uniform3f(gridColorLoc, 0.7, 0.7, 0.7); // 次网格浅灰 gl.uniform3f(thickColorLoc, 0.5, 0.5, 0.5); // 主网格深灰 // 绘制全屏四边形 gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer); gl.enableVertexAttribArray(posAttribLoc); gl.vertexAttribPointer(posAttribLoc, 2, gl.FLOAT, false, 0, 0); gl.drawArrays(gl.TRIANGLES, 0, 6); }
关键注意事项
- 矩阵运算:需自行实现视图投影矩阵及其逆矩阵的计算,这是WebGL 3D场景的基础能力。
- 深度测试:开启
gl.enable(gl.DEPTH_TEST),确保网格被场景中其他物体正确遮挡。 - 性能优化:全屏四边形仅6个顶点,性能开销极低,完全适配无限网格的需求。
内容的提问来源于stack exchange,提问作者thiago kaique
相关产品推荐
相关产品推荐

