The Importance of GGX – Flash Sword Master – Bloggarden (CNblogs.com)
PBR (Physics-based Rendering) Study Notes 2 – Tree of Wood – Blog Garden (CNblogs.com)
Reflection equation
Specular portion
Simplified partition summation approximation
Pre-filtered environment map
Importance sampling
- The known formula
- Transform the probability density function in spherical coordinates
// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
// Convert a 2d sampling sequence into a 3D sampling direction
vec3 importanceSample_GGX(vec2 Xi, float roughness, vec3 normal)
{
// Map 2d points to hemispherical and polar coordinates based on roughness
float alpha = roughness * roughness;
float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1;
float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y));
float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
vec3 H = vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
// Tangent space
vec3 up = abs(normal.z) < 0.999 ? vec3(0.0.0.0.1.0) : vec3(1.0.0.0.0.0);
vec3 tangentX = normalize(cross(up, normal));
vec3 tangentY = normalize(cross(normal, tangentX));
// Convert to world space
return normalize(tangentX * H.x + tangentY * H.y + normal * H.z);
}
Copy the code
GGX normal distribution function
float D_GGX(float dotNH, float roughness)
{
float alpha = roughness * roughness;
float alpha2 = alpha * alpha;
float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
return (alpha2)/(PI * denom*denom);
}
Copy the code
- PDF stands for probability density function, which means the probability that a specific sample occurs in the whole sample set.
Step 4. Step 5. Step 5. Step 5. Step 5. Step 5. Step 5. Step 5.Copy the code
The original
#version 450
layout (location = 0) in vec3 inPos;
layout (location = 0) out vec4 outColor;
layout (binding = 0) uniform samplerCube samplerEnv;
layout(push_constant) uniform PushConsts {
layout (offset = 64) float roughness;
layout (offset = 68) uint numSamples;
} consts;
const float PI = 3.1415926536;
// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
float random(vec2 co)
{
float a = 12.9898;
float b = 78.233;
float c = 43758.5453;
float dt= dot(co.xy ,vec2(a,b));
float sn= mod(dt,3.14);
return fract(sin(sn) * c);
}
vec2 hammersley2d(uint i, uint N)
{
// Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
uint bits = (i << 16u) | (i >> 16u);
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
float rdi = float(bits) * 2.3283064365386963 e-10;
return vec2(float(i) /float(N), rdi);
}
// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
vec3 importanceSample_GGX(vec2 Xi, float roughness, vec3 normal)
{
// Map 2d points to hemispheres based on roughness
float alpha = roughness * roughness;
float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1;
float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y));
float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
vec3 H = vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
// Tangent space
vec3 up = abs(normal.z) < 0.999 ? vec3(0.0.0.0.1.0) : vec3(1.0.0.0.0.0);
vec3 tangentX = normalize(cross(up, normal));
vec3 tangentY = normalize(cross(normal, tangentX));
// Convert to world space
return normalize(tangentX * H.x + tangentY * H.y + normal * H.z);
}
// Normal Distribution function
float D_GGX(float dotNH, float roughness)
{
float alpha = roughness * roughness;
float alpha2 = alpha * alpha;
float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
return (alpha2)/(PI * denom*denom);
}
vec3 prefilterEnvMap(vec3 R, float roughness)
{
vec3 N = R;
vec3 V = R;
vec3 color = vec3(0.0);
float totalWeight = 0.0;
float envMapDim = float(textureSize(samplerEnv, 0).s);
for(uint i = 0u; i < consts.numSamples; i++) {
vec2 Xi = hammersley2d(i, consts.numSamples);
vec3 H = importanceSample_GGX(Xi, roughness, N);// Convert a 2d sampling sequence into a 3D sampling direction
vec3 L = 2.0 * dot(V, H) * H - V;
float dotNL = clamp(dot(N, L), 0.0.1.0);
if(dotNL > 0.0) {
// Filtering based on https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-l ighting/
float dotNH = clamp(dot(N, H), 0.0.1.0);
float dotVH = clamp(dot(V, H), 0.0.1.0);
// Probability distribution function
float pdf = D_GGX(dotNH, roughness) * dotNH / (4.0 * dotVH) + 0.0001;
float omegaS = 1.0 / (float(consts.numSamples) * pdf);
// The solid Angle between all cube faces is 1 pixel
float omegaP = 4.0 * PI / (6.0 * envMapDim * envMapDim);
// Add +1 for better results
float mipLevel = roughness == 0.0 ? 0.0 : max(0.5 * log2(omegaS / omegaP) + 1.0.0.0f);
color += textureLod(samplerEnv, L, mipLevel).rgb * dotNL; totalWeight += dotNL; }}return (color / totalWeight);
}
void main()
{
vec3 N = normalize(inPos);
outColor = vec4(prefilterEnvMap(N, consts.roughness), 1.0);
}
Copy the code