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

如何在WebGL2中向片元着色器传递大型对象数组?

解决WebGL2光线追踪中大量三角形数据传递问题

Uniform数组的大小限制确实是硬伤,WebGL2提供了**着色器存储缓冲区对象(SSBO)**来处理这类大体积结构化数据,完全能满足你几百甚至上千三角形的需求,下面是具体实现方案:

1. 替换Uniform数组为SSBO

在片元着色器中,把原来的uniform Triangle Triangles[MAX_TRIS]替换为SSBO定义:

// 保持你的Triangle结构体(如果用vec4可以避免对齐问题,后面会说明)
struct Triangle {
  vec3 posA;
  vec3 posB;
  vec3 posC;
  vec3 normalA;
  vec3 normalB;
  vec3 normalC;
};

// 定义SSBO,binding=0对应JS端的绑定点
layout(std430, binding = 0) buffer TrianglesBuffer {
  Triangle triangles[]; // 动态长度数组
};
uniform int NumTris; // 保留这个变量表示实际三角形数量

如果想简化内存对齐的处理,推荐把结构体里的vec3改成vec4,着色器中使用时取xyz分量即可:

struct Triangle {
  vec4 posA;
  vec4 posB;
  vec4 posC;
  vec4 normalA;
  vec4 normalB;
  vec4 normalC;
};

2. JavaScript端创建并填充SSBO

// 创建SSBO对象
const triangleSSBO = gl.createBuffer();
gl.bindBuffer(gl.SHADER_STORAGE_BUFFER, triangleSSBO);

// 按vec4版本计算数据长度:每个Triangle有6个vec4,共24个浮点数
const data = new Float32Array(triangles.length * 24);

triangles.forEach((tri, index) => {
  const offset = index * 24;
  // 填充posA(vec4,第四个分量填0即可)
  data[offset] = tri.posA.x;
  data[offset+1] = tri.posA.y;
  data[offset+2] = tri.posA.z;
  data[offset+3] = 0;
  // 填充posB
  data[offset+4] = tri.posB.x;
  data[offset+5] = tri.posB.y;
  data[offset+6] = tri.posB.z;
  data[offset+7] = 0;
  // 填充posC
  data[offset+8] = tri.posC.x;
  data[offset+9] = tri.posC.y;
  data[offset+10] = tri.posC.z;
  data[offset+11] = 0;
  // 填充normalA
  data[offset+12] = tri.normalA.x;
  data[offset+13] = tri.normalA.y;
  data[offset+14] = tri.normalA.z;
  data[offset+15] = 0;
  // 填充normalB
  data[offset+16] = tri.normalB.x;
  data[offset+17] = tri.normalB.y;
  data[offset+18] = tri.normalB.z;
  data[offset+19] = 0;
  // 填充normalC
  data[offset+20] = tri.normalC.x;
  data[offset+21] = tri.normalC.y;
  data[offset+22] = tri.normalC.z;
  data[offset+23] = 0;
});

// 将数据写入SSBO,STATIC_DRAW表示数据不会频繁修改
gl.bufferData(gl.SHADER_STORAGE_BUFFER, data, gl.STATIC_DRAW);
// 绑定到binding=0,对应着色器中的绑定点
gl.bindBufferBase(gl.SHADER_STORAGE_BUFFER, 0, triangleSSBO);

3. 修改着色器的光线追踪逻辑

把原来遍历Uniform数组的代码改成遍历SSBO的动态数组:

HitInfo TraceRay(Ray ray) 
{
    HitInfo closestHit;
    closestHit.dst = 1000000000000.0;

    // 球体部分逻辑不变(如果球体数量也多,同样可以换成SSBO)
    for (int i = 0; i < NumSpheres; i++)
    {
        Sphere sphere = Spheres[i];
        HitInfo hitInfo = RaySphere(ray, sphere.position, sphere.radius);

        if (hitInfo.didHit && hitInfo.dst < closestHit.dst) 
        {
            closestHit = hitInfo;
            closestHit.material = sphere.material;
        }
    }

    // 遍历SSBO中的三角形数组
    for (int i = 0; i < NumTris; i++) 
    {
        Triangle triangle = triangles[i];
        HitInfo hitInfo = RayTriangle(ray, triangle);
        if (hitInfo.didHit && hitInfo.dst < closestHit.dst) {
            closestHit = hitInfo;
            closestHit.material.colour = vec3(1, 1, 1);
        }
    }

    return closestHit;
}

4. 复用原有网格数据结构

你原来的网格数据(首个三角形索引、三角形数量、材质)也可以用另一个SSBO存储,完全复用原逻辑:

// 定义Mesh结构体
struct Mesh {
  int firstTriIndex;
  int triCount;
  Material material;
};

// 第二个SSBO,binding=1
layout(std430, binding = 1) buffer MeshesBuffer {
  Mesh meshes[];
};
uniform int NumMeshes;

然后修改TraceRay中的三角形遍历逻辑,改为先遍历网格再遍历对应三角形:

// 替换原来的三角形遍历代码
for (int m = 0; m < NumMeshes; m++) {
  Mesh mesh = meshes[m];
  // 遍历该网格下的所有三角形
  for (int i = mesh.firstTriIndex; i < mesh.firstTriIndex + mesh.triCount; i++) {
    Triangle tri = triangles[i];
    HitInfo hitInfo = RayTriangle(ray, tri);
    if (hitInfo.didHit && hitInfo.dst < closestHit.dst) {
      closestHit = hitInfo;
      closestHit.material = mesh.material; // 使用网格的材质
    }
  }
}

为什么选择SSBO?

Uniform数组的大小受限于硬件的GL_MAX_UNIFORM_BLOCK_SIZE(通常只有几KB到几十KB),而SSBO可以支持几百MB甚至更大的内存,完全满足你处理大量三角形的需求,同时访问效率和Uniform相当。

内容的提问来源于stack exchange,提问作者Killjoy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 04:55:55