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