如何使用 WebGL2 将大型对象数组发送到片段着色器?

问题描述 投票:0回答:1

对于大学作业,我使用具有 2D 上下文的 Canvas 用 JavaScript 编写了一个光线追踪器。为了使其更快,我将其从 2D 上下文转换为 WebGL2 上下文。

在原始代码中,为了处理网格,我将三角形数据(顶点位置、顶点法线)存储在一个大数组中,并将网格数据(第一个三角形索引、三角形数量、材质数据)存储在另一个数组中。然后,我在渲染时循环遍历数组以计算光线交叉点。

我希望能够在我的新实现中做同样的事情,但我无法理解它,因为统一数组可以容纳的元素数量是有限的。

有没有办法将像这样的大型对象数组发送到片段着色器?

目前,我只是使用一个小尺寸的统一数组来存储三角形数据。我尚未将网格添加到我的 WebGL 实现中。

我在碎片着色器中定义数组,如下所示:

uniform int NumSpheres;
uniform Sphere Spheres[MAX_SPHERES];

uniform int NumTris;
uniform Triangle Triangles[MAX_TRIS];

并用此函数计算射线交点:

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

    for (int i = 0; i < MAX_SPHERES; i++)
    {
        if (i == NumSpheres) break;

        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;
        }
    }

    for (int i = 0; i < MAX_TRIS; i++) 
    {
        if (i == NumTris) break;
        
        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;
}

我现在正在使用以下代码填充三角形的统一数组:

let trianglesLocations = [];
for (let i = 0; i < triangles.length; i++) {
    trianglesLocations.push({
        posA: gl.getUniformLocation(raytraceProgram, `Triangles[${i}].posA`),
        posB: gl.getUniformLocation(raytraceProgram, `Triangles[${i}].posB`),
        posC: gl.getUniformLocation(raytraceProgram, `Triangles[${i}].posC`),
        normalA: gl.getUniformLocation(raytraceProgram, `Triangles[${i}].normalA`),
        normalB: gl.getUniformLocation(raytraceProgram, `Triangles[${i}].normalB`),
        normalC: gl.getUniformLocation(raytraceProgram, `Triangles[${i}].normalC`),
    });
}

function setTriangles() {
    let numTrisLocation = gl.getUniformLocation(raytraceProgram, "NumTris");
    gl.uniform1i(numTrisLocation, triangles.length);
    for (let i = 0; i < triangles.length; i++) {
        gl.uniform3fv(trianglesLocations[i].posA, [triangles[i].posA.x, triangles[i].posA.y, triangles[i].posA.z]);
        gl.uniform3fv(trianglesLocations[i].posB, [triangles[i].posB.x, triangles[i].posB.y, triangles[i].posB.z]);
        gl.uniform3fv(trianglesLocations[i].posC, [triangles[i].posC.x, triangles[i].posC.y, triangles[i].posC.z]);
        gl.uniform3fv(trianglesLocations[i].normalA, [triangles[i].normalA.x, triangles[i].normalA.y, triangles[i].normalA.z]);
        gl.uniform3fv(trianglesLocations[i].normalB, [triangles[i].normalB.x, triangles[i].normalB.y, triangles[i].normalB.z]);
        gl.uniform3fv(trianglesLocations[i].normalC, [triangles[i].normalC.x, triangles[i].normalC.y, triangles[i].normalC.z]);
    }
}

但是统一数组大小的限制意味着我无法使用此方法来渲染每个可能有 >200 个三角形的多个网格。

当前代码完美呈现:

Render Result

我只需要一种方法来处理潜在的许多三角形。

javascript glsl raytracing webgl2
1个回答
0
投票

均匀数组非常小,因此,您很快就会达到这些限制。

使用纹理是用于编码许多向量/法线或其他数据的最常见的解决方法。纹理通常由 3 个 (RGB) 或 4 个分量 (RGBA) 组成,并允许存储 0 到 255 之间的 3 或 4 个值。此值范围对可以使用纹理传递的数据应用非常严格的限制。 0-255 可能足以正常编码(例如,对于凹凸贴图),但对于坐标来说还不够。

为了克服此限制,您可以为每个值使用 2 个字节。这将为您提供 0-65535 的值范围,但会占用纹理上两倍的空间。使用这种方法,您必须将 x、y 和 z 打包为 6 个字节,这将占用纹理的 1.5 个 RGBA 像素。对于 4096x4096,这种方法将允许您存储 2'796'202 像素,这是很多!然后,您可以使用制服发送其他详细信息,例如与纹理或其他补充数据一起传递了多少个三角形。您甚至可以使用单个 RGBA 来打包 32 位(4 字节)值,并且您仍然能够打包 1'398'101 个向量,这是很多!

唯一的缺点是,您必须通过乘法/移位分量将这些值从 RGBA 解压缩为单个 32 位数字,这对于大量顶点来说可能会很慢。

如果这有帮助,请告诉我。

© www.soinside.com 2019 - 2024. All rights reserved.