1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22
23 #include "../Precompiled.h"
24
25 #include "../Core/Profiler.h"
26 #include "../Core/WorkQueue.h"
27 #include "../Graphics/Camera.h"
28 #include "../Graphics/DebugRenderer.h"
29 #include "../Graphics/Geometry.h"
30 #include "../Graphics/Graphics.h"
31 #include "../Graphics/GraphicsEvents.h"
32 #include "../Graphics/GraphicsImpl.h"
33 #include "../Graphics/Material.h"
34 #include "../Graphics/OcclusionBuffer.h"
35 #include "../Graphics/Octree.h"
36 #include "../Graphics/Renderer.h"
37 #include "../Graphics/RenderPath.h"
38 #include "../Graphics/ShaderVariation.h"
39 #include "../Graphics/Skybox.h"
40 #include "../Graphics/Technique.h"
41 #include "../Graphics/Texture2D.h"
42 #include "../Graphics/Texture2DArray.h"
43 #include "../Graphics/Texture3D.h"
44 #include "../Graphics/TextureCube.h"
45 #include "../Graphics/VertexBuffer.h"
46 #include "../Graphics/View.h"
47 #include "../IO/FileSystem.h"
48 #include "../IO/Log.h"
49 #include "../Resource/ResourceCache.h"
50 #include "../Scene/Scene.h"
51 #include "../UI/UI.h"
52
53 #include "../DebugNew.h"
54
55 namespace Urho3D
56 {
57
58 static const Vector3* directions[] =
59 {
60 &Vector3::RIGHT,
61 &Vector3::LEFT,
62 &Vector3::UP,
63 &Vector3::DOWN,
64 &Vector3::FORWARD,
65 &Vector3::BACK
66 };
67
68 /// %Frustum octree query for shadowcasters.
69 class ShadowCasterOctreeQuery : public FrustumOctreeQuery
70 {
71 public:
72 /// Construct with frustum and query parameters.
ShadowCasterOctreeQuery(PODVector<Drawable * > & result,const Frustum & frustum,unsigned char drawableFlags=DRAWABLE_ANY,unsigned viewMask=DEFAULT_VIEWMASK)73 ShadowCasterOctreeQuery(PODVector<Drawable*>& result, const Frustum& frustum, unsigned char drawableFlags = DRAWABLE_ANY,
74 unsigned viewMask = DEFAULT_VIEWMASK) :
75 FrustumOctreeQuery(result, frustum, drawableFlags, viewMask)
76 {
77 }
78
79 /// Intersection test for drawables.
TestDrawables(Drawable ** start,Drawable ** end,bool inside)80 virtual void TestDrawables(Drawable** start, Drawable** end, bool inside)
81 {
82 while (start != end)
83 {
84 Drawable* drawable = *start++;
85
86 if (drawable->GetCastShadows() && (drawable->GetDrawableFlags() & drawableFlags_) &&
87 (drawable->GetViewMask() & viewMask_))
88 {
89 if (inside || frustum_.IsInsideFast(drawable->GetWorldBoundingBox()))
90 result_.Push(drawable);
91 }
92 }
93 }
94 };
95
96 /// %Frustum octree query for zones and occluders.
97 class ZoneOccluderOctreeQuery : public FrustumOctreeQuery
98 {
99 public:
100 /// Construct with frustum and query parameters.
ZoneOccluderOctreeQuery(PODVector<Drawable * > & result,const Frustum & frustum,unsigned char drawableFlags=DRAWABLE_ANY,unsigned viewMask=DEFAULT_VIEWMASK)101 ZoneOccluderOctreeQuery(PODVector<Drawable*>& result, const Frustum& frustum, unsigned char drawableFlags = DRAWABLE_ANY,
102 unsigned viewMask = DEFAULT_VIEWMASK) :
103 FrustumOctreeQuery(result, frustum, drawableFlags, viewMask)
104 {
105 }
106
107 /// Intersection test for drawables.
TestDrawables(Drawable ** start,Drawable ** end,bool inside)108 virtual void TestDrawables(Drawable** start, Drawable** end, bool inside)
109 {
110 while (start != end)
111 {
112 Drawable* drawable = *start++;
113 unsigned char flags = drawable->GetDrawableFlags();
114
115 if ((flags == DRAWABLE_ZONE || (flags == DRAWABLE_GEOMETRY && drawable->IsOccluder())) &&
116 (drawable->GetViewMask() & viewMask_))
117 {
118 if (inside || frustum_.IsInsideFast(drawable->GetWorldBoundingBox()))
119 result_.Push(drawable);
120 }
121 }
122 }
123 };
124
125 /// %Frustum octree query with occlusion.
126 class OccludedFrustumOctreeQuery : public FrustumOctreeQuery
127 {
128 public:
129 /// Construct with frustum, occlusion buffer and query parameters.
OccludedFrustumOctreeQuery(PODVector<Drawable * > & result,const Frustum & frustum,OcclusionBuffer * buffer,unsigned char drawableFlags=DRAWABLE_ANY,unsigned viewMask=DEFAULT_VIEWMASK)130 OccludedFrustumOctreeQuery(PODVector<Drawable*>& result, const Frustum& frustum, OcclusionBuffer* buffer,
131 unsigned char drawableFlags = DRAWABLE_ANY, unsigned viewMask = DEFAULT_VIEWMASK) :
132 FrustumOctreeQuery(result, frustum, drawableFlags, viewMask),
133 buffer_(buffer)
134 {
135 }
136
137 /// Intersection test for an octant.
TestOctant(const BoundingBox & box,bool inside)138 virtual Intersection TestOctant(const BoundingBox& box, bool inside)
139 {
140 if (inside)
141 return buffer_->IsVisible(box) ? INSIDE : OUTSIDE;
142 else
143 {
144 Intersection result = frustum_.IsInside(box);
145 if (result != OUTSIDE && !buffer_->IsVisible(box))
146 result = OUTSIDE;
147 return result;
148 }
149 }
150
151 /// Intersection test for drawables. Note: drawable occlusion is performed later in worker threads.
TestDrawables(Drawable ** start,Drawable ** end,bool inside)152 virtual void TestDrawables(Drawable** start, Drawable** end, bool inside)
153 {
154 while (start != end)
155 {
156 Drawable* drawable = *start++;
157
158 if ((drawable->GetDrawableFlags() & drawableFlags_) && (drawable->GetViewMask() & viewMask_))
159 {
160 if (inside || frustum_.IsInsideFast(drawable->GetWorldBoundingBox()))
161 result_.Push(drawable);
162 }
163 }
164 }
165
166 /// Occlusion buffer.
167 OcclusionBuffer* buffer_;
168 };
169
CheckVisibilityWork(const WorkItem * item,unsigned threadIndex)170 void CheckVisibilityWork(const WorkItem* item, unsigned threadIndex)
171 {
172 View* view = reinterpret_cast<View*>(item->aux_);
173 Drawable** start = reinterpret_cast<Drawable**>(item->start_);
174 Drawable** end = reinterpret_cast<Drawable**>(item->end_);
175 OcclusionBuffer* buffer = view->occlusionBuffer_;
176 const Matrix3x4& viewMatrix = view->cullCamera_->GetView();
177 Vector3 viewZ = Vector3(viewMatrix.m20_, viewMatrix.m21_, viewMatrix.m22_);
178 Vector3 absViewZ = viewZ.Abs();
179 unsigned cameraViewMask = view->cullCamera_->GetViewMask();
180 bool cameraZoneOverride = view->cameraZoneOverride_;
181 PerThreadSceneResult& result = view->sceneResults_[threadIndex];
182
183 while (start != end)
184 {
185 Drawable* drawable = *start++;
186
187 if (!buffer || !drawable->IsOccludee() || buffer->IsVisible(drawable->GetWorldBoundingBox()))
188 {
189 drawable->UpdateBatches(view->frame_);
190 // If draw distance non-zero, update and check it
191 float maxDistance = drawable->GetDrawDistance();
192 if (maxDistance > 0.0f)
193 {
194 if (drawable->GetDistance() > maxDistance)
195 continue;
196 }
197
198 drawable->MarkInView(view->frame_);
199
200 // For geometries, find zone, clear lights and calculate view space Z range
201 if (drawable->GetDrawableFlags() & DRAWABLE_GEOMETRY)
202 {
203 Zone* drawableZone = drawable->GetZone();
204 if (!cameraZoneOverride &&
205 (drawable->IsZoneDirty() || !drawableZone || (drawableZone->GetViewMask() & cameraViewMask) == 0))
206 view->FindZone(drawable);
207
208 const BoundingBox& geomBox = drawable->GetWorldBoundingBox();
209 Vector3 center = geomBox.Center();
210 Vector3 edge = geomBox.Size() * 0.5f;
211
212 // Do not add "infinite" objects like skybox to prevent shadow map focusing behaving erroneously
213 if (edge.LengthSquared() < M_LARGE_VALUE * M_LARGE_VALUE)
214 {
215 float viewCenterZ = viewZ.DotProduct(center) + viewMatrix.m23_;
216 float viewEdgeZ = absViewZ.DotProduct(edge);
217 float minZ = viewCenterZ - viewEdgeZ;
218 float maxZ = viewCenterZ + viewEdgeZ;
219 drawable->SetMinMaxZ(viewCenterZ - viewEdgeZ, viewCenterZ + viewEdgeZ);
220 result.minZ_ = Min(result.minZ_, minZ);
221 result.maxZ_ = Max(result.maxZ_, maxZ);
222 }
223 else
224 drawable->SetMinMaxZ(M_LARGE_VALUE, M_LARGE_VALUE);
225
226 result.geometries_.Push(drawable);
227 }
228 else if (drawable->GetDrawableFlags() & DRAWABLE_LIGHT)
229 {
230 Light* light = static_cast<Light*>(drawable);
231 // Skip lights with zero brightness or black color
232 if (!light->GetEffectiveColor().Equals(Color::BLACK))
233 result.lights_.Push(light);
234 }
235 }
236 }
237 }
238
ProcessLightWork(const WorkItem * item,unsigned threadIndex)239 void ProcessLightWork(const WorkItem* item, unsigned threadIndex)
240 {
241 View* view = reinterpret_cast<View*>(item->aux_);
242 LightQueryResult* query = reinterpret_cast<LightQueryResult*>(item->start_);
243
244 view->ProcessLight(*query, threadIndex);
245 }
246
UpdateDrawableGeometriesWork(const WorkItem * item,unsigned threadIndex)247 void UpdateDrawableGeometriesWork(const WorkItem* item, unsigned threadIndex)
248 {
249 const FrameInfo& frame = *(reinterpret_cast<FrameInfo*>(item->aux_));
250 Drawable** start = reinterpret_cast<Drawable**>(item->start_);
251 Drawable** end = reinterpret_cast<Drawable**>(item->end_);
252
253 while (start != end)
254 {
255 Drawable* drawable = *start++;
256 // We may leave null pointer holes in the queue if a drawable is found out to require a main thread update
257 if (drawable)
258 drawable->UpdateGeometry(frame);
259 }
260 }
261
SortBatchQueueFrontToBackWork(const WorkItem * item,unsigned threadIndex)262 void SortBatchQueueFrontToBackWork(const WorkItem* item, unsigned threadIndex)
263 {
264 BatchQueue* queue = reinterpret_cast<BatchQueue*>(item->start_);
265
266 queue->SortFrontToBack();
267 }
268
SortBatchQueueBackToFrontWork(const WorkItem * item,unsigned threadIndex)269 void SortBatchQueueBackToFrontWork(const WorkItem* item, unsigned threadIndex)
270 {
271 BatchQueue* queue = reinterpret_cast<BatchQueue*>(item->start_);
272
273 queue->SortBackToFront();
274 }
275
SortLightQueueWork(const WorkItem * item,unsigned threadIndex)276 void SortLightQueueWork(const WorkItem* item, unsigned threadIndex)
277 {
278 LightBatchQueue* start = reinterpret_cast<LightBatchQueue*>(item->start_);
279 start->litBaseBatches_.SortFrontToBack();
280 start->litBatches_.SortFrontToBack();
281 }
282
SortShadowQueueWork(const WorkItem * item,unsigned threadIndex)283 void SortShadowQueueWork(const WorkItem* item, unsigned threadIndex)
284 {
285 LightBatchQueue* start = reinterpret_cast<LightBatchQueue*>(item->start_);
286 for (unsigned i = 0; i < start->shadowSplits_.Size(); ++i)
287 start->shadowSplits_[i].shadowBatches_.SortFrontToBack();
288 }
289
290 StringHash ParseTextureTypeXml(ResourceCache* cache, String filename);
291
View(Context * context)292 View::View(Context* context) :
293 Object(context),
294 graphics_(GetSubsystem<Graphics>()),
295 renderer_(GetSubsystem<Renderer>()),
296 scene_(0),
297 octree_(0),
298 cullCamera_(0),
299 camera_(0),
300 cameraZone_(0),
301 farClipZone_(0),
302 occlusionBuffer_(0),
303 renderTarget_(0),
304 substituteRenderTarget_(0),
305 passCommand_(0)
306 {
307 // Create octree query and scene results vector for each thread
308 unsigned numThreads = GetSubsystem<WorkQueue>()->GetNumThreads() + 1; // Worker threads + main thread
309 tempDrawables_.Resize(numThreads);
310 sceneResults_.Resize(numThreads);
311 frame_.camera_ = 0;
312 }
313
~View()314 View::~View()
315 {
316 }
317
Define(RenderSurface * renderTarget,Viewport * viewport)318 bool View::Define(RenderSurface* renderTarget, Viewport* viewport)
319 {
320 sourceView_ = 0;
321 renderPath_ = viewport->GetRenderPath();
322 if (!renderPath_)
323 return false;
324
325 renderTarget_ = renderTarget;
326 drawDebug_ = viewport->GetDrawDebug();
327
328 // Validate the rect and calculate size. If zero rect, use whole rendertarget size
329 int rtWidth = renderTarget ? renderTarget->GetWidth() : graphics_->GetWidth();
330 int rtHeight = renderTarget ? renderTarget->GetHeight() : graphics_->GetHeight();
331 const IntRect& rect = viewport->GetRect();
332
333 if (rect != IntRect::ZERO)
334 {
335 viewRect_.left_ = Clamp(rect.left_, 0, rtWidth - 1);
336 viewRect_.top_ = Clamp(rect.top_, 0, rtHeight - 1);
337 viewRect_.right_ = Clamp(rect.right_, viewRect_.left_ + 1, rtWidth);
338 viewRect_.bottom_ = Clamp(rect.bottom_, viewRect_.top_ + 1, rtHeight);
339 }
340 else
341 viewRect_ = IntRect(0, 0, rtWidth, rtHeight);
342
343 viewSize_ = viewRect_.Size();
344 rtSize_ = IntVector2(rtWidth, rtHeight);
345
346 // On OpenGL flip the viewport if rendering to a texture for consistent UV addressing with Direct3D9
347 #ifdef URHO3D_OPENGL
348 if (renderTarget_)
349 {
350 viewRect_.bottom_ = rtHeight - viewRect_.top_;
351 viewRect_.top_ = viewRect_.bottom_ - viewSize_.y_;
352 }
353 #endif
354
355 scene_ = viewport->GetScene();
356 cullCamera_ = viewport->GetCullCamera();
357 camera_ = viewport->GetCamera();
358 if (!cullCamera_)
359 cullCamera_ = camera_;
360 else
361 {
362 // If view specifies a culling camera (view preparation sharing), check if already prepared
363 sourceView_ = renderer_->GetPreparedView(cullCamera_);
364 if (sourceView_ && sourceView_->scene_ == scene_ && sourceView_->renderPath_ == renderPath_)
365 {
366 // Copy properties needed later in rendering
367 deferred_ = sourceView_->deferred_;
368 deferredAmbient_ = sourceView_->deferredAmbient_;
369 useLitBase_ = sourceView_->useLitBase_;
370 hasScenePasses_ = sourceView_->hasScenePasses_;
371 noStencil_ = sourceView_->noStencil_;
372 lightVolumeCommand_ = sourceView_->lightVolumeCommand_;
373 forwardLightsCommand_ = sourceView_->forwardLightsCommand_;
374 octree_ = sourceView_->octree_;
375 return true;
376 }
377 else
378 {
379 // Mismatch in scene or renderpath, fall back to unique view preparation
380 sourceView_ = 0;
381 }
382 }
383
384 // Set default passes
385 gBufferPassIndex_ = M_MAX_UNSIGNED;
386 basePassIndex_ = Technique::GetPassIndex("base");
387 alphaPassIndex_ = Technique::GetPassIndex("alpha");
388 lightPassIndex_ = Technique::GetPassIndex("light");
389 litBasePassIndex_ = Technique::GetPassIndex("litbase");
390 litAlphaPassIndex_ = Technique::GetPassIndex("litalpha");
391
392 deferred_ = false;
393 deferredAmbient_ = false;
394 useLitBase_ = false;
395 hasScenePasses_ = false;
396 noStencil_ = false;
397 lightVolumeCommand_ = 0;
398 forwardLightsCommand_ = 0;
399
400 scenePasses_.Clear();
401 geometriesUpdated_ = false;
402
403 #ifdef URHO3D_OPENGL
404 #ifdef GL_ES_VERSION_2_0
405 // On OpenGL ES we assume a stencil is not available or would not give a good performance, and disable light stencil
406 // optimizations in any case
407 noStencil_ = true;
408 #else
409 for (unsigned i = 0; i < renderPath_->commands_.Size(); ++i)
410 {
411 const RenderPathCommand& command = renderPath_->commands_[i];
412 if (!command.enabled_)
413 continue;
414 if (command.depthStencilName_.Length())
415 {
416 // Using a readable depth texture will disable light stencil optimizations on OpenGL, as for compatibility reasons
417 // we are using a depth format without stencil channel
418 noStencil_ = true;
419 break;
420 }
421 }
422 #endif
423 #endif
424
425 // Make sure that all necessary batch queues exist
426 for (unsigned i = 0; i < renderPath_->commands_.Size(); ++i)
427 {
428 RenderPathCommand& command = renderPath_->commands_[i];
429 if (!command.enabled_)
430 continue;
431
432 if (command.type_ == CMD_SCENEPASS)
433 {
434 hasScenePasses_ = true;
435
436 ScenePassInfo info;
437 info.passIndex_ = command.passIndex_ = Technique::GetPassIndex(command.pass_);
438 info.allowInstancing_ = command.sortMode_ != SORT_BACKTOFRONT;
439 info.markToStencil_ = !noStencil_ && command.markToStencil_;
440 info.vertexLights_ = command.vertexLights_;
441
442 // Check scenepass metadata for defining custom passes which interact with lighting
443 if (!command.metadata_.Empty())
444 {
445 if (command.metadata_ == "gbuffer")
446 gBufferPassIndex_ = command.passIndex_;
447 else if (command.metadata_ == "base" && command.pass_ != "base")
448 {
449 basePassIndex_ = command.passIndex_;
450 litBasePassIndex_ = Technique::GetPassIndex("lit" + command.pass_);
451 }
452 else if (command.metadata_ == "alpha" && command.pass_ != "alpha")
453 {
454 alphaPassIndex_ = command.passIndex_;
455 litAlphaPassIndex_ = Technique::GetPassIndex("lit" + command.pass_);
456 }
457 }
458
459 HashMap<unsigned, BatchQueue>::Iterator j = batchQueues_.Find(info.passIndex_);
460 if (j == batchQueues_.End())
461 j = batchQueues_.Insert(Pair<unsigned, BatchQueue>(info.passIndex_, BatchQueue()));
462 info.batchQueue_ = &j->second_;
463 SetQueueShaderDefines(*info.batchQueue_, command);
464
465 scenePasses_.Push(info);
466 }
467 // Allow a custom forward light pass
468 else if (command.type_ == CMD_FORWARDLIGHTS && !command.pass_.Empty())
469 lightPassIndex_ = command.passIndex_ = Technique::GetPassIndex(command.pass_);
470 }
471
472 octree_ = 0;
473 // Get default zone first in case we do not have zones defined
474 cameraZone_ = farClipZone_ = renderer_->GetDefaultZone();
475
476 if (hasScenePasses_)
477 {
478 if (!scene_ || !cullCamera_ || !cullCamera_->IsEnabledEffective())
479 return false;
480
481 // If scene is loading scene content asynchronously, it is incomplete and should not be rendered
482 if (scene_->IsAsyncLoading() && scene_->GetAsyncLoadMode() > LOAD_RESOURCES_ONLY)
483 return false;
484
485 octree_ = scene_->GetComponent<Octree>();
486 if (!octree_)
487 return false;
488
489 // Do not accept view if camera projection is illegal
490 // (there is a possibility of crash if occlusion is used and it can not clip properly)
491 if (!cullCamera_->IsProjectionValid())
492 return false;
493 }
494
495 // Go through commands to check for deferred rendering and other flags
496 for (unsigned i = 0; i < renderPath_->commands_.Size(); ++i)
497 {
498 const RenderPathCommand& command = renderPath_->commands_[i];
499 if (!command.enabled_)
500 continue;
501
502 // Check if ambient pass and G-buffer rendering happens at the same time
503 if (command.type_ == CMD_SCENEPASS && command.outputs_.Size() > 1)
504 {
505 if (CheckViewportWrite(command))
506 deferredAmbient_ = true;
507 }
508 else if (command.type_ == CMD_LIGHTVOLUMES)
509 {
510 lightVolumeCommand_ = &command;
511 deferred_ = true;
512 }
513 else if (command.type_ == CMD_FORWARDLIGHTS)
514 {
515 forwardLightsCommand_ = &command;
516 useLitBase_ = command.useLitBase_;
517 }
518 }
519
520 drawShadows_ = renderer_->GetDrawShadows();
521 materialQuality_ = renderer_->GetMaterialQuality();
522 maxOccluderTriangles_ = renderer_->GetMaxOccluderTriangles();
523 minInstances_ = renderer_->GetMinInstances();
524
525 // Set possible quality overrides from the camera
526 // Note that the culling camera is used here (its settings are authoritative) while the render camera
527 // will be just used for the final view & projection matrices
528 unsigned viewOverrideFlags = cullCamera_ ? cullCamera_->GetViewOverrideFlags() : VO_NONE;
529 if (viewOverrideFlags & VO_LOW_MATERIAL_QUALITY)
530 materialQuality_ = QUALITY_LOW;
531 if (viewOverrideFlags & VO_DISABLE_SHADOWS)
532 drawShadows_ = false;
533 if (viewOverrideFlags & VO_DISABLE_OCCLUSION)
534 maxOccluderTriangles_ = 0;
535
536 // Occlusion buffer has constant width. If resulting height would be too large due to aspect ratio, disable occlusion
537 if (viewSize_.y_ > viewSize_.x_ * 4)
538 maxOccluderTriangles_ = 0;
539
540 return true;
541 }
542
Update(const FrameInfo & frame)543 void View::Update(const FrameInfo& frame)
544 {
545 // No need to update if using another prepared view
546 if (sourceView_)
547 return;
548
549 frame_.camera_ = cullCamera_;
550 frame_.timeStep_ = frame.timeStep_;
551 frame_.frameNumber_ = frame.frameNumber_;
552 frame_.viewSize_ = viewSize_;
553
554 using namespace BeginViewUpdate;
555
556 SendViewEvent(E_BEGINVIEWUPDATE);
557
558 int maxSortedInstances = renderer_->GetMaxSortedInstances();
559
560 // Clear buffers, geometry, light, occluder & batch list
561 renderTargets_.Clear();
562 geometries_.Clear();
563 lights_.Clear();
564 zones_.Clear();
565 occluders_.Clear();
566 activeOccluders_ = 0;
567 vertexLightQueues_.Clear();
568 for (HashMap<unsigned, BatchQueue>::Iterator i = batchQueues_.Begin(); i != batchQueues_.End(); ++i)
569 i->second_.Clear(maxSortedInstances);
570
571 if (hasScenePasses_ && (!cullCamera_ || !octree_))
572 {
573 SendViewEvent(E_ENDVIEWUPDATE);
574 return;
575 }
576
577 // Set automatic aspect ratio if required
578 if (cullCamera_ && cullCamera_->GetAutoAspectRatio())
579 cullCamera_->SetAspectRatioInternal((float)frame_.viewSize_.x_ / (float)frame_.viewSize_.y_);
580
581 GetDrawables();
582 GetBatches();
583 renderer_->StorePreparedView(this, cullCamera_);
584
585 SendViewEvent(E_ENDVIEWUPDATE);
586 }
587
Render()588 void View::Render()
589 {
590 SendViewEvent(E_BEGINVIEWRENDER);
591
592 if (hasScenePasses_ && (!octree_ || !camera_))
593 {
594 SendViewEvent(E_ENDVIEWRENDER);
595 return;
596 }
597
598 UpdateGeometries();
599
600 // Allocate screen buffers as necessary
601 AllocateScreenBuffers();
602 SendViewEvent(E_VIEWBUFFERSREADY);
603
604 // Forget parameter sources from the previous view
605 graphics_->ClearParameterSources();
606
607 if (renderer_->GetDynamicInstancing() && graphics_->GetInstancingSupport())
608 PrepareInstancingBuffer();
609
610 // It is possible, though not recommended, that the same camera is used for multiple main views. Set automatic aspect ratio
611 // to ensure correct projection will be used
612 if (camera_ && camera_->GetAutoAspectRatio())
613 camera_->SetAspectRatioInternal((float)(viewSize_.x_) / (float)(viewSize_.y_));
614
615 // Bind the face selection and indirection cube maps for point light shadows
616 #ifndef GL_ES_VERSION_2_0
617 if (renderer_->GetDrawShadows())
618 {
619 graphics_->SetTexture(TU_FACESELECT, renderer_->GetFaceSelectCubeMap());
620 graphics_->SetTexture(TU_INDIRECTION, renderer_->GetIndirectionCubeMap());
621 }
622 #endif
623
624 if (renderTarget_)
625 {
626 // On OpenGL, flip the projection if rendering to a texture so that the texture can be addressed in the same way
627 // as a render texture produced on Direct3D9
628 #ifdef URHO3D_OPENGL
629 if (camera_)
630 camera_->SetFlipVertical(true);
631 #endif
632 }
633
634 // Render
635 ExecuteRenderPathCommands();
636
637 // Reset state after commands
638 graphics_->SetFillMode(FILL_SOLID);
639 graphics_->SetLineAntiAlias(false);
640 graphics_->SetClipPlane(false);
641 graphics_->SetColorWrite(true);
642 graphics_->SetDepthBias(0.0f, 0.0f);
643 graphics_->SetScissorTest(false);
644 graphics_->SetStencilTest(false);
645
646 // Draw the associated debug geometry now if enabled
647 if (drawDebug_ && octree_ && camera_)
648 {
649 DebugRenderer* debug = octree_->GetComponent<DebugRenderer>();
650 if (debug && debug->IsEnabledEffective() && debug->HasContent())
651 {
652 // If used resolve from backbuffer, blit first to the backbuffer to ensure correct depth buffer on OpenGL
653 // Otherwise use the last rendertarget and blit after debug geometry
654 if (usedResolve_ && currentRenderTarget_ != renderTarget_)
655 {
656 BlitFramebuffer(currentRenderTarget_->GetParentTexture(), renderTarget_, false);
657 currentRenderTarget_ = renderTarget_;
658 lastCustomDepthSurface_ = 0;
659 }
660
661 graphics_->SetRenderTarget(0, currentRenderTarget_);
662 for (unsigned i = 1; i < MAX_RENDERTARGETS; ++i)
663 graphics_->SetRenderTarget(i, (RenderSurface*)0);
664
665 // If a custom depth surface was used, use it also for debug rendering
666 graphics_->SetDepthStencil(lastCustomDepthSurface_ ? lastCustomDepthSurface_ : GetDepthStencil(currentRenderTarget_));
667
668 IntVector2 rtSizeNow = graphics_->GetRenderTargetDimensions();
669 IntRect viewport = (currentRenderTarget_ == renderTarget_) ? viewRect_ : IntRect(0, 0, rtSizeNow.x_,
670 rtSizeNow.y_);
671 graphics_->SetViewport(viewport);
672
673 debug->SetView(camera_);
674 debug->Render();
675 }
676 }
677
678 #ifdef URHO3D_OPENGL
679 if (camera_)
680 camera_->SetFlipVertical(false);
681 #endif
682
683 // Run framebuffer blitting if necessary. If scene was resolved from backbuffer, do not touch depth
684 // (backbuffer should contain proper depth already)
685 if (currentRenderTarget_ != renderTarget_)
686 BlitFramebuffer(currentRenderTarget_->GetParentTexture(), renderTarget_, !usedResolve_);
687
688 SendViewEvent(E_ENDVIEWRENDER);
689 }
690
GetGraphics() const691 Graphics* View::GetGraphics() const
692 {
693 return graphics_;
694 }
695
GetRenderer() const696 Renderer* View::GetRenderer() const
697 {
698 return renderer_;
699 }
700
GetSourceView() const701 View* View::GetSourceView() const
702 {
703 return sourceView_;
704 }
705
SetGlobalShaderParameters()706 void View::SetGlobalShaderParameters()
707 {
708 graphics_->SetShaderParameter(VSP_DELTATIME, frame_.timeStep_);
709 graphics_->SetShaderParameter(PSP_DELTATIME, frame_.timeStep_);
710
711 if (scene_)
712 {
713 float elapsedTime = scene_->GetElapsedTime();
714 graphics_->SetShaderParameter(VSP_ELAPSEDTIME, elapsedTime);
715 graphics_->SetShaderParameter(PSP_ELAPSEDTIME, elapsedTime);
716 }
717
718 SendViewEvent(E_VIEWGLOBALSHADERPARAMETERS);
719 }
720
SetCameraShaderParameters(Camera * camera)721 void View::SetCameraShaderParameters(Camera* camera)
722 {
723 if (!camera)
724 return;
725
726 Matrix3x4 cameraEffectiveTransform = camera->GetEffectiveWorldTransform();
727
728 graphics_->SetShaderParameter(VSP_CAMERAPOS, cameraEffectiveTransform.Translation());
729 graphics_->SetShaderParameter(VSP_VIEWINV, cameraEffectiveTransform);
730 graphics_->SetShaderParameter(VSP_VIEW, camera->GetView());
731 graphics_->SetShaderParameter(PSP_CAMERAPOS, cameraEffectiveTransform.Translation());
732
733 float nearClip = camera->GetNearClip();
734 float farClip = camera->GetFarClip();
735 graphics_->SetShaderParameter(VSP_NEARCLIP, nearClip);
736 graphics_->SetShaderParameter(VSP_FARCLIP, farClip);
737 graphics_->SetShaderParameter(PSP_NEARCLIP, nearClip);
738 graphics_->SetShaderParameter(PSP_FARCLIP, farClip);
739
740 Vector4 depthMode = Vector4::ZERO;
741 if (camera->IsOrthographic())
742 {
743 depthMode.x_ = 1.0f;
744 #ifdef URHO3D_OPENGL
745 depthMode.z_ = 0.5f;
746 depthMode.w_ = 0.5f;
747 #else
748 depthMode.z_ = 1.0f;
749 #endif
750 }
751 else
752 depthMode.w_ = 1.0f / camera->GetFarClip();
753
754 graphics_->SetShaderParameter(VSP_DEPTHMODE, depthMode);
755
756 Vector4 depthReconstruct
757 (farClip / (farClip - nearClip), -nearClip / (farClip - nearClip), camera->IsOrthographic() ? 1.0f : 0.0f,
758 camera->IsOrthographic() ? 0.0f : 1.0f);
759 graphics_->SetShaderParameter(PSP_DEPTHRECONSTRUCT, depthReconstruct);
760
761 Vector3 nearVector, farVector;
762 camera->GetFrustumSize(nearVector, farVector);
763 graphics_->SetShaderParameter(VSP_FRUSTUMSIZE, farVector);
764
765 Matrix4 projection = camera->GetGPUProjection();
766 #ifdef URHO3D_OPENGL
767 // Add constant depth bias manually to the projection matrix due to glPolygonOffset() inconsistency
768 float constantBias = 2.0f * graphics_->GetDepthConstantBias();
769 projection.m22_ += projection.m32_ * constantBias;
770 projection.m23_ += projection.m33_ * constantBias;
771 #endif
772
773 graphics_->SetShaderParameter(VSP_VIEWPROJ, projection * camera->GetView());
774
775 // If in a scene pass and the command defines shader parameters, set them now
776 if (passCommand_)
777 SetCommandShaderParameters(*passCommand_);
778 }
779
SetCommandShaderParameters(const RenderPathCommand & command)780 void View::SetCommandShaderParameters(const RenderPathCommand& command)
781 {
782 const HashMap<StringHash, Variant>& parameters = command.shaderParameters_;
783 for (HashMap<StringHash, Variant>::ConstIterator k = parameters.Begin(); k != parameters.End(); ++k)
784 graphics_->SetShaderParameter(k->first_, k->second_);
785 }
786
SetGBufferShaderParameters(const IntVector2 & texSize,const IntRect & viewRect)787 void View::SetGBufferShaderParameters(const IntVector2& texSize, const IntRect& viewRect)
788 {
789 float texWidth = (float)texSize.x_;
790 float texHeight = (float)texSize.y_;
791 float widthRange = 0.5f * viewRect.Width() / texWidth;
792 float heightRange = 0.5f * viewRect.Height() / texHeight;
793
794 #ifdef URHO3D_OPENGL
795 Vector4 bufferUVOffset(((float)viewRect.left_) / texWidth + widthRange,
796 1.0f - (((float)viewRect.top_) / texHeight + heightRange), widthRange, heightRange);
797 #else
798 const Vector2& pixelUVOffset = Graphics::GetPixelUVOffset();
799 Vector4 bufferUVOffset((pixelUVOffset.x_ + (float)viewRect.left_) / texWidth + widthRange,
800 (pixelUVOffset.y_ + (float)viewRect.top_) / texHeight + heightRange, widthRange, heightRange);
801 #endif
802 graphics_->SetShaderParameter(VSP_GBUFFEROFFSETS, bufferUVOffset);
803
804 float invSizeX = 1.0f / texWidth;
805 float invSizeY = 1.0f / texHeight;
806 graphics_->SetShaderParameter(PSP_GBUFFERINVSIZE, Vector2(invSizeX, invSizeY));
807 }
808
GetDrawables()809 void View::GetDrawables()
810 {
811 if (!octree_ || !cullCamera_)
812 return;
813
814 URHO3D_PROFILE(GetDrawables);
815
816 WorkQueue* queue = GetSubsystem<WorkQueue>();
817 PODVector<Drawable*>& tempDrawables = tempDrawables_[0];
818
819 // Get zones and occluders first
820 {
821 ZoneOccluderOctreeQuery
822 query(tempDrawables, cullCamera_->GetFrustum(), DRAWABLE_GEOMETRY | DRAWABLE_ZONE, cullCamera_->GetViewMask());
823 octree_->GetDrawables(query);
824 }
825
826 highestZonePriority_ = M_MIN_INT;
827 int bestPriority = M_MIN_INT;
828 Node* cameraNode = cullCamera_->GetNode();
829 Vector3 cameraPos = cameraNode->GetWorldPosition();
830
831 for (PODVector<Drawable*>::ConstIterator i = tempDrawables.Begin(); i != tempDrawables.End(); ++i)
832 {
833 Drawable* drawable = *i;
834 unsigned char flags = drawable->GetDrawableFlags();
835
836 if (flags & DRAWABLE_ZONE)
837 {
838 Zone* zone = static_cast<Zone*>(drawable);
839 zones_.Push(zone);
840 int priority = zone->GetPriority();
841 if (priority > highestZonePriority_)
842 highestZonePriority_ = priority;
843 if (priority > bestPriority && zone->IsInside(cameraPos))
844 {
845 cameraZone_ = zone;
846 bestPriority = priority;
847 }
848 }
849 else
850 occluders_.Push(drawable);
851 }
852
853 // Determine the zone at far clip distance. If not found, or camera zone has override mode, use camera zone
854 cameraZoneOverride_ = cameraZone_->GetOverride();
855 if (!cameraZoneOverride_)
856 {
857 Vector3 farClipPos = cameraPos + cameraNode->GetWorldDirection() * Vector3(0.0f, 0.0f, cullCamera_->GetFarClip());
858 bestPriority = M_MIN_INT;
859
860 for (PODVector<Zone*>::Iterator i = zones_.Begin(); i != zones_.End(); ++i)
861 {
862 int priority = (*i)->GetPriority();
863 if (priority > bestPriority && (*i)->IsInside(farClipPos))
864 {
865 farClipZone_ = *i;
866 bestPriority = priority;
867 }
868 }
869 }
870 if (farClipZone_ == renderer_->GetDefaultZone())
871 farClipZone_ = cameraZone_;
872
873 // If occlusion in use, get & render the occluders
874 occlusionBuffer_ = 0;
875 if (maxOccluderTriangles_ > 0)
876 {
877 UpdateOccluders(occluders_, cullCamera_);
878 if (occluders_.Size())
879 {
880 URHO3D_PROFILE(DrawOcclusion);
881
882 occlusionBuffer_ = renderer_->GetOcclusionBuffer(cullCamera_);
883 DrawOccluders(occlusionBuffer_, occluders_);
884 }
885 }
886 else
887 occluders_.Clear();
888
889 // Get lights and geometries. Coarse occlusion for octants is used at this point
890 if (occlusionBuffer_)
891 {
892 OccludedFrustumOctreeQuery query
893 (tempDrawables, cullCamera_->GetFrustum(), occlusionBuffer_, DRAWABLE_GEOMETRY | DRAWABLE_LIGHT, cullCamera_->GetViewMask());
894 octree_->GetDrawables(query);
895 }
896 else
897 {
898 FrustumOctreeQuery query(tempDrawables, cullCamera_->GetFrustum(), DRAWABLE_GEOMETRY | DRAWABLE_LIGHT, cullCamera_->GetViewMask());
899 octree_->GetDrawables(query);
900 }
901
902 // Check drawable occlusion, find zones for moved drawables and collect geometries & lights in worker threads
903 {
904 for (unsigned i = 0; i < sceneResults_.Size(); ++i)
905 {
906 PerThreadSceneResult& result = sceneResults_[i];
907
908 result.geometries_.Clear();
909 result.lights_.Clear();
910 result.minZ_ = M_INFINITY;
911 result.maxZ_ = 0.0f;
912 }
913
914 int numWorkItems = queue->GetNumThreads() + 1; // Worker threads + main thread
915 int drawablesPerItem = tempDrawables.Size() / numWorkItems;
916
917 PODVector<Drawable*>::Iterator start = tempDrawables.Begin();
918 // Create a work item for each thread
919 for (int i = 0; i < numWorkItems; ++i)
920 {
921 SharedPtr<WorkItem> item = queue->GetFreeItem();
922 item->priority_ = M_MAX_UNSIGNED;
923 item->workFunction_ = CheckVisibilityWork;
924 item->aux_ = this;
925
926 PODVector<Drawable*>::Iterator end = tempDrawables.End();
927 if (i < numWorkItems - 1 && end - start > drawablesPerItem)
928 end = start + drawablesPerItem;
929
930 item->start_ = &(*start);
931 item->end_ = &(*end);
932 queue->AddWorkItem(item);
933
934 start = end;
935 }
936
937 queue->Complete(M_MAX_UNSIGNED);
938 }
939
940 // Combine lights, geometries & scene Z range from the threads
941 geometries_.Clear();
942 lights_.Clear();
943 minZ_ = M_INFINITY;
944 maxZ_ = 0.0f;
945
946 if (sceneResults_.Size() > 1)
947 {
948 for (unsigned i = 0; i < sceneResults_.Size(); ++i)
949 {
950 PerThreadSceneResult& result = sceneResults_[i];
951 geometries_.Push(result.geometries_);
952 lights_.Push(result.lights_);
953 minZ_ = Min(minZ_, result.minZ_);
954 maxZ_ = Max(maxZ_, result.maxZ_);
955 }
956 }
957 else
958 {
959 // If just 1 thread, copy the results directly
960 PerThreadSceneResult& result = sceneResults_[0];
961 minZ_ = result.minZ_;
962 maxZ_ = result.maxZ_;
963 Swap(geometries_, result.geometries_);
964 Swap(lights_, result.lights_);
965 }
966
967 if (minZ_ == M_INFINITY)
968 minZ_ = 0.0f;
969
970 // Sort the lights to brightest/closest first, and per-vertex lights first so that per-vertex base pass can be evaluated first
971 for (unsigned i = 0; i < lights_.Size(); ++i)
972 {
973 Light* light = lights_[i];
974 light->SetIntensitySortValue(cullCamera_->GetDistance(light->GetNode()->GetWorldPosition()));
975 light->SetLightQueue(0);
976 }
977
978 Sort(lights_.Begin(), lights_.End(), CompareLights);
979 }
980
GetBatches()981 void View::GetBatches()
982 {
983 if (!octree_ || !cullCamera_)
984 return;
985
986 nonThreadedGeometries_.Clear();
987 threadedGeometries_.Clear();
988
989 ProcessLights();
990 GetLightBatches();
991 GetBaseBatches();
992 }
993
ProcessLights()994 void View::ProcessLights()
995 {
996 // Process lit geometries and shadow casters for each light
997 URHO3D_PROFILE(ProcessLights);
998
999 WorkQueue* queue = GetSubsystem<WorkQueue>();
1000 lightQueryResults_.Resize(lights_.Size());
1001
1002 for (unsigned i = 0; i < lightQueryResults_.Size(); ++i)
1003 {
1004 SharedPtr<WorkItem> item = queue->GetFreeItem();
1005 item->priority_ = M_MAX_UNSIGNED;
1006 item->workFunction_ = ProcessLightWork;
1007 item->aux_ = this;
1008
1009 LightQueryResult& query = lightQueryResults_[i];
1010 query.light_ = lights_[i];
1011
1012 item->start_ = &query;
1013 queue->AddWorkItem(item);
1014 }
1015
1016 // Ensure all lights have been processed before proceeding
1017 queue->Complete(M_MAX_UNSIGNED);
1018 }
1019
GetLightBatches()1020 void View::GetLightBatches()
1021 {
1022 BatchQueue* alphaQueue = batchQueues_.Contains(alphaPassIndex_) ? &batchQueues_[alphaPassIndex_] : (BatchQueue*)0;
1023
1024 // Build light queues and lit batches
1025 {
1026 URHO3D_PROFILE(GetLightBatches);
1027
1028 // Preallocate light queues: per-pixel lights which have lit geometries
1029 unsigned numLightQueues = 0;
1030 unsigned usedLightQueues = 0;
1031 for (Vector<LightQueryResult>::ConstIterator i = lightQueryResults_.Begin(); i != lightQueryResults_.End(); ++i)
1032 {
1033 if (!i->light_->GetPerVertex() && i->litGeometries_.Size())
1034 ++numLightQueues;
1035 }
1036
1037 lightQueues_.Resize(numLightQueues);
1038 maxLightsDrawables_.Clear();
1039 unsigned maxSortedInstances = (unsigned)renderer_->GetMaxSortedInstances();
1040
1041 for (Vector<LightQueryResult>::Iterator i = lightQueryResults_.Begin(); i != lightQueryResults_.End(); ++i)
1042 {
1043 LightQueryResult& query = *i;
1044
1045 // If light has no affected geometries, no need to process further
1046 if (query.litGeometries_.Empty())
1047 continue;
1048
1049 Light* light = query.light_;
1050
1051 // Per-pixel light
1052 if (!light->GetPerVertex())
1053 {
1054 unsigned shadowSplits = query.numSplits_;
1055
1056 // Initialize light queue and store it to the light so that it can be found later
1057 LightBatchQueue& lightQueue = lightQueues_[usedLightQueues++];
1058 light->SetLightQueue(&lightQueue);
1059 lightQueue.light_ = light;
1060 lightQueue.negative_ = light->IsNegative();
1061 lightQueue.shadowMap_ = 0;
1062 lightQueue.litBaseBatches_.Clear(maxSortedInstances);
1063 lightQueue.litBatches_.Clear(maxSortedInstances);
1064 if (forwardLightsCommand_)
1065 {
1066 SetQueueShaderDefines(lightQueue.litBaseBatches_, *forwardLightsCommand_);
1067 SetQueueShaderDefines(lightQueue.litBatches_, *forwardLightsCommand_);
1068 }
1069 else
1070 {
1071 lightQueue.litBaseBatches_.hasExtraDefines_ = false;
1072 lightQueue.litBatches_.hasExtraDefines_ = false;
1073 }
1074 lightQueue.volumeBatches_.Clear();
1075
1076 // Allocate shadow map now
1077 if (shadowSplits > 0)
1078 {
1079 lightQueue.shadowMap_ = renderer_->GetShadowMap(light, cullCamera_, (unsigned)viewSize_.x_, (unsigned)viewSize_.y_);
1080 // If did not manage to get a shadow map, convert the light to unshadowed
1081 if (!lightQueue.shadowMap_)
1082 shadowSplits = 0;
1083 }
1084
1085 // Setup shadow batch queues
1086 lightQueue.shadowSplits_.Resize(shadowSplits);
1087 for (unsigned j = 0; j < shadowSplits; ++j)
1088 {
1089 ShadowBatchQueue& shadowQueue = lightQueue.shadowSplits_[j];
1090 Camera* shadowCamera = query.shadowCameras_[j];
1091 shadowQueue.shadowCamera_ = shadowCamera;
1092 shadowQueue.nearSplit_ = query.shadowNearSplits_[j];
1093 shadowQueue.farSplit_ = query.shadowFarSplits_[j];
1094 shadowQueue.shadowBatches_.Clear(maxSortedInstances);
1095
1096 // Setup the shadow split viewport and finalize shadow camera parameters
1097 shadowQueue.shadowViewport_ = GetShadowMapViewport(light, j, lightQueue.shadowMap_);
1098 FinalizeShadowCamera(shadowCamera, light, shadowQueue.shadowViewport_, query.shadowCasterBox_[j]);
1099
1100 // Loop through shadow casters
1101 for (PODVector<Drawable*>::ConstIterator k = query.shadowCasters_.Begin() + query.shadowCasterBegin_[j];
1102 k < query.shadowCasters_.Begin() + query.shadowCasterEnd_[j]; ++k)
1103 {
1104 Drawable* drawable = *k;
1105 // If drawable is not in actual view frustum, mark it in view here and check its geometry update type
1106 if (!drawable->IsInView(frame_, true))
1107 {
1108 drawable->MarkInView(frame_.frameNumber_);
1109 UpdateGeometryType type = drawable->GetUpdateGeometryType();
1110 if (type == UPDATE_MAIN_THREAD)
1111 nonThreadedGeometries_.Push(drawable);
1112 else if (type == UPDATE_WORKER_THREAD)
1113 threadedGeometries_.Push(drawable);
1114 }
1115
1116 const Vector<SourceBatch>& batches = drawable->GetBatches();
1117
1118 for (unsigned l = 0; l < batches.Size(); ++l)
1119 {
1120 const SourceBatch& srcBatch = batches[l];
1121
1122 Technique* tech = GetTechnique(drawable, srcBatch.material_);
1123 if (!srcBatch.geometry_ || !srcBatch.numWorldTransforms_ || !tech)
1124 continue;
1125
1126 Pass* pass = tech->GetSupportedPass(Technique::shadowPassIndex);
1127 // Skip if material has no shadow pass
1128 if (!pass)
1129 continue;
1130
1131 Batch destBatch(srcBatch);
1132 destBatch.pass_ = pass;
1133 destBatch.zone_ = 0;
1134
1135 AddBatchToQueue(shadowQueue.shadowBatches_, destBatch, tech);
1136 }
1137 }
1138 }
1139
1140 // Process lit geometries
1141 for (PODVector<Drawable*>::ConstIterator j = query.litGeometries_.Begin(); j != query.litGeometries_.End(); ++j)
1142 {
1143 Drawable* drawable = *j;
1144 drawable->AddLight(light);
1145
1146 // If drawable limits maximum lights, only record the light, and check maximum count / build batches later
1147 if (!drawable->GetMaxLights())
1148 GetLitBatches(drawable, lightQueue, alphaQueue);
1149 else
1150 maxLightsDrawables_.Insert(drawable);
1151 }
1152
1153 // In deferred modes, store the light volume batch now. Since light mask 8 lowest bits are output to the stencil,
1154 // lights that have all zeroes in the low 8 bits can be skipped; they would not affect geometry anyway
1155 if (deferred_ && (light->GetLightMask() & 0xff) != 0)
1156 {
1157 Batch volumeBatch;
1158 volumeBatch.geometry_ = renderer_->GetLightGeometry(light);
1159 volumeBatch.geometryType_ = GEOM_STATIC;
1160 volumeBatch.worldTransform_ = &light->GetVolumeTransform(cullCamera_);
1161 volumeBatch.numWorldTransforms_ = 1;
1162 volumeBatch.lightQueue_ = &lightQueue;
1163 volumeBatch.distance_ = light->GetDistance();
1164 volumeBatch.material_ = 0;
1165 volumeBatch.pass_ = 0;
1166 volumeBatch.zone_ = 0;
1167 renderer_->SetLightVolumeBatchShaders(volumeBatch, cullCamera_, lightVolumeCommand_->vertexShaderName_,
1168 lightVolumeCommand_->pixelShaderName_, lightVolumeCommand_->vertexShaderDefines_,
1169 lightVolumeCommand_->pixelShaderDefines_);
1170 lightQueue.volumeBatches_.Push(volumeBatch);
1171 }
1172 }
1173 // Per-vertex light
1174 else
1175 {
1176 // Add the vertex light to lit drawables. It will be processed later during base pass batch generation
1177 for (PODVector<Drawable*>::ConstIterator j = query.litGeometries_.Begin(); j != query.litGeometries_.End(); ++j)
1178 {
1179 Drawable* drawable = *j;
1180 drawable->AddVertexLight(light);
1181 }
1182 }
1183 }
1184 }
1185
1186 // Process drawables with limited per-pixel light count
1187 if (maxLightsDrawables_.Size())
1188 {
1189 URHO3D_PROFILE(GetMaxLightsBatches);
1190
1191 for (HashSet<Drawable*>::Iterator i = maxLightsDrawables_.Begin(); i != maxLightsDrawables_.End(); ++i)
1192 {
1193 Drawable* drawable = *i;
1194 drawable->LimitLights();
1195 const PODVector<Light*>& lights = drawable->GetLights();
1196
1197 for (unsigned i = 0; i < lights.Size(); ++i)
1198 {
1199 Light* light = lights[i];
1200 // Find the correct light queue again
1201 LightBatchQueue* queue = light->GetLightQueue();
1202 if (queue)
1203 GetLitBatches(drawable, *queue, alphaQueue);
1204 }
1205 }
1206 }
1207 }
1208
GetBaseBatches()1209 void View::GetBaseBatches()
1210 {
1211 URHO3D_PROFILE(GetBaseBatches);
1212
1213 for (PODVector<Drawable*>::ConstIterator i = geometries_.Begin(); i != geometries_.End(); ++i)
1214 {
1215 Drawable* drawable = *i;
1216 UpdateGeometryType type = drawable->GetUpdateGeometryType();
1217 if (type == UPDATE_MAIN_THREAD)
1218 nonThreadedGeometries_.Push(drawable);
1219 else if (type == UPDATE_WORKER_THREAD)
1220 threadedGeometries_.Push(drawable);
1221
1222 const Vector<SourceBatch>& batches = drawable->GetBatches();
1223 bool vertexLightsProcessed = false;
1224
1225 for (unsigned j = 0; j < batches.Size(); ++j)
1226 {
1227 const SourceBatch& srcBatch = batches[j];
1228
1229 // Check here if the material refers to a rendertarget texture with camera(s) attached
1230 // Only check this for backbuffer views (null rendertarget)
1231 if (srcBatch.material_ && srcBatch.material_->GetAuxViewFrameNumber() != frame_.frameNumber_ && !renderTarget_)
1232 CheckMaterialForAuxView(srcBatch.material_);
1233
1234 Technique* tech = GetTechnique(drawable, srcBatch.material_);
1235 if (!srcBatch.geometry_ || !srcBatch.numWorldTransforms_ || !tech)
1236 continue;
1237
1238 // Check each of the scene passes
1239 for (unsigned k = 0; k < scenePasses_.Size(); ++k)
1240 {
1241 ScenePassInfo& info = scenePasses_[k];
1242 // Skip forward base pass if the corresponding litbase pass already exists
1243 if (info.passIndex_ == basePassIndex_ && j < 32 && drawable->HasBasePass(j))
1244 continue;
1245
1246 Pass* pass = tech->GetSupportedPass(info.passIndex_);
1247 if (!pass)
1248 continue;
1249
1250 Batch destBatch(srcBatch);
1251 destBatch.pass_ = pass;
1252 destBatch.zone_ = GetZone(drawable);
1253 destBatch.isBase_ = true;
1254 destBatch.lightMask_ = (unsigned char)GetLightMask(drawable);
1255
1256 if (info.vertexLights_)
1257 {
1258 const PODVector<Light*>& drawableVertexLights = drawable->GetVertexLights();
1259 if (drawableVertexLights.Size() && !vertexLightsProcessed)
1260 {
1261 // Limit vertex lights. If this is a deferred opaque batch, remove converted per-pixel lights,
1262 // as they will be rendered as light volumes in any case, and drawing them also as vertex lights
1263 // would result in double lighting
1264 drawable->LimitVertexLights(deferred_ && destBatch.pass_->GetBlendMode() == BLEND_REPLACE);
1265 vertexLightsProcessed = true;
1266 }
1267
1268 if (drawableVertexLights.Size())
1269 {
1270 // Find a vertex light queue. If not found, create new
1271 unsigned long long hash = GetVertexLightQueueHash(drawableVertexLights);
1272 HashMap<unsigned long long, LightBatchQueue>::Iterator i = vertexLightQueues_.Find(hash);
1273 if (i == vertexLightQueues_.End())
1274 {
1275 i = vertexLightQueues_.Insert(MakePair(hash, LightBatchQueue()));
1276 i->second_.light_ = 0;
1277 i->second_.shadowMap_ = 0;
1278 i->second_.vertexLights_ = drawableVertexLights;
1279 }
1280
1281 destBatch.lightQueue_ = &(i->second_);
1282 }
1283 }
1284 else
1285 destBatch.lightQueue_ = 0;
1286
1287 bool allowInstancing = info.allowInstancing_;
1288 if (allowInstancing && info.markToStencil_ && destBatch.lightMask_ != (destBatch.zone_->GetLightMask() & 0xff))
1289 allowInstancing = false;
1290
1291 AddBatchToQueue(*info.batchQueue_, destBatch, tech, allowInstancing);
1292 }
1293 }
1294 }
1295 }
1296
UpdateGeometries()1297 void View::UpdateGeometries()
1298 {
1299 // Update geometries in the source view if necessary (prepare order may differ from render order)
1300 if (sourceView_ && !sourceView_->geometriesUpdated_)
1301 {
1302 sourceView_->UpdateGeometries();
1303 return;
1304 }
1305
1306 URHO3D_PROFILE(SortAndUpdateGeometry);
1307
1308 WorkQueue* queue = GetSubsystem<WorkQueue>();
1309
1310 // Sort batches
1311 {
1312 for (unsigned i = 0; i < renderPath_->commands_.Size(); ++i)
1313 {
1314 const RenderPathCommand& command = renderPath_->commands_[i];
1315 if (!IsNecessary(command))
1316 continue;
1317
1318 if (command.type_ == CMD_SCENEPASS)
1319 {
1320 SharedPtr<WorkItem> item = queue->GetFreeItem();
1321 item->priority_ = M_MAX_UNSIGNED;
1322 item->workFunction_ =
1323 command.sortMode_ == SORT_FRONTTOBACK ? SortBatchQueueFrontToBackWork : SortBatchQueueBackToFrontWork;
1324 item->start_ = &batchQueues_[command.passIndex_];
1325 queue->AddWorkItem(item);
1326 }
1327 }
1328
1329 for (Vector<LightBatchQueue>::Iterator i = lightQueues_.Begin(); i != lightQueues_.End(); ++i)
1330 {
1331 SharedPtr<WorkItem> lightItem = queue->GetFreeItem();
1332 lightItem->priority_ = M_MAX_UNSIGNED;
1333 lightItem->workFunction_ = SortLightQueueWork;
1334 lightItem->start_ = &(*i);
1335 queue->AddWorkItem(lightItem);
1336
1337 if (i->shadowSplits_.Size())
1338 {
1339 SharedPtr<WorkItem> shadowItem = queue->GetFreeItem();
1340 shadowItem->priority_ = M_MAX_UNSIGNED;
1341 shadowItem->workFunction_ = SortShadowQueueWork;
1342 shadowItem->start_ = &(*i);
1343 queue->AddWorkItem(shadowItem);
1344 }
1345 }
1346 }
1347
1348 // Update geometries. Split into threaded and non-threaded updates.
1349 {
1350 if (threadedGeometries_.Size())
1351 {
1352 // In special cases (context loss, multi-view) a drawable may theoretically first have reported a threaded update, but will actually
1353 // require a main thread update. Check these cases first and move as applicable. The threaded work routine will tolerate the null
1354 // pointer holes that we leave to the threaded update queue.
1355 for (PODVector<Drawable*>::Iterator i = threadedGeometries_.Begin(); i != threadedGeometries_.End(); ++i)
1356 {
1357 if ((*i)->GetUpdateGeometryType() == UPDATE_MAIN_THREAD)
1358 {
1359 nonThreadedGeometries_.Push(*i);
1360 *i = 0;
1361 }
1362 }
1363
1364 int numWorkItems = queue->GetNumThreads() + 1; // Worker threads + main thread
1365 int drawablesPerItem = threadedGeometries_.Size() / numWorkItems;
1366
1367 PODVector<Drawable*>::Iterator start = threadedGeometries_.Begin();
1368 for (int i = 0; i < numWorkItems; ++i)
1369 {
1370 PODVector<Drawable*>::Iterator end = threadedGeometries_.End();
1371 if (i < numWorkItems - 1 && end - start > drawablesPerItem)
1372 end = start + drawablesPerItem;
1373
1374 SharedPtr<WorkItem> item = queue->GetFreeItem();
1375 item->priority_ = M_MAX_UNSIGNED;
1376 item->workFunction_ = UpdateDrawableGeometriesWork;
1377 item->aux_ = const_cast<FrameInfo*>(&frame_);
1378 item->start_ = &(*start);
1379 item->end_ = &(*end);
1380 queue->AddWorkItem(item);
1381
1382 start = end;
1383 }
1384 }
1385
1386 // While the work queue is processed, update non-threaded geometries
1387 for (PODVector<Drawable*>::ConstIterator i = nonThreadedGeometries_.Begin(); i != nonThreadedGeometries_.End(); ++i)
1388 (*i)->UpdateGeometry(frame_);
1389 }
1390
1391 // Finally ensure all threaded work has completed
1392 queue->Complete(M_MAX_UNSIGNED);
1393 geometriesUpdated_ = true;
1394 }
1395
GetLitBatches(Drawable * drawable,LightBatchQueue & lightQueue,BatchQueue * alphaQueue)1396 void View::GetLitBatches(Drawable* drawable, LightBatchQueue& lightQueue, BatchQueue* alphaQueue)
1397 {
1398 Light* light = lightQueue.light_;
1399 Zone* zone = GetZone(drawable);
1400 const Vector<SourceBatch>& batches = drawable->GetBatches();
1401
1402 bool allowLitBase =
1403 useLitBase_ && !lightQueue.negative_ && light == drawable->GetFirstLight() && drawable->GetVertexLights().Empty() &&
1404 !zone->GetAmbientGradient();
1405
1406 for (unsigned i = 0; i < batches.Size(); ++i)
1407 {
1408 const SourceBatch& srcBatch = batches[i];
1409
1410 Technique* tech = GetTechnique(drawable, srcBatch.material_);
1411 if (!srcBatch.geometry_ || !srcBatch.numWorldTransforms_ || !tech)
1412 continue;
1413
1414 // Do not create pixel lit forward passes for materials that render into the G-buffer
1415 if (gBufferPassIndex_ != M_MAX_UNSIGNED && tech->HasPass(gBufferPassIndex_))
1416 continue;
1417
1418 Batch destBatch(srcBatch);
1419 bool isLitAlpha = false;
1420
1421 // Check for lit base pass. Because it uses the replace blend mode, it must be ensured to be the first light
1422 // Also vertex lighting or ambient gradient require the non-lit base pass, so skip in those cases
1423 if (i < 32 && allowLitBase)
1424 {
1425 destBatch.pass_ = tech->GetSupportedPass(litBasePassIndex_);
1426 if (destBatch.pass_)
1427 {
1428 destBatch.isBase_ = true;
1429 drawable->SetBasePass(i);
1430 }
1431 else
1432 destBatch.pass_ = tech->GetSupportedPass(lightPassIndex_);
1433 }
1434 else
1435 destBatch.pass_ = tech->GetSupportedPass(lightPassIndex_);
1436
1437 // If no lit pass, check for lit alpha
1438 if (!destBatch.pass_)
1439 {
1440 destBatch.pass_ = tech->GetSupportedPass(litAlphaPassIndex_);
1441 isLitAlpha = true;
1442 }
1443
1444 // Skip if material does not receive light at all
1445 if (!destBatch.pass_)
1446 continue;
1447
1448 destBatch.lightQueue_ = &lightQueue;
1449 destBatch.zone_ = zone;
1450
1451 if (!isLitAlpha)
1452 {
1453 if (destBatch.isBase_)
1454 AddBatchToQueue(lightQueue.litBaseBatches_, destBatch, tech);
1455 else
1456 AddBatchToQueue(lightQueue.litBatches_, destBatch, tech);
1457 }
1458 else if (alphaQueue)
1459 {
1460 // Transparent batches can not be instanced, and shadows on transparencies can only be rendered if shadow maps are
1461 // not reused
1462 AddBatchToQueue(*alphaQueue, destBatch, tech, false, !renderer_->GetReuseShadowMaps());
1463 }
1464 }
1465 }
1466
ExecuteRenderPathCommands()1467 void View::ExecuteRenderPathCommands()
1468 {
1469 View* actualView = sourceView_ ? sourceView_ : this;
1470
1471 // If not reusing shadowmaps, render all of them first
1472 if (!renderer_->GetReuseShadowMaps() && renderer_->GetDrawShadows() && !actualView->lightQueues_.Empty())
1473 {
1474 URHO3D_PROFILE(RenderShadowMaps);
1475
1476 for (Vector<LightBatchQueue>::Iterator i = actualView->lightQueues_.Begin(); i != actualView->lightQueues_.End(); ++i)
1477 {
1478 if (NeedRenderShadowMap(*i))
1479 RenderShadowMap(*i);
1480 }
1481 }
1482
1483 {
1484 URHO3D_PROFILE(ExecuteRenderPath);
1485
1486 // Set for safety in case of empty renderpath
1487 currentRenderTarget_ = substituteRenderTarget_ ? substituteRenderTarget_ : renderTarget_;
1488 currentViewportTexture_ = 0;
1489 passCommand_ = 0;
1490
1491 bool viewportModified = false;
1492 bool isPingponging = false;
1493 usedResolve_ = false;
1494
1495 unsigned lastCommandIndex = 0;
1496 for (unsigned i = 0; i < renderPath_->commands_.Size(); ++i)
1497 {
1498 RenderPathCommand& command = renderPath_->commands_[i];
1499 if (actualView->IsNecessary(command))
1500 lastCommandIndex = i;
1501 }
1502
1503 for (unsigned i = 0; i < renderPath_->commands_.Size(); ++i)
1504 {
1505 RenderPathCommand& command = renderPath_->commands_[i];
1506 if (!actualView->IsNecessary(command))
1507 continue;
1508
1509 bool viewportRead = actualView->CheckViewportRead(command);
1510 bool viewportWrite = actualView->CheckViewportWrite(command);
1511 bool beginPingpong = actualView->CheckPingpong(i);
1512
1513 // Has the viewport been modified and will be read as a texture by the current command?
1514 if (viewportRead && viewportModified)
1515 {
1516 // Start pingponging without a blit if already rendering to the substitute render target
1517 if (currentRenderTarget_ && currentRenderTarget_ == substituteRenderTarget_ && beginPingpong)
1518 isPingponging = true;
1519
1520 // If not using pingponging, simply resolve/copy to the first viewport texture
1521 if (!isPingponging)
1522 {
1523 if (!currentRenderTarget_)
1524 {
1525 graphics_->ResolveToTexture(dynamic_cast<Texture2D*>(viewportTextures_[0]), viewRect_);
1526 currentViewportTexture_ = viewportTextures_[0];
1527 viewportModified = false;
1528 usedResolve_ = true;
1529 }
1530 else
1531 {
1532 if (viewportWrite)
1533 {
1534 BlitFramebuffer(currentRenderTarget_->GetParentTexture(),
1535 GetRenderSurfaceFromTexture(viewportTextures_[0]), false);
1536 currentViewportTexture_ = viewportTextures_[0];
1537 viewportModified = false;
1538 }
1539 else
1540 {
1541 // If the current render target is already a texture, and we are not writing to it, can read that
1542 // texture directly instead of blitting. However keep the viewport dirty flag in case a later command
1543 // will do both read and write, and then we need to blit / resolve
1544 currentViewportTexture_ = currentRenderTarget_->GetParentTexture();
1545 }
1546 }
1547 }
1548 else
1549 {
1550 // Swap the pingpong double buffer sides. Texture 0 will be read next
1551 viewportTextures_[1] = viewportTextures_[0];
1552 viewportTextures_[0] = currentRenderTarget_->GetParentTexture();
1553 currentViewportTexture_ = viewportTextures_[0];
1554 viewportModified = false;
1555 }
1556 }
1557
1558 if (beginPingpong)
1559 isPingponging = true;
1560
1561 // Determine viewport write target
1562 if (viewportWrite)
1563 {
1564 if (isPingponging)
1565 {
1566 currentRenderTarget_ = GetRenderSurfaceFromTexture(viewportTextures_[1]);
1567 // If the render path ends into a quad, it can be redirected to the final render target
1568 // However, on OpenGL we can not reliably do this in case the final target is the backbuffer, and we want to
1569 // render depth buffer sensitive debug geometry afterward (backbuffer and textures can not share depth)
1570 #ifndef URHO3D_OPENGL
1571 if (i == lastCommandIndex && command.type_ == CMD_QUAD)
1572 #else
1573 if (i == lastCommandIndex && command.type_ == CMD_QUAD && renderTarget_)
1574 #endif
1575 currentRenderTarget_ = renderTarget_;
1576 }
1577 else
1578 currentRenderTarget_ = substituteRenderTarget_ ? substituteRenderTarget_ : renderTarget_;
1579 }
1580
1581 switch (command.type_)
1582 {
1583 case CMD_CLEAR:
1584 {
1585 URHO3D_PROFILE(ClearRenderTarget);
1586
1587 Color clearColor = command.clearColor_;
1588 if (command.useFogColor_)
1589 clearColor = actualView->farClipZone_->GetFogColor();
1590
1591 SetRenderTargets(command);
1592 graphics_->Clear(command.clearFlags_, clearColor, command.clearDepth_, command.clearStencil_);
1593 }
1594 break;
1595
1596 case CMD_SCENEPASS:
1597 {
1598 BatchQueue& queue = actualView->batchQueues_[command.passIndex_];
1599 if (!queue.IsEmpty())
1600 {
1601 URHO3D_PROFILE(RenderScenePass);
1602
1603 SetRenderTargets(command);
1604 bool allowDepthWrite = SetTextures(command);
1605 graphics_->SetClipPlane(camera_->GetUseClipping(), camera_->GetClipPlane(), camera_->GetView(),
1606 camera_->GetGPUProjection());
1607
1608 if (command.shaderParameters_.Size())
1609 {
1610 // If pass defines shader parameters, reset parameter sources now to ensure they all will be set
1611 // (will be set after camera shader parameters)
1612 graphics_->ClearParameterSources();
1613 passCommand_ = &command;
1614 }
1615
1616 queue.Draw(this, camera_, command.markToStencil_, false, allowDepthWrite);
1617
1618 passCommand_ = 0;
1619 }
1620 }
1621 break;
1622
1623 case CMD_QUAD:
1624 {
1625 URHO3D_PROFILE(RenderQuad);
1626
1627 SetRenderTargets(command);
1628 SetTextures(command);
1629 RenderQuad(command);
1630 }
1631 break;
1632
1633 case CMD_FORWARDLIGHTS:
1634 // Render shadow maps + opaque objects' additive lighting
1635 if (!actualView->lightQueues_.Empty())
1636 {
1637 URHO3D_PROFILE(RenderLights);
1638
1639 SetRenderTargets(command);
1640
1641 for (Vector<LightBatchQueue>::Iterator i = actualView->lightQueues_.Begin(); i != actualView->lightQueues_.End(); ++i)
1642 {
1643 // If reusing shadowmaps, render each of them before the lit batches
1644 if (renderer_->GetReuseShadowMaps() && NeedRenderShadowMap(*i))
1645 {
1646 RenderShadowMap(*i);
1647 SetRenderTargets(command);
1648 }
1649
1650 bool allowDepthWrite = SetTextures(command);
1651 graphics_->SetClipPlane(camera_->GetUseClipping(), camera_->GetClipPlane(), camera_->GetView(),
1652 camera_->GetGPUProjection());
1653
1654 if (command.shaderParameters_.Size())
1655 {
1656 graphics_->ClearParameterSources();
1657 passCommand_ = &command;
1658 }
1659
1660 // Draw base (replace blend) batches first
1661 i->litBaseBatches_.Draw(this, camera_, false, false, allowDepthWrite);
1662
1663 // Then, if there are additive passes, optimize the light and draw them
1664 if (!i->litBatches_.IsEmpty())
1665 {
1666 renderer_->OptimizeLightByScissor(i->light_, camera_);
1667 if (!noStencil_)
1668 renderer_->OptimizeLightByStencil(i->light_, camera_);
1669 i->litBatches_.Draw(this, camera_, false, true, allowDepthWrite);
1670 }
1671
1672 passCommand_ = 0;
1673 }
1674
1675 graphics_->SetScissorTest(false);
1676 graphics_->SetStencilTest(false);
1677 }
1678 break;
1679
1680 case CMD_LIGHTVOLUMES:
1681 // Render shadow maps + light volumes
1682 if (!actualView->lightQueues_.Empty())
1683 {
1684 URHO3D_PROFILE(RenderLightVolumes);
1685
1686 SetRenderTargets(command);
1687 for (Vector<LightBatchQueue>::Iterator i = actualView->lightQueues_.Begin(); i != actualView->lightQueues_.End(); ++i)
1688 {
1689 // If reusing shadowmaps, render each of them before the lit batches
1690 if (renderer_->GetReuseShadowMaps() && NeedRenderShadowMap(*i))
1691 {
1692 RenderShadowMap(*i);
1693 SetRenderTargets(command);
1694 }
1695
1696 SetTextures(command);
1697
1698 if (command.shaderParameters_.Size())
1699 {
1700 graphics_->ClearParameterSources();
1701 passCommand_ = &command;
1702 }
1703
1704 for (unsigned j = 0; j < i->volumeBatches_.Size(); ++j)
1705 {
1706 SetupLightVolumeBatch(i->volumeBatches_[j]);
1707 i->volumeBatches_[j].Draw(this, camera_, false);
1708 }
1709
1710 passCommand_ = 0;
1711 }
1712
1713 graphics_->SetScissorTest(false);
1714 graphics_->SetStencilTest(false);
1715 }
1716 break;
1717
1718 case CMD_RENDERUI:
1719 {
1720 SetRenderTargets(command);
1721 GetSubsystem<UI>()->Render(false);
1722 }
1723 break;
1724
1725 case CMD_SENDEVENT:
1726 {
1727 using namespace RenderPathEvent;
1728
1729 VariantMap& eventData = GetEventDataMap();
1730 eventData[P_NAME] = command.eventName_;
1731 renderer_->SendEvent(E_RENDERPATHEVENT, eventData);
1732 }
1733 break;
1734
1735 default:
1736 break;
1737 }
1738
1739 // If current command output to the viewport, mark it modified
1740 if (viewportWrite)
1741 viewportModified = true;
1742 }
1743 }
1744 }
1745
SetRenderTargets(RenderPathCommand & command)1746 void View::SetRenderTargets(RenderPathCommand& command)
1747 {
1748 unsigned index = 0;
1749 bool useColorWrite = true;
1750 bool useCustomDepth = false;
1751 bool useViewportOutput = false;
1752
1753 while (index < command.outputs_.Size())
1754 {
1755 if (!command.outputs_[index].first_.Compare("viewport", false))
1756 {
1757 graphics_->SetRenderTarget(index, currentRenderTarget_);
1758 useViewportOutput = true;
1759 }
1760 else
1761 {
1762 Texture* texture = FindNamedTexture(command.outputs_[index].first_, true, false);
1763
1764 // Check for depth only rendering (by specifying a depth texture as the sole output)
1765 if (!index && command.outputs_.Size() == 1 && texture && (texture->GetFormat() == Graphics::GetReadableDepthFormat() ||
1766 texture->GetFormat() == Graphics::GetDepthStencilFormat()))
1767 {
1768 useColorWrite = false;
1769 useCustomDepth = true;
1770 #if !defined(URHO3D_OPENGL) && !defined(URHO3D_D3D11)
1771 // On D3D9 actual depth-only rendering is illegal, we need a color rendertarget
1772 if (!depthOnlyDummyTexture_)
1773 {
1774 depthOnlyDummyTexture_ = renderer_->GetScreenBuffer(texture->GetWidth(), texture->GetHeight(),
1775 graphics_->GetDummyColorFormat(), texture->GetMultiSample(), texture->GetAutoResolve(), false, false, false);
1776 }
1777 #endif
1778 graphics_->SetRenderTarget(0, GetRenderSurfaceFromTexture(depthOnlyDummyTexture_));
1779 graphics_->SetDepthStencil(GetRenderSurfaceFromTexture(texture));
1780 }
1781 else
1782 graphics_->SetRenderTarget(index, GetRenderSurfaceFromTexture(texture, command.outputs_[index].second_));
1783 }
1784
1785 ++index;
1786 }
1787
1788 while (index < MAX_RENDERTARGETS)
1789 {
1790 graphics_->SetRenderTarget(index, (RenderSurface*)0);
1791 ++index;
1792 }
1793
1794 if (command.depthStencilName_.Length())
1795 {
1796 Texture* depthTexture = FindNamedTexture(command.depthStencilName_, true, false);
1797 if (depthTexture)
1798 {
1799 useCustomDepth = true;
1800 lastCustomDepthSurface_ = GetRenderSurfaceFromTexture(depthTexture);
1801 graphics_->SetDepthStencil(lastCustomDepthSurface_);
1802 }
1803 }
1804
1805 // When rendering to the final destination rendertarget, use the actual viewport. Otherwise texture rendertargets should use
1806 // their full size as the viewport
1807 IntVector2 rtSizeNow = graphics_->GetRenderTargetDimensions();
1808 IntRect viewport = (useViewportOutput && currentRenderTarget_ == renderTarget_) ? viewRect_ : IntRect(0, 0, rtSizeNow.x_,
1809 rtSizeNow.y_);
1810
1811 if (!useCustomDepth)
1812 graphics_->SetDepthStencil(GetDepthStencil(graphics_->GetRenderTarget(0)));
1813 graphics_->SetViewport(viewport);
1814 graphics_->SetColorWrite(useColorWrite);
1815 }
1816
SetTextures(RenderPathCommand & command)1817 bool View::SetTextures(RenderPathCommand& command)
1818 {
1819 bool allowDepthWrite = true;
1820
1821 for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
1822 {
1823 if (command.textureNames_[i].Empty())
1824 continue;
1825
1826 // Bind the rendered output
1827 if (!command.textureNames_[i].Compare("viewport", false))
1828 {
1829 graphics_->SetTexture(i, currentViewportTexture_);
1830 continue;
1831 }
1832
1833 #ifdef DESKTOP_GRAPHICS
1834 Texture* texture = FindNamedTexture(command.textureNames_[i], false, i == TU_VOLUMEMAP);
1835 #else
1836 Texture* texture = FindNamedTexture(command.textureNames_[i], false, false);
1837 #endif
1838
1839 if (texture)
1840 {
1841 graphics_->SetTexture(i, texture);
1842 // Check if the current depth stencil is being sampled
1843 if (graphics_->GetDepthStencil() && texture == graphics_->GetDepthStencil()->GetParentTexture())
1844 allowDepthWrite = false;
1845 }
1846 else
1847 {
1848 // If requesting a texture fails, clear the texture name to prevent redundant attempts
1849 command.textureNames_[i] = String::EMPTY;
1850 }
1851 }
1852
1853 return allowDepthWrite;
1854 }
1855
RenderQuad(RenderPathCommand & command)1856 void View::RenderQuad(RenderPathCommand& command)
1857 {
1858 if (command.vertexShaderName_.Empty() || command.pixelShaderName_.Empty())
1859 return;
1860
1861 // If shader can not be found, clear it from the command to prevent redundant attempts
1862 ShaderVariation* vs = graphics_->GetShader(VS, command.vertexShaderName_, command.vertexShaderDefines_);
1863 if (!vs)
1864 command.vertexShaderName_ = String::EMPTY;
1865 ShaderVariation* ps = graphics_->GetShader(PS, command.pixelShaderName_, command.pixelShaderDefines_);
1866 if (!ps)
1867 command.pixelShaderName_ = String::EMPTY;
1868
1869 // Set shaders & shader parameters and textures
1870 graphics_->SetShaders(vs, ps);
1871
1872 SetGlobalShaderParameters();
1873 SetCameraShaderParameters(camera_);
1874
1875 // During renderpath commands the G-Buffer or viewport texture is assumed to always be viewport-sized
1876 IntRect viewport = graphics_->GetViewport();
1877 IntVector2 viewSize = IntVector2(viewport.Width(), viewport.Height());
1878 SetGBufferShaderParameters(viewSize, IntRect(0, 0, viewSize.x_, viewSize.y_));
1879
1880 // Set per-rendertarget inverse size / offset shader parameters as necessary
1881 for (unsigned i = 0; i < renderPath_->renderTargets_.Size(); ++i)
1882 {
1883 const RenderTargetInfo& rtInfo = renderPath_->renderTargets_[i];
1884 if (!rtInfo.enabled_)
1885 continue;
1886
1887 StringHash nameHash(rtInfo.name_);
1888 if (!renderTargets_.Contains(nameHash))
1889 continue;
1890
1891 String invSizeName = rtInfo.name_ + "InvSize";
1892 String offsetsName = rtInfo.name_ + "Offsets";
1893 float width = (float)renderTargets_[nameHash]->GetWidth();
1894 float height = (float)renderTargets_[nameHash]->GetHeight();
1895
1896 const Vector2& pixelUVOffset = Graphics::GetPixelUVOffset();
1897 graphics_->SetShaderParameter(invSizeName, Vector2(1.0f / width, 1.0f / height));
1898 graphics_->SetShaderParameter(offsetsName, Vector2(pixelUVOffset.x_ / width, pixelUVOffset.y_ / height));
1899 }
1900
1901 // Set command's shader parameters last to allow them to override any of the above
1902 SetCommandShaderParameters(command);
1903
1904 graphics_->SetBlendMode(command.blendMode_);
1905 graphics_->SetDepthTest(CMP_ALWAYS);
1906 graphics_->SetDepthWrite(false);
1907 graphics_->SetFillMode(FILL_SOLID);
1908 graphics_->SetLineAntiAlias(false);
1909 graphics_->SetClipPlane(false);
1910 graphics_->SetScissorTest(false);
1911 graphics_->SetStencilTest(false);
1912
1913 DrawFullscreenQuad(false);
1914 }
1915
IsNecessary(const RenderPathCommand & command)1916 bool View::IsNecessary(const RenderPathCommand& command)
1917 {
1918 return command.enabled_ && command.outputs_.Size() &&
1919 (command.type_ != CMD_SCENEPASS || !batchQueues_[command.passIndex_].IsEmpty());
1920 }
1921
CheckViewportRead(const RenderPathCommand & command)1922 bool View::CheckViewportRead(const RenderPathCommand& command)
1923 {
1924 for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
1925 {
1926 if (!command.textureNames_[i].Empty() && !command.textureNames_[i].Compare("viewport", false))
1927 return true;
1928 }
1929
1930 return false;
1931 }
1932
CheckViewportWrite(const RenderPathCommand & command)1933 bool View::CheckViewportWrite(const RenderPathCommand& command)
1934 {
1935 for (unsigned i = 0; i < command.outputs_.Size(); ++i)
1936 {
1937 if (!command.outputs_[i].first_.Compare("viewport", false))
1938 return true;
1939 }
1940
1941 return false;
1942 }
1943
1944
CheckPingpong(unsigned index)1945 bool View::CheckPingpong(unsigned index)
1946 {
1947 // Current command must be a viewport-reading & writing quad to begin the pingpong chain
1948 RenderPathCommand& current = renderPath_->commands_[index];
1949 if (current.type_ != CMD_QUAD || !CheckViewportRead(current) || !CheckViewportWrite(current))
1950 return false;
1951
1952 // If there are commands other than quads that target the viewport, we must keep rendering to the final target and resolving
1953 // to a viewport texture when necessary instead of pingponging, as a scene pass is not guaranteed to fill the entire viewport
1954 for (unsigned i = index + 1; i < renderPath_->commands_.Size(); ++i)
1955 {
1956 RenderPathCommand& command = renderPath_->commands_[i];
1957 if (!IsNecessary(command))
1958 continue;
1959 if (CheckViewportWrite(command))
1960 {
1961 if (command.type_ != CMD_QUAD)
1962 return false;
1963 }
1964 }
1965
1966 return true;
1967 }
1968
AllocateScreenBuffers()1969 void View::AllocateScreenBuffers()
1970 {
1971 View* actualView = sourceView_ ? sourceView_ : this;
1972
1973 bool hasScenePassToRTs = false;
1974 bool hasCustomDepth = false;
1975 bool hasViewportRead = false;
1976 bool hasPingpong = false;
1977 bool needSubstitute = false;
1978 unsigned numViewportTextures = 0;
1979 depthOnlyDummyTexture_ = 0;
1980 lastCustomDepthSurface_ = 0;
1981
1982 // Check for commands with special meaning: has custom depth, renders a scene pass to other than the destination viewport,
1983 // read the viewport, or pingpong between viewport textures. These may trigger the need to substitute the destination RT
1984 for (unsigned i = 0; i < renderPath_->commands_.Size(); ++i)
1985 {
1986 const RenderPathCommand& command = renderPath_->commands_[i];
1987 if (!actualView->IsNecessary(command))
1988 continue;
1989 if (!hasViewportRead && CheckViewportRead(command))
1990 hasViewportRead = true;
1991 if (!hasPingpong && CheckPingpong(i))
1992 hasPingpong = true;
1993 if (command.depthStencilName_.Length())
1994 hasCustomDepth = true;
1995 if (!hasScenePassToRTs && command.type_ == CMD_SCENEPASS)
1996 {
1997 for (unsigned j = 0; j < command.outputs_.Size(); ++j)
1998 {
1999 if (command.outputs_[j].first_.Compare("viewport", false))
2000 {
2001 hasScenePassToRTs = true;
2002 break;
2003 }
2004 }
2005 }
2006 }
2007
2008 #ifdef URHO3D_OPENGL
2009 // Due to FBO limitations, in OpenGL deferred modes need to render to texture first and then blit to the backbuffer
2010 // Also, if rendering to a texture with full deferred rendering, it must be RGBA to comply with the rest of the buffers,
2011 // unless using OpenGL 3
2012 if (((deferred_ || hasScenePassToRTs) && !renderTarget_) || (!Graphics::GetGL3Support() && deferredAmbient_ && renderTarget_
2013 && renderTarget_->GetParentTexture()->GetFormat() != Graphics::GetRGBAFormat()))
2014 needSubstitute = true;
2015 // Also need substitute if rendering to backbuffer using a custom (readable) depth buffer
2016 if (!renderTarget_ && hasCustomDepth)
2017 needSubstitute = true;
2018 #endif
2019 // If backbuffer is antialiased when using deferred rendering, need to reserve a buffer
2020 if (deferred_ && !renderTarget_ && graphics_->GetMultiSample() > 1)
2021 needSubstitute = true;
2022 // If viewport is smaller than whole texture/backbuffer in deferred rendering, need to reserve a buffer, as the G-buffer
2023 // textures will be sized equal to the viewport
2024 if (viewSize_.x_ < rtSize_.x_ || viewSize_.y_ < rtSize_.y_)
2025 {
2026 if (deferred_ || hasScenePassToRTs || hasCustomDepth)
2027 needSubstitute = true;
2028 }
2029
2030 // Follow final rendertarget format, or use RGB to match the backbuffer format
2031 unsigned format = renderTarget_ ? renderTarget_->GetParentTexture()->GetFormat() : Graphics::GetRGBFormat();
2032
2033 // If HDR rendering is enabled use RGBA16f and reserve a buffer
2034 if (renderer_->GetHDRRendering())
2035 {
2036 format = Graphics::GetRGBAFloat16Format();
2037 needSubstitute = true;
2038 }
2039
2040 #ifdef URHO3D_OPENGL
2041 // On OpenGL 2 ensure that all MRT buffers are RGBA in deferred rendering
2042 if (deferred_ && !renderer_->GetHDRRendering() && !Graphics::GetGL3Support())
2043 format = Graphics::GetRGBAFormat();
2044 #endif
2045
2046 if (hasViewportRead)
2047 {
2048 ++numViewportTextures;
2049
2050 // If OpenGL ES, use substitute target to avoid resolve from the backbuffer, which may be slow. However if multisampling
2051 // is specified, there is no choice
2052 #ifdef GL_ES_VERSION_2_0
2053 if (!renderTarget_ && graphics_->GetMultiSample() < 2)
2054 needSubstitute = true;
2055 #endif
2056
2057 // If we have viewport read and target is a cube map, must allocate a substitute target instead as BlitFramebuffer()
2058 // does not support reading a cube map
2059 if (renderTarget_ && renderTarget_->GetParentTexture()->GetType() == TextureCube::GetTypeStatic())
2060 needSubstitute = true;
2061
2062 // If rendering to a texture, but the viewport is less than the whole texture, use a substitute to ensure
2063 // postprocessing shaders will never read outside the viewport
2064 if (renderTarget_ && (viewSize_.x_ < renderTarget_->GetWidth() || viewSize_.y_ < renderTarget_->GetHeight()))
2065 needSubstitute = true;
2066
2067 if (hasPingpong && !needSubstitute)
2068 ++numViewportTextures;
2069 }
2070
2071 // Allocate screen buffers. Enable filtering in case the quad commands need that
2072 // Follow the sRGB mode of the destination render target
2073 bool sRGB = renderTarget_ ? renderTarget_->GetParentTexture()->GetSRGB() : graphics_->GetSRGB();
2074 int multiSample = renderTarget_ ? renderTarget_->GetMultiSample() : graphics_->GetMultiSample();
2075 bool autoResolve = renderTarget_ ? renderTarget_->GetAutoResolve() : true;
2076 substituteRenderTarget_ = needSubstitute ? GetRenderSurfaceFromTexture(renderer_->GetScreenBuffer(viewSize_.x_, viewSize_.y_,
2077 format, multiSample, autoResolve, false, true, sRGB)) : (RenderSurface*)0;
2078 for (unsigned i = 0; i < MAX_VIEWPORT_TEXTURES; ++i)
2079 {
2080 viewportTextures_[i] = i < numViewportTextures ? renderer_->GetScreenBuffer(viewSize_.x_, viewSize_.y_, format, multiSample,
2081 autoResolve, false, true, sRGB) : (Texture*)0;
2082 }
2083 // If using a substitute render target and pingponging, the substitute can act as the second viewport texture
2084 if (numViewportTextures == 1 && substituteRenderTarget_)
2085 viewportTextures_[1] = substituteRenderTarget_->GetParentTexture();
2086
2087 // Allocate extra render targets defined by the render path
2088 for (unsigned i = 0; i < renderPath_->renderTargets_.Size(); ++i)
2089 {
2090 const RenderTargetInfo& rtInfo = renderPath_->renderTargets_[i];
2091 if (!rtInfo.enabled_)
2092 continue;
2093
2094 float width = rtInfo.size_.x_;
2095 float height = rtInfo.size_.y_;
2096
2097 if (rtInfo.sizeMode_ == SIZE_VIEWPORTDIVISOR)
2098 {
2099 width = (float)viewSize_.x_ / Max(width, M_EPSILON);
2100 height = (float)viewSize_.y_ / Max(height, M_EPSILON);
2101 }
2102 else if (rtInfo.sizeMode_ == SIZE_VIEWPORTMULTIPLIER)
2103 {
2104 width = (float)viewSize_.x_ * width;
2105 height = (float)viewSize_.y_ * height;
2106 }
2107
2108 int intWidth = (int)(width + 0.5f);
2109 int intHeight = (int)(height + 0.5f);
2110
2111 // If the rendertarget is persistent, key it with a hash derived from the RT name and the view's pointer
2112 renderTargets_[rtInfo.name_] =
2113 renderer_->GetScreenBuffer(intWidth, intHeight, rtInfo.format_, rtInfo.multiSample_, rtInfo.autoResolve_,
2114 rtInfo.cubemap_, rtInfo.filtered_, rtInfo.sRGB_, rtInfo.persistent_ ? StringHash(rtInfo.name_).Value()
2115 + (unsigned)(size_t)this : 0);
2116 }
2117 }
2118
BlitFramebuffer(Texture * source,RenderSurface * destination,bool depthWrite)2119 void View::BlitFramebuffer(Texture* source, RenderSurface* destination, bool depthWrite)
2120 {
2121 if (!source)
2122 return;
2123
2124 URHO3D_PROFILE(BlitFramebuffer);
2125
2126 // If blitting to the destination rendertarget, use the actual viewport. Intermediate textures on the other hand
2127 // are always viewport-sized
2128 IntVector2 srcSize(source->GetWidth(), source->GetHeight());
2129 IntVector2 destSize = destination ? IntVector2(destination->GetWidth(), destination->GetHeight()) : IntVector2(
2130 graphics_->GetWidth(), graphics_->GetHeight());
2131
2132 IntRect srcRect = (GetRenderSurfaceFromTexture(source) == renderTarget_) ? viewRect_ : IntRect(0, 0, srcSize.x_, srcSize.y_);
2133 IntRect destRect = (destination == renderTarget_) ? viewRect_ : IntRect(0, 0, destSize.x_, destSize.y_);
2134
2135 graphics_->SetBlendMode(BLEND_REPLACE);
2136 graphics_->SetDepthTest(CMP_ALWAYS);
2137 graphics_->SetDepthWrite(depthWrite);
2138 graphics_->SetFillMode(FILL_SOLID);
2139 graphics_->SetLineAntiAlias(false);
2140 graphics_->SetClipPlane(false);
2141 graphics_->SetScissorTest(false);
2142 graphics_->SetStencilTest(false);
2143 graphics_->SetRenderTarget(0, destination);
2144 for (unsigned i = 1; i < MAX_RENDERTARGETS; ++i)
2145 graphics_->SetRenderTarget(i, (RenderSurface*)0);
2146 graphics_->SetDepthStencil(GetDepthStencil(destination));
2147 graphics_->SetViewport(destRect);
2148
2149 static const String shaderName("CopyFramebuffer");
2150 graphics_->SetShaders(graphics_->GetShader(VS, shaderName), graphics_->GetShader(PS, shaderName));
2151
2152 SetGBufferShaderParameters(srcSize, srcRect);
2153
2154 graphics_->SetTexture(TU_DIFFUSE, source);
2155 DrawFullscreenQuad(true);
2156 }
2157
DrawFullscreenQuad(bool setIdentityProjection)2158 void View::DrawFullscreenQuad(bool setIdentityProjection)
2159 {
2160 Geometry* geometry = renderer_->GetQuadGeometry();
2161
2162 // If no camera, no choice but to use identity projection
2163 if (!camera_)
2164 setIdentityProjection = true;
2165
2166 if (setIdentityProjection)
2167 {
2168 Matrix3x4 model = Matrix3x4::IDENTITY;
2169 Matrix4 projection = Matrix4::IDENTITY;
2170 #ifdef URHO3D_OPENGL
2171 if (camera_ && camera_->GetFlipVertical())
2172 projection.m11_ = -1.0f;
2173 model.m23_ = 0.0f;
2174 #else
2175 model.m23_ = 0.5f;
2176 #endif
2177
2178 graphics_->SetShaderParameter(VSP_MODEL, model);
2179 graphics_->SetShaderParameter(VSP_VIEWPROJ, projection);
2180 }
2181 else
2182 graphics_->SetShaderParameter(VSP_MODEL, Light::GetFullscreenQuadTransform(camera_));
2183
2184 graphics_->SetCullMode(CULL_NONE);
2185 graphics_->ClearTransformSources();
2186
2187 geometry->Draw(graphics_);
2188 }
2189
UpdateOccluders(PODVector<Drawable * > & occluders,Camera * camera)2190 void View::UpdateOccluders(PODVector<Drawable*>& occluders, Camera* camera)
2191 {
2192 float occluderSizeThreshold_ = renderer_->GetOccluderSizeThreshold();
2193 float halfViewSize = camera->GetHalfViewSize();
2194 float invOrthoSize = 1.0f / camera->GetOrthoSize();
2195
2196 for (PODVector<Drawable*>::Iterator i = occluders.Begin(); i != occluders.End();)
2197 {
2198 Drawable* occluder = *i;
2199 bool erase = false;
2200
2201 if (!occluder->IsInView(frame_, true))
2202 occluder->UpdateBatches(frame_);
2203
2204 // Check occluder's draw distance (in main camera view)
2205 float maxDistance = occluder->GetDrawDistance();
2206 if (maxDistance <= 0.0f || occluder->GetDistance() <= maxDistance)
2207 {
2208 // Check that occluder is big enough on the screen
2209 const BoundingBox& box = occluder->GetWorldBoundingBox();
2210 float diagonal = box.Size().Length();
2211 float compare;
2212 if (!camera->IsOrthographic())
2213 {
2214 // Occluders which are near the camera are more useful then occluders at the end of the camera's draw distance
2215 float cameraMaxDistanceFraction = occluder->GetDistance() / camera->GetFarClip();
2216 compare = diagonal * halfViewSize / (occluder->GetDistance() * cameraMaxDistanceFraction);
2217
2218 // Give higher priority to occluders which the camera is inside their AABB
2219 const Vector3& cameraPos = camera->GetNode() ? camera->GetNode()->GetWorldPosition() : Vector3::ZERO;
2220 if (box.IsInside(cameraPos))
2221 compare *= diagonal; // size^2
2222 }
2223 else
2224 compare = diagonal * invOrthoSize;
2225
2226 if (compare < occluderSizeThreshold_)
2227 erase = true;
2228 else
2229 {
2230 // Best occluders have big triangles (low density)
2231 float density = occluder->GetNumOccluderTriangles() / diagonal;
2232 // Lower value is higher priority
2233 occluder->SetSortValue(density / compare);
2234 }
2235 }
2236 else
2237 erase = true;
2238
2239 if (erase)
2240 i = occluders.Erase(i);
2241 else
2242 ++i;
2243 }
2244
2245 // Sort occluders so that if triangle budget is exceeded, best occluders have been drawn
2246 if (occluders.Size())
2247 Sort(occluders.Begin(), occluders.End(), CompareDrawables);
2248 }
2249
DrawOccluders(OcclusionBuffer * buffer,const PODVector<Drawable * > & occluders)2250 void View::DrawOccluders(OcclusionBuffer* buffer, const PODVector<Drawable*>& occluders)
2251 {
2252 buffer->SetMaxTriangles((unsigned)maxOccluderTriangles_);
2253 buffer->Clear();
2254
2255 if (!buffer->IsThreaded())
2256 {
2257 // If not threaded, draw occluders one by one and test the next occluder against already rasterized depth
2258 for (unsigned i = 0; i < occluders.Size(); ++i)
2259 {
2260 Drawable* occluder = occluders[i];
2261 if (i > 0)
2262 {
2263 // For subsequent occluders, do a test against the pixel-level occlusion buffer to see if rendering is necessary
2264 if (!buffer->IsVisible(occluder->GetWorldBoundingBox()))
2265 continue;
2266 }
2267
2268 // Check for running out of triangles
2269 ++activeOccluders_;
2270 bool success = occluder->DrawOcclusion(buffer);
2271 // Draw triangles submitted by this occluder
2272 buffer->DrawTriangles();
2273 if (!success)
2274 break;
2275 }
2276 }
2277 else
2278 {
2279 // In threaded mode submit all triangles first, then render (cannot test in this case)
2280 for (unsigned i = 0; i < occluders.Size(); ++i)
2281 {
2282 // Check for running out of triangles
2283 ++activeOccluders_;
2284 if (!occluders[i]->DrawOcclusion(buffer))
2285 break;
2286 }
2287
2288 buffer->DrawTriangles();
2289 }
2290
2291 // Finally build the depth mip levels
2292 buffer->BuildDepthHierarchy();
2293 }
2294
ProcessLight(LightQueryResult & query,unsigned threadIndex)2295 void View::ProcessLight(LightQueryResult& query, unsigned threadIndex)
2296 {
2297 Light* light = query.light_;
2298 LightType type = light->GetLightType();
2299 unsigned lightMask = light->GetLightMask();
2300 const Frustum& frustum = cullCamera_->GetFrustum();
2301
2302 // Check if light should be shadowed
2303 bool isShadowed = drawShadows_ && light->GetCastShadows() && !light->GetPerVertex() && light->GetShadowIntensity() < 1.0f;
2304 // If shadow distance non-zero, check it
2305 if (isShadowed && light->GetShadowDistance() > 0.0f && light->GetDistance() > light->GetShadowDistance())
2306 isShadowed = false;
2307 // OpenGL ES can not support point light shadows
2308 #ifdef GL_ES_VERSION_2_0
2309 if (isShadowed && type == LIGHT_POINT)
2310 isShadowed = false;
2311 #endif
2312 // Get lit geometries. They must match the light mask and be inside the main camera frustum to be considered
2313 PODVector<Drawable*>& tempDrawables = tempDrawables_[threadIndex];
2314 query.litGeometries_.Clear();
2315
2316 switch (type)
2317 {
2318 case LIGHT_DIRECTIONAL:
2319 for (unsigned i = 0; i < geometries_.Size(); ++i)
2320 {
2321 if (GetLightMask(geometries_[i]) & lightMask)
2322 query.litGeometries_.Push(geometries_[i]);
2323 }
2324 break;
2325
2326 case LIGHT_SPOT:
2327 {
2328 FrustumOctreeQuery octreeQuery(tempDrawables, light->GetFrustum(), DRAWABLE_GEOMETRY,
2329 cullCamera_->GetViewMask());
2330 octree_->GetDrawables(octreeQuery);
2331 for (unsigned i = 0; i < tempDrawables.Size(); ++i)
2332 {
2333 if (tempDrawables[i]->IsInView(frame_) && (GetLightMask(tempDrawables[i]) & lightMask))
2334 query.litGeometries_.Push(tempDrawables[i]);
2335 }
2336 }
2337 break;
2338
2339 case LIGHT_POINT:
2340 {
2341 SphereOctreeQuery octreeQuery(tempDrawables, Sphere(light->GetNode()->GetWorldPosition(), light->GetRange()),
2342 DRAWABLE_GEOMETRY, cullCamera_->GetViewMask());
2343 octree_->GetDrawables(octreeQuery);
2344 for (unsigned i = 0; i < tempDrawables.Size(); ++i)
2345 {
2346 if (tempDrawables[i]->IsInView(frame_) && (GetLightMask(tempDrawables[i]) & lightMask))
2347 query.litGeometries_.Push(tempDrawables[i]);
2348 }
2349 }
2350 break;
2351 }
2352
2353 // If no lit geometries or not shadowed, no need to process shadow cameras
2354 if (query.litGeometries_.Empty() || !isShadowed)
2355 {
2356 query.numSplits_ = 0;
2357 return;
2358 }
2359
2360 // Determine number of shadow cameras and setup their initial positions
2361 SetupShadowCameras(query);
2362
2363 // Process each split for shadow casters
2364 query.shadowCasters_.Clear();
2365 for (unsigned i = 0; i < query.numSplits_; ++i)
2366 {
2367 Camera* shadowCamera = query.shadowCameras_[i];
2368 const Frustum& shadowCameraFrustum = shadowCamera->GetFrustum();
2369 query.shadowCasterBegin_[i] = query.shadowCasterEnd_[i] = query.shadowCasters_.Size();
2370
2371 // For point light check that the face is visible: if not, can skip the split
2372 if (type == LIGHT_POINT && frustum.IsInsideFast(BoundingBox(shadowCameraFrustum)) == OUTSIDE)
2373 continue;
2374
2375 // For directional light check that the split is inside the visible scene: if not, can skip the split
2376 if (type == LIGHT_DIRECTIONAL)
2377 {
2378 if (minZ_ > query.shadowFarSplits_[i])
2379 continue;
2380 if (maxZ_ < query.shadowNearSplits_[i])
2381 continue;
2382
2383 // Reuse lit geometry query for all except directional lights
2384 ShadowCasterOctreeQuery query(tempDrawables, shadowCameraFrustum, DRAWABLE_GEOMETRY, cullCamera_->GetViewMask());
2385 octree_->GetDrawables(query);
2386 }
2387
2388 // Check which shadow casters actually contribute to the shadowing
2389 ProcessShadowCasters(query, tempDrawables, i);
2390 }
2391
2392 // If no shadow casters, the light can be rendered unshadowed. At this point we have not allocated a shadow map yet, so the
2393 // only cost has been the shadow camera setup & queries
2394 if (query.shadowCasters_.Empty())
2395 query.numSplits_ = 0;
2396 }
2397
ProcessShadowCasters(LightQueryResult & query,const PODVector<Drawable * > & drawables,unsigned splitIndex)2398 void View::ProcessShadowCasters(LightQueryResult& query, const PODVector<Drawable*>& drawables, unsigned splitIndex)
2399 {
2400 Light* light = query.light_;
2401 unsigned lightMask = light->GetLightMask();
2402
2403 Camera* shadowCamera = query.shadowCameras_[splitIndex];
2404 const Frustum& shadowCameraFrustum = shadowCamera->GetFrustum();
2405 const Matrix3x4& lightView = shadowCamera->GetView();
2406 const Matrix4& lightProj = shadowCamera->GetProjection();
2407 LightType type = light->GetLightType();
2408
2409 query.shadowCasterBox_[splitIndex].Clear();
2410
2411 // Transform scene frustum into shadow camera's view space for shadow caster visibility check. For point & spot lights,
2412 // we can use the whole scene frustum. For directional lights, use the intersection of the scene frustum and the split
2413 // frustum, so that shadow casters do not get rendered into unnecessary splits
2414 Frustum lightViewFrustum;
2415 if (type != LIGHT_DIRECTIONAL)
2416 lightViewFrustum = cullCamera_->GetSplitFrustum(minZ_, maxZ_).Transformed(lightView);
2417 else
2418 lightViewFrustum = cullCamera_->GetSplitFrustum(Max(minZ_, query.shadowNearSplits_[splitIndex]),
2419 Min(maxZ_, query.shadowFarSplits_[splitIndex])).Transformed(lightView);
2420
2421 BoundingBox lightViewFrustumBox(lightViewFrustum);
2422
2423 // Check for degenerate split frustum: in that case there is no need to get shadow casters
2424 if (lightViewFrustum.vertices_[0] == lightViewFrustum.vertices_[4])
2425 return;
2426
2427 BoundingBox lightViewBox;
2428 BoundingBox lightProjBox;
2429
2430 for (PODVector<Drawable*>::ConstIterator i = drawables.Begin(); i != drawables.End(); ++i)
2431 {
2432 Drawable* drawable = *i;
2433 // In case this is a point or spot light query result reused for optimization, we may have non-shadowcasters included.
2434 // Check for that first
2435 if (!drawable->GetCastShadows())
2436 continue;
2437 // Check shadow mask
2438 if (!(GetShadowMask(drawable) & lightMask))
2439 continue;
2440 // For point light, check that this drawable is inside the split shadow camera frustum
2441 if (type == LIGHT_POINT && shadowCameraFrustum.IsInsideFast(drawable->GetWorldBoundingBox()) == OUTSIDE)
2442 continue;
2443
2444 // Check shadow distance
2445 // Note: as lights are processed threaded, it is possible a drawable's UpdateBatches() function is called several
2446 // times. However, this should not cause problems as no scene modification happens at this point.
2447 if (!drawable->IsInView(frame_, true))
2448 drawable->UpdateBatches(frame_);
2449 float maxShadowDistance = drawable->GetShadowDistance();
2450 float drawDistance = drawable->GetDrawDistance();
2451 if (drawDistance > 0.0f && (maxShadowDistance <= 0.0f || drawDistance < maxShadowDistance))
2452 maxShadowDistance = drawDistance;
2453 if (maxShadowDistance > 0.0f && drawable->GetDistance() > maxShadowDistance)
2454 continue;
2455
2456 // Project shadow caster bounding box to light view space for visibility check
2457 lightViewBox = drawable->GetWorldBoundingBox().Transformed(lightView);
2458
2459 if (IsShadowCasterVisible(drawable, lightViewBox, shadowCamera, lightView, lightViewFrustum, lightViewFrustumBox))
2460 {
2461 // Merge to shadow caster bounding box (only needed for focused spot lights) and add to the list
2462 if (type == LIGHT_SPOT && light->GetShadowFocus().focus_)
2463 {
2464 lightProjBox = lightViewBox.Projected(lightProj);
2465 query.shadowCasterBox_[splitIndex].Merge(lightProjBox);
2466 }
2467 query.shadowCasters_.Push(drawable);
2468 }
2469 }
2470
2471 query.shadowCasterEnd_[splitIndex] = query.shadowCasters_.Size();
2472 }
2473
IsShadowCasterVisible(Drawable * drawable,BoundingBox lightViewBox,Camera * shadowCamera,const Matrix3x4 & lightView,const Frustum & lightViewFrustum,const BoundingBox & lightViewFrustumBox)2474 bool View::IsShadowCasterVisible(Drawable* drawable, BoundingBox lightViewBox, Camera* shadowCamera, const Matrix3x4& lightView,
2475 const Frustum& lightViewFrustum, const BoundingBox& lightViewFrustumBox)
2476 {
2477 if (shadowCamera->IsOrthographic())
2478 {
2479 // Extrude the light space bounding box up to the far edge of the frustum's light space bounding box
2480 lightViewBox.max_.z_ = Max(lightViewBox.max_.z_, lightViewFrustumBox.max_.z_);
2481 return lightViewFrustum.IsInsideFast(lightViewBox) != OUTSIDE;
2482 }
2483 else
2484 {
2485 // If light is not directional, can do a simple check: if object is visible, its shadow is too
2486 if (drawable->IsInView(frame_))
2487 return true;
2488
2489 // For perspective lights, extrusion direction depends on the position of the shadow caster
2490 Vector3 center = lightViewBox.Center();
2491 Ray extrusionRay(center, center);
2492
2493 float extrusionDistance = shadowCamera->GetFarClip();
2494 float originalDistance = Clamp(center.Length(), M_EPSILON, extrusionDistance);
2495
2496 // Because of the perspective, the bounding box must also grow when it is extruded to the distance
2497 float sizeFactor = extrusionDistance / originalDistance;
2498
2499 // Calculate the endpoint box and merge it to the original. Because it's axis-aligned, it will be larger
2500 // than necessary, so the test will be conservative
2501 Vector3 newCenter = extrusionDistance * extrusionRay.direction_;
2502 Vector3 newHalfSize = lightViewBox.Size() * sizeFactor * 0.5f;
2503 BoundingBox extrudedBox(newCenter - newHalfSize, newCenter + newHalfSize);
2504 lightViewBox.Merge(extrudedBox);
2505
2506 return lightViewFrustum.IsInsideFast(lightViewBox) != OUTSIDE;
2507 }
2508 }
2509
GetShadowMapViewport(Light * light,unsigned splitIndex,Texture2D * shadowMap)2510 IntRect View::GetShadowMapViewport(Light* light, unsigned splitIndex, Texture2D* shadowMap)
2511 {
2512 unsigned width = (unsigned)shadowMap->GetWidth();
2513 unsigned height = (unsigned)shadowMap->GetHeight();
2514
2515 switch (light->GetLightType())
2516 {
2517 case LIGHT_DIRECTIONAL:
2518 {
2519 int numSplits = light->GetNumShadowSplits();
2520 if (numSplits == 1)
2521 return IntRect(0, 0, width, height);
2522 else if (numSplits == 2)
2523 return IntRect(splitIndex * width / 2, 0, (splitIndex + 1) * width / 2, height);
2524 else
2525 return IntRect((splitIndex & 1) * width / 2, (splitIndex / 2) * height / 2, ((splitIndex & 1) + 1) * width / 2,
2526 (splitIndex / 2 + 1) * height / 2);
2527 }
2528
2529 case LIGHT_SPOT:
2530 return IntRect(0, 0, width, height);
2531
2532 case LIGHT_POINT:
2533 return IntRect((splitIndex & 1) * width / 2, (splitIndex / 2) * height / 3, ((splitIndex & 1) + 1) * width / 2,
2534 (splitIndex / 2 + 1) * height / 3);
2535 }
2536
2537 return IntRect();
2538 }
2539
SetupShadowCameras(LightQueryResult & query)2540 void View::SetupShadowCameras(LightQueryResult& query)
2541 {
2542 Light* light = query.light_;
2543
2544 int splits = 0;
2545
2546 switch (light->GetLightType())
2547 {
2548 case LIGHT_DIRECTIONAL:
2549 {
2550 const CascadeParameters& cascade = light->GetShadowCascade();
2551
2552 float nearSplit = cullCamera_->GetNearClip();
2553 float farSplit;
2554 int numSplits = light->GetNumShadowSplits();
2555
2556 while (splits < numSplits)
2557 {
2558 // If split is completely beyond camera far clip, we are done
2559 if (nearSplit > cullCamera_->GetFarClip())
2560 break;
2561
2562 farSplit = Min(cullCamera_->GetFarClip(), cascade.splits_[splits]);
2563 if (farSplit <= nearSplit)
2564 break;
2565
2566 // Setup the shadow camera for the split
2567 Camera* shadowCamera = renderer_->GetShadowCamera();
2568 query.shadowCameras_[splits] = shadowCamera;
2569 query.shadowNearSplits_[splits] = nearSplit;
2570 query.shadowFarSplits_[splits] = farSplit;
2571 SetupDirLightShadowCamera(shadowCamera, light, nearSplit, farSplit);
2572
2573 nearSplit = farSplit;
2574 ++splits;
2575 }
2576 }
2577 break;
2578
2579 case LIGHT_SPOT:
2580 {
2581 Camera* shadowCamera = renderer_->GetShadowCamera();
2582 query.shadowCameras_[0] = shadowCamera;
2583 Node* cameraNode = shadowCamera->GetNode();
2584 Node* lightNode = light->GetNode();
2585
2586 cameraNode->SetTransform(lightNode->GetWorldPosition(), lightNode->GetWorldRotation());
2587 shadowCamera->SetNearClip(light->GetShadowNearFarRatio() * light->GetRange());
2588 shadowCamera->SetFarClip(light->GetRange());
2589 shadowCamera->SetFov(light->GetFov());
2590 shadowCamera->SetAspectRatio(light->GetAspectRatio());
2591
2592 splits = 1;
2593 }
2594 break;
2595
2596 case LIGHT_POINT:
2597 {
2598 for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
2599 {
2600 Camera* shadowCamera = renderer_->GetShadowCamera();
2601 query.shadowCameras_[i] = shadowCamera;
2602 Node* cameraNode = shadowCamera->GetNode();
2603
2604 // When making a shadowed point light, align the splits along X, Y and Z axes regardless of light rotation
2605 cameraNode->SetPosition(light->GetNode()->GetWorldPosition());
2606 cameraNode->SetDirection(*directions[i]);
2607 shadowCamera->SetNearClip(light->GetShadowNearFarRatio() * light->GetRange());
2608 shadowCamera->SetFarClip(light->GetRange());
2609 shadowCamera->SetFov(90.0f);
2610 shadowCamera->SetAspectRatio(1.0f);
2611 }
2612
2613 splits = MAX_CUBEMAP_FACES;
2614 }
2615 break;
2616 }
2617
2618 query.numSplits_ = (unsigned)splits;
2619 }
2620
SetupDirLightShadowCamera(Camera * shadowCamera,Light * light,float nearSplit,float farSplit)2621 void View::SetupDirLightShadowCamera(Camera* shadowCamera, Light* light, float nearSplit, float farSplit)
2622 {
2623 Node* shadowCameraNode = shadowCamera->GetNode();
2624 Node* lightNode = light->GetNode();
2625 float extrusionDistance = Min(cullCamera_->GetFarClip(), light->GetShadowMaxExtrusion());
2626 const FocusParameters& parameters = light->GetShadowFocus();
2627
2628 // Calculate initial position & rotation
2629 Vector3 pos = cullCamera_->GetNode()->GetWorldPosition() - extrusionDistance * lightNode->GetWorldDirection();
2630 shadowCameraNode->SetTransform(pos, lightNode->GetWorldRotation());
2631
2632 // Calculate main camera shadowed frustum in light's view space
2633 farSplit = Min(farSplit, cullCamera_->GetFarClip());
2634 // Use the scene Z bounds to limit frustum size if applicable
2635 if (parameters.focus_)
2636 {
2637 nearSplit = Max(minZ_, nearSplit);
2638 farSplit = Min(maxZ_, farSplit);
2639 }
2640
2641 Frustum splitFrustum = cullCamera_->GetSplitFrustum(nearSplit, farSplit);
2642 Polyhedron frustumVolume;
2643 frustumVolume.Define(splitFrustum);
2644 // If focusing enabled, clip the frustum volume by the combined bounding box of the lit geometries within the frustum
2645 if (parameters.focus_)
2646 {
2647 BoundingBox litGeometriesBox;
2648 unsigned lightMask = light->GetLightMask();
2649
2650 for (unsigned i = 0; i < geometries_.Size(); ++i)
2651 {
2652 Drawable* drawable = geometries_[i];
2653 if (drawable->GetMinZ() <= farSplit && drawable->GetMaxZ() >= nearSplit &&
2654 (GetLightMask(drawable) & lightMask))
2655 litGeometriesBox.Merge(drawable->GetWorldBoundingBox());
2656 }
2657
2658 if (litGeometriesBox.Defined())
2659 {
2660 frustumVolume.Clip(litGeometriesBox);
2661 // If volume became empty, restore it to avoid zero size
2662 if (frustumVolume.Empty())
2663 frustumVolume.Define(splitFrustum);
2664 }
2665 }
2666
2667 // Transform frustum volume to light space
2668 const Matrix3x4& lightView = shadowCamera->GetView();
2669 frustumVolume.Transform(lightView);
2670
2671 // Fit the frustum volume inside a bounding box. If uniform size, use a sphere instead
2672 BoundingBox shadowBox;
2673 if (!parameters.nonUniform_)
2674 shadowBox.Define(Sphere(frustumVolume));
2675 else
2676 shadowBox.Define(frustumVolume);
2677
2678 shadowCamera->SetOrthographic(true);
2679 shadowCamera->SetAspectRatio(1.0f);
2680 shadowCamera->SetNearClip(0.0f);
2681 shadowCamera->SetFarClip(shadowBox.max_.z_);
2682
2683 // Center shadow camera on the bounding box. Can not snap to texels yet as the shadow map viewport is unknown
2684 QuantizeDirLightShadowCamera(shadowCamera, light, IntRect(0, 0, 0, 0), shadowBox);
2685 }
2686
FinalizeShadowCamera(Camera * shadowCamera,Light * light,const IntRect & shadowViewport,const BoundingBox & shadowCasterBox)2687 void View::FinalizeShadowCamera(Camera* shadowCamera, Light* light, const IntRect& shadowViewport,
2688 const BoundingBox& shadowCasterBox)
2689 {
2690 const FocusParameters& parameters = light->GetShadowFocus();
2691 float shadowMapWidth = (float)(shadowViewport.Width());
2692 LightType type = light->GetLightType();
2693
2694 if (type == LIGHT_DIRECTIONAL)
2695 {
2696 BoundingBox shadowBox;
2697 shadowBox.max_.y_ = shadowCamera->GetOrthoSize() * 0.5f;
2698 shadowBox.max_.x_ = shadowCamera->GetAspectRatio() * shadowBox.max_.y_;
2699 shadowBox.min_.y_ = -shadowBox.max_.y_;
2700 shadowBox.min_.x_ = -shadowBox.max_.x_;
2701
2702 // Requantize and snap to shadow map texels
2703 QuantizeDirLightShadowCamera(shadowCamera, light, shadowViewport, shadowBox);
2704 }
2705
2706 if (type == LIGHT_SPOT && parameters.focus_)
2707 {
2708 float viewSizeX = Max(Abs(shadowCasterBox.min_.x_), Abs(shadowCasterBox.max_.x_));
2709 float viewSizeY = Max(Abs(shadowCasterBox.min_.y_), Abs(shadowCasterBox.max_.y_));
2710 float viewSize = Max(viewSizeX, viewSizeY);
2711 // Scale the quantization parameters, because view size is in projection space (-1.0 - 1.0)
2712 float invOrthoSize = 1.0f / shadowCamera->GetOrthoSize();
2713 float quantize = parameters.quantize_ * invOrthoSize;
2714 float minView = parameters.minView_ * invOrthoSize;
2715
2716 viewSize = Max(ceilf(viewSize / quantize) * quantize, minView);
2717 if (viewSize < 1.0f)
2718 shadowCamera->SetZoom(1.0f / viewSize);
2719 }
2720
2721 // Perform a finalization step for all lights: ensure zoom out of 2 pixels to eliminate border filtering issues
2722 // For point lights use 4 pixels, as they must not cross sides of the virtual cube map (maximum 3x3 PCF)
2723 if (shadowCamera->GetZoom() >= 1.0f)
2724 {
2725 if (light->GetLightType() != LIGHT_POINT)
2726 shadowCamera->SetZoom(shadowCamera->GetZoom() * ((shadowMapWidth - 2.0f) / shadowMapWidth));
2727 else
2728 {
2729 #ifdef URHO3D_OPENGL
2730 shadowCamera->SetZoom(shadowCamera->GetZoom() * ((shadowMapWidth - 3.0f) / shadowMapWidth));
2731 #else
2732 shadowCamera->SetZoom(shadowCamera->GetZoom() * ((shadowMapWidth - 4.0f) / shadowMapWidth));
2733 #endif
2734 }
2735 }
2736 }
2737
QuantizeDirLightShadowCamera(Camera * shadowCamera,Light * light,const IntRect & shadowViewport,const BoundingBox & viewBox)2738 void View::QuantizeDirLightShadowCamera(Camera* shadowCamera, Light* light, const IntRect& shadowViewport,
2739 const BoundingBox& viewBox)
2740 {
2741 Node* shadowCameraNode = shadowCamera->GetNode();
2742 const FocusParameters& parameters = light->GetShadowFocus();
2743 float shadowMapWidth = (float)(shadowViewport.Width());
2744
2745 float minX = viewBox.min_.x_;
2746 float minY = viewBox.min_.y_;
2747 float maxX = viewBox.max_.x_;
2748 float maxY = viewBox.max_.y_;
2749
2750 Vector2 center((minX + maxX) * 0.5f, (minY + maxY) * 0.5f);
2751 Vector2 viewSize(maxX - minX, maxY - minY);
2752
2753 // Quantize size to reduce swimming
2754 // Note: if size is uniform and there is no focusing, quantization is unnecessary
2755 if (parameters.nonUniform_)
2756 {
2757 viewSize.x_ = ceilf(sqrtf(viewSize.x_ / parameters.quantize_));
2758 viewSize.y_ = ceilf(sqrtf(viewSize.y_ / parameters.quantize_));
2759 viewSize.x_ = Max(viewSize.x_ * viewSize.x_ * parameters.quantize_, parameters.minView_);
2760 viewSize.y_ = Max(viewSize.y_ * viewSize.y_ * parameters.quantize_, parameters.minView_);
2761 }
2762 else if (parameters.focus_)
2763 {
2764 viewSize.x_ = Max(viewSize.x_, viewSize.y_);
2765 viewSize.x_ = ceilf(sqrtf(viewSize.x_ / parameters.quantize_));
2766 viewSize.x_ = Max(viewSize.x_ * viewSize.x_ * parameters.quantize_, parameters.minView_);
2767 viewSize.y_ = viewSize.x_;
2768 }
2769
2770 shadowCamera->SetOrthoSize(viewSize);
2771
2772 // Center shadow camera to the view space bounding box
2773 Quaternion rot(shadowCameraNode->GetWorldRotation());
2774 Vector3 adjust(center.x_, center.y_, 0.0f);
2775 shadowCameraNode->Translate(rot * adjust, TS_WORLD);
2776
2777 // If the shadow map viewport is known, snap to whole texels
2778 if (shadowMapWidth > 0.0f)
2779 {
2780 Vector3 viewPos(rot.Inverse() * shadowCameraNode->GetWorldPosition());
2781 // Take into account that shadow map border will not be used
2782 float invActualSize = 1.0f / (shadowMapWidth - 2.0f);
2783 Vector2 texelSize(viewSize.x_ * invActualSize, viewSize.y_ * invActualSize);
2784 Vector3 snap(-fmodf(viewPos.x_, texelSize.x_), -fmodf(viewPos.y_, texelSize.y_), 0.0f);
2785 shadowCameraNode->Translate(rot * snap, TS_WORLD);
2786 }
2787 }
2788
FindZone(Drawable * drawable)2789 void View::FindZone(Drawable* drawable)
2790 {
2791 Vector3 center = drawable->GetWorldBoundingBox().Center();
2792 int bestPriority = M_MIN_INT;
2793 Zone* newZone = 0;
2794
2795 // If bounding box center is in view, the zone assignment is conclusive also for next frames. Otherwise it is temporary
2796 // (possibly incorrect) and must be re-evaluated on the next frame
2797 bool temporary = !cullCamera_->GetFrustum().IsInside(center);
2798
2799 // First check if the current zone remains a conclusive result
2800 Zone* lastZone = drawable->GetZone();
2801
2802 if (lastZone && (lastZone->GetViewMask() & cullCamera_->GetViewMask()) && lastZone->GetPriority() >= highestZonePriority_ &&
2803 (drawable->GetZoneMask() & lastZone->GetZoneMask()) && lastZone->IsInside(center))
2804 newZone = lastZone;
2805 else
2806 {
2807 for (PODVector<Zone*>::Iterator i = zones_.Begin(); i != zones_.End(); ++i)
2808 {
2809 Zone* zone = *i;
2810 int priority = zone->GetPriority();
2811 if (priority > bestPriority && (drawable->GetZoneMask() & zone->GetZoneMask()) && zone->IsInside(center))
2812 {
2813 newZone = zone;
2814 bestPriority = priority;
2815 }
2816 }
2817 }
2818
2819 drawable->SetZone(newZone, temporary);
2820 }
2821
GetTechnique(Drawable * drawable,Material * material)2822 Technique* View::GetTechnique(Drawable* drawable, Material* material)
2823 {
2824 if (!material)
2825 return renderer_->GetDefaultMaterial()->GetTechniques()[0].technique_;
2826
2827 const Vector<TechniqueEntry>& techniques = material->GetTechniques();
2828 // If only one technique, no choice
2829 if (techniques.Size() == 1)
2830 return techniques[0].technique_;
2831 else
2832 {
2833 float lodDistance = drawable->GetLodDistance();
2834
2835 // Check for suitable technique. Techniques should be ordered like this:
2836 // Most distant & highest quality
2837 // Most distant & lowest quality
2838 // Second most distant & highest quality
2839 // ...
2840 for (unsigned i = 0; i < techniques.Size(); ++i)
2841 {
2842 const TechniqueEntry& entry = techniques[i];
2843 Technique* tech = entry.technique_;
2844
2845 if (!tech || (!tech->IsSupported()) || materialQuality_ < entry.qualityLevel_)
2846 continue;
2847 if (lodDistance >= entry.lodDistance_)
2848 return tech;
2849 }
2850
2851 // If no suitable technique found, fallback to the last
2852 return techniques.Size() ? techniques.Back().technique_ : (Technique*)0;
2853 }
2854 }
2855
CheckMaterialForAuxView(Material * material)2856 void View::CheckMaterialForAuxView(Material* material)
2857 {
2858 const HashMap<TextureUnit, SharedPtr<Texture> >& textures = material->GetTextures();
2859
2860 for (HashMap<TextureUnit, SharedPtr<Texture> >::ConstIterator i = textures.Begin(); i != textures.End(); ++i)
2861 {
2862 Texture* texture = i->second_.Get();
2863 if (texture && texture->GetUsage() == TEXTURE_RENDERTARGET)
2864 {
2865 // Have to check cube & 2D textures separately
2866 if (texture->GetType() == Texture2D::GetTypeStatic())
2867 {
2868 Texture2D* tex2D = static_cast<Texture2D*>(texture);
2869 RenderSurface* target = tex2D->GetRenderSurface();
2870 if (target && target->GetUpdateMode() == SURFACE_UPDATEVISIBLE)
2871 target->QueueUpdate();
2872 }
2873 else if (texture->GetType() == TextureCube::GetTypeStatic())
2874 {
2875 TextureCube* texCube = static_cast<TextureCube*>(texture);
2876 for (unsigned j = 0; j < MAX_CUBEMAP_FACES; ++j)
2877 {
2878 RenderSurface* target = texCube->GetRenderSurface((CubeMapFace)j);
2879 if (target && target->GetUpdateMode() == SURFACE_UPDATEVISIBLE)
2880 target->QueueUpdate();
2881 }
2882 }
2883 }
2884 }
2885
2886 // Flag as processed so we can early-out next time we come across this material on the same frame
2887 material->MarkForAuxView(frame_.frameNumber_);
2888 }
2889
SetQueueShaderDefines(BatchQueue & queue,const RenderPathCommand & command)2890 void View::SetQueueShaderDefines(BatchQueue& queue, const RenderPathCommand& command)
2891 {
2892 String vsDefines = command.vertexShaderDefines_.Trimmed();
2893 String psDefines = command.pixelShaderDefines_.Trimmed();
2894 if (vsDefines.Length() || psDefines.Length())
2895 {
2896 queue.hasExtraDefines_ = true;
2897 queue.vsExtraDefines_ = vsDefines;
2898 queue.psExtraDefines_ = psDefines;
2899 queue.vsExtraDefinesHash_ = StringHash(vsDefines);
2900 queue.psExtraDefinesHash_ = StringHash(psDefines);
2901 }
2902 else
2903 queue.hasExtraDefines_ = false;
2904 }
2905
AddBatchToQueue(BatchQueue & queue,Batch & batch,Technique * tech,bool allowInstancing,bool allowShadows)2906 void View::AddBatchToQueue(BatchQueue& queue, Batch& batch, Technique* tech, bool allowInstancing, bool allowShadows)
2907 {
2908 if (!batch.material_)
2909 batch.material_ = renderer_->GetDefaultMaterial();
2910
2911 // Convert to instanced if possible
2912 if (allowInstancing && batch.geometryType_ == GEOM_STATIC && batch.geometry_->GetIndexBuffer())
2913 batch.geometryType_ = GEOM_INSTANCED;
2914
2915 if (batch.geometryType_ == GEOM_INSTANCED)
2916 {
2917 BatchGroupKey key(batch);
2918
2919 HashMap<BatchGroupKey, BatchGroup>::Iterator i = queue.batchGroups_.Find(key);
2920 if (i == queue.batchGroups_.End())
2921 {
2922 // Create a new group based on the batch
2923 // In case the group remains below the instancing limit, do not enable instancing shaders yet
2924 BatchGroup newGroup(batch);
2925 newGroup.geometryType_ = GEOM_STATIC;
2926 renderer_->SetBatchShaders(newGroup, tech, allowShadows, queue);
2927 newGroup.CalculateSortKey();
2928 i = queue.batchGroups_.Insert(MakePair(key, newGroup));
2929 }
2930
2931 int oldSize = i->second_.instances_.Size();
2932 i->second_.AddTransforms(batch);
2933 // Convert to using instancing shaders when the instancing limit is reached
2934 if (oldSize < minInstances_ && (int)i->second_.instances_.Size() >= minInstances_)
2935 {
2936 i->second_.geometryType_ = GEOM_INSTANCED;
2937 renderer_->SetBatchShaders(i->second_, tech, allowShadows, queue);
2938 i->second_.CalculateSortKey();
2939 }
2940 }
2941 else
2942 {
2943 renderer_->SetBatchShaders(batch, tech, allowShadows, queue);
2944 batch.CalculateSortKey();
2945
2946 // If batch is static with multiple world transforms and cannot instance, we must push copies of the batch individually
2947 if (batch.geometryType_ == GEOM_STATIC && batch.numWorldTransforms_ > 1)
2948 {
2949 unsigned numTransforms = batch.numWorldTransforms_;
2950 batch.numWorldTransforms_ = 1;
2951 for (unsigned i = 0; i < numTransforms; ++i)
2952 {
2953 // Move the transform pointer to generate copies of the batch which only refer to 1 world transform
2954 queue.batches_.Push(batch);
2955 ++batch.worldTransform_;
2956 }
2957 }
2958 else
2959 queue.batches_.Push(batch);
2960 }
2961 }
2962
PrepareInstancingBuffer()2963 void View::PrepareInstancingBuffer()
2964 {
2965 // Prepare instancing buffer from the source view
2966 /// \todo If rendering the same view several times back-to-back, would not need to refill the buffer
2967 if (sourceView_)
2968 {
2969 sourceView_->PrepareInstancingBuffer();
2970 return;
2971 }
2972
2973 URHO3D_PROFILE(PrepareInstancingBuffer);
2974
2975 unsigned totalInstances = 0;
2976
2977 for (HashMap<unsigned, BatchQueue>::Iterator i = batchQueues_.Begin(); i != batchQueues_.End(); ++i)
2978 totalInstances += i->second_.GetNumInstances();
2979
2980 for (Vector<LightBatchQueue>::Iterator i = lightQueues_.Begin(); i != lightQueues_.End(); ++i)
2981 {
2982 for (unsigned j = 0; j < i->shadowSplits_.Size(); ++j)
2983 totalInstances += i->shadowSplits_[j].shadowBatches_.GetNumInstances();
2984 totalInstances += i->litBaseBatches_.GetNumInstances();
2985 totalInstances += i->litBatches_.GetNumInstances();
2986 }
2987
2988 if (!totalInstances || !renderer_->ResizeInstancingBuffer(totalInstances))
2989 return;
2990
2991 VertexBuffer* instancingBuffer = renderer_->GetInstancingBuffer();
2992 unsigned freeIndex = 0;
2993 void* dest = instancingBuffer->Lock(0, totalInstances, true);
2994 if (!dest)
2995 return;
2996
2997 const unsigned stride = instancingBuffer->GetVertexSize();
2998 for (HashMap<unsigned, BatchQueue>::Iterator i = batchQueues_.Begin(); i != batchQueues_.End(); ++i)
2999 i->second_.SetInstancingData(dest, stride, freeIndex);
3000
3001 for (Vector<LightBatchQueue>::Iterator i = lightQueues_.Begin(); i != lightQueues_.End(); ++i)
3002 {
3003 for (unsigned j = 0; j < i->shadowSplits_.Size(); ++j)
3004 i->shadowSplits_[j].shadowBatches_.SetInstancingData(dest, stride, freeIndex);
3005 i->litBaseBatches_.SetInstancingData(dest, stride, freeIndex);
3006 i->litBatches_.SetInstancingData(dest, stride, freeIndex);
3007 }
3008
3009 instancingBuffer->Unlock();
3010 }
3011
SetupLightVolumeBatch(Batch & batch)3012 void View::SetupLightVolumeBatch(Batch& batch)
3013 {
3014 Light* light = batch.lightQueue_->light_;
3015 LightType type = light->GetLightType();
3016 Vector3 cameraPos = camera_->GetNode()->GetWorldPosition();
3017 float lightDist;
3018
3019 graphics_->SetBlendMode(light->IsNegative() ? BLEND_SUBTRACT : BLEND_ADD);
3020 graphics_->SetDepthBias(0.0f, 0.0f);
3021 graphics_->SetDepthWrite(false);
3022 graphics_->SetFillMode(FILL_SOLID);
3023 graphics_->SetLineAntiAlias(false);
3024 graphics_->SetClipPlane(false);
3025
3026 if (type != LIGHT_DIRECTIONAL)
3027 {
3028 if (type == LIGHT_POINT)
3029 lightDist = Sphere(light->GetNode()->GetWorldPosition(), light->GetRange() * 1.25f).Distance(cameraPos);
3030 else
3031 lightDist = light->GetFrustum().Distance(cameraPos);
3032
3033 // Draw front faces if not inside light volume
3034 if (lightDist < camera_->GetNearClip() * 2.0f)
3035 {
3036 renderer_->SetCullMode(CULL_CW, camera_);
3037 graphics_->SetDepthTest(CMP_GREATER);
3038 }
3039 else
3040 {
3041 renderer_->SetCullMode(CULL_CCW, camera_);
3042 graphics_->SetDepthTest(CMP_LESSEQUAL);
3043 }
3044 }
3045 else
3046 {
3047 // In case the same camera is used for multiple views with differing aspect ratios (not recommended)
3048 // refresh the directional light's model transform before rendering
3049 light->GetVolumeTransform(camera_);
3050 graphics_->SetCullMode(CULL_NONE);
3051 graphics_->SetDepthTest(CMP_ALWAYS);
3052 }
3053
3054 graphics_->SetScissorTest(false);
3055 if (!noStencil_)
3056 graphics_->SetStencilTest(true, CMP_NOTEQUAL, OP_KEEP, OP_KEEP, OP_KEEP, 0, light->GetLightMask());
3057 else
3058 graphics_->SetStencilTest(false);
3059 }
3060
NeedRenderShadowMap(const LightBatchQueue & queue)3061 bool View::NeedRenderShadowMap(const LightBatchQueue& queue)
3062 {
3063 // Must have a shadow map, and either forward or deferred lit batches
3064 return queue.shadowMap_ && (!queue.litBatches_.IsEmpty() || !queue.litBaseBatches_.IsEmpty() ||
3065 !queue.volumeBatches_.Empty());
3066 }
3067
RenderShadowMap(const LightBatchQueue & queue)3068 void View::RenderShadowMap(const LightBatchQueue& queue)
3069 {
3070 URHO3D_PROFILE(RenderShadowMap);
3071
3072 Texture2D* shadowMap = queue.shadowMap_;
3073 graphics_->SetTexture(TU_SHADOWMAP, 0);
3074
3075 graphics_->SetFillMode(FILL_SOLID);
3076 graphics_->SetClipPlane(false);
3077 graphics_->SetStencilTest(false);
3078
3079 // Set shadow depth bias
3080 BiasParameters parameters = queue.light_->GetShadowBias();
3081
3082 // The shadow map is a depth stencil texture
3083 if (shadowMap->GetUsage() == TEXTURE_DEPTHSTENCIL)
3084 {
3085 graphics_->SetColorWrite(false);
3086 graphics_->SetDepthStencil(shadowMap);
3087 graphics_->SetRenderTarget(0, shadowMap->GetRenderSurface()->GetLinkedRenderTarget());
3088 // Disable other render targets
3089 for (unsigned i = 1; i < MAX_RENDERTARGETS; ++i)
3090 graphics_->SetRenderTarget(i, (RenderSurface*) 0);
3091 graphics_->SetViewport(IntRect(0, 0, shadowMap->GetWidth(), shadowMap->GetHeight()));
3092 graphics_->Clear(CLEAR_DEPTH);
3093 }
3094 else // if the shadow map is a color rendertarget
3095 {
3096 graphics_->SetColorWrite(true);
3097 graphics_->SetRenderTarget(0, shadowMap);
3098 // Disable other render targets
3099 for (unsigned i = 1; i < MAX_RENDERTARGETS; ++i)
3100 graphics_->SetRenderTarget(i, (RenderSurface*) 0);
3101 graphics_->SetDepthStencil(renderer_->GetDepthStencil(shadowMap->GetWidth(), shadowMap->GetHeight(),
3102 shadowMap->GetMultiSample(), shadowMap->GetAutoResolve()));
3103 graphics_->SetViewport(IntRect(0, 0, shadowMap->GetWidth(), shadowMap->GetHeight()));
3104 graphics_->Clear(CLEAR_DEPTH | CLEAR_COLOR, Color::WHITE);
3105
3106 parameters = BiasParameters(0.0f, 0.0f);
3107 }
3108
3109 // Render each of the splits
3110 for (unsigned i = 0; i < queue.shadowSplits_.Size(); ++i)
3111 {
3112 const ShadowBatchQueue& shadowQueue = queue.shadowSplits_[i];
3113
3114 float multiplier = 1.0f;
3115 // For directional light cascade splits, adjust depth bias according to the far clip ratio of the splits
3116 if (i > 0 && queue.light_->GetLightType() == LIGHT_DIRECTIONAL)
3117 {
3118 multiplier =
3119 Max(shadowQueue.shadowCamera_->GetFarClip() / queue.shadowSplits_[0].shadowCamera_->GetFarClip(), 1.0f);
3120 multiplier = 1.0f + (multiplier - 1.0f) * queue.light_->GetShadowCascade().biasAutoAdjust_;
3121 // Quantize multiplier to prevent creation of too many rasterizer states on D3D11
3122 multiplier = (int)(multiplier * 10.0f) / 10.0f;
3123 }
3124
3125 // Perform further modification of depth bias on OpenGL ES, as shadow calculations' precision is limited
3126 float addition = 0.0f;
3127 #ifdef GL_ES_VERSION_2_0
3128 multiplier *= renderer_->GetMobileShadowBiasMul();
3129 addition = renderer_->GetMobileShadowBiasAdd();
3130 #endif
3131
3132 graphics_->SetDepthBias(multiplier * parameters.constantBias_ + addition, multiplier * parameters.slopeScaledBias_);
3133
3134 if (!shadowQueue.shadowBatches_.IsEmpty())
3135 {
3136 graphics_->SetViewport(shadowQueue.shadowViewport_);
3137 shadowQueue.shadowBatches_.Draw(this, shadowQueue.shadowCamera_, false, false, true);
3138 }
3139 }
3140
3141 // Scale filter blur amount to shadow map viewport size so that different shadow map resolutions don't behave differently
3142 float blurScale = queue.shadowSplits_[0].shadowViewport_.Width() / 1024.0f;
3143 renderer_->ApplyShadowMapFilter(this, shadowMap, blurScale);
3144
3145 // reset some parameters
3146 graphics_->SetColorWrite(true);
3147 graphics_->SetDepthBias(0.0f, 0.0f);
3148 }
3149
GetDepthStencil(RenderSurface * renderTarget)3150 RenderSurface* View::GetDepthStencil(RenderSurface* renderTarget)
3151 {
3152 // If using the backbuffer, return the backbuffer depth-stencil
3153 if (!renderTarget)
3154 return 0;
3155 // Then check for linked depth-stencil
3156 RenderSurface* depthStencil = renderTarget->GetLinkedDepthStencil();
3157 // Finally get one from Renderer
3158 if (!depthStencil)
3159 depthStencil = renderer_->GetDepthStencil(renderTarget->GetWidth(), renderTarget->GetHeight(),
3160 renderTarget->GetMultiSample(), renderTarget->GetAutoResolve());
3161 return depthStencil;
3162 }
3163
GetRenderSurfaceFromTexture(Texture * texture,CubeMapFace face)3164 RenderSurface* View::GetRenderSurfaceFromTexture(Texture* texture, CubeMapFace face)
3165 {
3166 if (!texture)
3167 return 0;
3168
3169 if (texture->GetType() == Texture2D::GetTypeStatic())
3170 return static_cast<Texture2D*>(texture)->GetRenderSurface();
3171 else if (texture->GetType() == TextureCube::GetTypeStatic())
3172 return static_cast<TextureCube*>(texture)->GetRenderSurface(face);
3173 else
3174 return 0;
3175 }
3176
SendViewEvent(StringHash eventType)3177 void View::SendViewEvent(StringHash eventType)
3178 {
3179 using namespace BeginViewRender;
3180
3181 VariantMap& eventData = GetEventDataMap();
3182
3183 eventData[P_VIEW] = this;
3184 eventData[P_SURFACE] = renderTarget_;
3185 eventData[P_TEXTURE] = (renderTarget_ ? renderTarget_->GetParentTexture() : 0);
3186 eventData[P_SCENE] = scene_;
3187 eventData[P_CAMERA] = cullCamera_;
3188
3189 renderer_->SendEvent(eventType, eventData);
3190 }
3191
FindNamedTexture(const String & name,bool isRenderTarget,bool isVolumeMap)3192 Texture* View::FindNamedTexture(const String& name, bool isRenderTarget, bool isVolumeMap)
3193 {
3194 // Check rendertargets first
3195 StringHash nameHash(name);
3196 if (renderTargets_.Contains(nameHash))
3197 return renderTargets_[nameHash];
3198
3199 // Then the resource system
3200 ResourceCache* cache = GetSubsystem<ResourceCache>();
3201
3202 // Check existing resources first. This does not load resources, so we can afford to guess the resource type wrong
3203 // without having to rely on the file extension
3204 Texture* texture = cache->GetExistingResource<Texture2D>(name);
3205 if (!texture)
3206 texture = cache->GetExistingResource<TextureCube>(name);
3207 if (!texture)
3208 texture = cache->GetExistingResource<Texture3D>(name);
3209 if (!texture)
3210 texture = cache->GetExistingResource<Texture2DArray>(name);
3211 if (texture)
3212 return texture;
3213
3214 // If not a rendertarget (which will never be loaded from a file), finally also try to load the texture
3215 // This will log an error if not found; the texture binding will be cleared in that case to not constantly spam the log
3216 if (!isRenderTarget)
3217 {
3218 if (GetExtension(name) == ".xml")
3219 {
3220 // Assume 3D textures are only bound to the volume map unit, otherwise it's a cube texture
3221 #ifdef DESKTOP_GRAPHICS
3222 StringHash type = ParseTextureTypeXml(cache, name);
3223 if (!type && isVolumeMap)
3224 type = Texture3D::GetTypeStatic();
3225
3226 if (type == Texture3D::GetTypeStatic())
3227 return cache->GetResource<Texture3D>(name);
3228 else if (type == Texture2DArray::GetTypeStatic())
3229 return cache->GetResource<Texture2DArray>(name);
3230 else
3231 #endif
3232 return cache->GetResource<TextureCube>(name);
3233 }
3234 else
3235 return cache->GetResource<Texture2D>(name);
3236 }
3237
3238 return 0;
3239 }
3240
3241 }
3242