Cogs.Core
CameraSystem.cpp
1#include "CameraSystem.h"
2
3#include "Components/Core/TransformComponent.h"
4
5#include "Renderer/CullingManager.h"
6#include "Renderer/IRenderer.h"
7#include "Renderer/RenderTexture.h"
8
9#include "Resources/Texture.h"
10
11#include "Systems/Core/TransformSystem.h"
12#include "Systems/Core/EnvironmentSystem.h"
13
14#include "Scene/GetBounds.h"
15
16#include "Platform/Instrumentation.h"
17
18#include "Context.h"
19#include "Engine.h"
20
21#include "Rendering/IContext.h"
22
23#include "Foundation/ComponentModel/Entity.h"
24#include "Foundation/ComponentModel/Component.h"
25#include "Foundation/Geometry/BoundingBox.hpp"
26#include "Foundation/Logging/Logger.h"
27
28using namespace glm;
29using namespace Cogs::Geometry;
30
31namespace
32{
33 Cogs::Logging::Log logger = Cogs::Logging::getLogger("CameraSystem");
34}
35
36
38{
39 hash = Cogs::fnv1a(reinterpret_cast<uint8_t*>(this), offsetof(RenderPassOptions, scissorRectangle));
40}
41
43{
45 auto & data = getData(component.resolveComponent<CameraComponent>());
46 data.camera = component;
47 return component;
48}
49
51 RenderResources* resources = static_cast<RenderResources*>(context->renderer->getResources());
52
53 for (auto& cameraComponent : pool) {
54 RenderTexture* sourceTexture = resources->getRenderTexture(cameraComponent.renderTexture);
55 RenderTexture* destTexture = resources->getRenderTexture(cameraComponent.resolveTarget);
56
57 if (sourceTexture && destTexture) {
58 IContext* gfxContext = context->device->getImmediateContext();
59
60 assert(cameraComponent.renderTexture->description.width == cameraComponent.resolveTarget->description.width);
61 assert(cameraComponent.renderTexture->description.height == cameraComponent.resolveTarget->description.height);
62
63 if (cameraComponent.renderTexture->description.samples > 1) {
64 gfxContext->resolveResource(sourceTexture->textureHandle, destTexture->textureHandle);
65 }
66 else {
67 gfxContext->copyResource(destTexture->textureHandle, sourceTexture->textureHandle);
68 }
69 }
70 }
71}
72
74{
75
76 for (auto & cameraComponent : pool) {
77 auto & cameraData = getData(&cameraComponent);
78
79 if (!std::isfinite(cameraComponent.nearPlaneLimit) ||
80 !std::isfinite(cameraComponent.farPlaneLimit) ||
81 (cameraComponent.farDistance <= cameraComponent.nearPlaneLimit))
82 {
83 LOG_ERROR(logger, "Broken camera invariants, nearPlaneLimit=%f, setting to 1.0, farPlaneLimit=%f, setting to 100000.0",
84 cameraComponent.nearPlaneLimit,
85 cameraComponent.farPlaneLimit);
86 cameraComponent.nearPlaneLimit = 1.f;
87 cameraComponent.nearPlaneLimit = 100000.f;
88 }
89
90
91 if (cameraComponent.environment) {
92 cameraData.environment = cameraComponent.environment->getComponentHandle<EnvironmentComponent>();
93 } else if (auto * env = context->environmentSystem->getGlobalEnvironment(); env) {
94 cameraData.environment = env->getComponentHandle<EnvironmentComponent>();
95 }
96 else {
97 cameraData.environment = ComponentHandle::Empty();
98 }
99
100 if (cameraComponent.supportsPicking() && &cameraComponent != mainCamera.resolve()) {
101 if (cameraData.rayPickId == -1) {
102 cameraData.rayPickId = nextRayPickId++;
103 }
104 }
105
106 auto transformComponent = cameraComponent.getContainer()->getComponent<TransformComponent>();
107
108 if (!transformComponent) {
109 LOG_ERROR(logger, "Camera %s missing transform component.", cameraComponent.getContainer()->getName().c_str());
110 continue;
111 }
112
113 cameraData.exposure = cameraComponent.exposure;
114 cameraData.layerMask = cameraComponent.layerMask;
115 cameraData.lightingMask = cameraComponent.lightingMask;
116 cameraData.viewportSize = cameraComponent.viewportSize;
117 cameraData.viewportOrigin = cameraComponent.viewportOrigin;
118 cameraData.viewMatrix = glm::inverse(context->transformSystem->getLocalToWorld(transformComponent));
119 cameraData.inverseViewMatrix = context->transformSystem->getLocalToWorld(transformComponent);
120
121 if ((cameraComponent.flags & CameraFlags::OverrideClearColor) == CameraFlags::OverrideClearColor) {
122 cameraData.clearColor = cameraComponent.clearColor;
123 cameraData.useClearColor = true;
124 }
125 else {
126 cameraData.useClearColor = false;
127 }
128 if (cameraData.viewportSize.x == 0 && HandleIsValid(cameraComponent.renderTexture)) {
129 auto cameraTexture = cameraComponent.renderTexture;
130
131 cameraData.viewportSize = { cameraTexture->description.width, cameraTexture->description.height };
132 }
133
134 cameraData.discardThreshold = 4.f*(cameraComponent.discardThreshold) / (cameraData.viewportSize.x*cameraData.viewportSize.y);
135 cameraData.keepThreshold = cameraComponent.keepThreshold;
136 }
137
138 for (auto & cameraComponent : pool) {
139 updateClippingPlanes(context, cameraComponent, false);
140 updateProjection(context, cameraComponent);
141 }
142
143}
144
146{
147 context->cullingManager->initializeCulling();
148
149 for (auto & cameraComponent : pool) {
150 if (updateClippingPlanes(context, cameraComponent, true)) {
151 updateProjection(context, cameraComponent);
152 }
153 }
154
155 base::postUpdate(context);
156}
157
159{
160 auto & cameraData = getData(&cameraComponent);
161
162 float aspect = cameraData.viewportSize.y == 0 ? 1.0f : cameraData.viewportSize.x / cameraData.viewportSize.y;
163
164 if (cameraComponent.projectionMode == ProjectionMode::Orthographic) {
165 const float height = cameraComponent.orthoHeight / 2.0f;
166
167 cameraData.rawProjectionMatrix = glm::ortho(-height * aspect, height * aspect,
168 -height, height,
169 cameraData.nearDistance,
170 cameraData.farDistance);
171 } else {
172 cameraData.fieldOfView = cameraComponent.fieldOfView;
173 cameraData.rawProjectionMatrix = glm::perspective(cameraComponent.fieldOfView,
174 aspect,
175 cameraData.nearDistance,
176 cameraData.farDistance);
177 }
178
179 if (glm::any(glm::notEqual(cameraComponent.subsetMin, glm::vec2(0.f)) || glm::notEqual(cameraComponent.subsetMax, glm::vec2(1.f)))) {
180 const auto& a = cameraComponent.subsetMin;
181 const auto& b = cameraComponent.subsetMax;
182 glm::vec2 s = glm::vec2(2.f) / (b - a);
183 if (std::isfinite(s.x) && std::isfinite(s.y)) {
184 glm::vec2 o = s * (glm::vec2(1) - 2.f * a) - glm::vec2(1);
185 cameraData.rawProjectionMatrix = glm::mat4(s.x, 0.f, 0.f, 0.f,
186 0.f, s.y, 0.f, 0.f,
187 0.f, 0.f, 1.f, 0.f,
188 o.x, o.y, 0.f, 1.f) * cameraData.rawProjectionMatrix;
189 }
190 else {
191 LOG_WARNING(logger, "Invalid camera frustum subset [%f,%f]x[%f,%f], ignoring.", a.x, a.y, b.x, b.y);
192 }
193 }
194
195
196 if((cameraComponent.flags & CameraFlags::FlipY) != 0){
197 static glm::mat4 flip(
198 1, 0, 0, 0,
199 0, -1, 0, 0,
200 0, 0, 1, 0,
201 0, 0, 0, 1
202 );
203 cameraData.flipWindingOrder = true;
204 cameraData.rawProjectionMatrix = flip * cameraData.rawProjectionMatrix;
205 }
206 else {
207 cameraData.flipWindingOrder = false;
208 }
209
210 cameraData.prevViewProjection = cameraData.viewProjection;
211 cameraData.rawViewProjection = cameraData.rawProjectionMatrix * cameraData.viewMatrix;
212 cameraData.rawViewCullMatrix = cameraData.rawViewProjection;
213 cameraData.projectionMatrix = context->renderer->getProjectionMatrix(cameraData.rawProjectionMatrix);
214 cameraData.viewProjection = cameraData.projectionMatrix * cameraData.viewMatrix;
215 cameraData.inverseViewProjectionMatrix = glm::inverse(cameraData.viewProjection);
216 cameraData.inverseProjectionMatrix = glm::inverse(cameraData.projectionMatrix);
217 cameraData.viewFromViewportMatrix = context->renderer->getViewFromViewportMatrix(cameraData.inverseProjectionMatrix);
218
219 cameraData.clientFlags = cameraComponent.clientFlags;
220 cameraData.frustum = Geometry::calculateFrustum<Geometry::Frustum, glm::mat4>(cameraData.viewProjection);
221}
222
223bool Cogs::Core::CameraSystem::updateClippingPlanes(Context * context, const CameraComponent & cameraComponent, bool secondPass)
224{
225 CpuInstrumentationScope(SCOPE_SYSTEMS, "CameraSystem::updateClippingPlanes");
226
227 bool tightEstimate = context->variables->get("camera.tightBounds", false);
228 auto & cameraData = getData(&cameraComponent);
229
230 cameraData.sceneBounds = Geometry::BoundingBox();
231
232 if (cameraComponent.enableClippingPlaneAdjustment) {
233 context->bounds->getSceneBounds(context, reinterpret_cast<float *>(&cameraData.sceneBounds), cameraComponent.layerMask & ~RenderLayers::Sky);
234 cameraData.sceneBounds = Bounds::getTransformedBounds(cameraData.sceneBounds, cameraData.viewMatrix);
235
236 float nearDist = std::numeric_limits<float>::max();
237 float farDist = -std::numeric_limits<float>::max();
238
239 if (tightEstimate && secondPass) {
240 CpuInstrumentationScope(SCOPE_SYSTEMS, "CameraSystem::getCameraDepthBounds");
241 context->bounds->getCameraDepthBounds(context, nearDist, farDist, cameraComponent);
242 } else if (!isEmpty(cameraData.sceneBounds)) {
243 context->bounds->getClipPlanes(context, nearDist, farDist, cameraComponent, cameraData.viewMatrix);
244 nearDist = std::max(nearDist, cameraComponent.nearPlaneLimit);
245 }
246
247 const float kMinRangeFactor = 0.0001f;
248
249 if(nearDist == std::numeric_limits<float>::max() && farDist == -std::numeric_limits<float>::max()){
250 nearDist = cameraComponent.nearPlaneLimit;
251 farDist = cameraComponent.farPlaneLimit;
252 }
253
254 nearDist = glm::clamp(nearDist, cameraComponent.nearPlaneLimit, cameraComponent.farPlaneLimit * (1 - kMinRangeFactor));
255 farDist = glm::clamp(farDist, glm::max(nearDist * (1 + kMinRangeFactor), nearDist + 1), cameraComponent.farPlaneLimit);
256
257 assert(nearDist < farDist);
258
259 const float calculationSlack = 0.001f;
260
261 const float newNearPlane = nearDist * (1.0f - calculationSlack);
262 const float newFarPlane = farDist * (1.0f + calculationSlack);
263
264 if (newNearPlane != cameraData.nearDistance || newFarPlane != cameraData.farDistance) {
265 cameraData.nearDistance = newNearPlane;
266 cameraData.farDistance = newFarPlane;
267
268 // TODO change camera system to only update clipping planes in one pass?
269 if (tightEstimate && secondPass){
270 if(cameraData.nearDepthBounds != newNearPlane || cameraData.farDepthBounds != newFarPlane){
271 context->engine->setDirty();
272 cameraData.nearDepthBounds = newNearPlane;
273 cameraData.farDepthBounds = newFarPlane;
274 }
275 }
276 else{
277 if(cameraData.nearClipPlanes != newNearPlane || cameraData.farClipPlanes != newFarPlane){
278 context->engine->setDirty();
279 cameraData.nearClipPlanes = newNearPlane;
280 cameraData.farClipPlanes = newFarPlane;
281 }
282 }
283 return true;
284 }
285 } else {
286 if (cameraComponent.nearDistance != cameraData.nearDistance || cameraComponent.farDistance != cameraData.farDistance) {
287 cameraData.nearDistance = cameraComponent.nearDistance;
288 cameraData.farDistance = cameraComponent.farDistance;
289
290 context->engine->setDirty();
291 return true;
292 }
293 }
294
295 return false;
296}
ComponentHandle getComponentHandle() const
Definition: Component.h:177
CameraFlags flags
Camera behavior flags.
float fieldOfView
Vertical field of view, given in radians.
uint32_t clientFlags
Application defined, camera specific flags passed to shaders.
float farPlaneLimit
Largest value allowed to adjust far plane to.
bool enableClippingPlaneAdjustment
If automatic adjustment of the clipping planes should be performed to fit as much of the scene as pos...
glm::vec2 subsetMin
Subset of the frustum to view (for only rendering a subset of the view covered by the viewport),...
ProjectionMode projectionMode
The projection mode to use for the camera.
RenderLayers layerMask
Layer mask used to determine which RenderComponent instances should be visible in this cameras viewpo...
float nearPlaneLimit
Smallest value allowed to adjust near plane to.
glm::vec2 subsetMax
Subset of the frustum to view (for only rendering a subset of the view covered by the viewport),...
float orthoHeight
Height of the viewport in scene units when using orthographic projection.
void postRender(const Context *context)
Called by the renderer after all rendering is complete.
ComponentHandle createComponent() override
bool updateClippingPlanes(Context *context, const CameraComponent &cameraComponent, bool secondPass)
Update near/far clipping plane calculations.
void updateProjection(Context *context, const CameraComponent &cameraComponent)
Update the projection matrix and derived matrices of a camera.
virtual ComponentHandle createComponent()
Create a new component instance.
void postUpdate()
Perform post update logic in the system.
void update()
Updates the system state to that of the current frame.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class CullingManager > cullingManager
CullingManager instance.
Definition: Context.h:195
class IRenderer * renderer
Renderer.
Definition: Context.h:228
std::unique_ptr< class Bounds > bounds
Bounds service instance.
Definition: Context.h:216
std::unique_ptr< class Variables > variables
Variables service instance.
Definition: Context.h:180
std::unique_ptr< class Engine > engine
Engine instance.
Definition: Context.h:222
virtual glm::mat4 getViewFromViewportMatrix(const glm::mat4 inverseProjectionMatrix)=0
Get an adjusted inverse projection matrix mainly used in post processing.
virtual glm::mat4 getProjectionMatrix(const glm::mat4 projectionMatrix)=0
Get an adjusted projection matrix used to render.
virtual IRenderResources * getResources()=0
Get the render resources interface.
Contains render resources used by the renderer.
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
Log implementation class.
Definition: LogManager.h:140
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
@ OverrideClearColor
Provide a clear colour for this camera to use.
@ FlipY
Flip the y-axis.
@ Orthographic
Orthographic projection.
Contains geometry calculations and generation.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
constexpr size_t fnv1a(uint8_t data, size_t hashValue) noexcept
Hashes a single byte using the fnv1a algorithm.
Definition: HashFunctions.h:37
Handle to a Component instance.
Definition: Component.h:67
static ComponentHandle Empty()
Returns an empty, invalid handle. Will evaluate to false if tested against using operator bool().
Definition: Component.h:119
COGSCORE_DLL_API void updateHash()
Represents a graphics device context which can receive rendering commands.
Definition: IContext.h:43
virtual void resolveResource(TextureHandle source, TextureHandle destination)=0
Resolves the given source resource target into the given destination texture.