Chapitre -110 · Rendu de la texture de profondeur

Rendu du GBuffer

Dans cette partie nous allons simplement créer un second pass (rendu) à l'écran afin de rendre la texture de profondeur tel quelle dans le stockage. Cela va nous permettre de comprendre un peu mieux encore.

Ta texture de profondeur, rendue à l'écran pour debug

La texture de profondeur est en cours d'utilisation comme attachment dans le pass — tu ne peux pas la sampler dans le même pass. Il faut un second render encoder après endEncoding().

Déclares un mesh carré en plein écran :

/// C++ — Renderer.hpp — update:18/05/26 — ta classe principale
private:
    NS::SharedPtr<MTL::RenderPipelineState> m_depthDebugPipelineState;
    Mesh3D  m_windowQuad;
/// C++ — Renderer.cpp — update:18/05/26 — constructeur
{
    memset(&m_windowQuad, 0x0, sizeof(Mesh3D)); // remplis d'abord avec des zéros
}
/// C++ —Renderer.cpp — update:18/05/26 — destructeur ~
meshes::releaseMesh(&m_windowQuad);

Le shader

/// MSL — GBuffer.metal — update:18/05/26
#include <metal_stdlib>
using namespace metal;

struct VertexOutGBuffer
{
    float4 position [[position]];
    float4 color;
};

fragment float4 fragment_depth(VertexOutGBuffer  in            [[stage_in]],
                               depth2d<float>    depthTexture  [[texture(0)]],
                               constant float2&  windowSize    [[buffer(1)]])
{
    constexpr sampler s(filter::nearest, address::clamp_to_edge);
    float2 uv = in.position.xy / windowSize;
    float d = depthTexture.sample(s, uv);
    return float4(d, d, d, 1.0); // grayscale
}

Le pipeline

/// C++ — Renderer.cpp — update:18/05/26 - buildPipeline()
NS::Error* error = nullptr; // existant, pas besoin d'enlever le reste
auto fragmentFn = NS::TransferPtr(m_shaderLibrary->newFunction(MTLSTR("fragment_depth")));
auto gbufferRenderPsoDesc = NS::TransferPtr(MTL::RenderPipelineDescriptor::alloc()->init());
gbufferRenderPsoDesc->setVertexFunction(vertexFunction.get()); // même vertex_main_shapes
gbufferRenderPsoDesc->setFragmentFunction(fragmentFn.get());
gbufferRenderPsoDesc->colorAttachments()->object(0)->setPixelFormat(m_pixelFormat);
// pas de depthAttachmentPixelFormat — pas de depth attachment dans ce pass

m_depthDebugPipelineState = NS::TransferPtr(m_device->newRenderPipelineState(gbufferRenderPsoDesc.get(), &error));
        /// C++ — Renderer.cpp — update:18/05/26 — après la première passe de rendu
        renderCommandEncoder->endEncoding(); // tu peux mettre de nouvelles braces après cette passe
        auto gbufferRenderPassDesc= NS::TransferPtr(MTL::RenderPassDescriptor::alloc()->init());
        gbufferRenderPassDesc->colorAttachments()->object(0)->setTexture(view->currentDrawable()->texture());
        gbufferRenderPassDesc->colorAttachments()->object(0)->setLoadAction(MTL::LoadActionLoad); // préserve le rendu
        gbufferRenderPassDesc->colorAttachments()->object(0)->setStoreAction(MTL::StoreActionStore);
        // pas de depthAttachment ici fatalement

        auto gbufferRenderCmdEnc = commandBuffer->renderCommandEncoder(gbufferRenderPassDesc.get());
        gbufferRenderCmdEnc->setRenderPipelineState(m_depthDebugPipelineState.get());
        gbufferRenderCmdEnc->setViewport(m_viewport);
        gbufferRenderCmdEnc->setFragmentTexture(m_depth.get(), 0);
        gbufferRenderCmdEnc->setFragmentBytes(&m_windowSize, sizeof(m_windowSize), 1);
        gbufferRenderCmdEnc->setVertexBuffer(m_windowQuad.vertices, 0, 0);
        gbufferRenderCmdEnc->setVertexBytes(&m_windowSize, sizeof(m_windowSize), 1);
        simd::float2 origin = { 0.f, 0.f };
        gbufferRenderCmdEnc->setVertexBytes(&origin, sizeof(origin), 2);
        gbufferRenderCmdEnc->drawIndexedPrimitives(MTL::PrimitiveTypeTriangle, m_windowQuad.numIndices,
                                                   m_windowQuad.indexType, m_windowQuad.indices, NS::UInteger(0));
        gbufferRenderCmdEnc->endEncoding();
    }

Tu verras les différents shaders en niveaux de gris — 0.0 noir, 0.999 blanc quasi-pur, le fond de la fenêtre à 1.0 blanc complet comme suit :

drawIndexedPrimitives

float  depth    = depthTex.read(coord).r;     // canal rouge = valeur depth
/// C++ — Renderer.cpp — update:18/05/26 — resizeViewWindow()
    makeAndResizeDepthTexture((float)width, (float)height);
    meshes::releaseMesh(&m_windowQuad); // add
    m_windowQuad = meshes::newWindowQuad(m_device, (float)width, (float)height); // add
⬡CheckPoint Compilation — Sans linéarisation

La valeur depth lue est en espace NDC [0, 1] — non linéaire. Pour obtenir une distance métrique utilisable (SSAO, brouillard…), il faut la linéariser :

// MSL
float linearize_depth(float d, float near, float far)
{
    return (near * far) / (far - d * (far - near));
}
/// MSL — GBuffer.metal — update:18/05/26
fragment float4 fragment_depth(VertexOutGBuffer  in            [[stage_in]],
                               depth2d<float>    depthTexture  [[texture(0)]],
                               constant float2&  windowSize    [[buffer(1)]])
{
    constexpr sampler s(filter::nearest, address::clamp_to_edge);
    float2 uv = in.position.xy / windowSize;
    float d = depthTexture.sample(s, uv);
    
    float linear = (2.0 * 0.1) / (100 + 0.1 - d * (100 - 0.1));
    return float4(linear, linear, linear, 1.0);
    return float4(d, d, d, 1.0); // grayscale
}
⬡CheckPoint Compilation — Avec linéarisation