| 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 | }} |