/** * @file irradianceGenF.glsl * * $LicenseInfo:firstyear=2022&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2022, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ /*[EXTRA_CODE_HERE]*/ #ifdef DEFINE_GL_FRAGCOLOR out vec4 frag_color; #else #define frag_color gl_FragColor #endif uniform samplerCubeArray reflectionProbes; uniform int sourceIdx; VARYING vec3 vary_dir; // Code below is derived from the Khronos GLTF Sample viewer: // https://github.com/KhronosGroup/glTF-Sample-Viewer/blob/master/source/shaders/ibl_filtering.frag #define MATH_PI 3.1415926535897932384626433832795 float u_roughness = 1.0; int u_sampleCount = 16; float u_lodBias = 2.0; int u_width = 64; // Hammersley Points on the Hemisphere // CC BY 3.0 (Holger Dammertz) // http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html // with adapted interface float radicalInverse_VdC(uint bits) { bits = (bits << 16u) | (bits >> 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); return float(bits) * 2.3283064365386963e-10; // / 0x100000000 } // hammersley2d describes a sequence of points in the 2d unit square [0,1)^2 // that can be used for quasi Monte Carlo integration vec2 hammersley2d(int i, int N) { return vec2(float(i)/float(N), radicalInverse_VdC(uint(i))); } // Hemisphere Sample // TBN generates a tangent bitangent normal coordinate frame from the normal // (the normal must be normalized) mat3 generateTBN(vec3 normal) { vec3 bitangent = vec3(0.0, 1.0, 0.0); float NdotUp = dot(normal, vec3(0.0, 1.0, 0.0)); float epsilon = 0.0000001; /*if (1.0 - abs(NdotUp) <= epsilon) { // Sampling +Y or -Y, so we need a more robust bitangent. if (NdotUp > 0.0) { bitangent = vec3(0.0, 0.0, 1.0); } else { bitangent = vec3(0.0, 0.0, -1.0); } }*/ vec3 tangent = normalize(cross(bitangent, normal)); bitangent = cross(normal, tangent); return mat3(tangent, bitangent, normal); } struct MicrofacetDistributionSample { float pdf; float cosTheta; float sinTheta; float phi; }; MicrofacetDistributionSample Lambertian(vec2 xi, float roughness) { MicrofacetDistributionSample lambertian; // Cosine weighted hemisphere sampling // http://www.pbr-book.org/3ed-2018/Monte_Carlo_Integration/2D_Sampling_with_Multidimensional_Transformations.html#Cosine-WeightedHemisphereSampling lambertian.cosTheta = sqrt(1.0 - xi.y); lambertian.sinTheta = sqrt(xi.y); // equivalent to `sqrt(1.0 - cosTheta*cosTheta)`; lambertian.phi = 2.0 * MATH_PI * xi.x; lambertian.pdf = lambertian.cosTheta / MATH_PI; // evaluation for solid angle, therefore drop the sinTheta return lambertian; } // getImportanceSample returns an importance sample direction with pdf in the .w component vec4 getImportanceSample(int sampleIndex, vec3 N, float roughness) { // generate a quasi monte carlo point in the unit square [0.1)^2 vec2 xi = hammersley2d(sampleIndex, u_sampleCount); MicrofacetDistributionSample importanceSample; // generate the points on the hemisphere with a fitting mapping for // the distribution (e.g. lambertian uses a cosine importance) importanceSample = Lambertian(xi, roughness); // transform the hemisphere sample to the normal coordinate frame // i.e. rotate the hemisphere to the normal direction vec3 localSpaceDirection = normalize(vec3( importanceSample.sinTheta * cos(importanceSample.phi), importanceSample.sinTheta * sin(importanceSample.phi), importanceSample.cosTheta )); mat3 TBN = generateTBN(N); vec3 direction = TBN * localSpaceDirection; return vec4(direction, importanceSample.pdf); } // Mipmap Filtered Samples (GPU Gems 3, 20.4) // https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-20-gpu-based-importance-sampling // https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf float computeLod(float pdf) { // // Solid angle of current sample -- bigger for less likely samples // float omegaS = 1.0 / (float(u_sampleCount) * pdf); // // Solid angle of texel // // note: the factor of 4.0 * MATH_PI // float omegaP = 4.0 * MATH_PI / (6.0 * float(u_width) * float(u_width)); // // Mip level is determined by the ratio of our sample's solid angle to a texel's solid angle // // note that 0.5 * log2 is equivalent to log4 // float lod = 0.5 * log2(omegaS / omegaP); // babylon introduces a factor of K (=4) to the solid angle ratio // this helps to avoid undersampling the environment map // this does not appear in the original formulation by Jaroslav Krivanek and Mark Colbert // log4(4) == 1 // lod += 1.0; // We achieved good results by using the original formulation from Krivanek & Colbert adapted to cubemaps // https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf float lod = 0.5 * log2( 6.0 * float(u_width) * float(u_width) / (float(u_sampleCount) * pdf)); return lod; } vec4 filterColor(vec3 N) { //return textureLod(uCubeMap, N, 3.0).rgb; vec4 color = vec4(0.f); float weight = 0.0f; for(int i = 0; i < u_sampleCount; ++i) { vec4 importanceSample = getImportanceSample(i, N, 1.0); vec3 H = vec3(importanceSample.xyz); float pdf = importanceSample.w; // mipmap filtered samples (GPU Gems 3, 20.4) float lod = computeLod(pdf); // apply the bias to the lod lod += u_lodBias; lod = clamp(lod, 0, 7); // sample lambertian at a lower resolution to avoid fireflies vec4 lambertian = textureLod(reflectionProbes, vec4(H, sourceIdx), lod); color += lambertian; } if(weight != 0.0f) { color /= weight; } else { color /= float(u_sampleCount); } return color; } // entry point void main() { vec4 color = vec4(0); color = filterColor(vary_dir); frag_color = color; }