| 1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
| 2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
| 3 | #include "BsRendererTextures.h" |
| 4 | #include "Math/BsVector2.h" |
| 5 | #include "Image/BsColor.h" |
| 6 | #include "Math/BsMath.h" |
| 7 | #include "Image/BsTexture.h" |
| 8 | #include "Image/BsPixelData.h" |
| 9 | #include "Renderer/BsIBLUtility.h" |
| 10 | |
| 11 | namespace bs { namespace ct |
| 12 | { |
| 13 | SPtr<Texture> generate4x4RandomizationTexture() |
| 14 | { |
| 15 | UINT32 mapping[16] = { 13, 5, 1, 9, 14, 3, 7, 11, 15, 2, 6, 12, 4, 8, 0, 10 }; |
| 16 | Vector2 bases[16]; |
| 17 | for (UINT32 i = 0; i < 16; ++i) |
| 18 | { |
| 19 | float angle = (mapping[i] / 16.0f) * Math::PI; |
| 20 | bases[i].x = cos(angle); |
| 21 | bases[i].y = sin(angle); |
| 22 | } |
| 23 | |
| 24 | SPtr<PixelData> pixelData = PixelData::create(4, 4, 1, PF_RG8); |
| 25 | for(UINT32 y = 0; y < 4; ++y) |
| 26 | for(UINT32 x = 0; x < 4; ++x) |
| 27 | { |
| 28 | UINT32 base = (y * 4) + x; |
| 29 | |
| 30 | Color color; |
| 31 | color.r = bases[base].x * 0.5f + 0.5f; |
| 32 | color.g = bases[base].y * 0.5f + 0.5f; |
| 33 | |
| 34 | pixelData->setColorAt(color, x, y); |
| 35 | } |
| 36 | |
| 37 | return Texture::create(pixelData); |
| 38 | } |
| 39 | |
| 40 | // Reverse bits functions used for Hammersley sequence |
| 41 | float reverseBits(UINT32 bits) |
| 42 | { |
| 43 | bits = (bits << 16u) | (bits >> 16u); |
| 44 | bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); |
| 45 | bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); |
| 46 | bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); |
| 47 | bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); |
| 48 | |
| 49 | return (float)(double(bits) / (double)0x100000000LL); |
| 50 | } |
| 51 | |
| 52 | void hammersleySequence(UINT32 i, UINT32 count, float& e0, float& e1) |
| 53 | { |
| 54 | e0 = i / (float)count; |
| 55 | e1 = reverseBits(i); |
| 56 | } |
| 57 | |
| 58 | Vector3 sphericalToCartesian(float cosTheta, float sinTheta, float phi) |
| 59 | { |
| 60 | Vector3 output; |
| 61 | output.x = sinTheta * cos(phi); |
| 62 | output.y = sinTheta * sin(phi); |
| 63 | output.z = cosTheta; |
| 64 | |
| 65 | return output; |
| 66 | } |
| 67 | |
| 68 | // Generates an angle in spherical coordinates, importance sampled for the specified roughness based on some uniformly |
| 69 | // distributed random variables in range [0, 1]. |
| 70 | void importanceSampleGGX(float e0, float e1, float roughness4, float& cosTheta, float& phi) |
| 71 | { |
| 72 | // See GGXImportanceSample.nb for derivation (essentially, take base GGX, normalize it, generate PDF, split PDF into |
| 73 | // marginal probability for theta and conditional probability for phi. Plug those into the CDF, invert it.) |
| 74 | cosTheta = sqrt((1.0f - e0) / (1.0f + (roughness4 - 1.0f) * e0)); |
| 75 | phi = 2.0f * Math::PI * e1; |
| 76 | } |
| 77 | |
| 78 | float calcMicrofacetShadowingSmithGGX(float roughness4, float NoV, float NoL) |
| 79 | { |
| 80 | // Note: See lighting shader for derivation. Includes microfacet BRDF divisor. |
| 81 | float g1V = NoV + sqrt(NoV * (NoV - NoV * roughness4) + roughness4); |
| 82 | float g1L = NoL + sqrt(NoL * (NoL - NoL * roughness4) + roughness4); |
| 83 | return 1.0f / (g1V * g1L); |
| 84 | } |
| 85 | |
| 86 | SPtr<Texture> generatePreintegratedEnvBRDF() |
| 87 | { |
| 88 | TEXTURE_DESC desc; |
| 89 | desc.type = TEX_TYPE_2D; |
| 90 | desc.format = PF_RG16F; |
| 91 | desc.width = 128; |
| 92 | desc.height = 32; |
| 93 | |
| 94 | SPtr<Texture> texture = Texture::create(desc); |
| 95 | PixelData pixelData = texture->lock(GBL_WRITE_ONLY_DISCARD); |
| 96 | |
| 97 | for (UINT32 y = 0; y < desc.height; y++) |
| 98 | { |
| 99 | float roughness = (float)(y + 0.5f) / desc.height; |
| 100 | float m = roughness * roughness; |
| 101 | float m2 = m*m; |
| 102 | |
| 103 | for (UINT32 x = 0; x < desc.width; x++) |
| 104 | { |
| 105 | float NoV = (float)(x + 0.5f) / desc.width; |
| 106 | |
| 107 | Vector3 V; |
| 108 | V.x = sqrt(1.0f - NoV * NoV); // sine |
| 109 | V.y = 0.0f; |
| 110 | V.z = NoV; |
| 111 | |
| 112 | // These are the two integrals resulting from the second part of the split-sum approximation. Described in |
| 113 | // Epic's 2013 paper: |
| 114 | // http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf |
| 115 | float scale = 0.0f; |
| 116 | float offset = 0.0f; |
| 117 | |
| 118 | // We use the same importance sampling function we use for reflection cube importance sampling, only we |
| 119 | // sample G and F, instead of D factors of the microfactet BRDF. See GGXImportanceSample.nb for derivation. |
| 120 | constexpr UINT32 NumSamples = 128; |
| 121 | for (UINT32 i = 0; i < NumSamples; i++) |
| 122 | { |
| 123 | float e0, e1; |
| 124 | hammersleySequence(i, NumSamples, e0, e1); |
| 125 | |
| 126 | float cosTheta, phi; |
| 127 | importanceSampleGGX(e0, e1, m2, cosTheta, phi); |
| 128 | |
| 129 | float sinTheta = sqrt(1.0f - cosTheta * cosTheta); |
| 130 | Vector3 H = sphericalToCartesian(cosTheta, sinTheta, phi); |
| 131 | Vector3 L = 2.0f * Vector3::dot(V, H) * H - V; |
| 132 | |
| 133 | float VoH = std::max(Vector3::dot(V, H), 0.0f); |
| 134 | float NoL = std::max(L.z, 0.0f); // N assumed (0, 0, 1) |
| 135 | float NoH = std::max(H.z, 0.0f); // N assumed (0, 0, 1) |
| 136 | |
| 137 | // Set second part of the split sum integral is split into two parts: |
| 138 | // F0*I[G * (1 - (1 - v.h)^5) * cos(theta)] + I[G * (1 - v.h)^5 * cos(theta)] (F0 * scale + bias) |
| 139 | |
| 140 | // We calculate the fresnel scale (1 - (1 - v.h)^5) and bias ((1 - v.h)^5) parts |
| 141 | float fc = pow(1.0f - VoH, 5.0f); |
| 142 | float fresnelScale = 1.0f - fc; |
| 143 | float fresnelOffset = fc; |
| 144 | |
| 145 | // We calculate the G part |
| 146 | float G = calcMicrofacetShadowingSmithGGX(m2, NoV, NoL); |
| 147 | |
| 148 | // When we factor out G and F, then divide D by PDF, this is what's left |
| 149 | // Note: This is based on PDF: D * NoH / (4 * VoH). (4 * VoH) factor comes from the Jacobian of the |
| 150 | // transformation from half vector to light vector |
| 151 | float pdfFactor = 4.0f * VoH / NoH; |
| 152 | |
| 153 | if (NoL > 0.0f) |
| 154 | { |
| 155 | scale += NoL * pdfFactor * G * fresnelScale; |
| 156 | offset += NoL * pdfFactor * G * fresnelOffset; |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | scale /= NumSamples; |
| 161 | offset /= NumSamples; |
| 162 | |
| 163 | Color color; |
| 164 | color.r = Math::clamp01(scale); |
| 165 | color.g = Math::clamp01(offset); |
| 166 | |
| 167 | pixelData.setColorAt(color, x, y); |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | texture->unlock(); |
| 172 | |
| 173 | return texture; |
| 174 | } |
| 175 | |
| 176 | SPtr<Texture> generateDefaultIndirect() |
| 177 | { |
| 178 | TEXTURE_DESC dummySkyDesc; |
| 179 | dummySkyDesc.type = TEX_TYPE_CUBE_MAP; |
| 180 | dummySkyDesc.format = PF_RG11B10F; |
| 181 | dummySkyDesc.width = 2; |
| 182 | dummySkyDesc.height = 2; |
| 183 | |
| 184 | // Note: Eventually replace this with a time of day model |
| 185 | float intensity = 1.0f; |
| 186 | Color skyColor = Color::White * intensity; |
| 187 | SPtr<Texture> skyTexture = Texture::create(dummySkyDesc); |
| 188 | |
| 189 | UINT32 sides[] = { CF_PositiveX, CF_NegativeX, CF_PositiveZ, CF_NegativeZ }; |
| 190 | for(UINT32 i = 0; i < 4; ++i) |
| 191 | { |
| 192 | PixelData data = skyTexture->lock(GBL_WRITE_ONLY_DISCARD, 0, sides[i]); |
| 193 | |
| 194 | data.setColorAt(skyColor, 0, 0); |
| 195 | data.setColorAt(skyColor, 1, 0); |
| 196 | data.setColorAt(Color::Black, 0, 1); |
| 197 | data.setColorAt(Color::Black, 1, 1); |
| 198 | |
| 199 | skyTexture->unlock(); |
| 200 | } |
| 201 | |
| 202 | { |
| 203 | PixelData data = skyTexture->lock(GBL_WRITE_ONLY_DISCARD, 0, CF_PositiveY); |
| 204 | |
| 205 | data.setColorAt(skyColor, 0, 0); |
| 206 | data.setColorAt(skyColor, 1, 0); |
| 207 | data.setColorAt(skyColor, 0, 1); |
| 208 | data.setColorAt(skyColor, 1, 1); |
| 209 | |
| 210 | skyTexture->unlock(); |
| 211 | } |
| 212 | |
| 213 | { |
| 214 | PixelData data = skyTexture->lock(GBL_WRITE_ONLY_DISCARD, 0, CF_NegativeY); |
| 215 | |
| 216 | data.setColorAt(Color::Black, 0, 0); |
| 217 | data.setColorAt(Color::Black, 1, 0); |
| 218 | data.setColorAt(Color::Black, 0, 1); |
| 219 | data.setColorAt(Color::Black, 1, 1); |
| 220 | |
| 221 | skyTexture->unlock(); |
| 222 | } |
| 223 | |
| 224 | TEXTURE_DESC irradianceCubemapDesc; |
| 225 | irradianceCubemapDesc.type = TEX_TYPE_CUBE_MAP; |
| 226 | irradianceCubemapDesc.format = PF_RG11B10F; |
| 227 | irradianceCubemapDesc.width = IBLUtility::IRRADIANCE_CUBEMAP_SIZE; |
| 228 | irradianceCubemapDesc.height = IBLUtility::IRRADIANCE_CUBEMAP_SIZE; |
| 229 | irradianceCubemapDesc.numMips = 0; |
| 230 | irradianceCubemapDesc.usage = TU_STATIC | TU_RENDERTARGET; |
| 231 | |
| 232 | SPtr<Texture> irradiance = Texture::create(irradianceCubemapDesc); |
| 233 | gIBLUtility().filterCubemapForIrradiance(skyTexture, irradiance); |
| 234 | |
| 235 | return irradiance; |
| 236 | } |
| 237 | |
| 238 | SPtr<Texture> RendererTextures::preintegratedEnvGF; |
| 239 | SPtr<Texture> RendererTextures::ssaoRandomization4x4; |
| 240 | SPtr<Texture> RendererTextures::defaultIndirect; |
| 241 | |
| 242 | void RendererTextures::startUp() |
| 243 | { |
| 244 | preintegratedEnvGF = generatePreintegratedEnvBRDF(); |
| 245 | ssaoRandomization4x4 = generate4x4RandomizationTexture(); |
| 246 | defaultIndirect = generateDefaultIndirect(); |
| 247 | } |
| 248 | |
| 249 | void RendererTextures::shutDown() |
| 250 | { |
| 251 | preintegratedEnvGF = nullptr; |
| 252 | ssaoRandomization4x4 = nullptr; |
| 253 | defaultIndirect = nullptr; |
| 254 | } |
| 255 | }} |
| 256 | |