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