Metal使用计算着色器模拟几何着色器

时间:2018-05-27 22:52:30

标签: macos shader metal

我试图在Metal中实施体素锥跟踪。算法中的一个步骤是使用几何着色器对几何体素进行体素化。 Metal没有几何着色器,因此我正在研究使用计算着色器模拟它们。我将顶点缓冲区传入计算着色器,执行几何着色器通常会执行的操作,并将结果写入输出缓冲区。我还向间接缓冲区添加了绘图命令。我使用输出缓冲区作为顶点着色器的顶点缓冲区。这工作正常,但我的顶点需要两倍的内存,一个用于顶点缓冲区,一个用于输出缓冲区。有没有办法直接将计算着色器的输出传递给顶点着色器而不将其存储在中间缓冲区中?我不需要保存计算着色器的输出缓冲区的内容。我只需要将结果提供给顶点着色器。

这可能吗?感谢

修改

基本上,我试图从glsl模拟以下着色器:

#version 450

layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;

layout(location = 0) in vec3 in_position[];
layout(location = 1) in vec3 in_normal[];
layout(location = 2) in vec2 in_uv[];

layout(location = 0) out vec3 out_position;
layout(location = 1) out vec3 out_normal;
layout(location = 2) out vec2 out_uv;

void main()
{
    vec3 p = abs(cross(in_position[1] - in_position[0], in_position[2] - in_position[0]));

    for (uint i = 0; i < 3; ++i)
    {
        out_position = in_position[i];
        out_normal = in_normal[i];
        out_uv = in_uv[i];

        if (p.z > p.x && p.z > p.y)
        {
            gl_Position = vec4(out_position.x, out_position.y, 0, 1);
        }
        else if (p.x > p.y && p.x > p.z)
        {
            gl_Position = vec4(out_position.y, out_position.z, 0, 1);
        }
        else
        {
            gl_Position = vec4(out_position.x, out_position.z, 0, 1);
        }

        EmitVertex();
    }

    EndPrimitive();
}

对于每个三角形,我需要在这些新位置输出一个带顶点的三角形。三角形顶点来自顶点缓冲区,并使用索引缓冲区绘制。我还计划添加将进行保守光栅化的代码(只需稍微增加三角形的大小),但这里没有显示。目前我在Metal计算着色器中所做的是使用索引缓冲区来获取顶点,在上面的几何着色器中执行相同的代码,然后在另一个缓冲区中输出新的顶点,然后我用它来绘制。

1 个答案:

答案 0 :(得分:2)

这取决于几何着色器需要做什么,这是一种非常推测的可能性。

我认为你可以做到这一点&#34;倒退&#34;仅使用顶点着色器而没有单独的计算着色器,代价是GPU上的冗余工作。您将绘制,就好像您有几何着色器的输出图元的所有输出顶点的缓冲区。不过,你实际上并不会拥有它。您将构建一个顶点着色器,用于在飞行中计算它们。

因此,在应用程序代码中,计算输出基元的数量,从而计算为给定计数的输入基元生成的输出顶点的数量。使用那么多顶点绘制输出基元类型。

提供一个缓冲区,输出顶点数据作为此绘图的输入。

您将提供原始索引缓冲区和原始顶点缓冲区作为该绘制的顶点着色器的输入。着色器将根据其输出原语的顶点ID以及该原语的哪个顶点(例如,分别为三角形,vid / 3vid % 3)来计算。从输出原语ID,它将计算在原始几何着色器中生成它的输入原语。

着色器将从索引缓冲区中查找该输入图元的索引,然后从顶点缓冲区中查找顶点数据。 (例如,这对三角形列表与三角形条带之间的区别很敏感。)它会将任何前几何体着色器顶点着色应用于该数据。然后,它将执行几何计算的一部分,该部分有助于识别的输出图元的识别顶点。计算出输出顶点数据后,可以应用所需的任何后几何着色器顶点着色(?)。结果是它将返回的结果。

如果几何着色器可以为每个输入图元生成可变数量的输出图元,那么,至少你有一个最大数。因此,您可以绘制最大潜在的顶点数,以获得最大可能的输出基元数。顶点着色器可以进行必要的计算,以确定几何着色器实际上是否会产生该原语。如果没有,顶点着色器可以通过将其放置在平截头体之外或使用输出顶点数据的[[clip_distance]]属性来安排整个基元被剪掉。

这避免了将生成的基元存储在缓冲区中。但是,它会使GPU重​​复执行一些前几何着色器顶点着色器和几何着色器计算。当然,它会被并行化,但可能仍然比你现在所做的要慢。此外,它可能会破坏一些关于获取索引和顶点数据的优化,这些优化可以通过更常规的顶点着色器实现。

以下是几何着色器的转换示例:

#include <metal_stdlib>
using namespace metal;

struct VertexIn {
    // maybe need packed types here depending on your vertex buffer layout
    // can't use [[attribute(n)]] for these because Metal isn't doing the vertex lookup for us
    float3 position;
    float3 normal;
    float2 uv;
};

struct VertexOut {
    float3 position;
    float3 normal;
    float2 uv;
    float4 new_position [[position]];
};


vertex VertexOut foo(uint vid [[vertex_id]],
                     device const uint *indexes [[buffer(0)]],
                     device const VertexIn *vertexes [[buffer(1)]])
{
    VertexOut out;

    const uint triangle_id = vid / 3;
    const uint vertex_of_triangle = vid % 3;

    // indexes is for a triangle strip even though this shader is invoked for a triangle list.
    const uint index[3] = { indexes[triangle_id], index[triangle_id + 1], index[triangle_id + 2] };
    const VertexIn v[3] = { vertexes[index[0]], vertexes[index[1]], vertexes[index[2]] };

    float3 p = abs(cross(v[1].position - v[0].position, v[2].position - v[0].position));

    out.position = v[vertex_of_triangle].position;
    out.normal = v[vertex_of_triangle].normal;
    out.uv = v[vertex_of_triangle].uv;

    if (p.z > p.x && p.z > p.y)
    {
        out.new_position = float4(out.position.x, out.position.y, 0, 1);
    }
    else if (p.x > p.y && p.x > p.z)
    {
        out.new_position = float4(out.position.y, out.position.z, 0, 1);
    }
    else
    {
        out.new_position = float4(out.position.x, out.position.z, 0, 1);
    }

    return out;
}