aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonas Karlsson <[email protected]>2025-01-07 23:12:35 +0100
committerQt Cherry-pick Bot <[email protected]>2025-01-13 16:07:58 +0000
commitd7256b29a1719e772dd6437eb086cc6d25f18cf6 (patch)
tree8c723f496da58f74a2b259391b9f5f4ec7ba3a79
parent0f5722f44f6419cdd30f91d231ea5223131e3cdd (diff)
Fix broken shadow mapping with orthographic camera
The issue was that QSSGRenderCamera::calculateViewProjectionMatrix is assuming the camera is a perspective camera. Instead of using this method we use the original frustum points of the camera and move them forward or backwards according to the split t values. The same issue was present when calculating the radius of the frustum when locked texels are active. This change also makes the clipNear be the same as the camera's instead of being hardcoded and clipFar not be limited. This change also moves the pcfRadius expansion out of the frustum calculation and does it on the shadowmap bounding box instead. Fixes: QTBUG-132081 Pick-to: 6.8 Change-Id: Ic7cc725609946ab5adf7e23cf943e06c2a01b968 Reviewed-by: Eskil Abrahamsen Blomfeldt <[email protected]> (cherry picked from commit e67c6bd6c86922bd833d9440a6c53d31380a5ed3) Reviewed-by: Qt Cherry-pick Bot <[email protected]>
-rw-r--r--src/runtimerender/rendererimpl/qssgrenderhelpers.cpp155
-rw-r--r--tests/baseline/data/shadows/CSMConesCylindersHighNear.qml218
-rw-r--r--tests/baseline/data/shadows/CustomCamera.qml117
-rw-r--r--tests/baseline/data/shadows/FrustumCamera.qml116
-rw-r--r--tests/baseline/data/shadows/Orthographic.qml111
-rw-r--r--tests/baseline/data/shadows/OrthographicLockedTexels.qml112
6 files changed, 729 insertions, 100 deletions
diff --git a/src/runtimerender/rendererimpl/qssgrenderhelpers.cpp b/src/runtimerender/rendererimpl/qssgrenderhelpers.cpp
index f67e4c00..8286d6a9 100644
--- a/src/runtimerender/rendererimpl/qssgrenderhelpers.cpp
+++ b/src/runtimerender/rendererimpl/qssgrenderhelpers.cpp
@@ -129,91 +129,44 @@ std::pair<QSSGBounds3, QSSGBounds3> RenderHelpers::calculateSortedObjectBounds(c
return { boundsCasting, boundsReceiving };
}
-static QSSGBoxPoints computeFrustumBounds(const QSSGRenderCamera &inCamera)
+static QSSGBoxPoints computeFrustumBounds(const QMatrix4x4 &projection)
{
- QMatrix4x4 viewProjection;
- inCamera.calculateViewProjectionMatrix(viewProjection);
-
bool invertible = false;
- QMatrix4x4 inv = viewProjection.inverted(&invertible);
+ QMatrix4x4 inv = projection.inverted(&invertible);
Q_ASSERT(invertible);
+ // The frustum points will be in this orientation
+ //
+ // bottom top
+ // 4__________5 7__________6
+ // \ / \ /
+ // \ / \ /
+ // \____/ \____/
+ // 0 1 3 2
return { inv.map(QVector3D(-1, -1, -1)), inv.map(QVector3D(+1, -1, -1)), inv.map(QVector3D(+1, +1, -1)),
inv.map(QVector3D(-1, +1, -1)), inv.map(QVector3D(-1, -1, +1)), inv.map(QVector3D(+1, -1, +1)),
inv.map(QVector3D(+1, +1, +1)), inv.map(QVector3D(-1, +1, +1)) };
}
-static QSSGBoxPoints computeFrustumBoundsWithNearFar(const QSSGRenderCamera &inCamera, float clipNear, float clipFar, float pcfRadius)
+static QSSGBoxPoints sliceFrustum(const QSSGBoxPoints &frustumPoints, float t0, float t1)
{
- QMatrix4x4 viewProjection;
- inCamera.calculateViewProjectionMatrix(viewProjection, clipNear, clipFar);
-
- bool invertible = false;
- QMatrix4x4 inv = viewProjection.inverted(&invertible);
- Q_ASSERT(invertible);
-
- QSSGBoxPoints pts = { inv.map(QVector3D(-1, -1, -1)), inv.map(QVector3D(+1, -1, -1)),
- inv.map(QVector3D(+1, +1, -1)), inv.map(QVector3D(-1, +1, -1)),
- inv.map(QVector3D(-1, -1, +1)), inv.map(QVector3D(+1, -1, +1)),
- inv.map(QVector3D(+1, +1, +1)), inv.map(QVector3D(-1, +1, +1)) };
-
- if (pcfRadius > 0.f) {
- // Expand the frustum in all directions to cover the pcf radius
- const QVector3D origin = inv.map(QVector3D(0, 0, 0));
- const QVector3D x = (inv.map(QVector3D(1, 0, 0)) - origin).normalized() * pcfRadius;
- const QVector3D y = (inv.map(QVector3D(0, 1, 0)) - origin).normalized() * pcfRadius;
- const QVector3D z = (inv.map(QVector3D(0, 0, 1)) - origin).normalized() * pcfRadius;
-
- // bottom top
- // 4__________5 7__________6
- // \ / \ /
- // \ / \ /
- // \____/ \____/
- // 0 1 3 2
- // left
- pts[0] -= x;
- pts[3] -= x;
- pts[7] -= x;
- pts[4] -= x;
- // right
- pts[1] += x;
- pts[5] += x;
- pts[6] += x;
- pts[2] += x;
- // top
- pts[3] += y;
- pts[2] += y;
- pts[6] += y;
- pts[7] += y;
- // bottom
- pts[0] -= y;
- pts[1] -= y;
- pts[5] -= y;
- pts[4] -= y;
- // back
- pts[0] -= z;
- pts[1] -= z;
- pts[2] -= z;
- pts[3] -= z;
- // front
- pts[4] += z;
- pts[5] += z;
- pts[6] += z;
- pts[7] += z;
+ QSSGBoxPoints pts;
+ for (int i = 0; i < 4; ++i) {
+ const QVector3D forward = frustumPoints[i + 4] - frustumPoints[i];
+ pts[i] = frustumPoints[i] + forward * t0;
+ pts[i + 4] = frustumPoints[i] + forward * t1;
}
-
return pts;
}
-static std::unique_ptr<QSSGRenderCamera> computeShadowCameraFromFrustum(const QSSGRenderCamera &inCamera,
- const QMatrix4x4 &lightMatrix,
+static std::unique_ptr<QSSGRenderCamera> computeShadowCameraFromFrustum(const QMatrix4x4 &lightMatrix,
const QMatrix4x4 &lightMatrixInverted,
const QVector3D &lightPivot,
const QVector3D &lightForward,
const QVector3D &lightUp,
const float shadowMapResolution,
const float pcfRadius,
- float clipRange,
+ const QSSGBoxPoints &frustumPoints,
float frustumStartT,
float frustumEndT,
float frustumRadius,
@@ -239,14 +192,11 @@ static std::unique_ptr<QSSGRenderCamera> computeShadowCameraFromFrustum(const QS
return result;
};
- const float clipNear = 1.0f + clipRange * frustumStartT;
- const float clipFar = 1.0f + clipRange * frustumEndT;
-
- QSSGBoxPoints frustumPoints = computeFrustumBoundsWithNearFar(inCamera, clipNear, clipFar, pcfRadius);
+ QSSGBoxPoints frustumPointsSliced = sliceFrustum(frustumPoints, frustumStartT, frustumEndT);
if (drawCascades)
- ShadowmapHelpers::addDebugFrustum(frustumPoints, QColorConstants::Black, debugDrawSystem);
+ ShadowmapHelpers::addDebugFrustum(frustumPointsSliced, QColorConstants::Black, debugDrawSystem);
- QList<QVector3D> receivingSliced = ShadowmapHelpers::intersectBoxByFrustum(frustumPoints,
+ QList<QVector3D> receivingSliced = ShadowmapHelpers::intersectBoxByFrustum(frustumPointsSliced,
receivingBox.toQSSGBoxPoints(),
drawSceneCascadeIntersection ? debugDrawSystem : nullptr,
QColorConstants::DarkGray);
@@ -276,6 +226,9 @@ static std::unique_ptr<QSSGRenderCamera> computeShadowCameraFromFrustum(const QS
castReceiveBounds.minimum.setZ(zMin);
}
+ // Expand to fit pcf radius
+ castReceiveBounds.fatten(pcfRadius);
+
QVector3D boundsCenterWorld = lightMatrixInverted.map(castReceiveBounds.center());
QVector3D boundsDims = castReceiveBounds.dimensions();
boundsDims.setZ(boundsDims.z() * 1.01f); // Expand slightly in z direction to avoid pancaking precision errors
@@ -319,6 +272,8 @@ static QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> setupCascadingCamer
const QSSGRenderLight *inLight,
const int shadowMapResolution,
const float pcfRadius,
+ const float clipNear,
+ const float clipFar,
const QSSGBounds3 &castingObjectsBox,
const QSSGBounds3 &receivingObjectsBox,
bool lockShadowmapTexels,
@@ -329,6 +284,9 @@ static QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> setupCascadingCamer
Q_ASSERT(inLight->type == QSSGRenderLight::Type::DirectionalLight);
QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> result;
+ if (clipNear >= clipFar || qFuzzyCompare(clipNear, clipFar))
+ return result;
+
const QVector3D lightDir = inLight->getDirection();
const QVector3D lightPivot = inLight->pivot;
@@ -345,26 +303,17 @@ static QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> setupCascadingCamer
lightMatrix.setRow(3, QVector4D(0.0f, 0.0f, 0.0f, 1.0f));
QMatrix4x4 lightMatrixInverted = lightMatrix.inverted();
- const float clipFar = qMax(2.0f, qMin(inLight->m_shadowMapFar, inCamera.clipFar));
- constexpr float clipNear = 1.0f;
- const float clipRange = clipFar - clipNear;
+ const float farScale = (clipFar - clipNear) / (inCamera.clipFar - inCamera.clipNear);
+
+ QMatrix4x4 viewProjection(Qt::Uninitialized);
+ inCamera.calculateViewProjectionMatrix(viewProjection);
+ const QSSGBoxPoints frustum = computeFrustumBounds(viewProjection);
+ const QSSGBoxPoints frustumUntransformed = lockShadowmapTexels ? computeFrustumBounds(inCamera.projection) : QSSGBoxPoints();
// We calculate the radius of the cascade without rotation or translation so we always get
// the same floating point value.
const auto calcFrustumRadius = [&](float t0, float t1) -> float {
- const float f = clipNear + clipRange * t1;
- const float n = clipNear + clipRange * t0;
- QMatrix4x4 proj = inCamera.projection;
- proj(2, 2) = -(f + n) / (f - n);
- proj(2, 3) = -2 * f * n / (f - n);
- bool invertible = false;
- QMatrix4x4 inv = proj.inverted(&invertible);
- Q_ASSERT(invertible);
-
- QSSGBoxPoints pts = { inv.map(QVector3D(-1, -1, -1)), inv.map(QVector3D(+1, -1, -1)),
- inv.map(QVector3D(+1, +1, -1)), inv.map(QVector3D(-1, +1, -1)),
- inv.map(QVector3D(-1, -1, +1)), inv.map(QVector3D(+1, -1, +1)),
- inv.map(QVector3D(+1, +1, +1)), inv.map(QVector3D(-1, +1, +1)) };
+ const QSSGBoxPoints pts = sliceFrustum(frustumUntransformed, t0 * farScale, t1 * farScale);
QVector3D center = QVector3D(0.f, 0.f, 0.f);
for (QVector3D point : pts) {
@@ -396,17 +345,16 @@ static QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> setupCascadingCamer
const auto computeFrustums = [&](const QVarLengthArray<float, 3> &splits) {
for (const auto &range : computeSplitRanges(splits)) {
const float frustumRadius = lockShadowmapTexels ? calcFrustumRadius(range.first, range.second) : 0.0f;
- auto camera = computeShadowCameraFromFrustum(inCamera,
- lightMatrix,
+ auto camera = computeShadowCameraFromFrustum(lightMatrix,
lightMatrixInverted,
lightPivot,
forward,
up,
shadowMapResolution,
pcfRadius,
- clipRange,
- range.first,
- range.second,
+ frustum,
+ range.first * farScale,
+ range.second * farScale,
frustumRadius,
lockShadowmapTexels,
castingObjectsBox,
@@ -1502,24 +1450,28 @@ void RenderHelpers::rhiRenderShadowMap(QSSGRhiContext *rhiCtx,
QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> cascades;
if (light->type == QSSGRenderLight::Type::DirectionalLight) {
const float pcfRadius = light->m_softShadowQuality == QSSGRenderLight::SoftShadowQuality::Hard ? 0.f : light->m_pcfFactor;
+ const float clipNear = camera.clipNear;
+ const float clipFar = qMin(light->m_shadowMapFar, camera.clipFar);
+ const float clipRange = clipFar - clipNear;
cascades = setupCascadingCamerasForShadowMap(disableShadowCameraUpdate ? *debugCamera : camera,
light,
size.width(),
pcfRadius,
+ clipNear,
+ clipFar,
castingObjectsBox,
receivingObjectsBox,
light->m_lockShadowmapTexels,
debugDrawSystem,
drawCascades,
drawSceneCascadeIntersection);
- const float shadowMapFar = qMax(2.0f, qMin(light->m_shadowMapFar, camera.clipFar));
// Write the split distances from value 0 in the z-axis of the eye view-space
- pEntry->m_csmSplits[0] = shadowMapFar * (light->m_csmNumSplits > 0 ? light->m_csmSplit1 : 1.0f);
- pEntry->m_csmSplits[1] = shadowMapFar * (light->m_csmNumSplits > 1 ? light->m_csmSplit2 : 1.0f);
- pEntry->m_csmSplits[2] = shadowMapFar * (light->m_csmNumSplits > 2 ? light->m_csmSplit3 : 1.0f);
- pEntry->m_csmSplits[3] = shadowMapFar * 1.0f;
- pEntry->m_shadowMapFar = shadowMapFar;
+ pEntry->m_csmSplits[0] = clipNear + clipRange * (light->m_csmNumSplits > 0 ? light->m_csmSplit1 : 1.0f);
+ pEntry->m_csmSplits[1] = clipNear + clipRange * (light->m_csmNumSplits > 1 ? light->m_csmSplit2 : 1.0f);
+ pEntry->m_csmSplits[2] = clipNear + clipRange * (light->m_csmNumSplits > 2 ? light->m_csmSplit3 : 1.0f);
+ pEntry->m_csmSplits[3] = clipNear + clipRange * 1.0f;
+ pEntry->m_shadowMapFar = clipFar;
} else if (light->type == QSSGRenderLight::Type::SpotLight) {
auto spotlightCamera = std::make_unique<QSSGRenderCamera>(QSSGRenderCamera::Type::PerspectiveCamera);
spotlightCamera->fov = qDegreesToRadians(light->m_coneAngle * 2.0f);
@@ -1566,8 +1518,11 @@ void RenderHelpers::rhiRenderShadowMap(QSSGRhiContext *rhiCtx,
cb->endPass();
QSSGRHICTX_STAT(rhiCtx, endRenderPass());
- if (drawDirectionalLightShadowBoxes)
- ShadowmapHelpers::addDirectionalLightDebugBox(computeFrustumBounds(*cascadeCamera), debugDrawSystem);
+ if (drawDirectionalLightShadowBoxes) {
+ QMatrix4x4 viewProjection(Qt::Uninitialized);
+ cascadeCamera->calculateViewProjectionMatrix(viewProjection);
+ ShadowmapHelpers::addDirectionalLightDebugBox(computeFrustumBounds(viewProjection), debugDrawSystem);
+ }
}
Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("shadow_map"));
} else {
diff --git a/tests/baseline/data/shadows/CSMConesCylindersHighNear.qml b/tests/baseline/data/shadows/CSMConesCylindersHighNear.qml
new file mode 100644
index 00000000..7260ee5e
--- /dev/null
+++ b/tests/baseline/data/shadows/CSMConesCylindersHighNear.qml
@@ -0,0 +1,218 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick3D
+import QtQuick3D.Helpers
+
+Item {
+ id: window
+ visible: true
+ width: 1200
+ height: 720
+
+ PerspectiveCamera {
+ id: camera1
+ position: Qt.vector3d(76.1126, 982.409, 1126.55)
+ eulerRotation: Qt.vector3d(-25.3464, -1.051, 0)
+ clipFar: 15000
+ clipNear: 2000
+ }
+
+ SceneEnvironment {
+ id: environment1
+ clearColor: "lightblue"
+ backgroundMode: SceneEnvironment.Color
+ antialiasingMode: SceneEnvironment.MSAA
+ antialiasingQuality: SceneEnvironment.High
+ }
+
+ property list<int> offsets: [0, width/4, 2*width/4, 3*width/4]
+
+ function addConesCylindersTriple(parentNode, z_pos) {
+ const newObject = Qt.createQmlObject(`
+ import QtQuick
+ import QtQuick3D
+
+ Node {
+ property var z_positions: [0, 100, 200]
+
+ PrincipledMaterial {
+ id: material
+ baseColor: "gray"
+ }
+
+ Model {
+ source: "#Cone"
+ position: Qt.vector3d(0, 450, z_positions[0])
+ eulerRotation.z: 180
+ scale.y: 5
+ materials: material
+ }
+
+ Model {
+ source: "#Cone"
+ position.z: z_positions[1]
+ scale.y: 2.5
+ materials: material
+ }
+
+ Model {
+ source: "#Cylinder"
+ position: Qt.vector3d(0, 175, z_positions[2])
+ materials: material
+ scale.y: 3.5
+ }
+ }
+ `,
+ parentNode,
+ "ConesCylinders"
+ );
+ newObject.z_positions = [z_pos, z_pos - 125, z_pos - 250];
+ }
+
+ View3D {
+ width: parent.width/4
+ height: parent.height
+ x: offsets[0]
+ camera: camera1
+ environment: environment1
+ DirectionalLight {
+ castsShadow: true
+ shadowFactor: 100
+ eulerRotation: Qt.vector3d(-40, -120, 0)
+ csmNumSplits: 0
+ shadowMapQuality: Light.ShadowMapQualityHigh
+ }
+
+ Model {
+ source: "#Cube"
+ scale: Qt.vector3d(25, 0.01, 135)
+ z: -5500
+ materials: DefaultMaterial {
+ diffuseColor: "gray"
+ }
+ castsShadows: false
+ }
+
+ Node {
+ Component.onCompleted: {
+ var z_pos = 0
+ for (var i = 0; i < 25; i++) {
+ addConesCylindersTriple(this, z_pos)
+ z_pos -= 450
+ }
+ }
+ }
+ }
+
+ View3D {
+ width: parent.width/4
+ height: parent.height
+ x: offsets[1]
+
+ camera: camera1
+ environment: environment1
+ DirectionalLight {
+ castsShadow: true
+ shadowFactor: 100
+ eulerRotation: Qt.vector3d(-40, -120, 0)
+ csmNumSplits: 1
+ shadowMapQuality: Light.ShadowMapQualityHigh
+ }
+
+ Model {
+ source: "#Cube"
+ scale: Qt.vector3d(25, 0.01, 135)
+ z: -5500
+ materials: DefaultMaterial {
+ diffuseColor: "gray"
+ }
+ castsShadows: false
+ }
+
+ Node {
+ Component.onCompleted: {
+ var z_pos = 0
+ for (var i = 0; i < 25; i++) {
+ addConesCylindersTriple(this, z_pos)
+ z_pos -= 450
+ }
+ }
+ }
+ }
+
+ View3D {
+ width: parent.width/4
+ height: parent.height
+ x: offsets[2]
+
+ camera: camera1
+ environment: environment1
+ DirectionalLight {
+ castsShadow: true
+ shadowFactor: 100
+ eulerRotation: Qt.vector3d(-40, -120, 0)
+ csmNumSplits: 2
+ shadowMapQuality: Light.ShadowMapQualityHigh
+ }
+
+ Model {
+ source: "#Cube"
+ scale: Qt.vector3d(25, 0.01, 135)
+ z: -5500
+ materials: DefaultMaterial {
+ diffuseColor: "gray"
+ }
+ castsShadows: false
+ }
+
+ Node {
+ Component.onCompleted: {
+ var z_pos = 0
+ for (var i = 0; i < 25; i++) {
+ addConesCylindersTriple(this, z_pos)
+ z_pos -= 450
+ }
+ }
+ }
+ }
+
+ View3D {
+ width: parent.width/4
+ height: parent.height
+ x: offsets[3]
+
+ camera: camera1
+ environment: environment1
+ DirectionalLight {
+ castsShadow: true
+ shadowFactor: 100
+ eulerRotation: Qt.vector3d(-40, -120, 0)
+ csmNumSplits: 3
+ shadowMapQuality: Light.ShadowMapQualityHigh
+ }
+
+ Model {
+ source: "#Cube"
+ scale: Qt.vector3d(25, 0.01, 135)
+ z: -5500
+ materials: DefaultMaterial {
+ diffuseColor: "gray"
+ }
+ castsShadows: false
+ }
+
+ Node {
+ Component.onCompleted: {
+ var z_pos = 0
+ for (var i = 0; i < 25; i++) {
+ addConesCylindersTriple(this, z_pos)
+ z_pos -= 450
+ }
+ }
+ }
+ }
+}
diff --git a/tests/baseline/data/shadows/CustomCamera.qml b/tests/baseline/data/shadows/CustomCamera.qml
new file mode 100644
index 00000000..94a5c436
--- /dev/null
+++ b/tests/baseline/data/shadows/CustomCamera.qml
@@ -0,0 +1,117 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick3D
+import QtQuick3D.Helpers
+
+Item {
+ id: window
+ visible: true
+ width: 1200
+ height: 720
+
+ SceneEnvironment {
+ id: sceneEnvironment
+ clearColor: "lightblue"
+ backgroundMode: SceneEnvironment.Color
+ antialiasingMode: SceneEnvironment.MSAA
+ antialiasingQuality: SceneEnvironment.High
+ }
+
+ CustomCamera {
+ id: camera1
+ eulerRotation: Qt.vector3d(-10.4172, 7.45044, 0)
+ position: Qt.vector3d(381.119, 842.535, 955.253)
+
+ property real near: 10.0
+ property real far: 10000.0
+ property real fov: 60.0 * Math.PI / 180.0
+ projection: Qt.matrix4x4(Math.cos(fov / 2) / Math.sin(fov / 2) * (window.height / window.width), 0, 0, 0,
+ 0, Math.cos(fov / 2) / Math.sin(fov / 2), 0, 0,
+ 0, 0, -(near + far) / (far - near), -(2.0 * near * far) / (far - near),
+ 0, 0, -1, 0);
+ }
+
+ Node {
+ id: sceneA
+ DirectionalLight {
+ castsShadow: true
+ eulerRotation: Qt.vector3d(-40, -120, 0)
+ csmNumSplits: 2
+ shadowMapQuality: Light.ShadowMapQualityLow
+ csmBlendRatio: 0.05
+ shadowBias: 20
+ pcfFactor: 0
+ softShadowQuality: Light.Hard
+ shadowMapFar: camera1.far
+ }
+
+ Model {
+ id: ground
+ source: "#Cube"
+ scale: Qt.vector3d(25, 0.01, 135)
+ z: -5500
+ materials: DefaultMaterial {
+ diffuseColor: "gray"
+ }
+ castsShadows: false
+ }
+
+ Node {
+ Component.onCompleted: {
+
+ var z_pos = 0
+ for (var i = 0; i < 25; i++) {
+ var conesAndCylinderTrio = Qt.createQmlObject(`
+ import QtQuick
+ import QtQuick3D
+
+ Node {
+ property var z_positions: [` + z_pos + `,` + (z_pos -125) + `,` + (z_pos - 250) + `]
+
+ PrincipledMaterial {
+ id: material
+ baseColor: "gray"
+ }
+
+ Model {
+ source: "#Cone"
+ position: Qt.vector3d(0, 450, z_positions[0])
+ eulerRotation.z: 180
+ scale.y: 5
+ materials: material
+ }
+
+ Model {
+ source: "#Cone"
+ position.z: z_positions[1]
+ scale.y: 2.5
+ materials: material
+ }
+
+ Model {
+ source: "#Cylinder"
+ position: Qt.vector3d(0, 175, z_positions[2])
+ materials: material
+ scale.y: 3.5
+ }
+ }`,
+ this,
+ "snippet" + i
+ );
+ z_pos -= 450
+ }
+ }
+ }
+ }
+
+ View3D {
+ id: view
+ width: parent.width
+ height: parent.height
+ camera: camera1
+ environment: sceneEnvironment
+ importScene: sceneA
+ }
+}
diff --git a/tests/baseline/data/shadows/FrustumCamera.qml b/tests/baseline/data/shadows/FrustumCamera.qml
new file mode 100644
index 00000000..db72c53e
--- /dev/null
+++ b/tests/baseline/data/shadows/FrustumCamera.qml
@@ -0,0 +1,116 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick3D
+import QtQuick3D.Helpers
+
+Item {
+ id: window
+ visible: true
+ width: 1200
+ height: 720
+
+ SceneEnvironment {
+ id: sceneEnvironment
+ clearColor: "lightblue"
+ backgroundMode: SceneEnvironment.Color
+ antialiasingMode: SceneEnvironment.MSAA
+ antialiasingQuality: SceneEnvironment.High
+ }
+
+ FrustumCamera {
+ id: camera1
+ eulerRotation: Qt.vector3d(-10.4172, 7.45044, 0)
+ position: Qt.vector3d(381.119, 842.535, 955.253)
+
+ clipNear: 100
+ clipFar: 10000
+ top: 10
+ bottom: -10
+ left: -10
+ right: 10
+ }
+
+ Node {
+ id: sceneA
+ DirectionalLight {
+ castsShadow: true
+ eulerRotation: Qt.vector3d(-40, -120, 0)
+ csmNumSplits: 2
+ shadowMapQuality: Light.ShadowMapQualityLow
+ csmBlendRatio: 0.05
+ shadowBias: 20
+ pcfFactor: 0
+ softShadowQuality: Light.Hard
+ shadowMapFar: camera1.clipFar
+ }
+
+ Model {
+ id: ground
+ source: "#Cube"
+ scale: Qt.vector3d(25, 0.01, 135)
+ z: -5500
+ materials: DefaultMaterial {
+ diffuseColor: "gray"
+ }
+ castsShadows: false
+ }
+
+ Node {
+ Component.onCompleted: {
+
+ var z_pos = 0
+ for (var i = 0; i < 25; i++) {
+ var conesAndCylinderTrio = Qt.createQmlObject(`
+ import QtQuick
+ import QtQuick3D
+
+ Node {
+ property var z_positions: [` + z_pos + `,` + (z_pos -125) + `,` + (z_pos - 250) + `]
+
+ PrincipledMaterial {
+ id: material
+ baseColor: "gray"
+ }
+
+ Model {
+ source: "#Cone"
+ position: Qt.vector3d(0, 450, z_positions[0])
+ eulerRotation.z: 180
+ scale.y: 5
+ materials: material
+ }
+
+ Model {
+ source: "#Cone"
+ position.z: z_positions[1]
+ scale.y: 2.5
+ materials: material
+ }
+
+ Model {
+ source: "#Cylinder"
+ position: Qt.vector3d(0, 175, z_positions[2])
+ materials: material
+ scale.y: 3.5
+ }
+ }`,
+ this,
+ "snippet" + i
+ );
+ z_pos -= 450
+ }
+ }
+ }
+ }
+
+ View3D {
+ id: view
+ width: parent.width
+ height: parent.height
+ camera: camera1
+ environment: sceneEnvironment
+ importScene: sceneA
+ }
+}
diff --git a/tests/baseline/data/shadows/Orthographic.qml b/tests/baseline/data/shadows/Orthographic.qml
new file mode 100644
index 00000000..3befdd70
--- /dev/null
+++ b/tests/baseline/data/shadows/Orthographic.qml
@@ -0,0 +1,111 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick3D
+import QtQuick3D.Helpers
+
+Item {
+ id: window
+ visible: true
+ width: 1200
+ height: 720
+
+ SceneEnvironment {
+ id: sceneEnvironment
+ clearColor: "lightblue"
+ backgroundMode: SceneEnvironment.Color
+ antialiasingMode: SceneEnvironment.MSAA
+ antialiasingQuality: SceneEnvironment.High
+ }
+
+ OrthographicCamera {
+ id: camera1
+ verticalMagnification: 0.35
+ horizontalMagnification: 0.35
+ eulerRotation: Qt.vector3d(-10.4172, 7.45044, 0)
+ position: Qt.vector3d(381.119, 842.535, 955.253)
+ }
+
+ Node {
+ id: sceneA
+ DirectionalLight {
+ castsShadow: true
+ eulerRotation: Qt.vector3d(-40, -120, 0)
+ csmNumSplits: 2
+ shadowMapQuality: Light.ShadowMapQualityLow
+ csmBlendRatio: 0.05
+ shadowBias: 20
+ pcfFactor: 0
+ softShadowQuality: Light.Hard
+ shadowMapFar: camera1.clipFar
+ }
+
+ Model {
+ id: ground
+ source: "#Cube"
+ scale: Qt.vector3d(25, 0.01, 135)
+ z: -5500
+ materials: DefaultMaterial {
+ diffuseColor: "gray"
+ }
+ castsShadows: false
+ }
+
+ Node {
+ Component.onCompleted: {
+
+ var z_pos = 0
+ for (var i = 0; i < 25; i++) {
+ var conesAndCylinderTrio = Qt.createQmlObject(`
+ import QtQuick
+ import QtQuick3D
+
+ Node {
+ property var z_positions: [` + z_pos + `,` + (z_pos -125) + `,` + (z_pos - 250) + `]
+
+ PrincipledMaterial {
+ id: material
+ baseColor: "gray"
+ }
+
+ Model {
+ source: "#Cone"
+ position: Qt.vector3d(0, 450, z_positions[0])
+ eulerRotation.z: 180
+ scale.y: 5
+ materials: material
+ }
+
+ Model {
+ source: "#Cone"
+ position.z: z_positions[1]
+ scale.y: 2.5
+ materials: material
+ }
+
+ Model {
+ source: "#Cylinder"
+ position: Qt.vector3d(0, 175, z_positions[2])
+ materials: material
+ scale.y: 3.5
+ }
+ }`,
+ this,
+ "snippet" + i
+ );
+ z_pos -= 450
+ }
+ }
+ }
+ }
+
+ View3D {
+ id: view
+ width: parent.width
+ height: parent.height
+ camera: camera1
+ environment: sceneEnvironment
+ importScene: sceneA
+ }
+}
diff --git a/tests/baseline/data/shadows/OrthographicLockedTexels.qml b/tests/baseline/data/shadows/OrthographicLockedTexels.qml
new file mode 100644
index 00000000..a59bd78a
--- /dev/null
+++ b/tests/baseline/data/shadows/OrthographicLockedTexels.qml
@@ -0,0 +1,112 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick3D
+import QtQuick3D.Helpers
+
+Item {
+ id: window
+ visible: true
+ width: 1200
+ height: 720
+
+ SceneEnvironment {
+ id: sceneEnvironment
+ clearColor: "lightblue"
+ backgroundMode: SceneEnvironment.Color
+ antialiasingMode: SceneEnvironment.MSAA
+ antialiasingQuality: SceneEnvironment.High
+ }
+
+ OrthographicCamera {
+ id: camera1
+ verticalMagnification: 0.35
+ horizontalMagnification: 0.35
+ eulerRotation: Qt.vector3d(-10.4172, 7.45044, 0)
+ position: Qt.vector3d(381.119, 842.535, 955.253)
+ }
+
+ Node {
+ id: sceneA
+ DirectionalLight {
+ castsShadow: true
+ eulerRotation: Qt.vector3d(-40, -120, 0)
+ csmNumSplits: 2
+ shadowMapQuality: Light.ShadowMapQualityHigh
+ csmBlendRatio: 0.05
+ shadowBias: 20
+ pcfFactor: 0
+ softShadowQuality: Light.Hard
+ shadowMapFar: camera1.clipFar
+ lockShadowmapTexels: true
+ }
+
+ Model {
+ id: ground
+ source: "#Cube"
+ scale: Qt.vector3d(25, 0.01, 135)
+ z: -5500
+ materials: DefaultMaterial {
+ diffuseColor: "gray"
+ }
+ castsShadows: false
+ }
+
+ Node {
+ Component.onCompleted: {
+
+ var z_pos = 0
+ for (var i = 0; i < 25; i++) {
+ var conesAndCylinderTrio = Qt.createQmlObject(`
+ import QtQuick
+ import QtQuick3D
+
+ Node {
+ property var z_positions: [` + z_pos + `,` + (z_pos -125) + `,` + (z_pos - 250) + `]
+
+ PrincipledMaterial {
+ id: material
+ baseColor: "gray"
+ }
+
+ Model {
+ source: "#Cone"
+ position: Qt.vector3d(0, 450, z_positions[0])
+ eulerRotation.z: 180
+ scale.y: 5
+ materials: material
+ }
+
+ Model {
+ source: "#Cone"
+ position.z: z_positions[1]
+ scale.y: 2.5
+ materials: material
+ }
+
+ Model {
+ source: "#Cylinder"
+ position: Qt.vector3d(0, 175, z_positions[2])
+ materials: material
+ scale.y: 3.5
+ }
+ }`,
+ this,
+ "snippet" + i
+ );
+ z_pos -= 450
+ }
+ }
+ }
+ }
+
+ View3D {
+ id: view
+ width: parent.width
+ height: parent.height
+ camera: camera1
+ environment: sceneEnvironment
+ importScene: sceneA
+ }
+}