-- glslfx version 0.1 // // Copyright 2019 Pixar // // Licensed under the Apache License, Version 2.0 (the "Apache License") // with the following modification; you may not use this file except in // compliance with the Apache License and the following modification to it: // Section 6. Trademarks. is deleted and replaced with: // // 6. Trademarks. This License does not grant permission to use the trade // names, trademarks, service marks, or product names of the Licensor // and its affiliates, except as required to comply with Section 4(c) of // the License and to reproduce the content of the NOTICE file. // // You may obtain a copy of the Apache License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the Apache License with the above modification is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the Apache License for the specific // language governing permissions and limitations under the Apache License. // -- configuration { "techniques": { "default": { "domeLightIrradiance": { "source": [ "DomeLight.Common", "DomeLight.Irradiance" ] }, "domeLightPrefilter": { "source": [ "DomeLight.Common", "DomeLight.CommonSampling", "DomeLight.Prefilter" ] }, "domeLightBRDF": { "source": [ "DomeLight.Common", "DomeLight.CommonSampling", "DomeLight.BRDF" ] } } } } --- -------------------------------------------------------------------------- -- glsl DomeLight.Common const float PI = 3.1415926536; layout(binding = 0) uniform sampler2D inTexture; layout(rgba16f, binding = 1) uniform image2D outTexture; // compute texture coords based on the size of the output image/texture vec2 GetTexCoords(ivec2 outCoords) { vec2 outDims = imageSize(outTexture); vec2 texCoords = outCoords / outDims; return texCoords; } // sample lat/long env map input texture vec3 SampleEnvMapLod(vec3 sampleVec, float sampleLod) { vec2 coord = vec2((atan(sampleVec.z, sampleVec.x) + PI) / (2.0 * PI), acos(sampleVec.y) / PI); return textureLod(inTexture, coord, sampleLod).rgb; } // compute world position from texture coords vec3 GetWorldPos(vec2 textureCoord) { // have theta range from [-PI, PI] so the origin is in the center // of the image float theta = (textureCoord.x * 2.0 * PI) - PI; float phi = (textureCoord.y * PI); float x = cos(theta) * sin(phi); float y = cos(phi); float z = sin(theta) * sin(phi); return vec3(x, y, z); } --- -------------------------------------------------------------------------- -- glsl DomeLight.CommonSampling float RadicalInverse(uint a) { return float(bitfieldReverse(a)) * 2.3283064365386963e-10; // 0x1p-32 } vec2 Hammersley2d(uint a, uint N) { return vec2(float(a) / float(N), RadicalInverse(a)); } vec3 ImportanceSample_GGX(vec2 Xi, float roughness, vec3 normal) { // Maps a 2D point to a hemisphere with spread based on roughness float alpha = roughness * roughness; float phi = 2.0 * PI * Xi.x; 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); } --- -------------------------------------------------------------------------- -- glsl DomeLight.Irradiance layout(local_size_x = 32, local_size_y = 32) in; const float deltaPhi = (2.0f * float(PI)) / 180.0f; const float deltaTheta = (0.5f * float(PI)) / 64.0f; vec3 SampleEnvMap(vec3 sampleVec) { // sample from a mipmap level of the environment map determined by the // size of the environment map and the number of samples we are taking ivec2 inDims = textureSize(inTexture, 0); float mipLevel = ceil(log2(inDims.x * deltaPhi/(2.0 * PI)) * 2.0f); return SampleEnvMapLod(sampleVec, mipLevel); } vec3 ComputeIrradiance(vec3 inPos) { vec3 N = normalize(inPos); vec3 up = vec3(0.0, 1.0, 0.0); vec3 right = normalize(cross(up, N)); up = cross(N, right); const float TWO_PI = PI * 2.0; const float HALF_PI = PI * 0.5; vec3 color = vec3(0.0); uint sampleCount = 0u; for (float phi = 0.0; phi < TWO_PI; phi += deltaPhi) { for (float theta = 0.0; theta < HALF_PI; theta += deltaTheta) { vec3 tempVec = cos(phi) * right + sin(phi) * up; vec3 sampleVector = cos(theta) * N + sin(theta) * tempVec; color += SampleEnvMap(sampleVector).rgb * cos(theta) * sin(theta); sampleCount++; } } return PI * color / float(sampleCount); } void main(void) { ivec2 outCoords = ivec2(gl_GlobalInvocationID.xy); vec2 texCoords = GetTexCoords(outCoords); vec3 pos3D = GetWorldPos(texCoords); vec4 outColor = vec4(ComputeIrradiance(pos3D), 1.0); imageStore(outTexture, outCoords, outColor); } --- -------------------------------------------------------------------------- -- glsl DomeLight.Prefilter layout(local_size_x = 32, local_size_y = 32) in; uniform float roughness; // Normal Distribution function float Distribution_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(inTexture, 0).s); const uint numSamples = 1024u; for (uint i = 0u; i < numSamples; i++) { vec2 Xi = Hammersley2d(i, numSamples); vec3 H = ImportanceSample_GGX(Xi, roughness, N); vec3 L = 2.0 * dot(V, H) * H - V; float dotNL = clamp(dot(N, L), 0.0, 1.0); if (dotNL > 0.0) { 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 = Distribution_GGX(dotNH, roughness) * dotNH / (4.0 * dotVH) + 0.0001; // Solid angle of current sample float omegaS = 1.0 / (float(numSamples) * pdf); // Solid angle of 1 pixel across all cube faces float omegaP = 4.0 * PI / (6.0 * envMapDim * envMapDim); // Biased (+1.0) mip level for better result float mipLevel = roughness == 0.0 ? 0.0 : max(0.5 * log2(omegaS / omegaP) + 1.0, 0.0f); color += SampleEnvMapLod(L, mipLevel).rgb * dotNL; totalWeight += dotNL; } } return (color / totalWeight); } void main(void) { ivec2 outCoords = ivec2(gl_GlobalInvocationID.xy); vec2 texCoords = GetTexCoords(outCoords); vec3 pos3D = GetWorldPos(texCoords); vec3 R = normalize(pos3D); vec4 outColor = vec4(PrefilterEnvMap(R, roughness), 1.0); imageStore(outTexture, outCoords, outColor); } --- -------------------------------------------------------------------------- -- glsl DomeLight.BRDF layout(local_size_x = 32, local_size_y = 32) in; uniform int sampleLevel = 0; float Geometry_SchlicksmithGGX(float dotNL, float dotNV, float roughness) { float k = (roughness * roughness) / 2.0; float GL = dotNL / (dotNL * (1.0 - k) + k); float GV = dotNV / (dotNV * (1.0 - k) + k); return GL * GV; } vec2 ComputeBRDF(float NoV, float roughness) { // make sure NoV doesn't go exactly to 0 to avoid NaN NoV = max(NoV, 0.001); // Normal always points along z-axis for the 2D lookup const vec3 N = vec3(0.0, 0.0, 1.0); vec3 V = vec3(sqrt(1.0 - NoV*NoV), 0.0, NoV); vec2 LUT = vec2(0.0); const uint NUM_SAMPLES = 1024u; for (uint i = 0u; i < NUM_SAMPLES; i++) { vec2 Xi = Hammersley2d(i, NUM_SAMPLES); vec3 H = ImportanceSample_GGX(Xi, roughness, N); vec3 L = 2.0 * dot(V, H) * H - V; float dotNL = max(dot(N, L), 0.0); float dotNV = max(dot(N, V), 0.0); float dotVH = max(dot(V, H), 0.0); float dotNH = max(dot(H, N), 0.0); if (dotNL > 0.0) { float G = Geometry_SchlicksmithGGX(dotNL, dotNV, roughness); float G_Vis = (G * dotVH) / (dotNH * dotNV); float Fc = pow(1.0 - dotVH, 5.0); LUT += vec2((1.0 - Fc) * G_Vis, Fc * G_Vis); } } return LUT / float(NUM_SAMPLES); } void main(void) { ivec2 outCoords = ivec2(gl_GlobalInvocationID.xy); vec2 texCoords = GetTexCoords(outCoords); // texCoords.x represents N dot E and texCoords.y represents roughness vec4 outColor = vec4(ComputeBRDF(texCoords.x, texCoords.y), 0.0, 1.0); imageStore(outTexture, outCoords, outColor); }