Metal 中的延迟屏幕空间贴花

Deferred Screenspace Decals in Metal

尝试创建延迟屏幕space 按照 this article 在 Metal 中渲染贴花。虽然似乎无法弄清楚...

这些是贴花的边界...

实际结果...

潜在问题

所以显然它不认为贴花与网格相交,我正确地采样了深度值,但是当计算 3D 中像素的实际位置时 space 有些东西没有加起来。

代码

vertex VertexOut vertex_decal(
    const VertexIn in [[ stage_in ]],
    constant DecalVertexUniforms &uniforms [[ buffer(2) ]]
) {
    VertexOut out;

    out.position = uniforms.projectionMatrix * uniforms.viewMatrix * uniforms.modelMatrix * in.position;
    out.viewPosition = (uniforms.viewMatrix * uniforms.modelMatrix * in.position).xyz;
    out.normal = uniforms.normalMatrix * in.normal;
    out.uv = in.uv;
    
    return out;
}

fragment float4 fragment_decal(
    const VertexOut in [[ stage_in ]],
    constant DecalFragmentUniforms &uniforms [[ buffer(3) ]],
    depth2d<float, access::sample> depthTexture [[ texture(0) ]]
) {
    constexpr sampler textureSampler (mag_filter::nearest, min_filter::nearest);

    float2 resolution = float2(
        depthTexture.get_width(),
        depthTexture.get_height()
    );
    
    float2 textureCoordinate = in.position.xy / resolution;
    float depth = depthTexture.sample(textureSampler, textureCoordinate);
    
    float3 viewRay = in.viewPosition * (uniforms.farClipPlane / in.viewPosition.z);

    float3 viewPosition = viewRay * depth;
    float3 worldPositon = (uniforms.inverseViewMatrix * float4(viewPosition, 1)).xyz;
    float3 objectPositon = (uniforms.inverseModelMatrix * float4(worldPositon, 1)).xyz;
    
    float distX = 0.5 - abs(objectPositon.x);
    float distY = 0.5 - abs(objectPositon.y);
    float distZ = 0.5 - abs(objectPositon.z);

    if(distX > 0 && distY > 0 && distZ > 0) {
        return float4(1, 0, 0, 0.5);
    } else {
        discard_fragment();
    }
}

编辑:

取得了一些进步,现在它至少渲染了一些东西,一旦贴花框在某个网格之外,它就会正确地剪裁贴花框,但网格上的部分仍然不完全正确..准确地说它也是渲染与贴花下的网格重叠的盒子侧面(您可以在下图中看到它,因为红色有点深)

要添加更多细节,depthTexture 是从之前的“pass”传递过来的,因此它只包含 icosphere,贴花立方体着色器不写入 depthTexture,只是从中读取。

深​​度模板定义为...

let stencilDescriptor = MTLDepthStencilDescriptor()
stencilDescriptor.depthCompareFunction = .less
stencilDescriptor.isDepthWriteEnabled = false

渲染管道定义为...

let renderPipelineDescriptor = MTLRenderPipelineDescriptor()
renderPipelineDescriptor.vertexDescriptor = vertexDescriptor

renderPipelineDescriptor.vertexFunction = vertexLibrary.makeFunction(name: "vertex_decal")
renderPipelineDescriptor.fragmentFunction = fragmentLibrary.makeFunction(name: "fragment_decal")

if let colorAttachment = renderPipelineDescriptor.colorAttachments[0] {
    colorAttachment.pixelFormat = .bgra8Unorm
    colorAttachment.isBlendingEnabled = true
    colorAttachment.rgbBlendOperation = .add
    colorAttachment.sourceRGBBlendFactor = .sourceAlpha
    colorAttachment.destinationRGBBlendFactor = .oneMinusSourceAlpha
}

renderPipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm
renderPipelineDescriptor.depthAttachmentPixelFormat = .depth32Float

所以当前的问题是它只丢弃被投影到的网格之外的像素,而不是 icosphere 表面“上方”的所有像素

新着色器代码

fragment float4 fragment_decal(
    const VertexOut in [[ stage_in ]],
    constant DecalFragmentUniforms &uniforms [[ buffer(3) ]],
    depth2d<float, access::sample> depthTexture [[ texture(0) ]]
) {
    constexpr sampler textureSampler (mag_filter::nearest, min_filter::nearest);

    float2 resolution = float2(
        depthTexture.get_width(),
        depthTexture.get_height()
    );
        
    float2 textureCoordinate = in.position.xy / resolution;
    float depth = depthTexture.sample(textureSampler, textureCoordinate);
    
    float3 screenPosition = float3(textureCoordinate * 2 - 1, depth);
    float4 viewPosition = uniforms.inverseProjectionMatrix * float4(screenPosition, 1);
    float4 worldPosition = uniforms.inverseViewMatrix * viewPosition;
    float3 objectPosition = (uniforms.inverseModelMatrix * worldPosition).xyz;
    
    if(abs(worldPosition.x) > 0.5 || abs(worldPosition.y) > 0.5 || abs(worldPosition.z) > 0.5) {
        discard_fragment();
    } else {
        return float4(1, 0, 0, 0.5);
    }
}

终于让它正常工作了,所以最终的着色器代码是...

最新着色器存在的问题是...

  • 在 screenPosition 上翻转了 Y 轴
  • 不将 objectPosition 转换为 NDC space (localPosition)
fragment float4 fragment_decal(
    const VertexOut in [[ stage_in ]],
    constant DecalFragmentUniforms &uniforms [[ buffer(3) ]],
    depth2d<float, access::sample> depthTexture [[ texture(0) ]],
    texture2d<float, access::sample> colorTexture [[ texture(1) ]]
) {
    constexpr sampler depthSampler (mag_filter::linear, min_filter::linear);

    float2 resolution = float2(
        depthTexture.get_width(),
        depthTexture.get_height()
    );
        
    float2 depthCoordinate = in.position.xy / resolution;
    float depth = depthTexture.sample(depthSampler, depthCoordinate);
    
    float3 screenPosition = float3((depthCoordinate.x * 2 - 1), -(depthCoordinate.y * 2 - 1), depth);
    float4 viewPosition = uniforms.inverseProjectionMatrix * float4(screenPosition, 1);
    float4 worldPosition = uniforms.inverseViewMatrix * viewPosition;
    float4 objectPosition = uniforms.inverseModelMatrix * worldPosition;
    float3 localPosition = objectPosition.xyz / objectPosition.w;
    
    if(abs(localPosition.x) > 0.5 || abs(localPosition.y) > 0.5 || abs(localPosition.z) > 0.5) {
        discard_fragment();
    } else {
        float2 textureCoordinate = localPosition.xy + 0.5;
        float4 color = colorTexture.sample(depthSampler, textureCoordinate);
        
        return float4(color.rgb, 1);
    }
}

最终结果是这样的(红色是保留的像素,蓝色像素被丢弃)...