You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 19:54:57