From 31578303a4eab6b6b083e57b6bf8d12ff3b3d991 Mon Sep 17 00:00:00 2001
From: x2048 <codeforsmile@gmail.com>
Date: Thu, 31 Mar 2022 22:40:06 +0200
Subject: [PATCH] Tune shadow perspective distortion (#12146)

* Pass perspective distortion parameters as uniforms
* Set all perspective bias parameters via ShadowRenderer
* Recalibrate perspective distortion and shadow range to render less shadow geometry with the same quality and observed shadow distance
---
 builtin/mainmenu/tab_settings.lua             | 10 ++--
 .../shaders/nodes_shader/opengl_fragment.glsl | 18 +++----
 .../shaders/nodes_shader/opengl_vertex.glsl   |  8 ++--
 .../object_shader/opengl_fragment.glsl        | 18 +++----
 .../shaders/object_shader/opengl_vertex.glsl  |  8 ++--
 .../shadow_shaders/pass1_trans_vertex.glsl    | 10 ++--
 .../shaders/shadow_shaders/pass1_vertex.glsl  | 10 ++--
 src/client/shader.cpp                         | 48 ++++++++++++++-----
 src/client/shadows/dynamicshadowsrender.cpp   | 25 ++++++++--
 src/client/shadows/dynamicshadowsrender.h     |  6 +++
 src/client/shadows/shadowsshadercallbacks.cpp |  6 +++
 src/client/shadows/shadowsshadercallbacks.h   |  9 +++-
 12 files changed, 117 insertions(+), 59 deletions(-)

diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua
index 42f7f8daf..0e761d324 100644
--- a/builtin/mainmenu/tab_settings.lua
+++ b/builtin/mainmenu/tab_settings.lua
@@ -364,11 +364,11 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
 		core.settings:set("enable_dynamic_shadows", "false")
 	else
 		local shadow_presets = {
-			[2] = { 80,  512,  "true", 0, "false" },
-			[3] = { 120, 1024, "true", 1, "false" },
-			[4] = { 350, 2048, "true", 1, "false" },
-			[5] = { 350, 2048, "true", 2,  "true" },
-			[6] = { 450, 4096, "true", 2,  "true" },
+			[2] = { 55,  512,  "true", 0, "false" },
+			[3] = { 82,  1024, "true", 1, "false" },
+			[4] = { 240, 2048, "true", 1, "false" },
+			[5] = { 240, 2048, "true", 2,  "true" },
+			[6] = { 300, 4096, "true", 2,  "true" },
 		}
 		local s = shadow_presets[table.indexof(labels.shadow_levels, fields["dd_shadows"])]
 		if s then
diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl
index adc8adccb..3dc66bdb3 100644
--- a/client/shaders/nodes_shader/opengl_fragment.glsl
+++ b/client/shaders/nodes_shader/opengl_fragment.glsl
@@ -47,17 +47,17 @@ const float fogShadingParameter = 1.0 / ( 1.0 - fogStart);
 
 
 #ifdef ENABLE_DYNAMIC_SHADOWS
-const float bias0 = 0.9;
-const float zPersFactor = 0.5;
-const float bias1 = 1.0 - bias0 + 1e-6;
+uniform float xyPerspectiveBias0;
+uniform float xyPerspectiveBias1;
+uniform float zPerspectiveBias;
 
 vec4 getPerspectiveFactor(in vec4 shadowPosition)
 {
 
 	float pDistance = length(shadowPosition.xy);
-	float pFactor = pDistance * bias0 + bias1;
+	float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1;
 
-	shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor);
+	shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPerspectiveBias);
 
 	return shadowPosition;
 }
@@ -172,12 +172,12 @@ float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDis
 float getBaseLength(vec2 smTexCoord)
 {
 	float l = length(2.0 * smTexCoord.xy - 1.0);     // length in texture coords
-	return bias1 / (1.0 / l - bias0); 				 // return to undistorted coords
+	return xyPerspectiveBias1 / (1.0 / l - xyPerspectiveBias0); 				 // return to undistorted coords
 }
 
 float getDeltaPerspectiveFactor(float l)
 {
-	return 0.1 / (bias0 * l + bias1);                      // original distortion factor, divided by 10
+	return 0.1 / (xyPerspectiveBias0 * l + xyPerspectiveBias1);                      // original distortion factor, divided by 10
 }
 
 float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance, float multiplier)
@@ -489,8 +489,8 @@ void main(void)
 		vec3 shadow_color = vec3(0.0, 0.0, 0.0);
 		vec3 posLightSpace = getLightSpacePosition();
 
-		float distance_rate = (1 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 20.0));
-		float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1,  posLightSpace.z  ),0.0);
+		float distance_rate = (1 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 50.0));
+		float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1,  posLightSpace.z),0.0);
 
 		if (distance_rate > 1e-7) {
 		
diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl
index 5e77ac719..935fbf043 100644
--- a/client/shaders/nodes_shader/opengl_vertex.glsl
+++ b/client/shaders/nodes_shader/opengl_vertex.glsl
@@ -45,8 +45,8 @@ varying float nightRatio;
 const vec3 artificialLight = vec3(1.04, 1.04, 1.04);
 const float e = 2.718281828459;
 const float BS = 10.0;
-const float bias0 = 0.9;
-const float bias1 = 1.0 - bias0;
+uniform float xyPerspectiveBias0;
+uniform float xyPerspectiveBias1;
 
 #ifdef ENABLE_DYNAMIC_SHADOWS
 // custom smoothstep implementation because it's not defined in glsl1.2
@@ -206,9 +206,9 @@ void main(void)
 		// Distance from the vertex to the player
 		float distanceToPlayer = length(eyeToVertex - v_LightDirection * dot(eyeToVertex, v_LightDirection)) / f_shadowfar;
 		// perspective factor estimation according to the 
-		float perspectiveFactor = distanceToPlayer * bias0 + bias1;
+		float perspectiveFactor = distanceToPlayer * xyPerspectiveBias0 + xyPerspectiveBias1;
 		float texelSize = f_shadowfar * perspectiveFactor * perspectiveFactor /
-				(f_textureresolution * bias1  - perspectiveFactor * bias0);
+				(f_textureresolution * xyPerspectiveBias1  - perspectiveFactor * xyPerspectiveBias0);
 		float slopeScale = clamp(pow(1.0 - cosLight*cosLight, 0.5), 0.0, 1.0);
 		normalOffsetScale = texelSize * slopeScale;
 
diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl
index edb9d2bb1..2b9a59cd7 100644
--- a/client/shaders/object_shader/opengl_fragment.glsl
+++ b/client/shaders/object_shader/opengl_fragment.glsl
@@ -43,17 +43,17 @@ const float fogShadingParameter = 1.0 / (1.0 - fogStart);
 #endif
 
 #ifdef ENABLE_DYNAMIC_SHADOWS
-const float bias0 = 0.9;
-const float zPersFactor = 0.5;
-const float bias1 = 1.0 - bias0 + 1e-6;
+uniform float xyPerspectiveBias0;
+uniform float xyPerspectiveBias1;
+uniform float zPerspectiveBias;
 
 vec4 getPerspectiveFactor(in vec4 shadowPosition)
 {
 
 	float pDistance = length(shadowPosition.xy);
-	float pFactor = pDistance * bias0 + bias1;
+	float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1;
 
-	shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor);
+	shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPerspectiveBias);
 
 	return shadowPosition;
 }
@@ -162,12 +162,12 @@ float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDis
 float getBaseLength(vec2 smTexCoord)
 {
 	float l = length(2.0 * smTexCoord.xy - 1.0);     // length in texture coords
-	return bias1 / (1.0 / l - bias0); 				 // return to undistorted coords
+	return xyPerspectiveBias1 / (1.0 / l - xyPerspectiveBias0); 				 // return to undistorted coords
 }
 
 float getDeltaPerspectiveFactor(float l)
 {
-	return 0.1 / (bias0 * l + bias1);                      // original distortion factor, divided by 10
+	return 0.1 / (xyPerspectiveBias0 * l + xyPerspectiveBias1);                      // original distortion factor, divided by 10
 }
 
 float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance, float multiplier)
@@ -477,8 +477,8 @@ void main(void)
 		vec3 shadow_color = vec3(0.0, 0.0, 0.0);
 		vec3 posLightSpace = getLightSpacePosition();
 
-		float distance_rate = (1 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 20.0));
-		float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1,  posLightSpace.z  ),0.0);
+		float distance_rate = (1 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 50.0));
+		float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1,  posLightSpace.z),0.0);
 
 		if (distance_rate > 1e-7) {
 	
diff --git a/client/shaders/object_shader/opengl_vertex.glsl b/client/shaders/object_shader/opengl_vertex.glsl
index ff0067302..55861b0e9 100644
--- a/client/shaders/object_shader/opengl_vertex.glsl
+++ b/client/shaders/object_shader/opengl_vertex.glsl
@@ -37,8 +37,8 @@ const vec3 artificialLight = vec3(1.04, 1.04, 1.04);
 varying float vIDiff;
 const float e = 2.718281828459;
 const float BS = 10.0;
-const float bias0 = 0.9;
-const float bias1 = 1.0 - bias0;
+uniform float xyPerspectiveBias0;
+uniform float xyPerspectiveBias1;
 
 #ifdef ENABLE_DYNAMIC_SHADOWS
 // custom smoothstep implementation because it's not defined in glsl1.2
@@ -116,9 +116,9 @@ void main(void)
 		// Distance from the vertex to the player
 		float distanceToPlayer = length(eyeToVertex - v_LightDirection * dot(eyeToVertex, v_LightDirection)) / f_shadowfar;
 		// perspective factor estimation according to the
-		float perspectiveFactor = distanceToPlayer * bias0 + bias1;
+		float perspectiveFactor = distanceToPlayer * xyPerspectiveBias0 + xyPerspectiveBias1;
 		float texelSize = f_shadowfar * perspectiveFactor * perspectiveFactor /
-				(f_textureresolution * bias1  - perspectiveFactor * bias0);
+				(f_textureresolution * xyPerspectiveBias1  - perspectiveFactor * xyPerspectiveBias0);
 		float slopeScale = clamp(pow(1.0 - cosLight*cosLight, 0.5), 0.0, 1.0);
 		normalOffsetScale = texelSize * slopeScale;
 
diff --git a/client/shaders/shadow_shaders/pass1_trans_vertex.glsl b/client/shaders/shadow_shaders/pass1_trans_vertex.glsl
index 0a9efe450..6d2877d18 100644
--- a/client/shaders/shadow_shaders/pass1_trans_vertex.glsl
+++ b/client/shaders/shadow_shaders/pass1_trans_vertex.glsl
@@ -4,15 +4,15 @@ varying vec4 tPos;
 varying vec3 varColor;
 #endif
 
-const float bias0 = 0.9;
-const float zPersFactor = 0.5;
-const float bias1 = 1.0 - bias0 + 1e-6;
+uniform float xyPerspectiveBias0;
+uniform float xyPerspectiveBias1;
+uniform float zPerspectiveBias;
 
 vec4 getPerspectiveFactor(in vec4 shadowPosition)
 {
 	float pDistance = length(shadowPosition.xy);
-	float pFactor = pDistance * bias0 + bias1;
-	shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor);
+	float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1;
+	shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPerspectiveBias);
 
 	return shadowPosition;
 }
diff --git a/client/shaders/shadow_shaders/pass1_vertex.glsl b/client/shaders/shadow_shaders/pass1_vertex.glsl
index feee9467f..3873ac6e6 100644
--- a/client/shaders/shadow_shaders/pass1_vertex.glsl
+++ b/client/shaders/shadow_shaders/pass1_vertex.glsl
@@ -1,15 +1,15 @@
 uniform mat4 LightMVP; // world matrix
 varying vec4 tPos;
 
-const float bias0 = 0.9;
-const float zPersFactor = 0.5;
-const float bias1 = 1.0 - bias0 + 1e-6;
+uniform float xyPerspectiveBias0;
+uniform float xyPerspectiveBias1;
+uniform float zPerspectiveBias;
 
 vec4 getPerspectiveFactor(in vec4 shadowPosition)
 {
 	float pDistance = length(shadowPosition.xy);
-	float pFactor = pDistance * bias0 + bias1;
-	shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor);
+	float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1;
+	shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPerspectiveBias);
 
 	return shadowPosition;
 }
diff --git a/src/client/shader.cpp b/src/client/shader.cpp
index fa5ffb914..d9c1952c4 100644
--- a/src/client/shader.cpp
+++ b/src/client/shader.cpp
@@ -210,17 +210,23 @@ public:
 
 class MainShaderConstantSetter : public IShaderConstantSetter
 {
-	CachedVertexShaderSetting<float, 16> m_world_view_proj;
-	CachedVertexShaderSetting<float, 16> m_world;
+	CachedVertexShaderSetting<f32, 16> m_world_view_proj;
+	CachedVertexShaderSetting<f32, 16> m_world;
 
 	// Shadow-related
-	CachedPixelShaderSetting<float, 16> m_shadow_view_proj;
-	CachedPixelShaderSetting<float, 3> m_light_direction;
-	CachedPixelShaderSetting<float> m_texture_res;
-	CachedPixelShaderSetting<float> m_shadow_strength;
-	CachedPixelShaderSetting<float> m_time_of_day;
-	CachedPixelShaderSetting<float> m_shadowfar;
+	CachedPixelShaderSetting<f32, 16> m_shadow_view_proj;
+	CachedPixelShaderSetting<f32, 3> m_light_direction;
+	CachedPixelShaderSetting<f32> m_texture_res;
+	CachedPixelShaderSetting<f32> m_shadow_strength;
+	CachedPixelShaderSetting<f32> m_time_of_day;
+	CachedPixelShaderSetting<f32> m_shadowfar;
 	CachedPixelShaderSetting<s32> m_shadow_texture;
+	CachedVertexShaderSetting<f32> m_perspective_bias0_vertex;
+	CachedPixelShaderSetting<f32> m_perspective_bias0_pixel;
+	CachedVertexShaderSetting<f32> m_perspective_bias1_vertex;
+	CachedPixelShaderSetting<f32> m_perspective_bias1_pixel;
+	CachedVertexShaderSetting<f32> m_perspective_zbias_vertex;
+	CachedPixelShaderSetting<f32> m_perspective_zbias_pixel;
 
 #if ENABLE_GLES
 	// Modelview matrix
@@ -247,6 +253,12 @@ public:
 		, m_time_of_day("f_timeofday")
 		, m_shadowfar("f_shadowfar")
 		, m_shadow_texture("ShadowMapSampler")
+		, m_perspective_bias0_vertex("xyPerspectiveBias0")
+		, m_perspective_bias0_pixel("xyPerspectiveBias0")
+		, m_perspective_bias1_vertex("xyPerspectiveBias1")
+		, m_perspective_bias1_pixel("xyPerspectiveBias1")
+		, m_perspective_zbias_vertex("zPerspectiveBias")
+		, m_perspective_zbias_pixel("zPerspectiveBias")
 	{}
 	~MainShaderConstantSetter() = default;
 
@@ -293,26 +305,36 @@ public:
 			shadowViewProj *= light.getViewMatrix();
 			m_shadow_view_proj.set(shadowViewProj.pointer(), services);
 
-			float v_LightDirection[3];
+			f32 v_LightDirection[3];
 			light.getDirection().getAs3Values(v_LightDirection);
 			m_light_direction.set(v_LightDirection, services);
 
-			float TextureResolution = light.getMapResolution();
+			f32 TextureResolution = light.getMapResolution();
 			m_texture_res.set(&TextureResolution, services);
 
-			float ShadowStrength = shadow->getShadowStrength();
+			f32 ShadowStrength = shadow->getShadowStrength();
 			m_shadow_strength.set(&ShadowStrength, services);
 
-			float timeOfDay = shadow->getTimeOfDay();
+			f32 timeOfDay = shadow->getTimeOfDay();
 			m_time_of_day.set(&timeOfDay, services);
 
-			float shadowFar = shadow->getMaxShadowFar();
+			f32 shadowFar = shadow->getMaxShadowFar();
 			m_shadowfar.set(&shadowFar, services);
 
 			// I dont like using this hardcoded value. maybe something like
 			// MAX_TEXTURE - 1 or somthing like that??
 			s32 TextureLayerID = 3;
 			m_shadow_texture.set(&TextureLayerID, services);
+
+			f32 bias0 = shadow->getPerspectiveBiasXY();
+			m_perspective_bias0_vertex.set(&bias0, services);
+			m_perspective_bias0_pixel.set(&bias0, services);
+			f32 bias1 = 1.0f - bias0 + 1e-5f;
+			m_perspective_bias1_vertex.set(&bias1, services);
+			m_perspective_bias1_pixel.set(&bias1, services);
+			f32 zbias = shadow->getPerspectiveBiasZ();
+			m_perspective_zbias_vertex.set(&zbias, services);
+			m_perspective_zbias_pixel.set(&zbias, services);
 		}
 	}
 };
diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp
index 24adb21e2..262711221 100644
--- a/src/client/shadows/dynamicshadowsrender.cpp
+++ b/src/client/shadows/dynamicshadowsrender.cpp
@@ -32,7 +32,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) :
 		m_device(device), m_smgr(device->getSceneManager()),
-		m_driver(device->getVideoDriver()), m_client(client), m_current_frame(0)
+		m_driver(device->getVideoDriver()), m_client(client), m_current_frame(0),
+		m_perspective_bias_xy(0.8), m_perspective_bias_z(0.5)
 {
 	m_shadows_supported = true; // assume shadows supported. We will check actual support in initialize
 	m_shadows_enabled = true;
@@ -59,6 +60,10 @@ ShadowRenderer::~ShadowRenderer()
 
 	if (m_shadow_depth_cb)
 		delete m_shadow_depth_cb;
+	if (m_shadow_depth_entity_cb)
+		delete m_shadow_depth_entity_cb;
+	if (m_shadow_depth_trans_cb)
+		delete m_shadow_depth_trans_cb;
 	if (m_shadow_mix_cb)
 		delete m_shadow_mix_cb;
 	m_shadow_node_array.clear();
@@ -250,8 +255,13 @@ void ShadowRenderer::updateSMTextures()
 		// Update SM incrementally:
 		for (DirectionalLight &light : m_light_list) {
 			// Static shader values.
-			m_shadow_depth_cb->MapRes = (f32)m_shadow_map_texture_size;
-			m_shadow_depth_cb->MaxFar = (f32)m_shadow_map_max_distance * BS;
+			for (auto cb : {m_shadow_depth_cb, m_shadow_depth_entity_cb, m_shadow_depth_trans_cb})
+				if (cb) {
+					cb->MapRes = (f32)m_shadow_map_texture_size;
+					cb->MaxFar = (f32)m_shadow_map_max_distance * BS;
+					cb->PerspectiveBiasXY = getPerspectiveBiasXY();
+					cb->PerspectiveBiasZ = getPerspectiveBiasZ();
+				}
 
 			// set the Render Target
 			// right now we can only render in usual RTT, not
@@ -533,6 +543,8 @@ void ShadowRenderer::createShaders()
 		if (depth_shader == -1) {
 			// upsi, something went wrong loading shader.
 			delete m_shadow_depth_cb;
+			m_shadow_depth_cb = nullptr;
+			m_shadows_enabled = false;
 			m_shadows_supported = false;
 			errorstream << "Error compiling shadow mapping shader." << std::endl;
 			return;
@@ -559,15 +571,19 @@ void ShadowRenderer::createShaders()
 			errorstream << "Error shadow mapping fs shader not found." << std::endl;
 			return;
 		}
+		m_shadow_depth_entity_cb = new ShadowDepthShaderCB();
 
 		depth_shader_entities = gpu->addHighLevelShaderMaterial(
 				readShaderFile(depth_shader_vs).c_str(), "vertexMain",
 				video::EVST_VS_1_1,
 				readShaderFile(depth_shader_fs).c_str(), "pixelMain",
-				video::EPST_PS_1_2, m_shadow_depth_cb);
+				video::EPST_PS_1_2, m_shadow_depth_entity_cb);
 
 		if (depth_shader_entities == -1) {
 			// upsi, something went wrong loading shader.
+			delete m_shadow_depth_entity_cb;
+			m_shadow_depth_entity_cb = nullptr;
+			m_shadows_enabled = false;
 			m_shadows_supported = false;
 			errorstream << "Error compiling shadow mapping shader (dynamic)." << std::endl;
 			return;
@@ -643,6 +659,7 @@ void ShadowRenderer::createShaders()
 		if (depth_shader_trans == -1) {
 			// upsi, something went wrong loading shader.
 			delete m_shadow_depth_trans_cb;
+			m_shadow_depth_trans_cb = nullptr;
 			m_shadow_map_colored = false;
 			m_shadows_supported = false;
 			errorstream << "Error compiling colored shadow mapping shader." << std::endl;
diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h
index dc8f79c56..bbeb254b0 100644
--- a/src/client/shadows/dynamicshadowsrender.h
+++ b/src/client/shadows/dynamicshadowsrender.h
@@ -90,6 +90,9 @@ public:
 	float getShadowStrength() const { return m_shadows_enabled ? m_shadow_strength : 0.0f; }
 	float getTimeOfDay() const { return m_time_day; }
 
+	f32 getPerspectiveBiasXY() { return m_perspective_bias_xy; }
+	f32 getPerspectiveBiasZ() { return m_perspective_bias_z; }
+
 private:
 	video::ITexture *getSMTexture(const std::string &shadow_map_name,
 			video::ECOLOR_FORMAT texture_format,
@@ -131,6 +134,8 @@ private:
 	bool m_shadow_map_colored;
 	u8 m_map_shadow_update_frames; /* Use this number of frames to update map shaodw */
 	u8 m_current_frame{0}; /* Current frame */
+	f32 m_perspective_bias_xy;
+	f32 m_perspective_bias_z;
 
 	video::ECOLOR_FORMAT m_texture_format{video::ECOLOR_FORMAT::ECF_R16F};
 	video::ECOLOR_FORMAT m_texture_format_color{video::ECOLOR_FORMAT::ECF_R16G16};
@@ -146,6 +151,7 @@ private:
 	s32 mixcsm_shader{-1};
 
 	ShadowDepthShaderCB *m_shadow_depth_cb{nullptr};
+	ShadowDepthShaderCB *m_shadow_depth_entity_cb{nullptr};
 	ShadowDepthShaderCB *m_shadow_depth_trans_cb{nullptr};
 
 	shadowScreenQuad *m_screen_quad{nullptr};
diff --git a/src/client/shadows/shadowsshadercallbacks.cpp b/src/client/shadows/shadowsshadercallbacks.cpp
index 65a63f49c..51ea8aa93 100644
--- a/src/client/shadows/shadowsshadercallbacks.cpp
+++ b/src/client/shadows/shadowsshadercallbacks.cpp
@@ -33,4 +33,10 @@ void ShadowDepthShaderCB::OnSetConstants(
 	m_max_far_setting.set(&MaxFar, services);
 	s32 TextureId = 0;
 	m_color_map_sampler_setting.set(&TextureId, services);
+	f32 bias0 = PerspectiveBiasXY;
+	m_perspective_bias0.set(&bias0, services);
+	f32 bias1 = 1.0f - bias0 + 1e-5f;
+	m_perspective_bias1.set(&bias1, services);
+	f32 zbias = PerspectiveBiasZ;
+	m_perspective_zbias.set(&zbias, services);
 }
diff --git a/src/client/shadows/shadowsshadercallbacks.h b/src/client/shadows/shadowsshadercallbacks.h
index 3549567c3..d00f59c37 100644
--- a/src/client/shadows/shadowsshadercallbacks.h
+++ b/src/client/shadows/shadowsshadercallbacks.h
@@ -30,7 +30,10 @@ public:
 			m_light_mvp_setting("LightMVP"),
 			m_map_resolution_setting("MapResolution"),
 			m_max_far_setting("MaxFar"),
-			m_color_map_sampler_setting("ColorMapSampler")
+			m_color_map_sampler_setting("ColorMapSampler"),
+			m_perspective_bias0("xyPerspectiveBias0"),
+			m_perspective_bias1("xyPerspectiveBias1"),
+			m_perspective_zbias("zPerspectiveBias")
 	{}
 
 	void OnSetMaterial(const video::SMaterial &material) override {}
@@ -39,10 +42,14 @@ public:
 			s32 userData) override;
 
 	f32 MaxFar{2048.0f}, MapRes{1024.0f};
+	f32 PerspectiveBiasXY {0.9f}, PerspectiveBiasZ {0.5f};
 
 private:
 	CachedVertexShaderSetting<f32, 16> m_light_mvp_setting;
 	CachedVertexShaderSetting<f32> m_map_resolution_setting;
 	CachedVertexShaderSetting<f32> m_max_far_setting;
 	CachedPixelShaderSetting<s32> m_color_map_sampler_setting;
+	CachedVertexShaderSetting<f32> m_perspective_bias0;
+	CachedVertexShaderSetting<f32> m_perspective_bias1;
+	CachedVertexShaderSetting<f32> m_perspective_zbias;
 };