#version 430 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D ssaoMap;
uniform float gaussianWeights[31];
uniform int radius;
uniform float s;
void main() {
float d = texture(gPosition, TexCoords).w;
vec3 N = texture(gNormal, TexCoords).xyz;
vec2 texelSize = 1.0 / vec2(textureSize(ssaoMap, 0));
float totalWeight = 0.0f, result = 0.0f;
for(int x = -radius; x <= radius; x++) {
for(int y = -radius; y <= radius; y++) {
vec2 sampleCords = TexCoords + vec2(x, y) * texelSize;
float aoSample = texture(ssaoMap, sampleCords).r;
vec3 Ni = texture(gNormal, sampleCords).xyz;
float di = texture(gPosition, sampleCords).w;
float spatial = gaussianWeights[abs(x)] * gaussianWeights[abs(y)];
float normalWeight = max(0.0f, dot(N, Ni));
float depthDiff = (d - di);
float depthWeight = exp(-(depthDiff * depthDiff)/ (2.0 * s));
float W = spatial * normalWeight * depthWeight;
result += aoSample * W;
totalWeight += W;
}
}
FragColor = vec4(vec3(result / totalWeight), 1.0);
}
#version 430 core
#define PI 3.14159265359
out vec4 FragColor;
in vec2 TexCoords;
// G-Buffer inputs
uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D gAlbedo;
uniform sampler2D gMetalRough;
uniform sampler2D envMap;
// Global Lighting parameters
uniform vec3 viewPos;
uniform float exposure = 1.5;
uniform bool enableMatControl;
// Material properties
uniform float o_metallic;
uniform float o_roughness;
uniform vec3 o_color;
struct IBL {
int N;
bool diffuseOnly;
int toneMapMethod;
};
uniform IBL ibl;
struct Material {
float metallic;
float roughness;
vec3 color;
bool enable;
};
uniform Material material;
layout(std140, binding = 0) uniform HammersleyBlock {
vec4 samples[2058];
};
uniform vec3 shCoeffs[9];
vec3 evalSHIrradiance(vec3 n) {
float x = n.x, y = n.y, z = n.z;
float Y[9];
Y[0] = 0.5 * sqrt(1.0 / PI);
Y[1] = 0.5 * sqrt(3.0 / PI) * y;
Y[2] = 0.5 * sqrt(3.0 / PI) * z;
Y[3] = 0.5 * sqrt(3.0 / PI) * x;
Y[4] = 0.5 * sqrt(15.0 / PI) * x * y;
Y[5] = 0.5 * sqrt(15.0 / PI) * y * z;
Y[6] = 0.25 * sqrt(5.0 / PI) * (3.0 * z*z - 1.0);
Y[7] = 0.5 * sqrt(15.0 / PI) * x * z;
Y[8] = 0.25 * sqrt(15.0 / PI) * (x*x - y*y);
vec3 irradiance = vec3(0.0);
for (int k = 0; k < 9; k++)
irradiance += shCoeffs[k] * Y[k];
return max(irradiance, vec3(0.0));
}
// GGX
float DistributionGGX(vec3 N, vec3 H, float alpha) {
float a2 = alpha * alpha;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH * NdotH;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return a2 / denom;
}
vec3 FresnelSchlick(float LdotH, vec3 Ks) {
return Ks + (1.0 - Ks) * pow(1.0 - LdotH, 5.0);
}
float GeometryG1(float dotVN, float alpha) {
float tanTheta2 = (1.0 - dotVN * dotVN) / (dotVN * dotVN);
return 2.0 / (1.0 + sqrt(1.0 + alpha * alpha * tanTheta2));
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float alpha) {
float NdotV = max(dot(N, V), 0.001);
float NdotL = max(dot(N, L), 0.001);
return GeometryG1(NdotV, alpha) * GeometryG1(NdotL, alpha);
}
vec2 uvOfW(vec3 w) {
w = normalize(w);
return vec2(
0.5 - (atan(w.x, w.z) / (2.0 * PI)),
acos(w.y) / PI
);
}
vec3 vectorOf(float u, float v) {
float phi = (u - 0.5) * 2.0 * PI;
float theta = v * PI;
float x = sin(theta) * cos(phi);
float z = sin(theta) * sin(phi);
float y = cos(theta);
return vec3(x, y, z);
}
vec3 computeLightDirVector(float e1, float e2, float roughness, vec3 R, vec3 tangent, vec3 bitangent) {
float a = roughness;
float theta = atan(a * sqrt(e2) / sqrt(1.0 - e2));
vec3 D = vectorOf(e1, theta / PI);
vec3 dir = tangent * D.x + R * D.y + bitangent * D.z;
return normalize(dir);
}
vec3 computeLightIn(vec3 L, float D, bool useMIS, float roughness) {
vec2 uv = uvOfW(L);
float level = 0.0;
ivec2 size = textureSize(envMap, 0);
int width = size.x;
int height = size.y;
level = 0.5 * (log2( float(width * height) / float(ibl.N))) - 0.5 * log2( max(D, 0.0001) / 4.0);
float maxLod = float(textureQueryLevels(envMap) - 1);
level = clamp(level, 0.0, maxLod);
return textureLod(envMap, uv, level).rgb;
}
vec3 CalculatePBRLightIBL(vec3 N, vec3 V, vec3 albedo, float metallic, float roughness) {
// Diffuse Calculation
vec2 texCoord = uvOfW(N);
vec3 irradN = vec3(0.0);
vec3 diffuse = vec3(0.0);
irradN = evalSHIrradiance(normalize(N));
diffuse = (albedo / PI) * irradN;
if(ibl.diffuseOnly) {
return diffuse;
}
vec3 L = vec3(0.0);
vec3 specColor = vec3(0.0f);
vec3 R = reflect(-V, N);
vec3 upVec = abs(R.y) < 0.999 ? vec3(0,1,0) : vec3(1,0,0);
vec3 tangent = normalize(cross(upVec, R));
vec3 bitangent = normalize(cross(R, tangent));
// Importance Sampling
int acceptedSamples = 0;
for(int i = 1; i <= ibl.N; i++) {
float e1 = samples[i-1].x;
float e2 = samples[i-1].y;
L = computeLightDirVector(e1, e2, roughness, R, tangent, bitangent);
if (dot(N, L) > 0.0) {
vec3 H = normalize(V + L);
float NdotV = max(dot(N, V), 0.001);
float NdotL = max(dot(N, L), 0.001);
float HdotV = max(dot(H, V), 0.001);
float NdotH = max(dot(H, N), 0.001);
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
// Cook-Torrance BRDF
float D = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 F = FresnelSchlick(HdotV, F0);
vec3 Li = computeLightIn(L, D, true, roughness);
vec3 numerator = G * F;
float denominator = 4.0 * NdotV * NdotL;
denominator = max(denominator, 0.001);
specColor += (Li * NdotL * (numerator / denominator));
acceptedSamples++;
}
}
//specColor /= float(ibl.N);
if (acceptedSamples > 0)
specColor /= float(acceptedSamples);
vec3 F0 = mix(vec3(0.04), albedo, metallic);
vec3 F = FresnelSchlick(max(dot(N, V), 0.0), F0);
vec3 kS = F;
vec3 kD = (1.0 - kS) * (1.0 - metallic);
return kD * diffuse + specColor;
}
// ACES Filmic Tone Mapping
vec3 aces(vec3 x) {
float a = 2.51;
float b = 0.03;
float c = 2.43;
float d = 0.59;
float e = 0.14;
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
}
// Reinhard
vec3 reinhard(vec3 x) {
return x / (1.0 + x);
}
vec3 uncharted2Partial(vec3 x) {
float A = 0.15; // Shoulder Strength
float B = 0.50; // Linear Strength
float C = 0.10; // Linear Angle
float D = 0.20; // Toe Strength
float E = 0.02; // Toe Numerator
float F = 0.30; // Toe Denominator
return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
}
// Uncharted 2 / Hable Filmic
vec3 uncharted2(vec3 x) {
float exposureBias = 2.0;
vec3 curr = uncharted2Partial(x * exposureBias);
vec3 whiteScale = 1.0 / uncharted2Partial(vec3(11.2)); // W = 11.2
return curr * whiteScale;
}
// Khronos PBR Neutral
vec3 pbrNeutral(vec3 x) {
float startCompression = 0.8 - 0.04;
float desaturation = 0.15;
float peak = max(x.r, max(x.g, x.b));
if (peak < startCompression) return x;
float d = 1.0 - startCompression;
float newPeak = 1.0 - d * d / (peak + d - startCompression);
x *= newPeak / peak;
float g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);
return mix(x, vec3(newPeak), g);
}
void main()
{
// Sample G-Buffer textures
vec3 FragPos = texture(gPosition, TexCoords).rgb;
vec3 N = normalize(texture(gNormal, TexCoords).rgb);
vec3 albedo = texture(gAlbedo, TexCoords).rgb;
vec2 metalRough = texture(gMetalRough, TexCoords).rg;
float metallic = metalRough.r;
float roughness = metalRough.g;
vec3 Lo = vec3(0.0f);
vec3 V = normalize(viewPos - FragPos);
vec3 color = vec3(0.0f);
if(material.enable) {
color = CalculatePBRLightIBL(N, V, material.color, material.metallic, max(material.roughness, 0.005));
} else {
color = CalculatePBRLightIBL(N, V, albedo, metallic, roughness);
}
}
color = CalculatePBRLightIBL(N, V, albedo, metallic, roughness);
} else {
color = CalculatePBRLightIBL(N, V, material.color, material.metallic, max(material.roughness, 0.005));
if(material.enable) {
vec3 color = vec3(0.0f);
vec3 V = normalize(viewPos - FragPos);
vec3 Lo = vec3(0.0f);
float roughness = metalRough.g;
float metallic = metalRough.r;
vec2 metalRough = texture(gMetalRough, TexCoords).rg;
vec3 albedo = texture(gAlbedo, TexCoords).rgb;
vec3 N = normalize(texture(gNormal, TexCoords).rgb);
vec3 FragPos = texture(gPosition, TexCoords).rgb;
// Sample G-Buffer textures
{
void main()
}
return mix(x, vec3(newPeak), g);
float g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);
x *= newPeak / peak;
float newPeak = 1.0 - d * d / (peak + d - startCompression);
float d = 1.0 - startCompression;
if (peak < startCompression) return x;
float peak = max(x.r, max(x.g, x.b));
float desaturation = 0.15;
float startCompression = 0.8 - 0.04;
vec3 pbrNeutral(vec3 x) {
// Khronos PBR Neutral
}
return curr * whiteScale;
vec3 whiteScale = 1.0 / uncharted2Partial(vec3(11.2)); // W = 11.2
vec3 curr = uncharted2Partial(x * exposureBias);
float exposureBias = 2.0;
vec3 uncharted2(vec3 x) {
// Uncharted 2 / Hable Filmic
}
return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
float F = 0.30; // Toe Denominator
float E = 0.02; // Toe Numerator
float D = 0.20; // Toe Strength
float C = 0.10; // Linear Angle
float B = 0.50; // Linear Strength
float A = 0.15; // Shoulder Strength
vec3 uncharted2Partial(vec3 x) {
}
return x / (1.0 + x);
vec3 reinhard(vec3 x) {
// Reinhard
}
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
float e = 0.14;
float d = 0.59;
float c = 2.43;
float b = 0.03;
float a = 2.51;
vec3 aces(vec3 x) {
// ACES Filmic Tone Mapping
}
return kD * diffuse + specColor;
vec3 kD = (1.0 - kS) * (1.0 - metallic);
vec3 kS = F;
vec3 F = FresnelSchlick(max(dot(N, V), 0.0), F0);
vec3 F0 = mix(vec3(0.04), albedo, metallic);
specColor /= float(acceptedSamples);
if (acceptedSamples > 0)
//specColor /= float(ibl.N);
}
}
acceptedSamples++;
specColor += (Li * NdotL * (numerator / denominator));
denominator = max(denominator, 0.001);
float denominator = 4.0 * NdotV * NdotL;
vec3 numerator = G * F;
vec3 Li = computeLightIn(L, D, true, roughness);
vec3 F = FresnelSchlick(HdotV, F0);
float G = GeometrySmith(N, V, L, roughness);
float D = DistributionGGX(N, H, roughness);
// Cook-Torrance BRDF
F0 = mix(F0, albedo, metallic);
vec3 F0 = vec3(0.04);
float NdotH = max(dot(H, N), 0.001);
float HdotV = max(dot(H, V), 0.001);
float NdotL = max(dot(N, L), 0.001);
float NdotV = max(dot(N, V), 0.001);
vec3 H = normalize(V + L);
if (dot(N, L) > 0.0) {
L = computeLightDirVector(e1, e2, roughness, R, tangent, bitangent);
float e2 = samples[i-1].y;
float e1 = samples[i-1].x;
for(int i = 1; i <= ibl.N; i++) {
int acceptedSamples = 0;
// Importance Sampling
vec3 bitangent = normalize(cross(R, tangent));
vec3 tangent = normalize(cross(upVec, R));
vec3 upVec = abs(R.y) < 0.999 ? vec3(0,1,0) : vec3(1,0,0);
vec3 R = reflect(-V, N);
vec3 specColor = vec3(0.0f);
vec3 L = vec3(0.0);
}
return diffuse;
if(ibl.diffuseOnly) {
diffuse = (albedo / PI) * irradN;
irradN = evalSHIrradiance(normalize(N));
vec3 diffuse = vec3(0.0);
vec3 irradN = vec3(0.0);
vec2 texCoord = uvOfW(N);
// Diffuse Calculation
vec3 CalculatePBRLightIBL(vec3 N, vec3 V, vec3 albedo, float metallic, float roughness) {
}
return textureLod(envMap, uv, level).rgb;
level = clamp(level, 0.0, maxLod);
float maxLod = float(textureQueryLevels(envMap) - 1);
level = 0.5 * (log2( float(width * height) / float(ibl.N))) - 0.5 * log2( max(D, 0.0001) / 4.0);
int height = size.y;
int width = size.x;
ivec2 size = textureSize(envMap, 0);
float level = 0.0;
vec2 uv = uvOfW(L);
vec3 computeLightIn(vec3 L, float D, bool useMIS, float roughness) {
}
return normalize(dir);
vec3 dir = tangent * D.x + R * D.y + bitangent * D.z;
vec3 D = vectorOf(e1, theta / PI);
float theta = atan(a * sqrt(e2) / sqrt(1.0 - e2));
float a = roughness;
vec3 computeLightDirVector(float e1, float e2, float roughness, vec3 R, vec3 tangent, vec3 bitangent) {
}
return vec3(x, y, z);
float y = cos(theta);
float z = sin(theta) * sin(phi);
float x = sin(theta) * cos(phi);
float theta = v * PI;
float phi = (u - 0.5) * 2.0 * PI;
vec3 vectorOf(float u, float v) {
}
);
acos(w.y) / PI
0.5 - (atan(w.x, w.z) / (2.0 * PI)),
return vec2(
w = normalize(w);
vec2 uvOfW(vec3 w) {
}
return GeometryG1(NdotV, alpha) * GeometryG1(NdotL, alpha);
float NdotL = max(dot(N, L), 0.001);
float NdotV = max(dot(N, V), 0.001);
float GeometrySmith(vec3 N, vec3 V, vec3 L, float alpha) {
}
return 2.0 / (1.0 + sqrt(1.0 + alpha * alpha * tanTheta2));
float tanTheta2 = (1.0 - dotVN * dotVN) / (dotVN * dotVN);
float GeometryG1(float dotVN, float alpha) {
}
return Ks + (1.0 - Ks) * pow(1.0 - LdotH, 5.0);
vec3 FresnelSchlick(float LdotH, vec3 Ks) {
}
return a2 / denom;
denom = PI * denom * denom;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
float NdotH2 = NdotH * NdotH;
float NdotH = max(dot(N, H), 0.0);
float a2 = alpha * alpha;
float DistributionGGX(vec3 N, vec3 H, float alpha) {
// GGX
}
return max(irradiance, vec3(0.0));
irradiance += shCoeffs[k] * Y[k];
for (int k = 0; k < 9; k++)
vec3 irradiance = vec3(0.0);
Y[8] = 0.25 * sqrt(15.0 / PI) * (x*x - y*y);
Y[7] = 0.5 * sqrt(15.0 / PI) * x * z;
Y[6] = 0.25 * sqrt(5.0 / PI) * (3.0 * z*z - 1.0);
Y[5] = 0.5 * sqrt(15.0 / PI) * y * z;
Y[4] = 0.5 * sqrt(15.0 / PI) * x * y;
Y[3] = 0.5 * sqrt(3.0 / PI) * x;
Y[2] = 0.5 * sqrt(3.0 / PI) * z;
Y[1] = 0.5 * sqrt(3.0 / PI) * y;
Y[0] = 0.5 * sqrt(1.0 / PI);
float Y[9];
float x = n.x, y = n.y, z = n.z;
vec3 evalSHIrradiance(vec3 n) {
uniform vec3 shCoeffs[9];
};
vec4 samples[2058];
layout(std140, binding = 0) uniform HammersleyBlock {
uniform Material material;
};
bool enable;
vec3 color;
float roughness;
float metallic;
struct Material {
uniform IBL ibl;
};
int toneMapMethod;
bool diffuseOnly;
int N;
struct IBL {
uniform vec3 o_color;
uniform float o_roughness;
uniform float o_metallic;
// Material properties
uniform bool enableMatControl;
uniform float exposure = 1.5;
uniform vec3 viewPos;
// Global Lighting parameters
uniform sampler2D envMap;
uniform sampler2D gMetalRough;
uniform sampler2D gAlbedo;
uniform sampler2D gNormal;
uniform sampler2D gPosition;
// G-Buffer inputs
in vec2 TexCoords;
out vec4 FragColor;
#define PI 3.14159265359
#version 430 core
}
FragColor = vec4(vec3(result / totalWeight), 1.0);
}
}
totalWeight += W;
result += aoSample * W;
float W = spatial * normalWeight * depthWeight;
float depthWeight = exp(-(depthDiff * depthDiff)/ (2.0 * s));
float depthDiff = (d - di);
float normalWeight = max(0.0f, dot(N, Ni));
float spatial = gaussianWeights[abs(x)] * gaussianWeights[abs(y)];
float di = texture(gPosition, sampleCords).w;
vec3 Ni = texture(gNormal, sampleCords).xyz;
float aoSample = texture(ssaoMap, sampleCords).r;
vec2 sampleCords = TexCoords + vec2(x, y) * texelSize;
for(int y = -radius; y <= radius; y++) {
for(int x = -radius; x <= radius; x++) {
float totalWeight = 0.0f, result = 0.0f;
vec2 texelSize = 1.0 / vec2(textureSize(ssaoMap, 0));
vec3 N = texture(gNormal, TexCoords).xyz;
float d = texture(gPosition, TexCoords).w;
void main() {
uniform float s;
uniform int radius;
uniform float gaussianWeights[31];
uniform sampler2D ssaoMap;
uniform sampler2D gNormal;
uniform sampler2D gPosition;
in vec2 TexCoords;
out vec4 FragColor;
#version 430 coreAdvanced Real Time Rendering
CS 562
A real-time deferred pipeline: G-buffers, shadow maps, image-based lighting, and the approximations that buy them a frame budget.
5 reports, in the order they were written.
- 1 Deferred Shading A deferred PBR pipeline: a G-buffer geometry pass, a lighting pass for the directional light, a local pass accumulating point lights, and debug views of each buffer. 9 February 2026
- 2 Shadow Mapping Shadow mapping for directional and spot lights on the deferred pipeline, from binary shadows through PCF to variance and moment shadow maps blurred in compute shaders. 1 March 2026
- 3 PBS and IBL Replacing direct lights with image-based lighting: a Cook-Torrance GGX BRDF, spherical-harmonic irradiance for diffuse, and GGX importance sampling with PDF-driven mip selection for specular. 28 March 2026
- 4 Screen Space Ambient Occlusion Alchemy SSAO on the deferred PBR and IBL pipeline: spiral screen-space samples for solid-angle obscurance, an edge-aware bilateral blur, and occlusion applied to the ambient term only. 4 April 2026
- 5 Screen Space Directional Occlusion Screen-space directional occlusion after Ritschel et al.: per-direction visibility against the environment map in visibility and radiance modes, plus a one-bounce indirect pass for colour bleeding. 18 April 2026