Skip to content

Commit 5f9f00e

Browse files
committed
Compress normals to two-component 16-bit float
Light pass now renders in camera space, since normals must be in view space to hide artifacts with z-poles
1 parent 0ee9771 commit 5f9f00e

File tree

7 files changed

+62
-29
lines changed

7 files changed

+62
-29
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ The code is programmed to support bgfx's OpenGL and DirectX 11/12 backends. I've
2727

2828
- G-Buffer with 4 render targets
2929
- diffuse RGB, roughness
30-
- normal
30+
- [encoded view-space normal](https://aras-p.info/texts/CompactNormalStorage.html#method04spheremap) (RG16F)
3131
- F0 RGB, metallic
3232
- emissive RGB, occlusion
3333
- light culling with light geometry
3434
- axis-aligned bounding box
3535
- backface rendering with reversed depth test
36-
- fragment world position reconstructed from geometry pass depth
36+
- fragment position reconstructed from depth buffer
3737
- final forward pass for transparent meshes
3838

3939
### Forward Shading

src/Renderer/DeferredRenderer.cpp

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,23 @@ DeferredRenderer::~DeferredRenderer()
5555
bool DeferredRenderer::supported()
5656
{
5757
const bgfx::Caps* caps = bgfx::getCaps();
58-
return Renderer::supported() &&
59-
// blitting depth texture after geometry pass
60-
(caps->supported & BGFX_CAPS_TEXTURE_BLIT) != 0 &&
61-
// fragment depth available in fragment shader
62-
(caps->supported & BGFX_CAPS_FRAGMENT_DEPTH) != 0 &&
63-
// render target for G-Buffer material parameters
64-
(caps->formats[bgfx::TextureFormat::BGRA8] & BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER) != 0 &&
65-
// render target for G-Buffer normals
66-
(caps->formats[bgfx::TextureFormat::RGB10A2] & BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER) != 0 &&
67-
// multiple render targets
68-
caps->limits.maxFBAttachments >= GBufferAttachment::Count; // does depth count as an attachment?
58+
bool supported = Renderer::supported() &&
59+
// blitting depth texture after geometry pass
60+
(caps->supported & BGFX_CAPS_TEXTURE_BLIT) != 0 &&
61+
// fragment depth available in fragment shader
62+
(caps->supported & BGFX_CAPS_FRAGMENT_DEPTH) != 0 &&
63+
// multiple render targets
64+
caps->limits.maxFBAttachments >= GBufferAttachment::Count; // does depth count as an attachment?
65+
if(!supported)
66+
return false;
67+
68+
for(bgfx::TextureFormat::Enum format : gBufferAttachmentFormats)
69+
{
70+
if((caps->formats[format] & BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER) == 0)
71+
return false;
72+
}
73+
74+
return true;
6975
}
7076

7177
void DeferredRenderer::onInitialize()

src/Renderer/DeferredRenderer.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ class DeferredRenderer : public Renderer
2828
// A = a (remapped roughness)
2929
Diffuse_A,
3030

31-
// TODO encode as two 16-bit components (RG16F)
32-
// https://aras-p.info/texts/CompactNormalStorage.html
33-
// Method #4: Spheremap Transform looks ideal
31+
// RG = encoded normal
3432
Normal,
3533

3634
// RGB = F0 (Fresnel at normal incidence)
@@ -47,12 +45,13 @@ class DeferredRenderer : public Renderer
4745
Count
4846
};
4947

50-
static constexpr bgfx::TextureFormat::Enum gBufferAttachmentFormats[] =
48+
static constexpr bgfx::TextureFormat::Enum gBufferAttachmentFormats[GBufferAttachment::Count - 1] =
5149
{
5250
bgfx::TextureFormat::BGRA8,
53-
bgfx::TextureFormat::RGB10A2,
51+
bgfx::TextureFormat::RG16F,
5452
bgfx::TextureFormat::BGRA8,
5553
bgfx::TextureFormat::BGRA8
54+
// depth format is determined dynamically
5655
};
5756

5857
TextureBuffer gBufferTextures[GBufferAttachment::Count + 1]; // includes depth, + null-terminated

src/Renderer/Shaders/fs_deferred_geometry.sc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@ void main()
1212
vec3 N = convertTangentNormal(v_normal, v_tangent, mat.normal);
1313
mat.a = specularAntiAliasing(N, mat.a);
1414

15+
// save normal in camera space
16+
// the other renderers render in world space but the
17+
// deferred renderer uses camera space to hide artifacts from
18+
// normal packing and to make fragment position reconstruction easier
19+
// the normal matrix transforms to world coordinates, so undo that
20+
N = mul(u_view, vec4(N, 0.0)).xyz;
21+
1522
// pack G-Buffer
1623
gl_FragData[0] = vec4(mat.diffuseColor, mat.a);
17-
// encode normal for unsigned normalized texture
18-
// w of 1.0 so it gets rendered in the debug visualization
19-
gl_FragData[1] = vec4(N * 0.5 + 0.5, 1.0);
24+
gl_FragData[1] = vec4(packNormal(N), 0.0, 0.0);
2025
gl_FragData[2] = vec4(mat.F0, mat.metallic);
2126
gl_FragData[3] = vec4(mat.emissive, mat.occlusion);
2227
}

src/Renderer/Shaders/fs_deferred_pointlight.sc

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ SAMPLER2D(s_texF0Metallic, SAMPLER_DEFERRED_F0_METALLIC);
1111
SAMPLER2D(s_texEmissiveOcclusion, SAMPLER_DEFERRED_EMISSIVE_OCCLUSION);
1212
SAMPLER2D(s_texDepth, SAMPLER_DEFERRED_DEPTH);
1313

14-
uniform vec4 u_camPos;
15-
1614
uniform vec4 u_lightIndexVec;
1715
#define u_lightIndex uint(u_lightIndexVec.x)
1816

@@ -21,7 +19,7 @@ void main()
2119
vec2 texcoord = gl_FragCoord.xy / u_viewRect.zw;
2220

2321
vec4 diffuseA = texture2D(s_texDiffuseA, texcoord);
24-
vec3 N = texture2D(s_texNormal, texcoord).xyz * 2.0 - 1.0;
22+
vec3 N = unpackNormal(texture2D(s_texNormal, texcoord).xy);
2523
vec4 F0Metallic = texture2D(s_texF0Metallic, texcoord);
2624

2725
// unpack material parameters used by the PBR BRDF function
@@ -31,18 +29,18 @@ void main()
3129
mat.F0 = F0Metallic.xyz;
3230
mat.metallic = F0Metallic.w;
3331

34-
// get fragment world position
32+
// get fragment position
33+
// rendering happens in view space
3534
vec4 screen = gl_FragCoord;
3635
screen.z = texture2D(s_texDepth, texcoord).x;
37-
vec4 eyePos = screen2Eye(screen);
38-
vec3 fragPos = mul(u_invView, eyePos).xyz;
36+
vec3 fragPos = screen2Eye(screen).xyz;
3937

40-
vec3 camPos = u_camPos.xyz;
41-
vec3 V = normalize(camPos - fragPos);
38+
vec3 V = normalize(-fragPos);
4239

4340
// lighting
4441

4542
PointLight light = getPointLight(u_lightIndex);
43+
light.position = mul(u_view, vec4(light.position, 1.0)).xyz;
4644

4745
vec3 radianceOut = vec3_splat(0.0);
4846
float dist = distance(light.position, fragPos);

src/Renderer/Shaders/util.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,27 @@ vec3 convertTangentNormal(vec3 normal_ref, vec3 tangent_ref, vec3 normal)
7272
return normalize(mul(TBN, normal));
7373
}
7474

75+
// compress viewspace normal into two components
76+
// spheremap transform used by Cry Engine 3
77+
// https://aras-p.info/texts/CompactNormalStorage.html#method04spheremap
78+
// must be viewspace so we can hide z-pole
79+
// both (0,0,1) and (0,0,-1) have the same encoding
80+
// the original implementation always decoded to (0,0,1)
81+
// changed it to return normals pointing towards the camera for
82+
// a left-handed coordinate system
83+
84+
vec2 packNormal(vec3 normal)
85+
{
86+
float f = sqrt(8.0 * -normal.z + 8.0);
87+
return normal.xy / f + 0.5;
88+
}
89+
90+
vec3 unpackNormal(vec2 encoded)
91+
{
92+
vec2 fenc = encoded * 4.0 - 2.0;
93+
float f = dot(fenc, fenc);
94+
float g = sqrt(1.0 - f * 0.25);
95+
return vec3(fenc * g, -(1.0 - f * 0.5));
96+
}
97+
7598
#endif // UTIL_SH_HEADER_GUARD

src/Renderer/Shaders/vs_deferred_light.sc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ $input a_position
55
void main()
66
{
77
gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0));
8+
// prevent far plane clipping for point light volumes outside the camera frustum
9+
//gl_Position.z = min(gl_Position.z / gl_Position.w, 1.0) * gl_Position.w;
810
}

0 commit comments

Comments
 (0)