1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
3 | #include "BsRendererLight.h" |
4 | #include "Material/BsMaterial.h" |
5 | #include "Material/BsGpuParamsSet.h" |
6 | #include "RenderAPI/BsGpuBuffer.h" |
7 | #include "RenderAPI/BsGpuParams.h" |
8 | #include "Renderer/BsLight.h" |
9 | #include "Renderer/BsRendererUtility.h" |
10 | #include "BsRenderBeast.h" |
11 | #include "Shading/BsStandardDeferred.h" |
12 | |
13 | namespace bs { namespace ct |
14 | { |
15 | static const UINT32 LIGHT_DATA_BUFFER_INCREMENT = 16 * sizeof(LightData); |
16 | |
17 | RendererLight::RendererLight(Light* light) |
18 | :internal(light) |
19 | { } |
20 | |
21 | void RendererLight::getParameters(LightData& output) const |
22 | { |
23 | Radian spotAngle = Math::clamp(internal->getSpotAngle() * 0.5f, Degree(0), Degree(89)); |
24 | Radian spotFalloffAngle = Math::clamp(internal->getSpotFalloffAngle() * 0.5f, Degree(0), (Degree)spotAngle); |
25 | Color color = internal->getColor(); |
26 | |
27 | const Transform& tfrm = internal->getTransform(); |
28 | output.position = tfrm.getPosition(); |
29 | output.boundsRadius = internal->getBounds().getRadius(); |
30 | output.srcRadius = internal->getSourceRadius(); |
31 | output.direction = -tfrm.getRotation().zAxis(); |
32 | output.luminance = internal->getLuminance(); |
33 | output.spotAngles.x = spotAngle.valueRadians(); |
34 | output.spotAngles.y = Math::cos(output.spotAngles.x); |
35 | output.spotAngles.z = 1.0f / std::max(Math::cos(spotFalloffAngle) - output.spotAngles.y, 0.001f); |
36 | output.attRadiusSqrdInv = 1.0f / (internal->getAttenuationRadius() * internal->getAttenuationRadius()); |
37 | output.color = Vector3(color.r, color.g, color.b); |
38 | |
39 | // If directional lights, convert angular radius in degrees to radians |
40 | if (internal->getType() == LightType::Directional) |
41 | output.srcRadius *= Math::DEG2RAD; |
42 | |
43 | output.shiftedLightPosition = getShiftedLightPosition(); |
44 | } |
45 | |
46 | void RendererLight::getParameters(SPtr<GpuParamBlockBuffer>& buffer) const |
47 | { |
48 | LightData lightData; |
49 | getParameters(lightData); |
50 | |
51 | float type = 0.0f; |
52 | switch (internal->getType()) |
53 | { |
54 | case LightType::Directional: |
55 | type = 0; |
56 | break; |
57 | case LightType::Radial: |
58 | type = 0.3f; |
59 | break; |
60 | case LightType::Spot: |
61 | type = 0.8f; |
62 | break; |
63 | default: |
64 | break; |
65 | } |
66 | |
67 | gPerLightParamDef.gLightPositionAndSrcRadius.set(buffer, Vector4(lightData.position, lightData.srcRadius)); |
68 | gPerLightParamDef.gLightColorAndLuminance.set(buffer, Vector4(lightData.color, lightData.luminance)); |
69 | gPerLightParamDef.gLightSpotAnglesAndSqrdInvAttRadius.set(buffer, Vector4(lightData.spotAngles, lightData.attRadiusSqrdInv)); |
70 | gPerLightParamDef.gLightDirectionAndBoundRadius.set(buffer, Vector4(lightData.direction, lightData.boundsRadius)); |
71 | gPerLightParamDef.gShiftedLightPositionAndType.set(buffer, Vector4(lightData.shiftedLightPosition, type)); |
72 | |
73 | Vector4 lightGeometry; |
74 | lightGeometry.x = internal->getType() == LightType::Spot ? (float)Light::LIGHT_CONE_NUM_SIDES : 0; |
75 | lightGeometry.y = (float)Light::LIGHT_CONE_NUM_SLICES; |
76 | lightGeometry.z = internal->getBounds().getRadius(); |
77 | |
78 | float = lightData.srcRadius / Math::tan(lightData.spotAngles.x * 0.5f); |
79 | float coneRadius = Math::sin(lightData.spotAngles.x) * (internal->getAttenuationRadius() + extraRadius); |
80 | lightGeometry.w = coneRadius; |
81 | |
82 | gPerLightParamDef.gLightGeometry.set(buffer, lightGeometry); |
83 | |
84 | const Transform& tfrm = internal->getTransform(); |
85 | |
86 | Quaternion lightRotation(BsIdentity); |
87 | lightRotation.lookRotation(-tfrm.getRotation().zAxis()); |
88 | |
89 | Matrix4 transform = Matrix4::TRS(lightData.shiftedLightPosition, lightRotation, Vector3::ONE); |
90 | gPerLightParamDef.gMatConeTransform.set(buffer, transform); |
91 | } |
92 | |
93 | Vector3 RendererLight::getShiftedLightPosition() const |
94 | { |
95 | const Transform& tfrm = internal->getTransform(); |
96 | Vector3 direction = -tfrm.getRotation().zAxis(); |
97 | |
98 | // Create position for fake attenuation for area spot lights (with disc center) |
99 | if (internal->getType() == LightType::Spot) |
100 | return tfrm.getPosition() - direction * (internal->getSourceRadius() / Math::tan(internal->getSpotAngle() * 0.5f)); |
101 | else |
102 | return tfrm.getPosition(); |
103 | } |
104 | |
105 | GBufferParams::GBufferParams(GpuProgramType type, const SPtr<GpuParams>& gpuParams) |
106 | : mParams(gpuParams) |
107 | { |
108 | if(mParams->hasTexture(type, "gGBufferATex" )) |
109 | mParams->getTextureParam(type, "gGBufferATex" , mGBufferA); |
110 | |
111 | if(mParams->hasTexture(type, "gGBufferBTex" )) |
112 | mParams->getTextureParam(type, "gGBufferBTex" , mGBufferB); |
113 | |
114 | if(mParams->hasTexture(type, "gGBufferCTex" )) |
115 | mParams->getTextureParam(type, "gGBufferCTex" , mGBufferC); |
116 | |
117 | if(mParams->hasTexture(type, "gDepthBufferTex" )) |
118 | mParams->getTextureParam(type, "gDepthBufferTex" , mGBufferDepth); |
119 | |
120 | if(mParams->hasSamplerState(type, "gDepthBufferSamp" )) |
121 | { |
122 | GpuParamSampState samplerStateParam; |
123 | mParams->getSamplerStateParam(type, "gDepthBufferSamp" , samplerStateParam); |
124 | |
125 | SAMPLER_STATE_DESC desc; |
126 | desc.minFilter = FO_POINT; |
127 | desc.magFilter = FO_POINT; |
128 | desc.mipFilter = FO_POINT; |
129 | |
130 | SPtr<SamplerState> ss = SamplerState::create(desc); |
131 | samplerStateParam.set(ss); |
132 | } |
133 | } |
134 | |
135 | void GBufferParams::bind(const GBufferTextures& gbuffer) |
136 | { |
137 | mGBufferA.set(gbuffer.albedo); |
138 | mGBufferB.set(gbuffer.normals); |
139 | mGBufferC.set(gbuffer.roughMetal); |
140 | mGBufferDepth.set(gbuffer.depth); |
141 | } |
142 | |
143 | void ForwardLightingParams::populate(const SPtr<GpuParams>& params, bool clustered) |
144 | { |
145 | if (clustered) |
146 | { |
147 | params->getParamInfo()->getBindings( |
148 | GpuPipelineParamInfoBase::ParamType::ParamBlock, |
149 | "GridParams" , |
150 | gridParamsBindings |
151 | ); |
152 | |
153 | if (params->hasBuffer(GPT_FRAGMENT_PROGRAM, "gLights" )) |
154 | params->getBufferParam(GPT_FRAGMENT_PROGRAM, "gLights" , lightsBufferParam); |
155 | |
156 | if (params->hasBuffer(GPT_FRAGMENT_PROGRAM, "gGridLightOffsetsAndSize" )) |
157 | params->getBufferParam(GPT_FRAGMENT_PROGRAM, "gGridLightOffsetsAndSize" , |
158 | gridLightOffsetsAndSizeParam); |
159 | |
160 | if (params->hasBuffer(GPT_FRAGMENT_PROGRAM, "gLightIndices" )) |
161 | params->getBufferParam(GPT_FRAGMENT_PROGRAM, "gLightIndices" , gridLightIndicesParam); |
162 | |
163 | if (params->hasBuffer(GPT_FRAGMENT_PROGRAM, "gGridProbeOffsetsAndSize" )) |
164 | params->getBufferParam(GPT_FRAGMENT_PROGRAM, "gGridProbeOffsetsAndSize" , |
165 | gridProbeOffsetsAndSizeParam); |
166 | } |
167 | else |
168 | { |
169 | params->getParamInfo()->getBinding( |
170 | GPT_FRAGMENT_PROGRAM, |
171 | GpuPipelineParamInfoBase::ParamType::ParamBlock, |
172 | "Lights" , |
173 | lightsParamBlockBinding |
174 | ); |
175 | |
176 | params->getParamInfo()->getBinding( |
177 | GPT_FRAGMENT_PROGRAM, |
178 | GpuPipelineParamInfoBase::ParamType::ParamBlock, |
179 | "LightAndReflProbeParams" , |
180 | lightAndReflProbeParamsParamBlockBinding |
181 | ); |
182 | } |
183 | } |
184 | |
185 | VisibleLightData::VisibleLightData() |
186 | :mNumLights{}, mNumShadowedLights{} |
187 | { } |
188 | |
189 | void VisibleLightData::update(const SceneInfo& sceneInfo, const RendererViewGroup& viewGroup) |
190 | { |
191 | const VisibilityInfo& visibility = viewGroup.getVisibilityInfo(); |
192 | |
193 | for (UINT32 i = 0; i < (UINT32)LightType::Count; i++) |
194 | mVisibleLights[i].clear(); |
195 | |
196 | // Generate a list of lights and their GPU buffers |
197 | UINT32 numDirLights = (UINT32)sceneInfo.directionalLights.size(); |
198 | for (UINT32 i = 0; i < numDirLights; i++) |
199 | mVisibleLights[(UINT32)LightType::Directional].push_back(&sceneInfo.directionalLights[i]); |
200 | |
201 | UINT32 numRadialLights = (UINT32)sceneInfo.radialLights.size(); |
202 | for(UINT32 i = 0; i < numRadialLights; i++) |
203 | { |
204 | if (!visibility.radialLights[i]) |
205 | continue; |
206 | |
207 | mVisibleLights[(UINT32)LightType::Radial].push_back(&sceneInfo.radialLights[i]); |
208 | } |
209 | |
210 | UINT32 numSpotLights = (UINT32)sceneInfo.spotLights.size(); |
211 | for (UINT32 i = 0; i < numSpotLights; i++) |
212 | { |
213 | if (!visibility.spotLights[i]) |
214 | continue; |
215 | |
216 | mVisibleLights[(UINT32)LightType::Spot].push_back(&sceneInfo.spotLights[i]); |
217 | } |
218 | |
219 | for (UINT32 i = 0; i < (UINT32)LightType::Count; i++) |
220 | mNumLights[i] = (UINT32)mVisibleLights[i].size(); |
221 | |
222 | // Partition all visible lights so that unshadowed ones come first |
223 | auto partition = [](Vector<const RendererLight*>& entries) |
224 | { |
225 | UINT32 numUnshadowed = 0; |
226 | int first = -1; |
227 | for (UINT32 i = 0; i < (UINT32)entries.size(); ++i) |
228 | { |
229 | if(entries[i]->internal->getCastsShadow()) |
230 | { |
231 | first = i; |
232 | break; |
233 | } |
234 | else |
235 | ++numUnshadowed; |
236 | } |
237 | |
238 | if(first != -1) |
239 | { |
240 | for(UINT32 i = first + 1; i < (UINT32)entries.size(); ++i) |
241 | { |
242 | if(!entries[i]->internal->getCastsShadow()) |
243 | { |
244 | std::swap(entries[i], entries[first]); |
245 | ++numUnshadowed; |
246 | } |
247 | } |
248 | } |
249 | |
250 | return numUnshadowed; |
251 | }; |
252 | |
253 | for (UINT32 i = 0; i < (UINT32)LightType::Count; i++) |
254 | mNumShadowedLights[i] = mNumLights[i] - partition(mVisibleLights[i]); |
255 | |
256 | // Generate light data to initialize the GPU buffer with |
257 | mVisibleLightData.clear(); |
258 | for(auto& lightsPerType : mVisibleLights) |
259 | { |
260 | for(auto& entry : lightsPerType) |
261 | { |
262 | mVisibleLightData.push_back(LightData()); |
263 | entry->getParameters(mVisibleLightData.back()); |
264 | } |
265 | } |
266 | |
267 | bool supportsStructuredBuffers = gRenderBeast()->getFeatureSet() == RenderBeastFeatureSet::Desktop; |
268 | if(supportsStructuredBuffers) |
269 | { |
270 | UINT32 size = (UINT32) mVisibleLightData.size() * sizeof(LightData); |
271 | UINT32 curBufferSize; |
272 | |
273 | if (mLightBuffer != nullptr) |
274 | curBufferSize = mLightBuffer->getSize(); |
275 | else |
276 | curBufferSize = 0; |
277 | |
278 | if (size > curBufferSize || curBufferSize == 0) |
279 | { |
280 | // Allocate at least one block even if no lights, to avoid issues with null buffers |
281 | UINT32 bufferSize = std::max(1, Math::ceilToInt(size / (float) LIGHT_DATA_BUFFER_INCREMENT)) * LIGHT_DATA_BUFFER_INCREMENT; |
282 | |
283 | GPU_BUFFER_DESC bufferDesc; |
284 | bufferDesc.type = GBT_STRUCTURED; |
285 | bufferDesc.elementCount = bufferSize / sizeof(LightData); |
286 | bufferDesc.elementSize = sizeof(LightData); |
287 | bufferDesc.format = BF_UNKNOWN; |
288 | |
289 | mLightBuffer = GpuBuffer::create(bufferDesc); |
290 | } |
291 | |
292 | if (size > 0) |
293 | mLightBuffer->writeData(0, size, mVisibleLightData.data(), BWT_DISCARD); |
294 | } |
295 | } |
296 | |
297 | void VisibleLightData::gatherInfluencingLights(const Bounds& bounds, |
298 | const LightData* (&output)[STANDARD_FORWARD_MAX_NUM_LIGHTS], Vector3I& counts) const |
299 | { |
300 | UINT32 outputIndices[STANDARD_FORWARD_MAX_NUM_LIGHTS]; |
301 | UINT32 numInfluencingLights = 0; |
302 | |
303 | UINT32 numDirLights = getNumDirLights(); |
304 | for(UINT32 i = 0; i < numDirLights; i++) |
305 | { |
306 | if (numInfluencingLights >= STANDARD_FORWARD_MAX_NUM_LIGHTS) |
307 | return; |
308 | |
309 | outputIndices[numInfluencingLights] = i; |
310 | numInfluencingLights++; |
311 | } |
312 | |
313 | UINT32 pointLightOffset = numInfluencingLights; |
314 | |
315 | float distances[STANDARD_FORWARD_MAX_NUM_LIGHTS]; |
316 | for(UINT32 i = 0; i < STANDARD_FORWARD_MAX_NUM_LIGHTS; i++) |
317 | distances[i] = std::numeric_limits<float>::max(); |
318 | |
319 | // Note: This is an ad-hoc way of evaluating light influence, a better way might be wanted |
320 | UINT32 numLights = (UINT32)mVisibleLightData.size(); |
321 | UINT32 furthestLightIdx = (UINT32)-1; |
322 | float furthestDistance = 0.0f; |
323 | for (UINT32 j = numDirLights; j < numLights; j++) |
324 | { |
325 | const LightData* lightData = &mVisibleLightData[j]; |
326 | |
327 | Sphere lightSphere(lightData->position, lightData->boundsRadius); |
328 | if (bounds.getSphere().intersects(lightSphere)) |
329 | { |
330 | float distance = bounds.getSphere().getCenter().squaredDistance(lightData->position); |
331 | |
332 | // See where in the array can we fit the light |
333 | if (numInfluencingLights < STANDARD_FORWARD_MAX_NUM_LIGHTS) |
334 | { |
335 | outputIndices[numInfluencingLights] = j; |
336 | distances[numInfluencingLights] = distance; |
337 | |
338 | if (distance > furthestDistance) |
339 | { |
340 | furthestLightIdx = numInfluencingLights; |
341 | furthestDistance = distance; |
342 | } |
343 | |
344 | numInfluencingLights++; |
345 | } |
346 | else if (distance < furthestDistance) |
347 | { |
348 | outputIndices[furthestLightIdx] = j; |
349 | distances[furthestLightIdx] = distance; |
350 | |
351 | furthestDistance = distance; |
352 | for (UINT32 k = 0; k < STANDARD_FORWARD_MAX_NUM_LIGHTS; k++) |
353 | { |
354 | if (distances[k] > furthestDistance) |
355 | { |
356 | furthestDistance = distances[k]; |
357 | furthestLightIdx = k; |
358 | } |
359 | } |
360 | } |
361 | } |
362 | } |
363 | |
364 | // Output actual light data, sorted by type |
365 | counts = Vector3I(0, 0, 0); |
366 | |
367 | for(UINT32 i = 0; i < pointLightOffset; i++) |
368 | { |
369 | output[i] = &mVisibleLightData[outputIndices[i]]; |
370 | counts.x += 1; |
371 | } |
372 | |
373 | UINT32 outputIdx = pointLightOffset; |
374 | UINT32 spotLightIdx = getNumDirLights() + getNumRadialLights(); |
375 | for(UINT32 i = pointLightOffset; i < numInfluencingLights; i++) |
376 | { |
377 | bool isSpot = outputIndices[i] >= spotLightIdx; |
378 | if(isSpot) |
379 | continue; |
380 | |
381 | output[outputIdx++] = &mVisibleLightData[outputIndices[i]]; |
382 | counts.y += 1; |
383 | } |
384 | |
385 | for(UINT32 i = pointLightOffset; i < numInfluencingLights; i++) |
386 | { |
387 | bool isSpot = outputIndices[i] >= spotLightIdx; |
388 | if(!isSpot) |
389 | continue; |
390 | |
391 | output[outputIdx++] = &mVisibleLightData[outputIndices[i]]; |
392 | counts.z += 1; |
393 | } |
394 | } |
395 | |
396 | LightsParamDef gLightsParamDef; |
397 | LightAndReflProbeParamsParamDef gLightAndReflProbeParamsParamDef; |
398 | }} |