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 "BsRendererView.h" |
4 | #include "Renderer/BsCamera.h" |
5 | #include "Renderer/BsRenderable.h" |
6 | #include "Renderer/BsRendererUtility.h" |
7 | #include "Material/BsMaterial.h" |
8 | #include "Material/BsShader.h" |
9 | #include "Material/BsGpuParamsSet.h" |
10 | #include "BsRendererLight.h" |
11 | #include "BsRendererScene.h" |
12 | #include "BsRenderBeast.h" |
13 | #include <BsRendererDecal.h> |
14 | |
15 | namespace bs { namespace ct |
16 | { |
17 | PerCameraParamDef gPerCameraParamDef; |
18 | SkyboxParamDef gSkyboxParamDef; |
19 | |
20 | SkyboxMat::SkyboxMat() |
21 | { |
22 | if(mParams->hasTexture(GPT_FRAGMENT_PROGRAM, "gSkyTex" )) |
23 | mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gSkyTex" , mSkyTextureParam); |
24 | |
25 | mParamBuffer = gSkyboxParamDef.createBuffer(); |
26 | |
27 | if(mParams->hasParamBlock(GPT_FRAGMENT_PROGRAM, "Params" )) |
28 | mParams->setParamBlockBuffer("Params" , mParamBuffer); |
29 | } |
30 | |
31 | void SkyboxMat::bind(const SPtr<GpuParamBlockBuffer>& perCamera, const SPtr<Texture>& texture, const Color& solidColor) |
32 | { |
33 | mParams->setParamBlockBuffer("PerCamera" , perCamera); |
34 | |
35 | mSkyTextureParam.set(texture); |
36 | |
37 | gSkyboxParamDef.gClearColor.set(mParamBuffer, solidColor); |
38 | mParamBuffer->flushToGPU(); |
39 | |
40 | RendererMaterial::bind(); |
41 | } |
42 | |
43 | SkyboxMat* SkyboxMat::getVariation(bool color) |
44 | { |
45 | if (color) |
46 | return get(getVariation<true>()); |
47 | |
48 | return get(getVariation<false>()); |
49 | } |
50 | |
51 | RendererViewData::RendererViewData() |
52 | :encodeDepth(false), depthEncodeNear(0.0f), depthEncodeFar(0.0f) |
53 | { |
54 | |
55 | } |
56 | |
57 | RendererViewProperties::RendererViewProperties(const RENDERER_VIEW_DESC& src) |
58 | :RendererViewData(src), frameIdx(0), target(src.target) |
59 | { |
60 | viewProjTransform = src.projTransform * src.viewTransform; |
61 | } |
62 | |
63 | RendererView::RendererView() |
64 | : mCamera(nullptr), mRenderSettingsHash(0), mViewIdx(-1) |
65 | { |
66 | mParamBuffer = gPerCameraParamDef.createBuffer(); |
67 | } |
68 | |
69 | RendererView::RendererView(const RENDERER_VIEW_DESC& desc) |
70 | : mProperties(desc), mCamera(desc.sceneCamera), mRenderSettingsHash(0), mViewIdx(-1) |
71 | { |
72 | mParamBuffer = gPerCameraParamDef.createBuffer(); |
73 | mProperties.prevViewProjTransform = mProperties.viewProjTransform; |
74 | |
75 | setStateReductionMode(desc.stateReduction); |
76 | } |
77 | |
78 | void RendererView::setStateReductionMode(StateReduction reductionMode) |
79 | { |
80 | mDeferredOpaqueQueue = bs_shared_ptr_new<RenderQueue>(reductionMode); |
81 | mForwardOpaqueQueue = bs_shared_ptr_new<RenderQueue>(reductionMode); |
82 | |
83 | StateReduction transparentStateReduction = reductionMode; |
84 | if (transparentStateReduction == StateReduction::Material) |
85 | transparentStateReduction = StateReduction::Distance; // Transparent object MUST be sorted by distance |
86 | |
87 | mTransparentQueue = bs_shared_ptr_new<RenderQueue>(transparentStateReduction); |
88 | mDecalQueue = bs_shared_ptr_new<RenderQueue>(StateReduction::Material); |
89 | } |
90 | |
91 | void RendererView::setRenderSettings(const SPtr<RenderSettings>& settings) |
92 | { |
93 | if (mRenderSettings == nullptr) |
94 | mRenderSettings = bs_shared_ptr_new<RenderSettings>(); |
95 | |
96 | if (settings != nullptr) |
97 | *mRenderSettings = *settings; |
98 | |
99 | mRenderSettingsHash++; |
100 | |
101 | // Update compositor hierarchy (Note: Needs to be called even when viewport size (or other information) changes, |
102 | // but we're currently calling it here as all such calls are followed by setRenderSettings. |
103 | mCompositor.build(*this, RCNodeFinalResolve::getNodeId()); |
104 | } |
105 | |
106 | void RendererView::setTransform(const Vector3& origin, const Vector3& direction, const Matrix4& view, |
107 | const Matrix4& proj, const ConvexVolume& worldFrustum) |
108 | { |
109 | mProperties.viewOrigin = origin; |
110 | mProperties.viewDirection = direction; |
111 | mProperties.viewTransform = view; |
112 | mProperties.projTransform = proj; |
113 | mProperties.cullFrustum = worldFrustum; |
114 | mProperties.viewProjTransform = proj * view; |
115 | } |
116 | |
117 | void RendererView::setView(const RENDERER_VIEW_DESC& desc) |
118 | { |
119 | mCamera = desc.sceneCamera; |
120 | mProperties = desc; |
121 | mProperties.viewProjTransform = desc.projTransform * desc.viewTransform; |
122 | mProperties.prevViewProjTransform = Matrix4::IDENTITY; |
123 | mProperties.target = desc.target; |
124 | |
125 | setStateReductionMode(desc.stateReduction); |
126 | } |
127 | |
128 | void RendererView::beginFrame() |
129 | { |
130 | // Check if render target resized and update the view properties accordingly |
131 | // Note: Normally we rely on the renderer notify* methods to let us know of changes to camera/viewport, but since |
132 | // render target resize can often originate from the core thread, this avoids the back and forth between |
133 | // main <-> core thread, and the frame delay that comes with it |
134 | if(mCamera) |
135 | { |
136 | const SPtr<Viewport>& viewport = mCamera->getViewport(); |
137 | if(viewport) |
138 | { |
139 | UINT32 newTargetWidth = 0; |
140 | UINT32 newTargetHeight = 0; |
141 | if (mProperties.target.target != nullptr) |
142 | { |
143 | newTargetWidth = mProperties.target.target->getProperties().width; |
144 | newTargetHeight = mProperties.target.target->getProperties().height; |
145 | } |
146 | |
147 | if(newTargetWidth != mProperties.target.targetWidth || |
148 | newTargetHeight != mProperties.target.targetHeight) |
149 | { |
150 | mProperties.target.viewRect = viewport->getPixelArea(); |
151 | mProperties.target.targetWidth = newTargetWidth; |
152 | mProperties.target.targetHeight = newTargetHeight; |
153 | |
154 | updatePerViewBuffer(); |
155 | } |
156 | } |
157 | } |
158 | |
159 | // Note: inverse view-projection can be cached, it doesn't change every frame |
160 | Matrix4 viewProj = mProperties.projTransform * mProperties.viewTransform; |
161 | Matrix4 invViewProj = viewProj.inverse(); |
162 | Matrix4 NDCToPrevNDC = mProperties.prevViewProjTransform * invViewProj; |
163 | |
164 | gPerCameraParamDef.gNDCToPrevNDC.set(mParamBuffer, NDCToPrevNDC); |
165 | } |
166 | |
167 | void RendererView::endFrame() |
168 | { |
169 | // Save view-projection matrix to use for temporal filtering |
170 | mProperties.prevViewProjTransform = mProperties.viewProjTransform; |
171 | |
172 | // Advance per-view frame index. This is used primarily by temporal rendering effects, and pausing the frame index |
173 | // allows you to freeze the current rendering as is, without temporal artifacts. |
174 | mProperties.frameIdx++; |
175 | |
176 | mDeferredOpaqueQueue->clear(); |
177 | mForwardOpaqueQueue->clear(); |
178 | mTransparentQueue->clear(); |
179 | mDecalQueue->clear(); |
180 | } |
181 | |
182 | void RendererView::determineVisible(const Vector<RendererRenderable*>& renderables, const Vector<CullInfo>& cullInfos, |
183 | Vector<bool>* visibility) |
184 | { |
185 | mVisibility.renderables.clear(); |
186 | mVisibility.renderables.resize(renderables.size(), false); |
187 | |
188 | if (mRenderSettings->overlayOnly) |
189 | return; |
190 | |
191 | calculateVisibility(cullInfos, mVisibility.renderables); |
192 | |
193 | if(visibility != nullptr) |
194 | { |
195 | for (UINT32 i = 0; i < (UINT32)renderables.size(); i++) |
196 | { |
197 | bool visible = (*visibility)[i]; |
198 | |
199 | (*visibility)[i] = visible || mVisibility.renderables[i]; |
200 | } |
201 | } |
202 | } |
203 | |
204 | void RendererView::determineVisible(const Vector<RendererParticles>& particleSystems, const Vector<CullInfo>& cullInfos, |
205 | Vector<bool>* visibility) |
206 | { |
207 | mVisibility.particleSystems.clear(); |
208 | mVisibility.particleSystems.resize(particleSystems.size(), false); |
209 | |
210 | if (mRenderSettings->overlayOnly) |
211 | return; |
212 | |
213 | calculateVisibility(cullInfos, mVisibility.particleSystems); |
214 | |
215 | if(visibility != nullptr) |
216 | { |
217 | for (UINT32 i = 0; i < (UINT32)particleSystems.size(); i++) |
218 | { |
219 | bool visible = (*visibility)[i]; |
220 | |
221 | (*visibility)[i] = visible || mVisibility.particleSystems[i]; |
222 | } |
223 | } |
224 | } |
225 | |
226 | void RendererView::determineVisible(const Vector<RendererDecal>& decals, const Vector<CullInfo>& cullInfos, |
227 | Vector<bool>* visibility) |
228 | { |
229 | mVisibility.decals.clear(); |
230 | mVisibility.decals.resize(decals.size(), false); |
231 | |
232 | if (mRenderSettings->overlayOnly) |
233 | return; |
234 | |
235 | calculateVisibility(cullInfos, mVisibility.decals); |
236 | |
237 | if(visibility != nullptr) |
238 | { |
239 | for (UINT32 i = 0; i < (UINT32)decals.size(); i++) |
240 | { |
241 | bool visible = (*visibility)[i]; |
242 | |
243 | (*visibility)[i] = visible || mVisibility.decals[i]; |
244 | } |
245 | } |
246 | } |
247 | |
248 | void RendererView::determineVisible(const Vector<RendererLight>& lights, const Vector<Sphere>& bounds, |
249 | LightType lightType, Vector<bool>* visibility) |
250 | { |
251 | // Special case for directional lights, they're always visible |
252 | if(lightType == LightType::Directional) |
253 | { |
254 | if (visibility) |
255 | visibility->assign(lights.size(), true); |
256 | |
257 | return; |
258 | } |
259 | |
260 | Vector<bool>* perViewVisibility; |
261 | if(lightType == LightType::Radial) |
262 | { |
263 | mVisibility.radialLights.clear(); |
264 | mVisibility.radialLights.resize(lights.size(), false); |
265 | |
266 | perViewVisibility = &mVisibility.radialLights; |
267 | } |
268 | else // Spot |
269 | { |
270 | mVisibility.spotLights.clear(); |
271 | mVisibility.spotLights.resize(lights.size(), false); |
272 | |
273 | perViewVisibility = &mVisibility.spotLights; |
274 | } |
275 | |
276 | if (mRenderSettings->overlayOnly) |
277 | return; |
278 | |
279 | calculateVisibility(bounds, *perViewVisibility); |
280 | |
281 | if(visibility != nullptr) |
282 | { |
283 | for (UINT32 i = 0; i < (UINT32)lights.size(); i++) |
284 | { |
285 | bool visible = (*visibility)[i]; |
286 | |
287 | (*visibility)[i] = visible || (*perViewVisibility)[i]; |
288 | } |
289 | } |
290 | } |
291 | |
292 | void RendererView::calculateVisibility(const Vector<CullInfo>& cullInfos, Vector<bool>& visibility) const |
293 | { |
294 | UINT64 cameraLayers = mProperties.visibleLayers; |
295 | const ConvexVolume& worldFrustum = mProperties.cullFrustum; |
296 | const Vector3& worldCameraPosition = mProperties.viewOrigin; |
297 | float baseCullDistance = mRenderSettings->cullDistance; |
298 | |
299 | for (UINT32 i = 0; i < (UINT32)cullInfos.size(); i++) |
300 | { |
301 | if ((cullInfos[i].layer & cameraLayers) == 0) |
302 | continue; |
303 | |
304 | // Do distance culling |
305 | const Sphere& boundingSphere = cullInfos[i].bounds.getSphere(); |
306 | const Vector3& worldRenderablePosition = boundingSphere.getCenter(); |
307 | |
308 | float distanceToCameraSq = worldCameraPosition.squaredDistance(worldRenderablePosition); |
309 | float correctedCullDistance = cullInfos[i].cullDistanceFactor * baseCullDistance; |
310 | float maxDistanceToCamera = correctedCullDistance + boundingSphere.getRadius(); |
311 | |
312 | if (distanceToCameraSq > maxDistanceToCamera * maxDistanceToCamera) |
313 | continue; |
314 | |
315 | // Do frustum culling |
316 | // Note: This is bound to be a bottleneck at some point. When it is ensure that intersect methods use vector |
317 | // operations, as it is trivial to update them. Also consider spatial partitioning. |
318 | if (worldFrustum.intersects(boundingSphere)) |
319 | { |
320 | // More precise with the box |
321 | const AABox& boundingBox = cullInfos[i].bounds.getBox(); |
322 | |
323 | if (worldFrustum.intersects(boundingBox)) |
324 | visibility[i] = true; |
325 | } |
326 | } |
327 | } |
328 | |
329 | void RendererView::calculateVisibility(const Vector<Sphere>& bounds, Vector<bool>& visibility) const |
330 | { |
331 | const ConvexVolume& worldFrustum = mProperties.cullFrustum; |
332 | |
333 | for (UINT32 i = 0; i < (UINT32)bounds.size(); i++) |
334 | { |
335 | if (worldFrustum.intersects(bounds[i])) |
336 | visibility[i] = true; |
337 | } |
338 | } |
339 | |
340 | void RendererView::calculateVisibility(const Vector<AABox>& bounds, Vector<bool>& visibility) const |
341 | { |
342 | const ConvexVolume& worldFrustum = mProperties.cullFrustum; |
343 | |
344 | for (UINT32 i = 0; i < (UINT32)bounds.size(); i++) |
345 | { |
346 | if (worldFrustum.intersects(bounds[i])) |
347 | visibility[i] = true; |
348 | } |
349 | } |
350 | |
351 | void RendererView::queueRenderElements(const SceneInfo& sceneInfo) |
352 | { |
353 | if (mRenderSettings->overlayOnly) |
354 | return; |
355 | |
356 | // Queue renderables |
357 | for(UINT32 i = 0; i < (UINT32)sceneInfo.renderables.size(); i++) |
358 | { |
359 | if (!mVisibility.renderables[i]) |
360 | continue; |
361 | |
362 | const AABox& boundingBox = sceneInfo.renderableCullInfos[i].bounds.getBox(); |
363 | const float distanceToCamera = (mProperties.viewOrigin - boundingBox.getCenter()).length(); |
364 | |
365 | for (auto& renderElem : sceneInfo.renderables[i]->elements) |
366 | { |
367 | // Note: I could keep renderables in multiple separate arrays, so I don't need to do the check here |
368 | ShaderFlags shaderFlags = renderElem.material->getShader()->getFlags(); |
369 | |
370 | if (shaderFlags.isSet(ShaderFlag::Transparent)) |
371 | mTransparentQueue->add(&renderElem, distanceToCamera, renderElem.techniqueIdx); |
372 | else if (shaderFlags.isSet(ShaderFlag::Forward)) |
373 | mForwardOpaqueQueue->add(&renderElem, distanceToCamera, renderElem.techniqueIdx); |
374 | else |
375 | mDeferredOpaqueQueue->add(&renderElem, distanceToCamera, renderElem.techniqueIdx); |
376 | } |
377 | } |
378 | |
379 | // Queue particle systems |
380 | for(UINT32 i = 0; i < (UINT32)sceneInfo.particleSystems.size(); i++) |
381 | { |
382 | if (!mVisibility.particleSystems[i]) |
383 | continue; |
384 | |
385 | const ParticlesRenderElement& renderElem = sceneInfo.particleSystems[i].renderElement; |
386 | if (!renderElem.isValid()) |
387 | continue; |
388 | |
389 | const AABox& boundingBox = sceneInfo.particleSystemCullInfos[i].bounds.getBox(); |
390 | const float distanceToCamera = (mProperties.viewOrigin - boundingBox.getCenter()).length(); |
391 | |
392 | ShaderFlags shaderFlags = renderElem.material->getShader()->getFlags(); |
393 | |
394 | if (shaderFlags.isSet(ShaderFlag::Transparent)) |
395 | mTransparentQueue->add(&renderElem, distanceToCamera, renderElem.techniqueIdx); |
396 | else if (shaderFlags.isSet(ShaderFlag::Forward)) |
397 | mForwardOpaqueQueue->add(&renderElem, distanceToCamera, renderElem.techniqueIdx); |
398 | else |
399 | mDeferredOpaqueQueue->add(&renderElem, distanceToCamera, renderElem.techniqueIdx); |
400 | } |
401 | |
402 | // Queue decals |
403 | const bool isMSAA = mProperties.target.numSamples > 1; |
404 | for(UINT32 i = 0; i < (UINT32)sceneInfo.decals.size(); i++) |
405 | { |
406 | if (!mVisibility.decals[i]) |
407 | continue; |
408 | |
409 | const DecalRenderElement& renderElem = sceneInfo.decals[i].renderElement; |
410 | |
411 | // Note: I could keep renderables in multiple separate arrays, so I don't need to do the check here |
412 | ShaderFlags shaderFlags = renderElem.material->getShader()->getFlags(); |
413 | |
414 | // Decals are only supported using deferred rendering |
415 | if (shaderFlags.isSetAny(ShaderFlag::Transparent | ShaderFlag::Forward)) |
416 | continue; |
417 | |
418 | const AABox& boundingBox = sceneInfo.decalCullInfos[i].bounds.getBox(); |
419 | const float distanceToCamera = (mProperties.viewOrigin - boundingBox.getCenter()).length(); |
420 | |
421 | // Check if viewer is inside the decal volume |
422 | |
423 | // Extend the bounds slighty to cover the case when the viewer is outside, but the near plane is intersecting |
424 | // the decal bounds. We need to be conservative since the material for rendering outside will not properly |
425 | // render the inside of the decal volume. |
426 | const bool isInside = boundingBox.contains(mProperties.viewOrigin, mProperties.nearPlane * 3.0f); |
427 | const UINT32* techniqueIndices = renderElem.techniqueIndices[(INT32)isInside]; |
428 | |
429 | // No MSAA evaluation, or same value for all samples (no divergence between samples) |
430 | mDecalQueue->add(&renderElem, distanceToCamera, |
431 | techniqueIndices[(INT32)(isMSAA ? MSAAMode::Single : MSAAMode::None)]); |
432 | |
433 | // Evaluates all MSAA samples for pixels that are marked as divergent |
434 | if(isMSAA) |
435 | mDecalQueue->add(&renderElem, distanceToCamera, techniqueIndices[(INT32)MSAAMode::Full]); |
436 | } |
437 | |
438 | mForwardOpaqueQueue->sort(); |
439 | mDeferredOpaqueQueue->sort(); |
440 | mTransparentQueue->sort(); |
441 | mDecalQueue->sort(); |
442 | } |
443 | |
444 | Vector2 RendererView::getDeviceZToViewZ(const Matrix4& projMatrix) |
445 | { |
446 | // Returns a set of values that will transform depth buffer values (in range [0, 1]) to a distance |
447 | // in view space. This involes applying the inverse projection transform to the depth value. When you multiply |
448 | // a vector with the projection matrix you get [clipX, clipY, Az + B, C * z], where we don't care about clipX/clipY. |
449 | // A is [2, 2], B is [2, 3] and C is [3, 2] elements of the projection matrix (only ones that matter for our depth |
450 | // value). The hardware will also automatically divide the z value with w to get the depth, therefore the final |
451 | // formula is: |
452 | // depth = (Az + B) / (C * z) |
453 | |
454 | // To get the z coordinate back we simply do the opposite: |
455 | // z = B / (depth * C - A) |
456 | |
457 | // However some APIs will also do a transformation on the depth values before storing them to the texture |
458 | // (e.g. OpenGL will transform from [-1, 1] to [0, 1]). And we need to reverse that as well. Therefore the final |
459 | // formula is: |
460 | // z = B / ((depth * (maxDepth - minDepth) + minDepth) * C - A) |
461 | |
462 | // Are we reorganize it because it needs to fit the "(1.0f / (depth + y)) * x" format used in the shader: |
463 | // z = 1.0f / (depth + minDepth/(maxDepth - minDepth) - A/((maxDepth - minDepth) * C)) * B/((maxDepth - minDepth) * C) |
464 | |
465 | const RenderAPICapabilities& caps = gCaps(); |
466 | |
467 | float depthRange = caps.maxDepth - caps.minDepth; |
468 | float minDepth = caps.minDepth; |
469 | |
470 | float a = projMatrix[2][2]; |
471 | float b = projMatrix[2][3]; |
472 | float c = projMatrix[3][2]; |
473 | |
474 | Vector2 output; |
475 | |
476 | if (c != 0.0f) |
477 | { |
478 | output.x = b / (depthRange * c); |
479 | output.y = minDepth / depthRange - a / (depthRange * c); |
480 | } |
481 | else // Ortographic, assuming viewing towards negative Z |
482 | { |
483 | output.x = b / -depthRange; |
484 | output.y = minDepth / depthRange - a / -depthRange; |
485 | } |
486 | |
487 | return output; |
488 | } |
489 | |
490 | Vector2 RendererView::getNDCZToViewZ(const Matrix4& projMatrix) |
491 | { |
492 | // Returns a set of values that will transform depth buffer values (e.g. [0, 1] in DX, [-1, 1] in GL) to a distance |
493 | // in view space. This involes applying the inverse projection transform to the depth value. When you multiply |
494 | // a vector with the projection matrix you get [clipX, clipY, Az + B, C * z], where we don't care about clipX/clipY. |
495 | // A is [2, 2], B is [2, 3] and C is [3, 2] elements of the projection matrix (only ones that matter for our depth |
496 | // value). The hardware will also automatically divide the z value with w to get the depth, therefore the final |
497 | // formula is: |
498 | // depth = (Az + B) / (C * z) |
499 | |
500 | // To get the z coordinate back we simply do the opposite: |
501 | // z = B / (depth * C - A) |
502 | |
503 | // Are we reorganize it because it needs to fit the "(1.0f / (depth + y)) * x" format used in the shader: |
504 | // z = 1.0f / (depth - A/C) * B/C |
505 | |
506 | float a = projMatrix[2][2]; |
507 | float b = projMatrix[2][3]; |
508 | float c = projMatrix[3][2]; |
509 | |
510 | Vector2 output; |
511 | |
512 | if (c != 0.0f) |
513 | { |
514 | output.x = b / c; |
515 | output.y = -a / c; |
516 | } |
517 | else // Ortographic, assuming viewing towards negative Z |
518 | { |
519 | output.x = -b; |
520 | output.y = a; |
521 | } |
522 | |
523 | return output; |
524 | } |
525 | |
526 | Vector2 RendererView::getNDCZToDeviceZ() |
527 | { |
528 | const RenderAPICapabilities& caps = gCaps(); |
529 | |
530 | Vector2 ndcZToDeviceZ; |
531 | ndcZToDeviceZ.x = 1.0f / (caps.maxDepth - caps.minDepth); |
532 | ndcZToDeviceZ.y = -caps.minDepth; |
533 | |
534 | return ndcZToDeviceZ; |
535 | } |
536 | |
537 | Matrix4 invertProjectionMatrix(const Matrix4& mat) |
538 | { |
539 | // Try to solve the most common case using high percision calculations, in order to reduce depth error |
540 | if(mat[0][1] == 0.0f && mat[0][3] == 0.0f && |
541 | mat[1][0] == 0.0f && mat[1][3] == 0.0f && |
542 | mat[2][0] == 0.0f && mat[2][1] == 0.0f && |
543 | mat[3][0] == 0.0f && mat[3][1] == 0.0f && |
544 | mat[3][2] == -1.0f && mat[3][3] == 0.0f) |
545 | { |
546 | double a = mat[0][0]; |
547 | double b = mat[1][1]; |
548 | double c = mat[2][2]; |
549 | double d = mat[2][3]; |
550 | double s = mat[0][2]; |
551 | double t = mat[1][2]; |
552 | |
553 | return Matrix4( |
554 | (float)(1.0/a), 0.0f, 0.0f, (float)(-s/a), |
555 | 0.0f, (float)(1.0/b), 0.0f, (float)(-t/b), |
556 | 0.0f, 0.0f, 0.0f, -1.0f, |
557 | 0.0f, 0.0f, (float)(1.0/d), (float)(c/d) |
558 | ); |
559 | } |
560 | else |
561 | { |
562 | return mat.inverse(); |
563 | } |
564 | } |
565 | |
566 | void RendererView::updatePerViewBuffer() |
567 | { |
568 | Matrix4 viewProj = mProperties.projTransform * mProperties.viewTransform; |
569 | Matrix4 invProj = invertProjectionMatrix(mProperties.projTransform); |
570 | Matrix4 invView = mProperties.viewTransform.inverseAffine(); |
571 | Matrix4 invViewProj = invView * invProj; |
572 | |
573 | gPerCameraParamDef.gMatProj.set(mParamBuffer, mProperties.projTransform); |
574 | gPerCameraParamDef.gMatView.set(mParamBuffer, mProperties.viewTransform); |
575 | gPerCameraParamDef.gMatViewProj.set(mParamBuffer, viewProj); |
576 | gPerCameraParamDef.gMatInvViewProj.set(mParamBuffer, invViewProj); |
577 | gPerCameraParamDef.gMatInvProj.set(mParamBuffer, invProj); |
578 | |
579 | // Construct a special inverse view-projection matrix that had projection entries that effect z and w eliminated. |
580 | // Used to transform a vector(clip_x, clip_y, view_z, view_w), where clip_x/clip_y are in clip space, and |
581 | // view_z/view_w in view space, into world space. |
582 | |
583 | // Only projects z/w coordinates (cancels out with the inverse matrix below) |
584 | Matrix4 projZ = Matrix4::IDENTITY; |
585 | projZ[2][2] = mProperties.projTransform[2][2]; |
586 | projZ[2][3] = mProperties.projTransform[2][3]; |
587 | projZ[3][2] = mProperties.projTransform[3][2]; |
588 | projZ[3][3] = 0.0f; |
589 | |
590 | Matrix4 NDCToPrevNDC = mProperties.prevViewProjTransform * invViewProj; |
591 | |
592 | gPerCameraParamDef.gMatScreenToWorld.set(mParamBuffer, invViewProj * projZ); |
593 | gPerCameraParamDef.gNDCToPrevNDC.set(mParamBuffer, NDCToPrevNDC); |
594 | gPerCameraParamDef.gViewDir.set(mParamBuffer, mProperties.viewDirection); |
595 | gPerCameraParamDef.gViewOrigin.set(mParamBuffer, mProperties.viewOrigin); |
596 | gPerCameraParamDef.gDeviceZToWorldZ.set(mParamBuffer, getDeviceZToViewZ(mProperties.projTransform)); |
597 | gPerCameraParamDef.gNDCZToWorldZ.set(mParamBuffer, getNDCZToViewZ(mProperties.projTransform)); |
598 | gPerCameraParamDef.gNDCZToDeviceZ.set(mParamBuffer, getNDCZToDeviceZ()); |
599 | |
600 | Vector2 nearFar(mProperties.nearPlane, mProperties.farPlane); |
601 | gPerCameraParamDef.gNearFar.set(mParamBuffer, nearFar); |
602 | |
603 | const Rect2I& viewRect = mProperties.target.viewRect; |
604 | |
605 | Vector4I viewportRect; |
606 | viewportRect[0] = viewRect.x; |
607 | viewportRect[1] = viewRect.y; |
608 | viewportRect[2] = viewRect.width; |
609 | viewportRect[3] = viewRect.height; |
610 | |
611 | gPerCameraParamDef.gViewportRectangle.set(mParamBuffer, viewportRect); |
612 | |
613 | Vector4 ndcToUV = getNDCToUV(); |
614 | gPerCameraParamDef.gClipToUVScaleOffset.set(mParamBuffer, ndcToUV); |
615 | |
616 | Vector4 uvToNDC( |
617 | 1.0f / ndcToUV.x, |
618 | 1.0f / ndcToUV.y, |
619 | -ndcToUV.z / ndcToUV.x, |
620 | -ndcToUV.w / ndcToUV.y); |
621 | gPerCameraParamDef.gUVToClipScaleOffset.set(mParamBuffer, uvToNDC); |
622 | |
623 | if (!mRenderSettings->enableLighting) |
624 | gPerCameraParamDef.gAmbientFactor.set(mParamBuffer, 100.0f); |
625 | else |
626 | gPerCameraParamDef.gAmbientFactor.set(mParamBuffer, 0.0f); |
627 | } |
628 | |
629 | Vector4 RendererView::getNDCToUV() const |
630 | { |
631 | const RenderAPICapabilities& caps = gCaps(); |
632 | const Rect2I& viewRect = mProperties.target.viewRect; |
633 | |
634 | float halfWidth = viewRect.width * 0.5f; |
635 | float halfHeight = viewRect.height * 0.5f; |
636 | |
637 | float rtWidth = mProperties.target.targetWidth != 0 ? (float)mProperties.target.targetWidth : 20.0f; |
638 | float rtHeight = mProperties.target.targetHeight != 0 ? (float)mProperties.target.targetHeight : 20.0f; |
639 | |
640 | Vector4 ndcToUV; |
641 | ndcToUV.x = halfWidth / rtWidth; |
642 | ndcToUV.y = -halfHeight / rtHeight; |
643 | ndcToUV.z = viewRect.x / rtWidth + (halfWidth + caps.horizontalTexelOffset) / rtWidth; |
644 | ndcToUV.w = viewRect.y / rtHeight + (halfHeight + caps.verticalTexelOffset) / rtHeight; |
645 | |
646 | // Either of these flips the Y axis, but if they're both true they cancel out |
647 | if ((caps.conventions.uvYAxis == Conventions::Axis::Up) ^ (caps.conventions.ndcYAxis == Conventions::Axis::Down)) |
648 | ndcToUV.y = -ndcToUV.y; |
649 | |
650 | return ndcToUV; |
651 | } |
652 | |
653 | void RendererView::updateLightGrid(const VisibleLightData& visibleLightData, |
654 | const VisibleReflProbeData& visibleReflProbeData) |
655 | { |
656 | mLightGrid.updateGrid(*this, visibleLightData, visibleReflProbeData, !mRenderSettings->enableLighting); |
657 | } |
658 | |
659 | RendererViewGroup::RendererViewGroup(RendererView** views, UINT32 numViews, bool mainPass, UINT32 shadowMapSize) |
660 | : mIsMainPass(mainPass), mShadowRenderer(shadowMapSize) |
661 | { |
662 | setViews(views, numViews); |
663 | } |
664 | |
665 | void RendererViewGroup::setViews(RendererView** views, UINT32 numViews) |
666 | { |
667 | mViews.clear(); |
668 | |
669 | for (UINT32 i = 0; i < numViews; i++) |
670 | { |
671 | mViews.push_back(views[i]); |
672 | views[i]->_setViewIdx(i); |
673 | } |
674 | } |
675 | |
676 | void RendererViewGroup::determineVisibility(const SceneInfo& sceneInfo) |
677 | { |
678 | const auto numViews = (UINT32)mViews.size(); |
679 | |
680 | // Early exit if no views render scene geometry |
681 | bool allViewsOverlay = false; |
682 | for (UINT32 i = 0; i < numViews; i++) |
683 | { |
684 | if (!mViews[i]->getRenderSettings().overlayOnly) |
685 | { |
686 | allViewsOverlay = false; |
687 | break; |
688 | } |
689 | } |
690 | |
691 | if (allViewsOverlay) |
692 | return; |
693 | |
694 | // Calculate renderable visibility per view |
695 | mVisibility.renderables.resize(sceneInfo.renderables.size(), false); |
696 | mVisibility.renderables.assign(sceneInfo.renderables.size(), false); |
697 | |
698 | mVisibility.particleSystems.resize(sceneInfo.particleSystems.size(), false); |
699 | mVisibility.particleSystems.assign(sceneInfo.particleSystems.size(), false); |
700 | |
701 | mVisibility.decals.resize(sceneInfo.decals.size(), false); |
702 | mVisibility.decals.assign(sceneInfo.decals.size(), false); |
703 | |
704 | for(UINT32 i = 0; i < numViews; i++) |
705 | { |
706 | mViews[i]->determineVisible(sceneInfo.renderables, sceneInfo.renderableCullInfos, &mVisibility.renderables); |
707 | mViews[i]->determineVisible(sceneInfo.particleSystems, sceneInfo.particleSystemCullInfos, &mVisibility.particleSystems); |
708 | mViews[i]->determineVisible(sceneInfo.decals, sceneInfo.decalCullInfos, &mVisibility.decals); |
709 | } |
710 | |
711 | // Generate render queues per camera |
712 | for(UINT32 i = 0; i < numViews; i++) |
713 | mViews[i]->queueRenderElements(sceneInfo); |
714 | |
715 | // Calculate light visibility for all views |
716 | const auto numRadialLights = (UINT32)sceneInfo.radialLights.size(); |
717 | mVisibility.radialLights.resize(numRadialLights, false); |
718 | mVisibility.radialLights.assign(numRadialLights, false); |
719 | |
720 | const auto numSpotLights = (UINT32)sceneInfo.spotLights.size(); |
721 | mVisibility.spotLights.resize(numSpotLights, false); |
722 | mVisibility.spotLights.assign(numSpotLights, false); |
723 | |
724 | for (UINT32 i = 0; i < numViews; i++) |
725 | { |
726 | if (mViews[i]->getRenderSettings().overlayOnly) |
727 | continue; |
728 | |
729 | mViews[i]->determineVisible(sceneInfo.radialLights, sceneInfo.radialLightWorldBounds, LightType::Radial, |
730 | &mVisibility.radialLights); |
731 | |
732 | mViews[i]->determineVisible(sceneInfo.spotLights, sceneInfo.spotLightWorldBounds, LightType::Spot, |
733 | &mVisibility.spotLights); |
734 | } |
735 | |
736 | // Calculate refl. probe visibility for all views |
737 | const auto numProbes = (UINT32)sceneInfo.reflProbes.size(); |
738 | mVisibility.reflProbes.resize(numProbes, false); |
739 | mVisibility.reflProbes.assign(numProbes, false); |
740 | |
741 | // Note: Per-view visibility for refl. probes currently isn't calculated |
742 | for (UINT32 i = 0; i < numViews; i++) |
743 | { |
744 | const auto& viewProps = mViews[i]->getProperties(); |
745 | |
746 | // Don't recursively render reflection probes when generating reflection probe maps |
747 | if (viewProps.capturingReflections) |
748 | continue; |
749 | |
750 | mViews[i]->calculateVisibility(sceneInfo.reflProbeWorldBounds, mVisibility.reflProbes); |
751 | } |
752 | |
753 | // Organize light and refl. probe visibility information in a more GPU friendly manner |
754 | |
755 | // Note: I'm determining light and refl. probe visibility for the entire group. It might be more performance |
756 | // efficient to do it per view. Additionally I'm using a single GPU buffer to hold their information, which is |
757 | // then updated when each view group is rendered. It might be better to keep one buffer reserved per-view. |
758 | mVisibleLightData.update(sceneInfo, *this); |
759 | mVisibleReflProbeData.update(sceneInfo, *this); |
760 | |
761 | const bool supportsClusteredForward = gRenderBeast()->getFeatureSet() == RenderBeastFeatureSet::Desktop; |
762 | if(supportsClusteredForward) |
763 | { |
764 | for (UINT32 i = 0; i < numViews; i++) |
765 | { |
766 | if (mViews[i]->getRenderSettings().overlayOnly) |
767 | continue; |
768 | |
769 | mViews[i]->updateLightGrid(mVisibleLightData, mVisibleReflProbeData); |
770 | } |
771 | } |
772 | } |
773 | }} |
774 | |