1 | // Copyright (C) 2019 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qquick3ddirectionallight_p.h" |
5 | #include "qquick3dobject_p.h" |
6 | |
7 | #include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h> |
8 | |
9 | #include "qquick3dnode_p_p.h" |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | /*! |
14 | \qmltype DirectionalLight |
15 | \inherits Light |
16 | \inqmlmodule QtQuick3D |
17 | \brief Defines a directional light in the scene. |
18 | |
19 | The directional light emits light in one direction from an unidentifiable source located |
20 | infinitely far away. This is similar to the way sunlight works in real life. A directional |
21 | light has infinite range and does not diminish. |
22 | |
23 | If \l {Light::castsShadow}{castsShadow} is enabled, shadows will be parallel to the light |
24 | direction. |
25 | |
26 | A directional light effectively have no position, so moving it does not |
27 | have any effect. The light will always be emitted in the direction of the |
28 | light's Z axis. |
29 | |
30 | Rotating the light along its X or Y axis will change the direction of the light emission. |
31 | |
32 | Scaling a directional light will only have an effect in the following cases: |
33 | \list |
34 | \li If Z scale is set to a negative number, the light will be emitted in the opposite direction. |
35 | \li If the scale of any axis is set to 0, the light will be emitted along the world's Z axis. |
36 | \note Rotating the light will then have no effect. |
37 | \endlist |
38 | |
39 | Let's look at a simple example: |
40 | |
41 | \qml |
42 | import QtQuick |
43 | import QtQuick3D |
44 | View3D { |
45 | anchors.fill: parent |
46 | |
47 | PerspectiveCamera { z: 600 } |
48 | |
49 | DirectionalLight { |
50 | } |
51 | |
52 | Model { |
53 | source: "#Sphere" |
54 | scale: Qt.vector3d(4, 4, 4) |
55 | materials: PrincipledMaterial { |
56 | baseColor: "#40c060" |
57 | roughness: 0.1 // make specular highlight visible |
58 | } |
59 | } |
60 | } |
61 | \endqml |
62 | |
63 | Here the DirectionalLight uses the default \c white color, emitting in the |
64 | direction of the DirectionalLight node's Z axis. |
65 | |
66 | \image directionallight-1.png |
67 | |
68 | Rotating 60 degrees around the X axis would lead to the following. Instead |
69 | of emitting straight in the direction of the Z axis, the light is now |
70 | pointing 60 degrees "down": |
71 | |
72 | \qml |
73 | DirectionalLight { |
74 | eulerRotation.x: 60 |
75 | } |
76 | \endqml |
77 | |
78 | \image directionallight-2.png |
79 | |
80 | For further usage examples, see \l{Qt Quick 3D - Lights Example}. |
81 | |
82 | \sa PointLight, SpotLight, {Shadow Mapping} |
83 | */ |
84 | |
85 | /*! |
86 | \qmlproperty float DirectionalLight::csmSplit1 |
87 | \since 6.8 |
88 | |
89 | This property defines where the first cascade of the shadow map split will occur when |
90 | CSM is active. |
91 | |
92 | Range: \c{[0.0, 1.0]} |
93 | |
94 | Default value: \c{0} |
95 | |
96 | \sa csmSplit2, csmSplit3 |
97 | \note This property is only used when DirectionalLight::csmNumSplits is greater than \c{0}. |
98 | */ |
99 | |
100 | /*! |
101 | \qmlproperty float DirectionalLight::csmSplit2 |
102 | \since 6.8 |
103 | |
104 | This property defines where the second cascade of the shadow map split will occur when |
105 | CSM is active. |
106 | |
107 | Range: \c{[0.0, 1.0]} |
108 | |
109 | Default value: \c{0.25} |
110 | |
111 | \sa csmSplit1, csmSplit3 |
112 | \note This property is only used when DirectionalLight::csmNumSplits is greater than \c{1}. |
113 | */ |
114 | |
115 | /*! |
116 | \qmlproperty float DirectionalLight::csmSplit3 |
117 | \since 6.8 |
118 | |
119 | This property defines where the third cascade of the shadow map split will occur when |
120 | CSM is active. |
121 | |
122 | Range: \c{[0.0, 1.0]} |
123 | |
124 | Default value: \c{0.5} |
125 | |
126 | \sa csmSplit1, csmSplit2 |
127 | \note This property is only used when DirectionalLight::csmNumSplits is greater than \c{2}. |
128 | */ |
129 | |
130 | /*! |
131 | \qmlproperty int DirectionalLight::csmNumSplits |
132 | \since 6.8 |
133 | |
134 | This property defines the number of splits the frustum should be split by when |
135 | rendering the shadowmap. No splits means that the shadowmap will be rendered |
136 | so that it covers the bounding box of all shadow casting and receiving objects. |
137 | |
138 | Range: \c{[0, 3]} |
139 | |
140 | Default value: \c{0} |
141 | |
142 | \sa csmSplit1, csmSplit2, csmSplit3 |
143 | */ |
144 | |
145 | /*! |
146 | \qmlproperty float DirectionalLight::csmBlendRatio |
147 | \since 6.8 |
148 | |
149 | This property defines how much of the shadow of any cascade should be blended |
150 | together with the previous one. |
151 | |
152 | Range: \c{[0.0, 1.0]} |
153 | |
154 | Default value: \c{0.05} |
155 | */ |
156 | |
157 | QQuick3DDirectionalLight::QQuick3DDirectionalLight(QQuick3DNode *parent) |
158 | : QQuick3DAbstractLight(*(new QQuick3DNodePrivate(QQuick3DNodePrivate::Type::DirectionalLight)), parent) {} |
159 | |
160 | float QQuick3DDirectionalLight::csmSplit1() const |
161 | { |
162 | return m_csmSplit1; |
163 | } |
164 | |
165 | float QQuick3DDirectionalLight::csmSplit2() const |
166 | { |
167 | return m_csmSplit2; |
168 | } |
169 | |
170 | float QQuick3DDirectionalLight::csmSplit3() const |
171 | { |
172 | return m_csmSplit3; |
173 | } |
174 | |
175 | int QQuick3DDirectionalLight::csmNumSplits() const |
176 | { |
177 | return m_csmNumSplits; |
178 | } |
179 | |
180 | float QQuick3DDirectionalLight::csmBlendRatio() const |
181 | { |
182 | return m_csmBlendRatio; |
183 | } |
184 | |
185 | void QQuick3DDirectionalLight::setCsmSplit1(float newcsmSplit1) |
186 | { |
187 | newcsmSplit1 = qBound(min: 0.0f, val: newcsmSplit1, max: 1.0f); |
188 | if (qFuzzyCompare(p1: m_csmSplit1, p2: newcsmSplit1)) |
189 | return; |
190 | |
191 | m_csmSplit1 = newcsmSplit1; |
192 | emit csmSplit1Changed(); |
193 | m_dirtyFlags.setFlag(flag: QQuick3DAbstractLight::DirtyFlag::ShadowDirty); |
194 | update(); |
195 | } |
196 | |
197 | void QQuick3DDirectionalLight::setCsmSplit2(float newcsmSplit2) |
198 | { |
199 | newcsmSplit2 = qBound(min: 0.0f, val: newcsmSplit2, max: 1.0f); |
200 | if (qFuzzyCompare(p1: m_csmSplit2, p2: newcsmSplit2)) |
201 | return; |
202 | |
203 | m_csmSplit2 = newcsmSplit2; |
204 | emit csmSplit2Changed(); |
205 | m_dirtyFlags.setFlag(flag: QQuick3DAbstractLight::DirtyFlag::ShadowDirty); |
206 | update(); |
207 | } |
208 | |
209 | void QQuick3DDirectionalLight::setCsmSplit3(float newcsmSplit3) |
210 | { |
211 | newcsmSplit3 = qBound(min: 0.0f, val: newcsmSplit3, max: 1.0f); |
212 | if (qFuzzyCompare(p1: m_csmSplit3, p2: newcsmSplit3)) |
213 | return; |
214 | |
215 | m_csmSplit3 = newcsmSplit3; |
216 | emit csmSplit3Changed(); |
217 | m_dirtyFlags.setFlag(flag: QQuick3DAbstractLight::DirtyFlag::ShadowDirty); |
218 | update(); |
219 | } |
220 | |
221 | void QQuick3DDirectionalLight::setCsmNumSplits(int newcsmNumSplits) |
222 | { |
223 | newcsmNumSplits = qBound(min: 0, val: newcsmNumSplits, max: 3); |
224 | if (m_csmNumSplits == newcsmNumSplits) |
225 | return; |
226 | |
227 | m_csmNumSplits = newcsmNumSplits; |
228 | emit csmNumSplitsChanged(); |
229 | m_dirtyFlags.setFlag(flag: QQuick3DAbstractLight::DirtyFlag::ShadowDirty); |
230 | update(); |
231 | } |
232 | |
233 | void QQuick3DDirectionalLight::setCsmBlendRatio(float newcsmBlendRatio) |
234 | { |
235 | newcsmBlendRatio = qBound(min: 0.0, val: newcsmBlendRatio, max: 1.0); |
236 | if (m_csmBlendRatio == newcsmBlendRatio) |
237 | return; |
238 | |
239 | m_csmBlendRatio = newcsmBlendRatio; |
240 | emit csmBlendRatioChanged(); |
241 | m_dirtyFlags.setFlag(flag: QQuick3DAbstractLight::DirtyFlag::ShadowDirty); |
242 | update(); |
243 | } |
244 | |
245 | QSSGRenderGraphObject *QQuick3DDirectionalLight::updateSpatialNode(QSSGRenderGraphObject *node) |
246 | { |
247 | if (!node) { |
248 | markAllDirty(); |
249 | node = new QSSGRenderLight(/* defaults to directional */); |
250 | } |
251 | |
252 | if (m_dirtyFlags.testFlag(flag: DirtyFlag::ShadowDirty)) { |
253 | QSSGRenderLight *light = static_cast<QSSGRenderLight *>(node); |
254 | light->m_csmSplit1 = m_csmSplit1; |
255 | light->m_csmSplit2 = m_csmSplit2; |
256 | light->m_csmSplit3 = m_csmSplit3; |
257 | light->m_csmNumSplits = m_csmNumSplits; |
258 | light->m_csmBlendRatio = m_csmBlendRatio; |
259 | } |
260 | |
261 | QQuick3DAbstractLight::updateSpatialNode(node); // Marks the light node dirty if m_dirtyFlags != 0 |
262 | |
263 | return node; |
264 | } |
265 | |
266 | QT_END_NAMESPACE |
267 | |