I'm trying to implement the deferred decals renderer.
I was provided with a few instruction points:
- decal instance has 2 matrices:
decalToWorldandworldToDecal - decals are rendered with
front face culling - vertex shader transforms unit cube vertices to the clip space
- pixel shader uses clip space XY to sample the depth texture, then it transforms it to the world space using
inverseViewProjectionand then to the decal space usingworldToDecal - On the DecalSpace position we perform clipping test: if X, Y or Z coordinate lies outside of the unit cube, then the pixel is discarded
I spawn the decal on key G clicked in position of the intersection point with the parent object. Thus I've got 2 matrices: decalModel and parentModel. Using these matrices I can implement the first point
1.
struct GPUInstance
{
glm::mat4 decalToWorld;
glm::mat4 worldToDecal;
};
void UpdateDecalInstanceData()
{
GPUInstance& dst = getDecalGPUInstance();
dst.decalToWorld = parentModel * decalModel;
dst.worldToDecal = glm::inverse(dst.decalToWorld);
}
second point is straigth forward: 2.
D3D11_RASTERIZER_DESC rasterizerDesc{};
rasterizerDesc.CullMode = D3D11_CULL_FRONT;
rasterizerDesc.FrontCounterClockwise = TRUE;
... other settings
D3D11_DEPTH_STENCIL_DESC dsDesc{};
dsDesc.DepthEnable = false;
... other settings
I've also disabled depth testing (I've also tried with depth testing enabled but in read-only mode)
Point 3. is also easy, I guess:
struct VertexInput
{
float3 position : POSITION;
//I_ -> instanced
nointerpolation float4 decalToWorld1 : I_DECAL_TO_WORLD_ONE;
nointerpolation float4 decalToWorld2 : I_DECAL_TO_WORLD_TWO;
nointerpolation float4 decalToWorld3 : I_DECAL_TO_WORLD_THREE;
nointerpolation float4 decalToWorld4 : I_DECAL_TO_WORLD_FOUR;
nointerpolation float4 worldToDecal1 : I_WORLD_TO_DECAL_ONE;
nointerpolation float4 worldToDecal2 : I_WORLD_TO_DECAL_TWO;
nointerpolation float4 worldToDecal3 : I_WORLD_TO_DECAL_THREE;
nointerpolation float4 worldToDecal4 : I_WORLD_TO_DECAL_FOUR;
};
struct VertexOutput
{
float4 position : SV_POSITION;
float4x4 decalToWorld : DECAL_TO_WORLD;
float4x4 worldToDecal : WORLD_TO_DECAL;
};
cbuffer PerFrame : register(b0)
{
row_major float4x4 cameraModel;
row_major float4x4 view;
row_major float4x4 projection;
row_major float4x4 viewProjection;
row_major float4x4 inverseProjection;
row_major float4x4 inverseViewProjection;
};
VertexOutput VSMain(VertexInput input)
{
VertexOutput output;
output.decalToWorld = float4x4(input.decalToWorld1, input.decalToWorld2, input.decalToWorld3, input.decalToWorld4);
output.worldToDecal = float4x4(input.worldToDecal1, input.worldToDecal2, input.worldToDecal3, input.worldToDecal4);
float4 worldPosition = mul(float4(input.position, 1.0f), output.decalToWorld);
output.position = mul(worldPosition, viewProjection);
return output;
}
and points 4 and 5 are where the problems begin:
cbuffer PerFrame : register(b0)
{
row_major float4x4 cameraModel;
row_major float4x4 view;
row_major float4x4 projection;
row_major float4x4 viewProjection;
row_major float4x4 inverseProjection;
row_major float4x4 inverseViewProjection;
};
struct PixelInput
{
float4 position : SV_POSITION;
float4x4 decalToWorld : DECAL_TO_WORLD;
float4x4 worldToDecal : WORLD_TO_DECAL;
};
struct PixelOutput
{
float4 color : SV_TARGET0;
};
Texture2D<float> t_copiedDepthMap : register(t0);
PixelOutput PSMain(PixelInput input)
{
const float depth = t_copiedDepthMap.Load(float3(input.position.xy, 0.0f));
const float4 clipSpacePosition = float4(input.position.x, input.position.y, depth, input.position.w);
const float4 worldSpacePosition = mul(clipSpacePosition, inverseViewProjection);
const float4 decalSpacePosition = mul(worldSpacePosition, input.worldToDecal);
//clip(0.5f - abs(decalSpacePosition.xyz));
PixelOutput output;
if(abs(decalSpacePosition.x) > 0.5 || abs(decalSpacePosition.y) > 0.5 || abs(decalSpacePosition.z) > 0.5)
output.color = float4(1.0f, 0.0f, 0.0f, 1.0f);
else
output.color = float4(0.0f, 0.0f, 1.0f, 1.0f);
return output;
}
since decals are rendered in the same pass as a normal geometry, I have to copy the depth texture because it's not possible to write and sample it at the same time. t_copiedDepthMap is a copy of G Buffer's depth texture, it does not contain the decal unit cube. SV_TARGET0 is the G Buffer's albedo texture.
Result:
Something is wrong, I believe some pixels should be shaded blue.
I've found this question: Deferred Screenspace Decals in Metal
and I've tried using the code from it:
PixelOutput PSMain(PixelInput input)
{
const float depth = t_copiedDepthMap.Load(float3(input.position.xy, 0.0f));
const float2 depthCoordinate = input.position.xy / resolution.xy;
const float3 screenPosition = float3((depthCoordinate.x * 2 - 1), -(depthCoordinate.y * 2 - 1), depth);
const float4 worldPosition = mul(float4(screenPosition, 1), inverseViewProjection);
const float4 objectPosition = mul(worldPosition, input.worldToDecal);
const float3 localPosition = objectPosition.xyz / objectPosition.w;
PixelOutput output;
if(abs(localPosition.x) > 0.5 || abs(localPosition.y) > 0.5 || abs(localPosition.z) > 0.5) {
output.color = float4(1.0f, 0.0f, 0.0f, 1.0f);
} else {
output.color = float4(0.0f, 0.0f, 1.0f, 1.0f);
}
return output;
}
Now, it looks fine:
Why in the first screenshot the pixels are not shaded correctly?
I've found this article: https://mtnphil.wordpress.com/2014/05/24/decals-deferred-rendering/
In this article, the author, also uses objectPosition which is decalSpacePosition in my implementation.
Also, as a side question: I've tried sampling a decal color map using the method from Deferred Screenspace Decals in Metal
float2 textureCoordinate = localPosition.xy + 0.5;
float4 color = t_baseColor.Sample(g_linearClamp, textureCoordinate);
output.color = color;
and it does not look good:
why is that?



