Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgrendercamera.cpp
Go to the documentation of this file.
1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2019 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4// Qt-Security score:significant reason:default
5
6
7
9
10#include <QtQuick3DRuntimeRender/private/qssgrendererutil_p.h>
11
12#include <QtQuick3DUtils/private/qssgutils_p.h>
13
14#include <QtGui/qquaternion.h>
15#include <QtGui/QVector2D>
16
17#include <qmath.h>
18
20
21static inline float getAspectRatio(const QRectF &inViewport)
22{
23 return inViewport.height() != 0 ? inViewport.width() / inViewport.height() : 0.0f;
24}
25
26QSSGRenderCamera::QSSGRenderCamera(QSSGRenderGraphObject::Type type)
27 : QSSGRenderNode(type)
28{
29 Q_ASSERT(QSSGRenderCamera::isCamera(type));
30 markDirty(DirtyFlag::CameraDirty);
31}
32
33bool QSSGRenderCamera::calculateProjectionInternal(QSSGRenderCamera &camera,
34 const QRectF &inViewport,
35 Configuration config)
36{
37 return camera.calculateProjection(inViewport, config);
38}
39
40bool QSSGRenderCamera::calculateProjection(const QRectF &inViewport, Configuration config)
41{
42 bool retval = false;
43
44 // NOTE: We always update the dpr, under normal surcumstances it won't change, but we have at least
45 // one broken use-cases (front-end camera class) where we depend on the dpr being available from
46 // here...
47 dpr = config.dpr;
48
49 const bool argumentsChanged = inViewport != previousInViewport;
50 if (!argumentsChanged && !isDirty(DirtyFlag::CameraDirty))
51 return true;
52 previousInViewport = inViewport;
53 clearDirty(DirtyFlag::CameraDirty);
54
55 switch (type) {
56 case QSSGRenderGraphObject::Type::OrthographicCamera:
57 retval = computeFrustumOrtho(inViewport, clipPlanes, magnification, config, projection);
58 break;
59 case QSSGRenderGraphObject::Type::PerspectiveCamera:
60 retval = computeFrustumPerspective(inViewport, fov, clipPlanes, projection);
61 break;
62 case QSSGRenderGraphObject::Type::CustomCamera:
63 retval = true; // Do nothing
64 break;
65 case QSSGRenderGraphObject::Type::CustomFrustumCamera:
66 retval = computeCustomFrustum(customFrustum, clipPlanes, projection);
67 break;
68 default:
69 Q_UNREACHABLE();
70 }
71
72 if (retval) {
73 float *writePtr(projection.data());
74 frustumScale.setX(writePtr[0]);
75 frustumScale.setY(writePtr[5]);
76 }
77 return retval;
78}
79
80//==============================================================================
81/**
82 * Compute the projection matrix for a perspective camera
83 * @return true if the computed projection matrix is valid
84 */
85bool QSSGRenderCamera::computeFrustumPerspective(QRectF inViewport,
86 QSSGRenderCamera::FieldOfView fov,
87 QSSGRenderCamera::ClipPlanes clipPlane,
88 QMatrix4x4 &outProjection)
89{
90 outProjection.setToIdentity();
91 const float aspectRatio = getAspectRatio(inViewport);
92 const auto vfov = fov.asVerticalFov(aspectRatio);
93 outProjection.perspective(vfov.degrees(), aspectRatio, clipPlane.clipNear(), clipPlane.clipFar());
94 return true;
95}
96
97bool QSSGRenderCamera::computeCustomFrustum(QSSGRenderCamera::Frustum frustum,
98 QSSGRenderCamera::ClipPlanes clipPlanes,
99 QMatrix4x4 &outProjection)
100{
101 outProjection.setToIdentity();
102 outProjection.frustum(frustum.left, frustum.right, frustum.bottom, frustum.top, clipPlanes.clipNear(), clipPlanes.clipFar());
103 return true;
104}
105
106void QSSGRenderCamera::calculateViewProjectionMatrix(const QMatrix4x4 &globalTransform,
107 const QMatrix4x4 &projection,
108 QMatrix4x4 &outMatrix)
109{
110 QMatrix4x4 nonScaledGlobal(Qt::Uninitialized);
111 nonScaledGlobal.setColumn(0, globalTransform.column(0).normalized());
112 nonScaledGlobal.setColumn(1, globalTransform.column(1).normalized());
113 nonScaledGlobal.setColumn(2, globalTransform.column(2).normalized());
114 nonScaledGlobal.setColumn(3, globalTransform.column(3));
115 outMatrix = projection * nonScaledGlobal.inverted();
116}
117
118//==============================================================================
119/**
120 * Compute the outProjection matrix for a orthographic camera
121 * @return true if the computed outProjection matrix is valid
122 */
123bool QSSGRenderCamera::computeFrustumOrtho(QRectF inViewport,
124 QSSGRenderCamera::ClipPlanes clipPlanes,
125 QSSGRenderCamera::Magnification magnification,
126 Configuration config,
127 QMatrix4x4 &outProjection)
128{
129
130
131 outProjection.setToIdentity();
132 // When using ssaa we need to zoom with the ssaa multiplier since otherwise the
133 // orthographic camera will be zoomed out due to the bigger viewport. We therefore
134 // scale the magnification before calulating the projection.
135 // Since the same camera can be used in several View3Ds with or without ssaa we
136 // cannot store the magnification permanently.
137 magnification *= config.ssaaMultiplier;
138 const float halfWidth = inViewport.width() / 2.0f / magnification.horizontal() / config.dpr;
139 const float halfHeight = inViewport.height() / 2.0f / magnification.vertical() / config.dpr;
140 outProjection.ortho(-halfWidth, halfWidth, -halfHeight, halfHeight, clipPlanes.clipNear(), clipPlanes.clipFar());
141 return true;
142}
143
144static QQuaternion rotationQuaternionForLookAt(const QVector3D &sourcePosition,
145 const QVector3D &sourceDirection,
146 const QVector3D &targetPosition,
147 const QVector3D &upDirection)
148{
149 QVector3D targetDirection = sourcePosition - targetPosition;
150 targetDirection.normalize();
151
152 QVector3D rotationAxis = QVector3D::crossProduct(sourceDirection, targetDirection);
153
154 const QVector3D normalizedAxis = rotationAxis.normalized();
155 if (qFuzzyIsNull(normalizedAxis.lengthSquared()))
156 rotationAxis = upDirection;
157
158 float dot = QVector3D::dotProduct(sourceDirection, targetDirection);
159 dot = qBound(-1.0f, dot, 1.0f);
160 float rotationAngle = float(qRadiansToDegrees(qAcos(qreal(dot))));
161
162 return QQuaternion::fromAxisAndAngle(rotationAxis, rotationAngle);
163}
164
165void QSSGRenderCamera::lookAt(const QVector3D &inCameraPos, const QVector3D &inUpDir, const QVector3D &inTargetPos, const QVector3D &pivot)
166{
167 QQuaternion rotation = rotationQuaternionForLookAt(inCameraPos, getScalingCorrectDirection(localTransform), inTargetPos, inUpDir.normalized());
168 localTransform = QSSGRenderNode::calculateTransformMatrix(inCameraPos, QSSGRenderNode::initScale, pivot, rotation);
169 QSSGRenderNode::markDirty(QSSGRenderNode::DirtyFlag::TransformDirty);
170}
171
172void QSSGRenderCamera::calculateViewProjectionMatrix(const QMatrix4x4 &globalTransform, QMatrix4x4 &outMatrix) const
173{
174 QMatrix4x4 nonScaledGlobal(Qt::Uninitialized);
175 nonScaledGlobal.setColumn(0, globalTransform.column(0).normalized());
176 nonScaledGlobal.setColumn(1, globalTransform.column(1).normalized());
177 nonScaledGlobal.setColumn(2, globalTransform.column(2).normalized());
178 nonScaledGlobal.setColumn(3, globalTransform.column(3));
179 outMatrix = projection * nonScaledGlobal.inverted();
180}
181
182void QSSGRenderCamera::calculateViewProjectionWithoutTranslation(const QMatrix4x4 &globalTransform, float clipNear, float clipFar, QMatrix4x4 &outMatrix) const
183{
184 if (qFuzzyIsNull(clipFar - clipNear)) {
185 qWarning() << "QSSGRenderCamera::calculateViewProjection: far == near";
186 return;
187 }
188
189 QMatrix4x4 proj = projection;
190 proj(2, 2) = -(clipFar + clipNear) / (clipFar - clipNear);
191 proj(2, 3) = -2 * clipFar * clipNear / (clipFar - clipNear);
192 QMatrix4x4 nonScaledGlobal(Qt::Uninitialized);
193 nonScaledGlobal.setColumn(0, globalTransform.column(0).normalized());
194 nonScaledGlobal.setColumn(1, globalTransform.column(1).normalized());
195 nonScaledGlobal.setColumn(2, globalTransform.column(2).normalized());
196 nonScaledGlobal.setColumn(3, QVector4D(0, 0, 0, 1));
197 outMatrix = proj * nonScaledGlobal.inverted();
198}
199
200QSSGRenderRay QSSGRenderCamera::unproject(const QMatrix4x4 &globalTransform,
201 const QVector2D &inViewportRelativeCoords,
202 const QRectF &inViewport) const
203{
204 QSSGRenderRay theRay;
205 QVector2D normalizedCoords = QSSGUtils::rect::relativeToNormalizedCoordinates(inViewport, inViewportRelativeCoords);
206 QVector3D &outOrigin(theRay.origin);
207 QVector3D &outDir(theRay.direction);
208 QVector2D inverseFrustumScale(1.0f / frustumScale.x(), 1.0f / frustumScale.y());
209 QVector2D scaledCoords(inverseFrustumScale.x() * normalizedCoords.x(), inverseFrustumScale.y() * normalizedCoords.y());
210
211 if (type == QSSGRenderCamera::Type::OrthographicCamera) {
212 outOrigin.setX(scaledCoords.x());
213 outOrigin.setY(scaledCoords.y());
214 outOrigin.setZ(0.0f);
215
216 outDir.setX(0.0f);
217 outDir.setY(0.0f);
218 outDir.setZ(-1.0f);
219 } else {
220 outOrigin.setX(0.0f);
221 outOrigin.setY(0.0f);
222 outOrigin.setZ(0.0f);
223
224 outDir.setX(scaledCoords.x());
225 outDir.setY(scaledCoords.y());
226 outDir.setZ(-1.0f);
227 }
228
229 outOrigin = QSSGUtils::mat44::transform(globalTransform, outOrigin);
230 QMatrix3x3 theNormalMatrix = globalTransform.normalMatrix();
231
232 outDir = QSSGUtils::mat33::transform(theNormalMatrix, outDir);
233 outDir.normalize();
234
235 return theRay;
236}
237
238void QSSGRenderCamera::markDirty(DirtyFlag dirtyFlag)
239{
240 cameraDirtyFlags |= FlagT(dirtyFlag);
241 QSSGRenderNode::markDirty(QSSGRenderNode::DirtyFlag::SubNodeDirty);
242}
243
244void QSSGRenderCamera::clearDirty(DirtyFlag dirtyFlag)
245{
246 cameraDirtyFlags &= ~FlagT(dirtyFlag);
247 QSSGRenderNode::clearDirty(QSSGRenderNode::DirtyFlag::SubNodeDirty);
248}
249
250static float getZNear(const QMatrix4x4 &projection)
251{
252 const float *data = projection.constData();
253 QSSGPlane plane(QVector3D(data[3] + data[2], data[7] + data[6], data[11] + data[10]), -data[15] - data[14]);
254 plane.normalize();
255 return plane.d;
256}
257
258static QVector2D getViewportHalfExtents(const QMatrix4x4 &projection) {
259 const float *data = projection.constData();
260
261 QSSGPlane nearPlane(QVector3D(data[3] + data[2], data[7] + data[6], data[11] + data[10]), -data[15] - data[14]);
262 nearPlane.normalize();
263 QSSGPlane rightPlane(QVector3D(data[3] - data[0], data[7] - data[4], data[11] - data[8]), -data[15] + data[12]);
264 rightPlane.normalize();
265 QSSGPlane topPlane(QVector3D(data[3] - data[1], data[7] - data[5], data[11] - data[9]), -data[15] + data[13]);
266 topPlane.normalize();
267
268 // Get intersection the 3 planes
269 float denom = QVector3D::dotProduct(QVector3D::crossProduct(nearPlane.n, rightPlane.n), topPlane.n);
270 if (qFuzzyIsNull(denom))
271 return QVector2D();
272
273 QVector3D intersection = (QVector3D::crossProduct(rightPlane.n, topPlane.n) * nearPlane.d +
274 (QVector3D::crossProduct(topPlane.n, nearPlane.n) * rightPlane.d) +
275 (QVector3D::crossProduct(nearPlane.n, rightPlane.n) * topPlane.d)) / denom;
276
277 return QVector2D(intersection.x(), intersection.y());
278}
279
280float QSSGRenderCamera::getLevelOfDetailMultiplier() const
281{
282 if (type == QSSGRenderGraphObject::Type::OrthographicCamera)
283 return getViewportHalfExtents(projection).x();
284
285 float zn = getZNear(projection);
286 float width = getViewportHalfExtents(projection).x() * 2.0;
287 return 1.0 / (zn / width);
288
289}
290
291QT_END_NAMESPACE
Combined button and popup list for selecting options.
static QVector2D getViewportHalfExtents(const QMatrix4x4 &projection)
static float getZNear(const QMatrix4x4 &projection)
static QT_BEGIN_NAMESPACE float getAspectRatio(const QRectF &inViewport)
static QQuaternion rotationQuaternionForLookAt(const QVector3D &sourcePosition, const QVector3D &sourceDirection, const QVector3D &targetPosition, const QVector3D &upDirection)