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 "BsRenderBeastIBLUtility.h" |
4 | #include "Image/BsTexture.h" |
5 | #include "Material/BsGpuParamsSet.h" |
6 | #include "Renderer/BsRendererUtility.h" |
7 | #include "RenderAPI/BsGpuBuffer.h" |
8 | #include "BsRenderBeast.h" |
9 | |
10 | namespace bs { namespace ct |
11 | { |
12 | ReflectionCubeDownsampleParamDef gReflectionCubeDownsampleParamDef; |
13 | |
14 | ReflectionCubeDownsampleMat::ReflectionCubeDownsampleMat() |
15 | { |
16 | mParamBuffer = gReflectionCubeDownsampleParamDef.createBuffer(); |
17 | |
18 | mParams->setParamBlockBuffer("Input" , mParamBuffer); |
19 | mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gInputTex" , mInputTexture); |
20 | } |
21 | |
22 | void ReflectionCubeDownsampleMat::execute(const SPtr<Texture>& source, UINT32 face, UINT32 mip, |
23 | const SPtr<RenderTarget>& target) |
24 | { |
25 | BS_RENMAT_PROFILE_BLOCK |
26 | |
27 | gReflectionCubeDownsampleParamDef.gCubeFace.set(mParamBuffer, face); |
28 | |
29 | const RenderAPICapabilities& caps = gCaps(); |
30 | if(caps.hasCapability(RSC_TEXTURE_VIEWS)) |
31 | { |
32 | mInputTexture.set(source, TextureSurface(mip, 1, 0, 6)); |
33 | gReflectionCubeDownsampleParamDef.gMipLevel.set(mParamBuffer, 0); |
34 | } |
35 | else |
36 | { |
37 | mInputTexture.set(source); |
38 | gReflectionCubeDownsampleParamDef.gMipLevel.set(mParamBuffer, mip); |
39 | } |
40 | |
41 | RenderAPI& rapi = RenderAPI::instance(); |
42 | rapi.setRenderTarget(target); |
43 | |
44 | bind(); |
45 | gRendererUtility().drawScreenQuad(); |
46 | } |
47 | |
48 | const UINT32 ReflectionCubeImportanceSampleMat::NUM_SAMPLES = 1024; |
49 | ReflectionCubeImportanceSampleParamDef gReflectionCubeImportanceSampleParamDef; |
50 | |
51 | ReflectionCubeImportanceSampleMat::ReflectionCubeImportanceSampleMat() |
52 | { |
53 | mParamBuffer = gReflectionCubeImportanceSampleParamDef.createBuffer(); |
54 | |
55 | mParams->setParamBlockBuffer("Input" , mParamBuffer); |
56 | mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gInputTex" , mInputTexture); |
57 | } |
58 | |
59 | void ReflectionCubeImportanceSampleMat::_initDefines(ShaderDefines& defines) |
60 | { |
61 | defines.set("NUM_SAMPLES" , NUM_SAMPLES); |
62 | } |
63 | |
64 | void ReflectionCubeImportanceSampleMat::execute(const SPtr<Texture>& source, UINT32 face, UINT32 mip, |
65 | const SPtr<RenderTarget>& target) |
66 | { |
67 | BS_RENMAT_PROFILE_BLOCK |
68 | |
69 | mInputTexture.set(source); |
70 | gReflectionCubeImportanceSampleParamDef.gCubeFace.set(mParamBuffer, face); |
71 | gReflectionCubeImportanceSampleParamDef.gMipLevel.set(mParamBuffer, mip); |
72 | gReflectionCubeImportanceSampleParamDef.gNumMips.set(mParamBuffer, source->getProperties().getNumMipmaps() + 1); |
73 | |
74 | float width = (float)source->getProperties().getWidth(); |
75 | float height = (float)source->getProperties().getHeight(); |
76 | |
77 | // First part of the equation for determining mip level to sample from. |
78 | // See http://http.developer.nvidia.com/GPUGems3/gpugems3_ch20.html |
79 | float mipFactor = 0.5f * std::log2(width * height / NUM_SAMPLES); |
80 | gReflectionCubeImportanceSampleParamDef.gPrecomputedMipFactor.set(mParamBuffer, mipFactor); |
81 | |
82 | RenderAPI& rapi = RenderAPI::instance(); |
83 | rapi.setRenderTarget(target); |
84 | |
85 | bind(); |
86 | gRendererUtility().drawScreenQuad(); |
87 | } |
88 | |
89 | IrradianceComputeSHParamDef gIrradianceComputeSHParamDef; |
90 | |
91 | // TILE_WIDTH * TILE_HEIGHT must be pow2 because of parallel reduction algorithm |
92 | const static UINT32 TILE_WIDTH = 8; |
93 | const static UINT32 TILE_HEIGHT = 8; |
94 | |
95 | // For very small textures this should be reduced so number of launched threads can properly utilize GPU cores |
96 | const static UINT32 PIXELS_PER_THREAD = 4; |
97 | |
98 | IrradianceComputeSHMat::IrradianceComputeSHMat() |
99 | { |
100 | mParamBuffer = gIrradianceComputeSHParamDef.createBuffer(); |
101 | |
102 | mParams->setParamBlockBuffer("Params" , mParamBuffer); |
103 | mParams->getTextureParam(GPT_COMPUTE_PROGRAM, "gInputTex" , mInputTexture); |
104 | mParams->getBufferParam(GPT_COMPUTE_PROGRAM, "gOutput" , mOutputBuffer); |
105 | } |
106 | |
107 | void IrradianceComputeSHMat::_initDefines(ShaderDefines& defines) |
108 | { |
109 | defines.set("TILE_WIDTH" , TILE_WIDTH); |
110 | defines.set("TILE_HEIGHT" , TILE_HEIGHT); |
111 | defines.set("PIXELS_PER_THREAD" , PIXELS_PER_THREAD); |
112 | } |
113 | |
114 | void IrradianceComputeSHMat::execute(const SPtr<Texture>& source, UINT32 face, const SPtr<GpuBuffer>& output) |
115 | { |
116 | BS_RENMAT_PROFILE_BLOCK |
117 | |
118 | auto& props = source->getProperties(); |
119 | UINT32 faceSize = props.getWidth(); |
120 | assert(faceSize == props.getHeight()); |
121 | |
122 | Vector2I dispatchSize; |
123 | dispatchSize.x = Math::divideAndRoundUp(faceSize, TILE_WIDTH * PIXELS_PER_THREAD); |
124 | dispatchSize.y = Math::divideAndRoundUp(faceSize, TILE_HEIGHT * PIXELS_PER_THREAD); |
125 | |
126 | mInputTexture.set(source); |
127 | gIrradianceComputeSHParamDef.gCubeFace.set(mParamBuffer, face); |
128 | gIrradianceComputeSHParamDef.gFaceSize.set(mParamBuffer, source->getProperties().getWidth()); |
129 | gIrradianceComputeSHParamDef.gDispatchSize.set(mParamBuffer, dispatchSize); |
130 | |
131 | mOutputBuffer.set(output); |
132 | |
133 | RenderAPI& rapi = RenderAPI::instance(); |
134 | |
135 | bind(); |
136 | rapi.dispatchCompute(dispatchSize.x, dispatchSize.y); |
137 | } |
138 | |
139 | SPtr<GpuBuffer> IrradianceComputeSHMat::createOutputBuffer(const SPtr<Texture>& source, UINT32& numCoeffSets) |
140 | { |
141 | auto& props = source->getProperties(); |
142 | UINT32 faceSize = props.getWidth(); |
143 | assert(faceSize == props.getHeight()); |
144 | |
145 | Vector2I dispatchSize; |
146 | dispatchSize.x = Math::divideAndRoundUp(faceSize, TILE_WIDTH * PIXELS_PER_THREAD); |
147 | dispatchSize.y = Math::divideAndRoundUp(faceSize, TILE_HEIGHT * PIXELS_PER_THREAD); |
148 | |
149 | numCoeffSets = dispatchSize.x * dispatchSize.y * 6; |
150 | |
151 | GPU_BUFFER_DESC bufferDesc; |
152 | bufferDesc.type = GBT_STRUCTURED; |
153 | bufferDesc.elementCount = numCoeffSets; |
154 | bufferDesc.format = BF_UNKNOWN; |
155 | bufferDesc.usage = GBU_LOADSTORE; |
156 | |
157 | if(mVariation.getInt("SH_ORDER" ) == 3) |
158 | bufferDesc.elementSize = sizeof(SHCoeffsAndWeight3); |
159 | else |
160 | bufferDesc.elementSize = sizeof(SHCoeffsAndWeight5); |
161 | |
162 | return GpuBuffer::create(bufferDesc); |
163 | } |
164 | |
165 | IrradianceComputeSHMat* IrradianceComputeSHMat::getVariation(int order) |
166 | { |
167 | if (order == 3) |
168 | return get(getVariation<3>()); |
169 | |
170 | return get(getVariation<5>()); |
171 | } |
172 | |
173 | IrradianceComputeSHFragParamDef gIrradianceComputeSHFragParamDef; |
174 | |
175 | IrradianceComputeSHFragMat::IrradianceComputeSHFragMat() |
176 | { |
177 | mParamBuffer = gIrradianceComputeSHFragParamDef.createBuffer(); |
178 | |
179 | mParams->setParamBlockBuffer("Params" , mParamBuffer); |
180 | mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gInputTex" , mInputTexture); |
181 | } |
182 | |
183 | void IrradianceComputeSHFragMat::execute(const SPtr<Texture>& source, UINT32 face, UINT32 coefficientIdx, |
184 | const SPtr<RenderTarget>& output) |
185 | { |
186 | BS_RENMAT_PROFILE_BLOCK |
187 | |
188 | // Set parameters |
189 | mInputTexture.set(source); |
190 | |
191 | gIrradianceComputeSHFragParamDef.gCubeFace.set(mParamBuffer, face); |
192 | gIrradianceComputeSHFragParamDef.gFaceSize.set(mParamBuffer, source->getProperties().getWidth()); |
193 | gIrradianceComputeSHFragParamDef.gCoeffEntryIdx.set(mParamBuffer, coefficientIdx / 4); |
194 | gIrradianceComputeSHFragParamDef.gCoeffComponentIdx.set(mParamBuffer, coefficientIdx % 4); |
195 | |
196 | // Render |
197 | RenderAPI& rapi = RenderAPI::instance(); |
198 | rapi.setRenderTarget(output); |
199 | |
200 | bind(); |
201 | gRendererUtility().drawScreenQuad(); |
202 | |
203 | rapi.setRenderTarget(nullptr); |
204 | } |
205 | |
206 | POOLED_RENDER_TEXTURE_DESC IrradianceComputeSHFragMat::getOutputDesc(const SPtr<Texture>& input) |
207 | { |
208 | auto& props = input->getProperties(); |
209 | return POOLED_RENDER_TEXTURE_DESC::createCube(PF_RGBA16F, props.getWidth(), props.getHeight(), TU_RENDERTARGET); |
210 | } |
211 | |
212 | IrradianceAccumulateSHParamDef gIrradianceAccumulateSHParamDef; |
213 | |
214 | IrradianceAccumulateSHMat::IrradianceAccumulateSHMat() |
215 | { |
216 | mParamBuffer = gIrradianceAccumulateSHParamDef.createBuffer(); |
217 | |
218 | mParams->setParamBlockBuffer("Params" , mParamBuffer); |
219 | mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gInputTex" , mInputTexture); |
220 | } |
221 | |
222 | void IrradianceAccumulateSHMat::execute(const SPtr<Texture>& source, UINT32 face, UINT32 sourceMip, |
223 | const SPtr<RenderTarget>& output) |
224 | { |
225 | BS_RENMAT_PROFILE_BLOCK |
226 | |
227 | // Set parameters |
228 | mInputTexture.set(source); |
229 | |
230 | auto& props = source->getProperties(); |
231 | Vector2 halfPixel(0.5f / props.getWidth(), 0.5f / props.getHeight()); |
232 | |
233 | gIrradianceAccumulateSHParamDef.gCubeFace.set(mParamBuffer, face); |
234 | gIrradianceAccumulateSHParamDef.gCubeMip.set(mParamBuffer, sourceMip); |
235 | gIrradianceAccumulateSHParamDef.gHalfPixel.set(mParamBuffer, halfPixel); |
236 | |
237 | // Render |
238 | RenderAPI& rapi = RenderAPI::instance(); |
239 | rapi.setRenderTarget(output); |
240 | |
241 | bind(); |
242 | gRendererUtility().drawScreenQuad(); |
243 | |
244 | rapi.setRenderTarget(nullptr); |
245 | } |
246 | |
247 | POOLED_RENDER_TEXTURE_DESC IrradianceAccumulateSHMat::getOutputDesc(const SPtr<Texture>& input) |
248 | { |
249 | auto& props = input->getProperties(); |
250 | |
251 | // Assuming it's a cubemap |
252 | UINT32 size = std::max(1U, (UINT32)(props.getWidth() * 0.5f)); |
253 | |
254 | return POOLED_RENDER_TEXTURE_DESC::createCube(PF_RGBA32F, size, size, TU_RENDERTARGET); |
255 | } |
256 | |
257 | IrradianceAccumulateCubeSHMat::IrradianceAccumulateCubeSHMat() |
258 | { |
259 | mParamBuffer = gIrradianceAccumulateSHParamDef.createBuffer(); |
260 | |
261 | mParams->setParamBlockBuffer("Params" , mParamBuffer); |
262 | mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gInputTex" , mInputTexture); |
263 | } |
264 | |
265 | void IrradianceAccumulateCubeSHMat::execute(const SPtr<Texture>& source, UINT32 sourceMip, const Vector2I& outputOffset, |
266 | UINT32 coefficientIdx, const SPtr<RenderTarget>& output) |
267 | { |
268 | BS_RENMAT_PROFILE_BLOCK |
269 | |
270 | // Set parameters |
271 | mInputTexture.set(source); |
272 | |
273 | auto& props = source->getProperties(); |
274 | Vector2 halfPixel(0.5f / props.getWidth(), 0.5f / props.getHeight()); |
275 | |
276 | gIrradianceAccumulateSHParamDef.gCubeFace.set(mParamBuffer, 0); |
277 | gIrradianceAccumulateSHParamDef.gCubeMip.set(mParamBuffer, sourceMip); |
278 | gIrradianceAccumulateSHParamDef.gHalfPixel.set(mParamBuffer, halfPixel); |
279 | |
280 | auto& rtProps = output->getProperties(); |
281 | |
282 | // Render to just one pixel corresponding to the coefficient |
283 | Rect2 viewRect; |
284 | viewRect.x = (outputOffset.x + coefficientIdx) / (float)rtProps.width; |
285 | viewRect.y = outputOffset.y / (float)rtProps.height; |
286 | |
287 | viewRect.width = 1.0f / rtProps.width; |
288 | viewRect.height = 1.0f / rtProps.height; |
289 | |
290 | // Render |
291 | RenderAPI& rapi = RenderAPI::instance(); |
292 | rapi.setRenderTarget(output); |
293 | rapi.setViewport(viewRect); |
294 | |
295 | bind(); |
296 | gRendererUtility().drawScreenQuad(); |
297 | |
298 | rapi.setRenderTarget(nullptr); |
299 | rapi.setViewport(Rect2(0, 0, 1, 1)); |
300 | } |
301 | |
302 | POOLED_RENDER_TEXTURE_DESC IrradianceAccumulateCubeSHMat::getOutputDesc() |
303 | { |
304 | return POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA32F, 9, 1, TU_RENDERTARGET); |
305 | } |
306 | |
307 | IrradianceReduceSHParamDef gIrradianceReduceSHParamDef; |
308 | |
309 | IrradianceReduceSHMat::IrradianceReduceSHMat() |
310 | { |
311 | mParamBuffer = gIrradianceReduceSHParamDef.createBuffer(); |
312 | |
313 | mParams->setParamBlockBuffer("Params" , mParamBuffer); |
314 | mParams->getBufferParam(GPT_COMPUTE_PROGRAM, "gInput" , mInputBuffer); |
315 | mParams->getLoadStoreTextureParam(GPT_COMPUTE_PROGRAM, "gOutput" , mOutputTexture); |
316 | } |
317 | |
318 | void IrradianceReduceSHMat::execute(const SPtr<GpuBuffer>& source, UINT32 numCoeffSets, |
319 | const SPtr<Texture>& output, UINT32 outputIdx) |
320 | { |
321 | BS_RENMAT_PROFILE_BLOCK |
322 | |
323 | UINT32 shOrder = (UINT32)mVariation.getInt("SH_ORDER" ); |
324 | |
325 | Vector2I outputCoords = IBLUtility::getSHCoeffXYFromIdx(outputIdx, shOrder); |
326 | gIrradianceReduceSHParamDef.gOutputIdx.set(mParamBuffer, outputCoords); |
327 | gIrradianceReduceSHParamDef.gNumEntries.set(mParamBuffer, numCoeffSets); |
328 | |
329 | mInputBuffer.set(source); |
330 | mOutputTexture.set(output); |
331 | |
332 | bind(); |
333 | |
334 | RenderAPI& rapi = RenderAPI::instance(); |
335 | rapi.dispatchCompute(1); |
336 | } |
337 | |
338 | SPtr<Texture> IrradianceReduceSHMat::createOutputTexture(UINT32 numCoeffSets) |
339 | { |
340 | UINT32 shOrder = (UINT32)mVariation.getInt("SH_ORDER" ); |
341 | Vector2I size = IBLUtility::getSHCoeffTextureSize(numCoeffSets, shOrder); |
342 | |
343 | TEXTURE_DESC textureDesc; |
344 | textureDesc.width = (UINT32)size.x; |
345 | textureDesc.height = (UINT32)size.y; |
346 | textureDesc.format = PF_RGBA32F; |
347 | textureDesc.usage = TU_STATIC | TU_LOADSTORE; |
348 | |
349 | return Texture::create(textureDesc); |
350 | } |
351 | |
352 | IrradianceReduceSHMat* IrradianceReduceSHMat::getVariation(int order) |
353 | { |
354 | if (order == 3) |
355 | return get(getVariation<3>()); |
356 | |
357 | return get(getVariation<5>()); |
358 | } |
359 | |
360 | IrradianceProjectSHParamDef gIrradianceProjectSHParamDef; |
361 | |
362 | IrradianceProjectSHMat::IrradianceProjectSHMat() |
363 | { |
364 | mParamBuffer = gIrradianceProjectSHParamDef.createBuffer(); |
365 | |
366 | mParams->setParamBlockBuffer("Params" , mParamBuffer); |
367 | mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gSHCoeffs" , mInputTexture); |
368 | } |
369 | |
370 | void IrradianceProjectSHMat::execute(const SPtr<Texture>& shCoeffs, UINT32 face, const SPtr<RenderTarget>& target) |
371 | { |
372 | BS_RENMAT_PROFILE_BLOCK |
373 | |
374 | gIrradianceProjectSHParamDef.gCubeFace.set(mParamBuffer, face); |
375 | |
376 | mInputTexture.set(shCoeffs); |
377 | |
378 | RenderAPI& rapi = RenderAPI::instance(); |
379 | rapi.setRenderTarget(target); |
380 | |
381 | bind(); |
382 | gRendererUtility().drawScreenQuad(); |
383 | } |
384 | |
385 | void RenderBeastIBLUtility::filterCubemapForSpecular(const SPtr<Texture>& cubemap, const SPtr<Texture>& scratch) const |
386 | { |
387 | auto& props = cubemap->getProperties(); |
388 | |
389 | SPtr<Texture> scratchCubemap = scratch; |
390 | if (scratchCubemap == nullptr) |
391 | { |
392 | TEXTURE_DESC cubemapDesc; |
393 | cubemapDesc.type = TEX_TYPE_CUBE_MAP; |
394 | cubemapDesc.format = props.getFormat(); |
395 | cubemapDesc.width = props.getWidth(); |
396 | cubemapDesc.height = props.getHeight(); |
397 | cubemapDesc.numMips = PixelUtil::getMaxMipmaps(cubemapDesc.width, cubemapDesc.height, 1, cubemapDesc.format); |
398 | cubemapDesc.usage = TU_STATIC | TU_RENDERTARGET; |
399 | |
400 | scratchCubemap = Texture::create(cubemapDesc); |
401 | } |
402 | |
403 | // We sample the cubemaps using importance sampling to generate roughness |
404 | UINT32 numMips = props.getNumMipmaps() + 1; |
405 | |
406 | // Before importance sampling the cubemaps we first create box filtered versions for each mip level. This helps fix |
407 | // the aliasing artifacts that would otherwise be noticeable on importance sampled cubemaps. The aliasing happens |
408 | // because: |
409 | // 1. We use the same random samples for all pixels, which appears to duplicate reflections instead of creating |
410 | // noise, which is usually more acceptable |
411 | // 2. Even if we were to use fully random samples we would need a lot to avoid noticeable noise, which isn't |
412 | // practical |
413 | |
414 | // Copy base mip level to scratch cubemap |
415 | for (UINT32 face = 0; face < 6; face++) |
416 | { |
417 | TEXTURE_COPY_DESC copyDesc; |
418 | copyDesc.srcFace = face; |
419 | copyDesc.dstFace = face; |
420 | |
421 | cubemap->copy(scratchCubemap, copyDesc); |
422 | } |
423 | |
424 | // Fill out remaining scratch mip levels by downsampling |
425 | for (UINT32 mip = 1; mip < numMips; mip++) |
426 | { |
427 | UINT32 sourceMip = mip - 1; |
428 | downsampleCubemap(scratchCubemap, sourceMip, scratchCubemap, mip); |
429 | } |
430 | |
431 | // Importance sample |
432 | for (UINT32 mip = 1; mip < numMips; mip++) |
433 | { |
434 | for (UINT32 face = 0; face < 6; face++) |
435 | { |
436 | RENDER_TEXTURE_DESC cubeFaceRTDesc; |
437 | cubeFaceRTDesc.colorSurfaces[0].texture = cubemap; |
438 | cubeFaceRTDesc.colorSurfaces[0].face = face; |
439 | cubeFaceRTDesc.colorSurfaces[0].numFaces = 1; |
440 | cubeFaceRTDesc.colorSurfaces[0].mipLevel = mip; |
441 | |
442 | SPtr<RenderTarget> target = RenderTexture::create(cubeFaceRTDesc); |
443 | |
444 | ReflectionCubeImportanceSampleMat* material = ReflectionCubeImportanceSampleMat::get(); |
445 | material->execute(scratchCubemap, face, mip, target); |
446 | } |
447 | } |
448 | |
449 | RenderAPI& rapi = RenderAPI::instance(); |
450 | rapi.setRenderTarget(nullptr); |
451 | } |
452 | |
453 | bool supportsComputeSH() |
454 | { |
455 | return gRenderBeast()->getFeatureSet() == RenderBeastFeatureSet::Desktop; |
456 | } |
457 | |
458 | void RenderBeastIBLUtility::filterCubemapForIrradiance(const SPtr<Texture>& cubemap, const SPtr<Texture>& output) const |
459 | { |
460 | SPtr<Texture> coeffTexture; |
461 | if(supportsComputeSH()) |
462 | { |
463 | IrradianceComputeSHMat* shCompute = IrradianceComputeSHMat::getVariation(5); |
464 | IrradianceReduceSHMat* shReduce = IrradianceReduceSHMat::getVariation(5); |
465 | |
466 | UINT32 numCoeffSets; |
467 | SPtr<GpuBuffer> coeffSetBuffer = shCompute->createOutputBuffer(cubemap, numCoeffSets); |
468 | for (UINT32 face = 0; face < 6; face++) |
469 | shCompute->execute(cubemap, face, coeffSetBuffer); |
470 | |
471 | coeffTexture = shReduce->createOutputTexture(1); |
472 | shReduce->execute(coeffSetBuffer, numCoeffSets, coeffTexture, 0); |
473 | } |
474 | else |
475 | { |
476 | GpuResourcePool& resPool = GpuResourcePool::instance(); |
477 | SPtr<PooledRenderTexture> finalCoeffs = resPool.get(IrradianceAccumulateCubeSHMat::getOutputDesc()); |
478 | |
479 | filterCubemapForIrradianceNonCompute(cubemap, 0, finalCoeffs->renderTexture); |
480 | coeffTexture = finalCoeffs->texture; |
481 | } |
482 | |
483 | IrradianceProjectSHMat* shProject = IrradianceProjectSHMat::get(); |
484 | for (UINT32 face = 0; face < 6; face++) |
485 | { |
486 | RENDER_TEXTURE_DESC cubeFaceRTDesc; |
487 | cubeFaceRTDesc.colorSurfaces[0].texture = output; |
488 | cubeFaceRTDesc.colorSurfaces[0].face = face; |
489 | cubeFaceRTDesc.colorSurfaces[0].numFaces = 1; |
490 | cubeFaceRTDesc.colorSurfaces[0].mipLevel = 0; |
491 | |
492 | SPtr<RenderTarget> target = RenderTexture::create(cubeFaceRTDesc); |
493 | shProject->execute(coeffTexture, face, target); |
494 | } |
495 | } |
496 | |
497 | void RenderBeastIBLUtility::filterCubemapForIrradiance(const SPtr<Texture>& cubemap, const SPtr<Texture>& output, |
498 | UINT32 outputIdx) const |
499 | { |
500 | if(supportsComputeSH()) |
501 | { |
502 | IrradianceComputeSHMat* shCompute = IrradianceComputeSHMat::getVariation(3); |
503 | IrradianceReduceSHMat* shReduce = IrradianceReduceSHMat::getVariation(3); |
504 | |
505 | UINT32 numCoeffSets; |
506 | SPtr<GpuBuffer> coeffSetBuffer = shCompute->createOutputBuffer(cubemap, numCoeffSets); |
507 | for (UINT32 face = 0; face < 6; face++) |
508 | shCompute->execute(cubemap, face, coeffSetBuffer); |
509 | |
510 | shReduce->execute(coeffSetBuffer, numCoeffSets, output, outputIdx); |
511 | } |
512 | else |
513 | { |
514 | RENDER_TEXTURE_DESC rtDesc; |
515 | rtDesc.colorSurfaces[0].texture = output; |
516 | |
517 | SPtr<RenderTexture> target = RenderTexture::create(rtDesc); |
518 | filterCubemapForIrradianceNonCompute(cubemap, outputIdx, target); |
519 | } |
520 | } |
521 | |
522 | void RenderBeastIBLUtility::scaleCubemap(const SPtr<Texture>& src, UINT32 srcMip, const SPtr<Texture>& dst, |
523 | UINT32 dstMip) const |
524 | { |
525 | auto& srcProps = src->getProperties(); |
526 | auto& dstProps = dst->getProperties(); |
527 | |
528 | SPtr<Texture> scratchTex = src; |
529 | int sizeSrcLog2 = (int)log2((float)srcProps.getWidth()); |
530 | int sizeDstLog2 = (int)log2((float)dstProps.getWidth()); |
531 | |
532 | int sizeLog2Diff = sizeSrcLog2 - sizeDstLog2; |
533 | |
534 | // If size difference is greater than one mip-level and we're downscaling, we need to generate intermediate mip |
535 | // levels |
536 | if(sizeLog2Diff > 1) |
537 | { |
538 | UINT32 mipSize = (UINT32)exp2((float)(sizeSrcLog2 - 1)); |
539 | UINT32 numDownsamples = sizeLog2Diff - 1; |
540 | |
541 | TEXTURE_DESC cubemapDesc; |
542 | cubemapDesc.type = TEX_TYPE_CUBE_MAP; |
543 | cubemapDesc.format = srcProps.getFormat(); |
544 | cubemapDesc.width = mipSize; |
545 | cubemapDesc.height = mipSize; |
546 | cubemapDesc.numMips = numDownsamples - 1; |
547 | cubemapDesc.usage = TU_STATIC | TU_RENDERTARGET; |
548 | |
549 | scratchTex = Texture::create(cubemapDesc); |
550 | |
551 | downsampleCubemap(src, srcMip, scratchTex, 0); |
552 | for(UINT32 i = 0; i < cubemapDesc.numMips; i++) |
553 | downsampleCubemap(scratchTex, i, scratchTex, i + 1); |
554 | |
555 | srcMip = cubemapDesc.numMips; |
556 | } |
557 | |
558 | // Same size so just copy |
559 | if(sizeSrcLog2 == sizeDstLog2) |
560 | { |
561 | for (UINT32 face = 0; face < 6; face++) |
562 | { |
563 | TEXTURE_COPY_DESC copyDesc; |
564 | copyDesc.srcFace = face; |
565 | copyDesc.srcMip = srcMip; |
566 | copyDesc.dstFace = face; |
567 | copyDesc.dstMip = dstMip; |
568 | |
569 | src->copy(dst, copyDesc); |
570 | } |
571 | } |
572 | else |
573 | downsampleCubemap(scratchTex, srcMip, dst, dstMip); |
574 | } |
575 | |
576 | void RenderBeastIBLUtility::downsampleCubemap(const SPtr<Texture>& src, UINT32 srcMip, const SPtr<Texture>& dst, |
577 | UINT32 dstMip) |
578 | { |
579 | for (UINT32 face = 0; face < 6; face++) |
580 | { |
581 | RENDER_TEXTURE_DESC cubeFaceRTDesc; |
582 | cubeFaceRTDesc.colorSurfaces[0].texture = dst; |
583 | cubeFaceRTDesc.colorSurfaces[0].face = face; |
584 | cubeFaceRTDesc.colorSurfaces[0].numFaces = 1; |
585 | cubeFaceRTDesc.colorSurfaces[0].mipLevel = dstMip; |
586 | |
587 | SPtr<RenderTarget> target = RenderTexture::create(cubeFaceRTDesc); |
588 | |
589 | ReflectionCubeDownsampleMat* material = ReflectionCubeDownsampleMat::get(); |
590 | material->execute(src, face, srcMip, target); |
591 | } |
592 | } |
593 | |
594 | void RenderBeastIBLUtility::filterCubemapForIrradianceNonCompute(const SPtr<Texture>& cubemap, UINT32 outputIdx, |
595 | const SPtr<RenderTexture>& output) |
596 | { |
597 | static const UINT32 NUM_COEFFS = 9; |
598 | |
599 | GpuResourcePool& resPool = GpuResourcePool::instance(); |
600 | IrradianceComputeSHFragMat* shCompute = IrradianceComputeSHFragMat::get(); |
601 | IrradianceAccumulateSHMat* shAccum = IrradianceAccumulateSHMat::get(); |
602 | IrradianceAccumulateCubeSHMat* shAccumCube = IrradianceAccumulateCubeSHMat::get(); |
603 | |
604 | for(UINT32 coeff = 0; coeff < NUM_COEFFS; ++coeff) |
605 | { |
606 | SPtr<PooledRenderTexture> coeffsTex = resPool.get(shCompute->getOutputDesc(cubemap)); |
607 | |
608 | // Generate SH coefficients and weights per-texel |
609 | for(UINT32 face = 0; face < 6; face++) |
610 | { |
611 | RENDER_TEXTURE_DESC cubeFaceRTDesc; |
612 | cubeFaceRTDesc.colorSurfaces[0].texture = coeffsTex->texture; |
613 | cubeFaceRTDesc.colorSurfaces[0].face = face; |
614 | cubeFaceRTDesc.colorSurfaces[0].numFaces = 1; |
615 | cubeFaceRTDesc.colorSurfaces[0].mipLevel = 0; |
616 | |
617 | SPtr<RenderTarget> target = RenderTexture::create(cubeFaceRTDesc); |
618 | shCompute->execute(cubemap, face, coeff, target); |
619 | } |
620 | |
621 | // Downsample, summing up coefficients and weights all the way down to 1x1 |
622 | auto& sourceProps = cubemap->getProperties(); |
623 | UINT32 numMips = PixelUtil::getMaxMipmaps(sourceProps.getWidth(), sourceProps.getHeight(), 1, |
624 | sourceProps.getFormat()); |
625 | |
626 | SPtr<PooledRenderTexture> downsampleInput = coeffsTex; |
627 | coeffsTex = nullptr; |
628 | |
629 | for(UINT32 mip = 0; mip < numMips; mip++) |
630 | { |
631 | SPtr<PooledRenderTexture> accumCoeffsTex = resPool.get(shAccum->getOutputDesc(downsampleInput->texture)); |
632 | |
633 | for(UINT32 face = 0; face < 6; face++) |
634 | { |
635 | RENDER_TEXTURE_DESC cubeFaceRTDesc; |
636 | cubeFaceRTDesc.colorSurfaces[0].texture = accumCoeffsTex->texture; |
637 | cubeFaceRTDesc.colorSurfaces[0].face = face; |
638 | cubeFaceRTDesc.colorSurfaces[0].numFaces = 1; |
639 | cubeFaceRTDesc.colorSurfaces[0].mipLevel = 0; |
640 | |
641 | SPtr<RenderTarget> target = RenderTexture::create(cubeFaceRTDesc); |
642 | shAccum->execute(downsampleInput->texture, face, 0, target); |
643 | } |
644 | |
645 | downsampleInput = accumCoeffsTex; |
646 | } |
647 | |
648 | // Sum up all the faces and write the coefficient to the final texture |
649 | Vector2I outputOffset = getSHCoeffXYFromIdx(outputIdx, 3); |
650 | shAccumCube->execute(downsampleInput->texture, 0, outputOffset, coeff, output); |
651 | } |
652 | } |
653 | }} |
654 | |