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 | |