1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dspotlight_p.h"
5#include "qquick3dobject_p.h"
6
7#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
8
9#include "qquick3dnode_p_p.h"
10
11QT_BEGIN_NAMESPACE
12
13/*!
14 \qmltype SpotLight
15 \inherits Light
16 \inqmlmodule QtQuick3D
17 \brief Defines a spot light in the scene.
18 \since 5.15
19
20 The spot light emits light towards one direction in a cone shape, which is defined by the
21 \l {coneAngle} property. The light intensity diminishes when approaching the \l {coneAngle}.
22 The angle at which the light intensity starts to diminish is defined by \l {innerConeAngle}.
23 Both angles are defined in degrees.
24
25 Inside the \l {innerConeAngle}, the spot light behaves similarly to the point light.
26 There the light intensity diminishes according to inverse-square-law. However, the fade-off
27 (and range) can be controlled with the \l {constantFade}, \l {linearFade}, and
28 \l quadraticFade properties. Light attenuation is calculated using the formula:
29 \l {constantFade} + \c distance * (\l {linearFade} * 0.01) + \c distance * (\l {quadraticFade} * 0.0001)^2
30
31 Let's look at a simple example. Here a SpotLight is placed at 300 on the Z
32 axis, so halfway between the camera and the scene center. By default the
33 light is emitting in the direction of the Z axis. The \l {Light::}{brightness} is
34 increased to 10 to make it look more like a typical spot light.
35
36 \qml
37 import QtQuick
38 import QtQuick3D
39 View3D {
40 anchors.fill: parent
41
42 PerspectiveCamera { z: 600 }
43
44 SpotLight {
45 z: 300
46 brightness: 10
47 ambientColor: Qt.rgba(0.1, 0.1, 0.1, 1.0)
48 }
49
50 Model {
51 source: "#Rectangle"
52 scale: Qt.vector3d(10, 10, 10)
53 z: -100
54 materials: PrincipledMaterial { }
55 }
56
57 Model {
58 source: "#Sphere"
59 scale: Qt.vector3d(2, 2, 2)
60 materials: PrincipledMaterial {
61 baseColor: "#40c060"
62 roughness: 0.1
63 }
64 }
65 }
66 \endqml
67
68 \image spotlight-1.png
69
70 Rotations happens similarly to \l DirectionalLight. Here we want to light to
71 emit more to the right, so we rotate around the Y axis by -20 degrees. The
72 cone is reduced by setting coneAngle to 30 instead of the default 40. We
73 also make the intensity start diminish earlier, by changing innerConeAngle
74 to 10.
75
76 \qml
77 SpotLight {
78 z: 300
79 brightness: 10
80 ambientColor: Qt.rgba(0.1, 0.1, 0.1, 1.0)
81 eulerRotation.y: -20
82 coneAngle: 30
83 innerConeAngle: 10
84 }
85 \endqml
86
87 \image spotlight-2.png
88
89 For further usage examples, see \l{Qt Quick 3D - Lights Example}.
90
91 \sa DirectionalLight, PointLight
92*/
93
94/*!
95 \qmlproperty real SpotLight::constantFade
96
97 This property is constant factor of the attenuation term of the light.
98 The default value is 1.0.
99 */
100
101/*!
102 \qmlproperty real SpotLight::linearFade
103
104 This property increases the rate at which the lighting effect dims the light
105 in proportion to the distance to the light. The default value is \c 0.0, which means the light
106 doesn't have linear fade. The value used here is multiplied by \c 0.01 before being used to
107 calculate light attenuation.
108*/
109
110/*!
111 \qmlproperty real SpotLight::quadraticFade
112
113 This property increases the rate at which the lighting effect dims the light
114 in proportion to the inverse square law. The default value is 1.0, which means the spot light
115 fade exactly follows the inverse square law, i.e. when distance to an object doubles the
116 light intensity decreases to 1/4th. The value used here is multiplied by \c 0.0001 before
117 being used to calculate light attenuation.
118*/
119
120/*!
121 \qmlproperty real SpotLight::coneAngle
122
123 This property defines the cut-off angle (from edge to edge) beyond which the light doesn't affect the scene.
124 Defined in degrees between 0 and 180. The default value is 40.
125*/
126
127/*!
128 \qmlproperty real SpotLight::innerConeAngle
129
130 This property defines the angle (from edge to edge) at which the light intensity starts to gradually diminish
131 as it approaches \l {coneAngle}. Defined in degrees between 0 and 180. If the value is set
132 larger than \l {coneAngle}, it'll behave as if it had the same value as \l {coneAngle}.
133 The default value is 30.
134*/
135
136QQuick3DSpotLight::QQuick3DSpotLight(QQuick3DNode *parent)
137 : QQuick3DAbstractLight(*(new QQuick3DNodePrivate(QQuick3DNodePrivate::Type::SpotLight)), parent) {}
138
139float QQuick3DSpotLight::constantFade() const
140{
141 return m_constantFade;
142}
143
144float QQuick3DSpotLight::linearFade() const
145{
146 return m_linearFade;
147}
148
149float QQuick3DSpotLight::quadraticFade() const
150{
151 return m_quadraticFade;
152}
153
154float QQuick3DSpotLight::coneAngle() const
155{
156 return m_coneAngle;
157}
158
159float QQuick3DSpotLight::innerConeAngle() const
160{
161 return m_innerConeAngle;
162}
163
164void QQuick3DSpotLight::setConstantFade(float constantFade)
165{
166 if (qFuzzyCompare(p1: m_constantFade, p2: constantFade))
167 return;
168
169 m_constantFade = constantFade;
170 m_dirtyFlags.setFlag(flag: DirtyFlag::FadeDirty);
171 emit constantFadeChanged();
172 update();
173}
174
175void QQuick3DSpotLight::setLinearFade(float linearFade)
176{
177 if (qFuzzyCompare(p1: m_linearFade, p2: linearFade))
178 return;
179
180 m_linearFade = linearFade;
181 m_dirtyFlags.setFlag(flag: DirtyFlag::FadeDirty);
182 emit linearFadeChanged();
183 update();
184}
185
186void QQuick3DSpotLight::setQuadraticFade(float quadraticFade)
187{
188 if (qFuzzyCompare(p1: m_quadraticFade, p2: quadraticFade))
189 return;
190
191 m_quadraticFade = quadraticFade;
192 m_dirtyFlags.setFlag(flag: DirtyFlag::FadeDirty);
193 emit quadraticFadeChanged();
194 update();
195}
196
197void QQuick3DSpotLight::setConeAngle(float coneAngle)
198{
199 if (coneAngle < 0.f)
200 coneAngle = 0.f;
201 else if (coneAngle > 180.f)
202 coneAngle = 180.f;
203
204 if (qFuzzyCompare(p1: m_coneAngle, p2: coneAngle))
205 return;
206
207 m_coneAngle = coneAngle;
208 m_dirtyFlags.setFlag(flag: DirtyFlag::AreaDirty);
209 emit coneAngleChanged();
210 update();
211}
212
213void QQuick3DSpotLight::setInnerConeAngle(float innerConeAngle)
214{
215 if (innerConeAngle < 0.f)
216 innerConeAngle = 0.f;
217 else if (innerConeAngle > 180.f)
218 innerConeAngle = 180.f;
219
220 if (qFuzzyCompare(p1: m_innerConeAngle, p2: innerConeAngle))
221 return;
222
223 m_innerConeAngle = innerConeAngle;
224 m_dirtyFlags.setFlag(flag: DirtyFlag::AreaDirty);
225 emit innerConeAngleChanged();
226 update();
227}
228
229QSSGRenderGraphObject *QQuick3DSpotLight::updateSpatialNode(QSSGRenderGraphObject *node)
230{
231 if (!node) {
232 markAllDirty();
233 node = new QSSGRenderLight(QSSGRenderLight::Type::SpotLight);
234 }
235
236 QQuick3DAbstractLight::updateSpatialNode(node); // Marks the light node dirty if m_dirtyFlags != 0
237
238 QSSGRenderLight *light = static_cast<QSSGRenderLight *>(node);
239
240 if (m_dirtyFlags.testFlag(flag: DirtyFlag::FadeDirty)) {
241 m_dirtyFlags.setFlag(flag: DirtyFlag::FadeDirty, on: false);
242 light->m_constantFade = m_constantFade;
243 light->m_linearFade = m_linearFade;
244 light->m_quadraticFade = m_quadraticFade;
245 }
246
247 if (m_dirtyFlags.testFlag(flag: DirtyFlag::AreaDirty)) {
248 m_dirtyFlags.setFlag(flag: DirtyFlag::AreaDirty, on: false);
249 light->m_coneAngle = qBound(min: 0.0f, val: m_coneAngle * 0.5, max: 90.0f);
250 light->m_innerConeAngle = qBound(min: 0.0f, val: m_innerConeAngle * 0.5, max: 90.0f);
251 }
252
253 return node;
254}
255
256QT_END_NAMESPACE
257

source code of qtquick3d/src/quick3d/qquick3dspotlight.cpp