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 for (auto& cameraComponent : pool) {
52 if ((cameraComponent.renderTexture != TextureHandle::NoHandle) && (cameraComponent.resolveTarget != TextureHandle::NoHandle)) {
53 IContext* gfxContext = context->device->getImmediateContext();
54 RenderResources* resources = static_cast<RenderResources*>(context->renderer->getResources());
55 RenderTexture* sourceTexture = resources->getRenderTexture(cameraComponent.renderTexture);
56 RenderTexture* destTexture = resources->getRenderTexture(cameraComponent.resolveTarget);
57
58 assert(cameraComponent.renderTexture->description.width == cameraComponent.resolveTarget->description.width);
59 assert(cameraComponent.renderTexture->description.height == cameraComponent.resolveTarget->description.height);
60
61 if (cameraComponent.renderTexture->description.samples > 1) {
62 gfxContext->resolveResource(sourceTexture->textureHandle, destTexture->textureHandle);
63 }
64 else {
65 gfxContext->copyResource(destTexture->textureHandle, sourceTexture->textureHandle);
66 }
67 }
68 }
69}
70
72{
73
74 for (auto & cameraComponent : pool) {
75 auto & cameraData = getData(&cameraComponent);
76
77 if (!std::isfinite(cameraComponent.nearPlaneLimit) ||
78 !std::isfinite(cameraComponent.farPlaneLimit) ||
79 (cameraComponent.farDistance <= cameraComponent.nearPlaneLimit))
80 {
81 LOG_ERROR(logger, "Broken camera invariants, nearPlaneLimit=%f, setting to 1.0, farPlaneLimit=%f, setting to 100000.0",
82 cameraComponent.nearPlaneLimit,
83 cameraComponent.farPlaneLimit);
84 cameraComponent.nearPlaneLimit = 1.f;
85 cameraComponent.nearPlaneLimit = 100000.f;
86 }
87
88
89 if (cameraComponent.environment) {
90 cameraData.environment = cameraComponent.environment->getComponentHandle<EnvironmentComponent>();
91 } else if (auto * env = context->environmentSystem->getGlobalEnvironment(); env) {
92 cameraData.environment = env->getComponentHandle<EnvironmentComponent>();
93 }
94 else {
95 cameraData.environment = ComponentHandle::Empty();
96 }
97
98 if (cameraComponent.supportsPicking() && &cameraComponent != mainCamera.resolve()) {
99 if (cameraData.rayPickId == -1) {
100 cameraData.rayPickId = nextRayPickId++;
101 }
102 }
103
104 auto transformComponent = cameraComponent.getContainer()->getComponent<TransformComponent>();
105
106 if (!transformComponent) {
107 LOG_ERROR(logger, "Camera %s missing transform component.", cameraComponent.getContainer()->getName().c_str());
108 continue;
109 }
110
111 cameraData.exposure = cameraComponent.exposure;
112 cameraData.layerMask = cameraComponent.layerMask;
113 cameraData.lightingMask = cameraComponent.lightingMask;
114 cameraData.viewportSize = cameraComponent.viewportSize;
115 cameraData.viewportOrigin = cameraComponent.viewportOrigin;
116 cameraData.viewMatrix = glm::inverse(context->transformSystem->getLocalToWorld(transformComponent));
117 cameraData.inverseViewMatrix = context->transformSystem->getLocalToWorld(transformComponent);
118
119 if ((cameraComponent.flags & CameraFlags::OverrideClearColor) == CameraFlags::OverrideClearColor) {
120 cameraData.clearColor = cameraComponent.clearColor;
121 cameraData.useClearColor = true;
122 }
123 else {
124 cameraData.useClearColor = false;
125 }
126 if (cameraData.viewportSize.x == 0 && HandleIsValid(cameraComponent.renderTexture)) {
127 auto cameraTexture = cameraComponent.renderTexture;
128
129 cameraData.viewportSize = { cameraTexture->description.width, cameraTexture->description.height };
130 }
131
132 cameraData.discardThreshold = 4.f*(cameraComponent.discardThreshold) / (cameraData.viewportSize.x*cameraData.viewportSize.y);
133 cameraData.keepThreshold = cameraComponent.keepThreshold;
134 }
135
136 for (auto & cameraComponent : pool) {
137 updateClippingPlanes(context, cameraComponent, false);
138 updateProjection(context, cameraComponent);
139 }
140
141}
142
144{
145 context->cullingManager->initializeCulling();
146
147 for (auto & cameraComponent : pool) {
148 if (updateClippingPlanes(context, cameraComponent, true)) {
149 updateProjection(context, cameraComponent);
150 }
151 }
152
153 base::postUpdate(context);
154}
155
157{
158 auto & cameraData = getData(&cameraComponent);
159
160 float aspect = cameraData.viewportSize.y == 0 ? 1.0f : cameraData.viewportSize.x / cameraData.viewportSize.y;
161
162 if (cameraComponent.projectionMode == ProjectionMode::Orthographic) {
163 const float height = cameraComponent.orthoHeight / 2.0f;
164
165 cameraData.rawProjectionMatrix = glm::ortho(-height * aspect, height * aspect,
166 -height, height,
167 cameraData.nearDistance,
168 cameraData.farDistance);
169 } else {
170 cameraData.fieldOfView = cameraComponent.fieldOfView;
171 cameraData.rawProjectionMatrix = glm::perspective(cameraComponent.fieldOfView,
172 aspect,
173 cameraData.nearDistance,
174 cameraData.farDistance);
175 }
176
177 if (glm::any(glm::notEqual(cameraComponent.subsetMin, glm::vec2(0.f)) || glm::notEqual(cameraComponent.subsetMax, glm::vec2(1.f)))) {
178 const auto& a = cameraComponent.subsetMin;
179 const auto& b = cameraComponent.subsetMax;
180 glm::vec2 s = glm::vec2(2.f) / (b - a);
181 if (std::isfinite(s.x) && std::isfinite(s.y)) {
182 glm::vec2 o = s * (glm::vec2(1) - 2.f * a) - glm::vec2(1);
183 cameraData.rawProjectionMatrix = glm::mat4(s.x, 0.f, 0.f, 0.f,
184 0.f, s.y, 0.f, 0.f,
185 0.f, 0.f, 1.f, 0.f,
186 o.x, o.y, 0.f, 1.f) * cameraData.rawProjectionMatrix;
187 }
188 else {
189 LOG_WARNING(logger, "Invalid camera frustum subset [%f,%f]x[%f,%f], ignoring.", a.x, a.y, b.x, b.y);
190 }
191 }
192
193
194 if((cameraComponent.flags & CameraFlags::FlipY) != 0){
195 static glm::mat4 flip(
196 1, 0, 0, 0,
197 0, -1, 0, 0,
198 0, 0, 1, 0,
199 0, 0, 0, 1
200 );
201 cameraData.flipWindingOrder = true;
202 cameraData.rawProjectionMatrix = flip * cameraData.rawProjectionMatrix;
203 }
204 else {
205 cameraData.flipWindingOrder = false;
206 }
207
208 cameraData.prevViewProjection = cameraData.viewProjection;
209 cameraData.rawViewProjection = cameraData.rawProjectionMatrix * cameraData.viewMatrix;
210 cameraData.rawViewCullMatrix = cameraData.rawViewProjection;
211 cameraData.projectionMatrix = context->renderer->getProjectionMatrix(cameraData.rawProjectionMatrix);
212 cameraData.viewProjection = cameraData.projectionMatrix * cameraData.viewMatrix;
213 cameraData.inverseViewProjectionMatrix = glm::inverse(cameraData.viewProjection);
214 cameraData.inverseProjectionMatrix = glm::inverse(cameraData.projectionMatrix);
215 cameraData.viewFromViewportMatrix = context->renderer->getViewFromViewportMatrix(cameraData.inverseProjectionMatrix);
216
217 cameraData.clientFlags = cameraComponent.clientFlags;
218 cameraData.frustum = Geometry::calculateFrustum<Geometry::Frustum, glm::mat4>(cameraData.viewProjection);
219}
220
221bool Cogs::Core::CameraSystem::updateClippingPlanes(Context * context, const CameraComponent & cameraComponent, bool secondPass)
222{
223 CpuInstrumentationScope(SCOPE_SYSTEMS, "CameraSystem::updateClippingPlanes");
224
225 bool tightEstimate = context->variables->get("camera.tightBounds", false);
226 auto & cameraData = getData(&cameraComponent);
227
228 cameraData.sceneBounds = Geometry::BoundingBox();
229
230 if (cameraComponent.enableClippingPlaneAdjustment) {
231 context->bounds->getSceneBounds(context, reinterpret_cast<float *>(&cameraData.sceneBounds), cameraComponent.layerMask & ~RenderLayers::Sky);
232 cameraData.sceneBounds = Bounds::getTransformedBounds(cameraData.sceneBounds, cameraData.viewMatrix);
233
234 float nearDist = std::numeric_limits<float>::max();
235 float farDist = -std::numeric_limits<float>::max();
236
237 if (tightEstimate && secondPass) {
238 CpuInstrumentationScope(SCOPE_SYSTEMS, "CameraSystem::getCameraDepthBounds");
239 context->bounds->getCameraDepthBounds(context, nearDist, farDist, cameraComponent);
240 } else if (!isEmpty(cameraData.sceneBounds)) {
241 context->bounds->getClipPlanes(context, nearDist, farDist, cameraComponent, cameraData.viewMatrix);
242 nearDist = std::max(nearDist, cameraComponent.nearPlaneLimit);
243 }
244
245 const float kMinRangeFactor = 0.0001f;
246
247 if(nearDist == std::numeric_limits<float>::max() && farDist == -std::numeric_limits<float>::max()){
248 nearDist = cameraComponent.nearPlaneLimit;
249 farDist = cameraComponent.farPlaneLimit;
250 }
251
252 nearDist = glm::clamp(nearDist, cameraComponent.nearPlaneLimit, cameraComponent.farPlaneLimit * (1 - kMinRangeFactor));
253 farDist = glm::clamp(farDist, glm::max(nearDist * (1 + kMinRangeFactor), nearDist + 1), cameraComponent.farPlaneLimit);
254
255 assert(nearDist < farDist);
256
257 const float calculationSlack = 0.001f;
258
259 const float newNearPlane = nearDist * (1.0f - calculationSlack);
260 const float newFarPlane = farDist * (1.0f + calculationSlack);
261
262 if (newNearPlane != cameraData.nearDistance || newFarPlane != cameraData.farDistance) {
263 cameraData.nearDistance = newNearPlane;
264 cameraData.farDistance = newFarPlane;
265
266 // TODO change camera system to only update clipping planes in one pass?
267 if (tightEstimate && secondPass){
268 if(cameraData.nearDepthBounds != newNearPlane || cameraData.farDepthBounds != newFarPlane){
269 context->engine->setDirty();
270 cameraData.nearDepthBounds = newNearPlane;
271 cameraData.farDepthBounds = newFarPlane;
272 }
273 }
274 else{
275 if(cameraData.nearClipPlanes != newNearPlane || cameraData.farClipPlanes != newFarPlane){
276 context->engine->setDirty();
277 cameraData.nearClipPlanes = newNearPlane;
278 cameraData.farClipPlanes = newFarPlane;
279 }
280 }
281 return true;
282 }
283 } else {
284 if (cameraComponent.nearDistance != cameraData.nearDistance || cameraComponent.farDistance != cameraData.farDistance) {
285 cameraData.nearDistance = cameraComponent.nearDistance;
286 cameraData.farDistance = cameraComponent.farDistance;
287
288 context->engine->setDirty();
289 return true;
290 }
291 }
292
293 return false;
294}
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:139
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:180
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()
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
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.