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