rendering: formalize main light shadow bias settings

This commit is contained in:
2026-04-13 01:40:29 +08:00
parent 2ee74e7761
commit 1d6f2e290d
11 changed files with 113 additions and 26 deletions

View File

@@ -65,7 +65,6 @@ Shader "Builtin Shadow Caster"
Cull Back Cull Back
ZWrite On ZWrite On
ZTest LEqual ZTest LEqual
Offset 1.0, 2
HLSLPROGRAM HLSLPROGRAM
#pragma target 4.5 #pragma target 4.5
#pragma vertex MainVS #pragma vertex MainVS

View File

@@ -5,6 +5,7 @@
#include <XCEngine/Core/Math/Vector4.h> #include <XCEngine/Core/Math/Vector4.h>
#include <XCEngine/Rendering/FrameData/RenderCameraData.h> #include <XCEngine/Rendering/FrameData/RenderCameraData.h>
#include <XCEngine/Rendering/FrameData/RenderEnvironmentData.h> #include <XCEngine/Rendering/FrameData/RenderEnvironmentData.h>
#include <XCEngine/Rendering/Shadow/DirectionalShadowSettings.h>
#include <XCEngine/Rendering/FrameData/VisibleGaussianSplatItem.h> #include <XCEngine/Rendering/FrameData/VisibleGaussianSplatItem.h>
#include <XCEngine/Rendering/FrameData/VisibleRenderItem.h> #include <XCEngine/Rendering/FrameData/VisibleRenderItem.h>
#include <XCEngine/Rendering/FrameData/VisibleVolumeItem.h> #include <XCEngine/Rendering/FrameData/VisibleVolumeItem.h>
@@ -63,20 +64,23 @@ static_assert(
struct RenderDirectionalShadowSamplingData { struct RenderDirectionalShadowSamplingData {
float enabled = 0.0f; float enabled = 0.0f;
float receiverDepthBias = 0.0f; DirectionalShadowSamplingSettings settings = {};
float normalBiasScale = 0.0f;
float shadowStrength = 0.0f;
}; };
static_assert( static_assert(
sizeof(RenderDirectionalShadowSamplingData) == sizeof(float) * 4u, sizeof(RenderDirectionalShadowSamplingData) == sizeof(float) * 4u,
"RenderDirectionalShadowSamplingData must stay float4-sized for GPU constant layout"); "RenderDirectionalShadowSamplingData must stay float4-sized for GPU constant layout");
struct RenderDirectionalShadowCasterBiasData {
DirectionalShadowCasterBiasSettings settings = {};
};
struct RenderDirectionalShadowData { struct RenderDirectionalShadowData {
bool enabled = false; bool enabled = false;
Math::Matrix4x4 viewProjection = Math::Matrix4x4::Identity(); Math::Matrix4x4 viewProjection = Math::Matrix4x4::Identity();
RenderDirectionalShadowMapMetrics mapMetrics = {}; RenderDirectionalShadowMapMetrics mapMetrics = {};
RenderDirectionalShadowSamplingData sampling = {}; RenderDirectionalShadowSamplingData sampling = {};
RenderDirectionalShadowCasterBiasData casterBias = {};
RHI::RHIResourceView* shadowMap = nullptr; RHI::RHIResourceView* shadowMap = nullptr;
bool IsValid() const { bool IsValid() const {

View File

@@ -171,6 +171,10 @@ private:
ResolvedShaderPass ResolveSurfaceShaderPass( ResolvedShaderPass ResolveSurfaceShaderPass(
const RenderSceneData& sceneData, const RenderSceneData& sceneData,
const Resources::Material* material) const; const Resources::Material* material) const;
Resources::MaterialRenderState ResolveEffectiveDepthRenderState(
const Resources::ShaderPass* shaderPass,
const Resources::Material* material,
const RenderSceneData& sceneData) const;
bool TryBuildSupportedBindingPlan( bool TryBuildSupportedBindingPlan(
const Resources::ShaderPass& shaderPass, const Resources::ShaderPass& shaderPass,
BuiltinPassResourceBindingPlan& outPlan, BuiltinPassResourceBindingPlan& outPlan,

View File

@@ -5,6 +5,7 @@
#include <XCEngine/Rendering/RenderContext.h> #include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderPass.h> #include <XCEngine/Rendering/RenderPass.h>
#include <XCEngine/Rendering/RenderSurface.h> #include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Shadow/DirectionalShadowSettings.h>
#include <array> #include <array>
#include <cstdint> #include <cstdint>
@@ -131,6 +132,8 @@ struct DirectionalShadowRenderPlan {
float texelWorldSize = 0.0f; float texelWorldSize = 0.0f;
float nearClipPlane = 0.1f; float nearClipPlane = 0.1f;
float farClipPlane = 0.0f; float farClipPlane = 0.0f;
DirectionalShadowSamplingSettings sampling = {};
DirectionalShadowCasterBiasSettings casterBias = {};
uint32_t mapWidth = 0; uint32_t mapWidth = 0;
uint32_t mapHeight = 0; uint32_t mapHeight = 0;
RenderCameraData cameraData = {}; RenderCameraData cameraData = {};

View File

@@ -12,9 +12,9 @@ class Scene;
namespace Rendering { namespace Rendering {
// Planning-only knobs for fitting the single main-light shadow camera to the // Planner-owned knobs for the single main-light shadow, including both camera
// current view. Receiver-side bias/filtering parameters live in runtime shadow // fitting and the default sampling / caster bias values emitted into the
// data and are not configured here. // runtime shadow contract.
struct DirectionalShadowPlanningSettings { struct DirectionalShadowPlanningSettings {
uint32_t mapDimension = 1024u; uint32_t mapDimension = 1024u;
float minFocusDistance = 5.0f; float minFocusDistance = 5.0f;
@@ -24,6 +24,8 @@ struct DirectionalShadowPlanningSettings {
float minDepthRange = 20.0f; float minDepthRange = 20.0f;
float boundsPadding = 1.0f; float boundsPadding = 1.0f;
float minDepthPadding = 2.0f; float minDepthPadding = 2.0f;
DirectionalShadowSamplingSettings sampling = {};
DirectionalShadowCasterBiasSettings casterBias = {};
}; };
class SceneRenderRequestPlanner { class SceneRenderRequestPlanner {

View File

@@ -0,0 +1,20 @@
#pragma once
#include <cstdint>
namespace XCEngine {
namespace Rendering {
struct DirectionalShadowSamplingSettings {
float receiverDepthBias = 0.0015f;
float normalBiasScale = 1.5f;
float shadowStrength = 0.85f;
};
struct DirectionalShadowCasterBiasSettings {
float depthBiasFactor = 1.0f;
int32_t depthBiasUnits = 2;
};
} // namespace Rendering
} // namespace XCEngine

View File

@@ -19,10 +19,6 @@ namespace Rendering {
namespace { namespace {
constexpr float kDirectionalShadowReceiverDepthBias = 0.0015f;
constexpr float kDirectionalShadowNormalBiasScale = 1.5f;
constexpr float kDirectionalShadowStrength = 0.85f;
std::shared_ptr<const RenderPipelineAsset> CreateDefaultPipelineAsset() { std::shared_ptr<const RenderPipelineAsset> CreateDefaultPipelineAsset() {
static const std::shared_ptr<const RenderPipelineAsset> s_defaultPipelineAsset = static const std::shared_ptr<const RenderPipelineAsset> s_defaultPipelineAsset =
std::make_shared<Pipelines::BuiltinForwardPipelineAsset>(); std::make_shared<Pipelines::BuiltinForwardPipelineAsset>();
@@ -395,9 +391,8 @@ RenderDirectionalShadowData BuildDirectionalShadowData(
1.0f / static_cast<float>(plan.mapHeight)); 1.0f / static_cast<float>(plan.mapHeight));
shadowData.mapMetrics.worldTexelSize = texelWorldSize; shadowData.mapMetrics.worldTexelSize = texelWorldSize;
shadowData.sampling.enabled = 1.0f; shadowData.sampling.enabled = 1.0f;
shadowData.sampling.receiverDepthBias = kDirectionalShadowReceiverDepthBias; shadowData.sampling.settings = plan.sampling;
shadowData.sampling.normalBiasScale = kDirectionalShadowNormalBiasScale; shadowData.casterBias.settings = plan.casterBias;
shadowData.sampling.shadowStrength = kDirectionalShadowStrength;
return shadowData; return shadowData;
} }

View File

@@ -108,7 +108,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
const Resources::ShaderPass& shaderPass, const Resources::ShaderPass& shaderPass,
const Containers::String& passName, const Containers::String& passName,
const Resources::ShaderKeywordSet& keywordSet, const Resources::ShaderKeywordSet& keywordSet,
const Resources::Material* material, const Resources::MaterialRenderState& renderState,
const RenderSurface& surface, const RenderSurface& surface,
const RHI::InputLayoutDesc& inputLayout) { const RHI::InputLayoutDesc& inputLayout) {
RHI::GraphicsPipelineDesc pipelineDesc = {}; RHI::GraphicsPipelineDesc pipelineDesc = {};
@@ -116,7 +116,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle); pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
::XCEngine::Rendering::Internal::ApplySurfacePropertiesToGraphicsPipelineDesc(surface, pipelineDesc); ::XCEngine::Rendering::Internal::ApplySurfacePropertiesToGraphicsPipelineDesc(surface, pipelineDesc);
pipelineDesc.inputLayout = inputLayout; pipelineDesc.inputLayout = inputLayout;
ApplyResolvedRenderState(&shaderPass, material, pipelineDesc); ApplyRenderState(renderState, pipelineDesc);
if (!shaderPass.hasFixedFunctionState) { if (!shaderPass.hasFixedFunctionState) {
pipelineDesc.blendState.blendEnable = false; pipelineDesc.blendState.blendEnable = false;
@@ -345,6 +345,20 @@ BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::Resolve
return {}; return {};
} }
Resources::MaterialRenderState BuiltinDepthStylePassBase::ResolveEffectiveDepthRenderState(
const Resources::ShaderPass* shaderPass,
const Resources::Material* material,
const RenderSceneData& sceneData) const {
Resources::MaterialRenderState renderState = ResolveEffectiveRenderState(shaderPass, material);
if (m_passType == BuiltinMaterialPass::ShadowCaster) {
renderState.depthBiasFactor =
sceneData.lighting.mainDirectionalShadow.casterBias.settings.depthBiasFactor;
renderState.depthBiasUnits =
sceneData.lighting.mainDirectionalShadow.casterBias.settings.depthBiasUnits;
}
return renderState;
}
bool BuiltinDepthStylePassBase::TryBuildSupportedBindingPlan( bool BuiltinDepthStylePassBase::TryBuildSupportedBindingPlan(
const Resources::ShaderPass& shaderPass, const Resources::ShaderPass& shaderPass,
BuiltinPassResourceBindingPlan& outPlan, BuiltinPassResourceBindingPlan& outPlan,
@@ -466,8 +480,9 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
} }
PipelineStateKey pipelineKey = {}; PipelineStateKey pipelineKey = {};
pipelineKey.renderState = const Resources::MaterialRenderState effectiveRenderState =
BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material)); ResolveEffectiveDepthRenderState(resolvedShaderPass.pass, material, sceneData);
pipelineKey.renderState = BuildStaticPipelineRenderStateKey(effectiveRenderState);
pipelineKey.shader = resolvedShaderPass.shader; pipelineKey.shader = resolvedShaderPass.shader;
pipelineKey.passName = resolvedShaderPass.passName; pipelineKey.passName = resolvedShaderPass.passName;
pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet); pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet);
@@ -493,7 +508,7 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
*resolvedShaderPass.pass, *resolvedShaderPass.pass,
resolvedShaderPass.passName, resolvedShaderPass.passName,
keywordSet, keywordSet,
material, effectiveRenderState,
surface, surface,
BuildCommonInputLayout()); BuildCommonInputLayout());
RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc); RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc);
@@ -743,7 +758,7 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
return false; return false;
} }
const Resources::MaterialRenderState effectiveRenderState = const Resources::MaterialRenderState effectiveRenderState =
ResolveEffectiveRenderState(resolvedShaderPass.pass, material); ResolveEffectiveDepthRenderState(resolvedShaderPass.pass, material, sceneData);
PassLayoutKey passLayoutKey = {}; PassLayoutKey passLayoutKey = {};
passLayoutKey.shader = resolvedShaderPass.shader; passLayoutKey.shader = resolvedShaderPass.shader;

View File

@@ -111,6 +111,27 @@ DirectionalShadowPlanningSettings SanitizeDirectionalShadowPlanningSettings(
if (sanitized.minDepthPadding < 0.0f) { if (sanitized.minDepthPadding < 0.0f) {
sanitized.minDepthPadding = defaults.minDepthPadding; sanitized.minDepthPadding = defaults.minDepthPadding;
} }
if (!std::isfinite(sanitized.sampling.receiverDepthBias) ||
sanitized.sampling.receiverDepthBias < 0.0f) {
sanitized.sampling.receiverDepthBias = defaults.sampling.receiverDepthBias;
}
if (!std::isfinite(sanitized.sampling.normalBiasScale) ||
sanitized.sampling.normalBiasScale < 0.0f) {
sanitized.sampling.normalBiasScale = defaults.sampling.normalBiasScale;
}
if (!std::isfinite(sanitized.sampling.shadowStrength)) {
sanitized.sampling.shadowStrength = defaults.sampling.shadowStrength;
} else {
sanitized.sampling.shadowStrength =
std::clamp(sanitized.sampling.shadowStrength, 0.0f, 1.0f);
}
if (!std::isfinite(sanitized.casterBias.depthBiasFactor) ||
sanitized.casterBias.depthBiasFactor < 0.0f) {
sanitized.casterBias.depthBiasFactor = defaults.casterBias.depthBiasFactor;
}
if (sanitized.casterBias.depthBiasUnits < 0) {
sanitized.casterBias.depthBiasUnits = defaults.casterBias.depthBiasUnits;
}
return sanitized; return sanitized;
} }
@@ -340,6 +361,8 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
plan.texelWorldSize = std::max(shadowTexelSizeX, shadowTexelSizeY); plan.texelWorldSize = std::max(shadowTexelSizeX, shadowTexelSizeY);
plan.nearClipPlane = minZ; plan.nearClipPlane = minZ;
plan.farClipPlane = maxZ; plan.farClipPlane = maxZ;
plan.sampling = shadowSettings.sampling;
plan.casterBias = shadowSettings.casterBias;
plan.mapWidth = shadowSettings.mapDimension; plan.mapWidth = shadowSettings.mapDimension;
plan.mapHeight = shadowSettings.mapDimension; plan.mapHeight = shadowSettings.mapDimension;
plan.cameraData.view = view.Transpose(); plan.cameraData.view = view.Transpose();

View File

@@ -44,7 +44,8 @@ struct MockPipelineState {
bool lastHasMainDirectionalShadow = false; bool lastHasMainDirectionalShadow = false;
XCEngine::RHI::RHIResourceView* lastShadowMap = nullptr; XCEngine::RHI::RHIResourceView* lastShadowMap = nullptr;
XCEngine::Math::Matrix4x4 lastShadowViewProjection = XCEngine::Math::Matrix4x4::Identity(); XCEngine::Math::Matrix4x4 lastShadowViewProjection = XCEngine::Math::Matrix4x4::Identity();
XCEngine::Math::Vector4 lastShadowParams = XCEngine::Math::Vector4::Zero(); RenderDirectionalShadowMapMetrics lastShadowMapMetrics = {};
RenderDirectionalShadowSamplingData lastShadowSampling = {};
bool lastHasMainDirectionalShadowKeyword = false; bool lastHasMainDirectionalShadowKeyword = false;
RenderEnvironmentMode lastEnvironmentMode = RenderEnvironmentMode::None; RenderEnvironmentMode lastEnvironmentMode = RenderEnvironmentMode::None;
bool lastHasSkybox = false; bool lastHasSkybox = false;
@@ -327,7 +328,8 @@ public:
m_state->lastHasMainDirectionalShadow = sceneData.lighting.HasMainDirectionalShadow(); m_state->lastHasMainDirectionalShadow = sceneData.lighting.HasMainDirectionalShadow();
m_state->lastShadowMap = sceneData.lighting.mainDirectionalShadow.shadowMap; m_state->lastShadowMap = sceneData.lighting.mainDirectionalShadow.shadowMap;
m_state->lastShadowViewProjection = sceneData.lighting.mainDirectionalShadow.viewProjection; m_state->lastShadowViewProjection = sceneData.lighting.mainDirectionalShadow.viewProjection;
m_state->lastShadowParams = sceneData.lighting.mainDirectionalShadow.shadowParams; m_state->lastShadowMapMetrics = sceneData.lighting.mainDirectionalShadow.mapMetrics;
m_state->lastShadowSampling = sceneData.lighting.mainDirectionalShadow.sampling;
m_state->lastHasMainDirectionalShadowKeyword = m_state->lastHasMainDirectionalShadowKeyword =
XCEngine::Resources::ShaderKeywordSetContains( XCEngine::Resources::ShaderKeywordSetContains(
sceneData.globalShaderKeywords, sceneData.globalShaderKeywords,
@@ -1338,10 +1340,10 @@ TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) {
EXPECT_FLOAT_EQ(pipelineState->lastShadowViewProjection.m[0][3], 11.0f); EXPECT_FLOAT_EQ(pipelineState->lastShadowViewProjection.m[0][3], 11.0f);
EXPECT_FLOAT_EQ(pipelineState->lastShadowViewProjection.m[1][3], 12.0f); EXPECT_FLOAT_EQ(pipelineState->lastShadowViewProjection.m[1][3], 12.0f);
EXPECT_FLOAT_EQ(pipelineState->lastShadowViewProjection.m[2][3], 13.0f); EXPECT_FLOAT_EQ(pipelineState->lastShadowViewProjection.m[2][3], 13.0f);
EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.x, 0.0015f); EXPECT_FLOAT_EQ(pipelineState->lastShadowSampling.settings.receiverDepthBias, 0.0015f);
EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.y, 1.0f / 256.0f); EXPECT_FLOAT_EQ(pipelineState->lastShadowMapMetrics.inverseMapSize.x, 1.0f / 256.0f);
EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.z, 1.0f / 128.0f); EXPECT_FLOAT_EQ(pipelineState->lastShadowMapMetrics.inverseMapSize.y, 1.0f / 128.0f);
EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.w, 0.85f); EXPECT_FLOAT_EQ(pipelineState->lastShadowSampling.settings.shadowStrength, 0.85f);
EXPECT_TRUE(pipelineState->lastHasMainDirectionalShadowKeyword); EXPECT_TRUE(pipelineState->lastHasMainDirectionalShadowKeyword);
} }

View File

@@ -224,6 +224,11 @@ TEST(SceneRenderRequestPlanner_Test, AppliesConfiguredDirectionalShadowPlanningS
settings.maxFocusDistance = 12.0f; settings.maxFocusDistance = 12.0f;
settings.perspectiveFocusFactor = 0.01f; settings.perspectiveFocusFactor = 0.01f;
settings.minDepthRange = 80.0f; settings.minDepthRange = 80.0f;
settings.sampling.receiverDepthBias = 0.0025f;
settings.sampling.normalBiasScale = 2.0f;
settings.sampling.shadowStrength = 0.75f;
settings.casterBias.depthBiasFactor = 2.5f;
settings.casterBias.depthBiasUnits = 4;
planner.SetDirectionalShadowPlanningSettings(settings); planner.SetDirectionalShadowPlanningSettings(settings);
const std::vector<CameraRenderRequest> requests = planner.BuildRequests( const std::vector<CameraRenderRequest> requests = planner.BuildRequests(
@@ -241,6 +246,11 @@ TEST(SceneRenderRequestPlanner_Test, AppliesConfiguredDirectionalShadowPlanningS
EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportHeight, 2048u); EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportHeight, 2048u);
EXPECT_GE(request.directionalShadow.focusPoint.z, 6.0f); EXPECT_GE(request.directionalShadow.focusPoint.z, 6.0f);
EXPECT_LE(request.directionalShadow.focusPoint.z, 6.3f); EXPECT_LE(request.directionalShadow.focusPoint.z, 6.3f);
EXPECT_FLOAT_EQ(request.directionalShadow.sampling.receiverDepthBias, 0.0025f);
EXPECT_FLOAT_EQ(request.directionalShadow.sampling.normalBiasScale, 2.0f);
EXPECT_FLOAT_EQ(request.directionalShadow.sampling.shadowStrength, 0.75f);
EXPECT_FLOAT_EQ(request.directionalShadow.casterBias.depthBiasFactor, 2.5f);
EXPECT_EQ(request.directionalShadow.casterBias.depthBiasUnits, 4);
EXPECT_GE( EXPECT_GE(
request.directionalShadow.farClipPlane - request.directionalShadow.nearClipPlane, request.directionalShadow.farClipPlane - request.directionalShadow.nearClipPlane,
80.0f); 80.0f);
@@ -258,6 +268,11 @@ TEST(SceneRenderRequestPlanner_Test, SanitizesInvalidDirectionalShadowPlanningSe
invalidSettings.minDepthRange = 0.0f; invalidSettings.minDepthRange = 0.0f;
invalidSettings.boundsPadding = -1.0f; invalidSettings.boundsPadding = -1.0f;
invalidSettings.minDepthPadding = -3.0f; invalidSettings.minDepthPadding = -3.0f;
invalidSettings.sampling.receiverDepthBias = -0.5f;
invalidSettings.sampling.normalBiasScale = -2.0f;
invalidSettings.sampling.shadowStrength = 3.0f;
invalidSettings.casterBias.depthBiasFactor = -1.0f;
invalidSettings.casterBias.depthBiasUnits = -6;
planner.SetDirectionalShadowPlanningSettings(invalidSettings); planner.SetDirectionalShadowPlanningSettings(invalidSettings);
const DirectionalShadowPlanningSettings& settings = const DirectionalShadowPlanningSettings& settings =
@@ -270,4 +285,9 @@ TEST(SceneRenderRequestPlanner_Test, SanitizesInvalidDirectionalShadowPlanningSe
EXPECT_FLOAT_EQ(settings.minDepthRange, 20.0f); EXPECT_FLOAT_EQ(settings.minDepthRange, 20.0f);
EXPECT_FLOAT_EQ(settings.boundsPadding, 1.0f); EXPECT_FLOAT_EQ(settings.boundsPadding, 1.0f);
EXPECT_FLOAT_EQ(settings.minDepthPadding, 2.0f); EXPECT_FLOAT_EQ(settings.minDepthPadding, 2.0f);
EXPECT_FLOAT_EQ(settings.sampling.receiverDepthBias, 0.0015f);
EXPECT_FLOAT_EQ(settings.sampling.normalBiasScale, 1.5f);
EXPECT_FLOAT_EQ(settings.sampling.shadowStrength, 1.0f);
EXPECT_FLOAT_EQ(settings.casterBias.depthBiasFactor, 1.0f);
EXPECT_EQ(settings.casterBias.depthBiasUnits, 2);
} }