如何在WebGL中将体素3D网格传入片元着色器?
体素3D网格传入WebGL片元着色器的几种实用方法
1. 3D纹理传递(最常用方案)
这是大规模体素场景的首选方式,直接将3D体素网格映射为WebGL的3D纹理,片元着色器通过纹理坐标采样对应体素数据。
JS端创建3D纹理
// 假设已获取WebGL上下文(gl变量) const voxelTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_3D, voxelTexture); // 体素数据:Uint8Array格式,每个体素存RGBA信息,尺寸为width*height*depth*4 const voxelData = new Uint8Array(width * height * depth * 4); // 这里编写填充voxelData的逻辑(比如根据体素类型赋值对应颜色) // 上传纹理数据到GPU gl.texImage3D( gl.TEXTURE_3D, 0, gl.RGBA, width, height, depth, 0, gl.RGBA, gl.UNSIGNED_BYTE, voxelData ); // 设置纹理参数:体素渲染用最近邻过滤避免模糊,边缘clamp防止越界采样 gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE); // 绑定到纹理单元并告知着色器 gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_3D, voxelTexture); const voxelTextureLoc = gl.getUniformLocation(program, "voxelTexture"); gl.uniform1i(voxelTextureLoc, 0); // 传递体素网格尺寸给着色器 const gridSizeLoc = gl.getUniformLocation(program, "voxelGridSize"); gl.uniform3f(gridSizeLoc, width, height, depth);
片元着色器采样
precision mediump float; uniform sampler3D voxelTexture; uniform vec3 voxelGridSize; varying vec3 worldPos; // 从顶点着色器传递的世界坐标 void main() { // 将世界坐标转换为0~1范围的纹理坐标 vec3 texCoord = worldPos / voxelGridSize; // 采样得到对应体素的RGBA数据 vec4 voxelInfo = texture(voxelTexture, texCoord); // 后续根据voxelInfo处理渲染(示例直接输出颜色) gl_FragColor = voxelInfo; }
注意:WebGL 1.0需要开启
OES_texture_3D扩展才能使用3D纹理,WebGL 2.0原生支持。
2. 2D纹理图集平铺切片(兼容WebGL 1.0)
如果需要兼容不支持3D纹理的环境,可以把3D体素网格按某一轴(比如Z轴)切片,将所有切片平铺到一张2D纹理上,通过计算坐标定位到对应切片的体素。
JS端创建2D图集纹理
const atlasTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, atlasTexture); // 每个切片尺寸为width*height,共depth个切片,2D纹理尺寸为width*(height*depth) const atlasWidth = width; const atlasHeight = height * depth; const flattenedData = new Uint8Array(atlasWidth * atlasHeight * 4); // 将3D体素数据平铺到2D数组(按Z轴切片) for (let z = 0; z < depth; z++) { for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const srcIndex = (x + y * width + z * width * height) * 4; const destIndex = (x + (y + z * height) * atlasWidth) * 4; flattenedData[destIndex] = voxelData[srcIndex]; flattenedData[destIndex+1] = voxelData[srcIndex+1]; flattenedData[destIndex+2] = voxelData[srcIndex+2]; flattenedData[destIndex+3] = voxelData[srcIndex+3]; } } } // 上传纹理数据 gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, atlasWidth, atlasHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, flattenedData ); // 设置纹理参数 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
片元着色器采样
precision mediump float; uniform sampler2D voxelAtlas; uniform vec3 voxelGridSize; varying vec3 worldPos; void main() { vec3 gridPos = floor(worldPos); // 计算切片索引和切片内的纹理坐标 float sliceIndex = gridPos.z; vec2 texCoord = vec2( gridPos.x / voxelGridSize.x, (gridPos.y + sliceIndex * voxelGridSize.y) / (voxelGridSize.y * voxelGridSize.z) ); vec4 voxelInfo = texture(voxelAtlas, texCoord); gl_FragColor = voxelInfo; }
3. SSBO/UBO结构化缓冲区(适合频繁更新或复杂数据)
如果体素数据需要频繁修改,或者要存储颜色之外的复杂属性(比如体素类型ID、物理属性),可以用WebGL 2.0的Shader Storage Buffer Object(SSBO)直接传递结构化数据。
JS端创建SSBO
const voxelSSBO = gl.createBuffer(); gl.bindBuffer(gl.SHADER_STORAGE_BUFFER, voxelSSBO); // 用uint存储体素类型ID,数据尺寸为width*height*depth const voxelTypeData = new Uint32Array(width * height * depth); // 编写填充体素类型数据的逻辑 gl.bufferData(gl.SHADER_STORAGE_BUFFER, voxelTypeData, gl.DYNAMIC_DRAW); // 绑定到绑定点0 gl.bindBufferBase(gl.SHADER_STORAGE_BUFFER, 0, voxelSSBO);
片元着色器访问
#version 300 es precision mediump float; // 定义SSBO结构 layout(std430, binding = 0) buffer VoxelTypeBuffer { uint voxelTypes[]; }; uniform vec3 voxelGridSize; varying vec3 worldPos; // 示例:根据体素类型映射颜色 vec3 getVoxelColor(uint type) { if (type == 1) return vec3(0.2, 0.8, 0.2); // 草地 if (type == 2) return vec3(0.6, 0.4, 0.2); // 泥土 return vec3(0.1); // 空气或未知 } void main() { vec3 gridPos = floor(worldPos); // 计算线性索引:x + y*width + z*width*height uint index = uint(gridPos.x) + uint(gridPos.y) * uint(voxelGridSize.x) + uint(gridPos.z) * uint(voxelGridSize.x) * uint(voxelGridSize.y); uint voxelType = voxelTypes[index]; gl_FragColor = vec4(getVoxelColor(voxelType), 1.0); }
注意:WebGL 1.0不支持SSBO,UBO支持有限,仅适合小规模数据传递。
4. 顶点属性编码(仅适合小范围体素)
如果只处理局部极小的体素网格,可以将体素数据编码到顶点的附加属性中,通过顶点插值传递到片元着色器。但这种方法局限性极大,不适合大规模场景,仅作补充参考。
内容的提问来源于stack exchange,提问作者Druide
相关产品推荐
相关产品推荐

