Browse Source

Added initial fallback mode (no MRT, no hardware shadows.)

Lasse Öörni 14 years ago
parent
commit
292ebed51f

+ 10 - 4
CMakeLists.txt

@@ -105,18 +105,24 @@ if (USE_OPENGL)
 else()
     macro (add_shader NAME)
         add_custom_command (
-            OUTPUT ../../Bin/CoreData/Shaders/SM2/${NAME}.vs2
-            COMMAND ../../Bin/ShaderCompiler ${NAME}.xml ../../Bin/CoreData/Shaders/SM2 SM2
+            OUTPUT ../../Bin/CoreData/Shaders/SM2FB/${NAME}.vs2
+            COMMAND ../../Bin/ShaderCompiler ${NAME}.xml ../../Bin/CoreData/Shaders/SM2FB FALLBACK
             DEPENDS ShaderCompiler Uniforms.hlsl Samplers.hlsl Transform.hlsl ScreenPos.hlsl Lighting.hlsl Fog.hlsl ${NAME}.hlsl ${NAME}.xml
         )
         
+        add_custom_command (
+            OUTPUT ../../Bin/CoreData/Shaders/SM2/${NAME}.vs2
+            COMMAND ../../Bin/ShaderCompiler ${NAME}.xml ../../Bin/CoreData/Shaders/SM2
+            DEPENDS ShaderCompiler Uniforms.hlsl Samplers.hlsl Transform.hlsl ScreenPos.hlsl Lighting.hlsl Fog.hlsl ${NAME}.hlsl ${NAME}.xml
+        )
+            
         add_custom_command (
             OUTPUT ../../Bin/CoreData/Shaders/SM3/${NAME}.vs3
             COMMAND ../../Bin/ShaderCompiler ${NAME}.xml ../../Bin/CoreData/Shaders/SM3 SM3
             DEPENDS ShaderCompiler Uniforms.hlsl Samplers.hlsl Transform.hlsl ScreenPos.hlsl Lighting.hlsl Fog.hlsl ${NAME}.hlsl ${NAME}.xml
         )
-        
-        set (ALL_SHADERS ${ALL_SHADERS} ../../Bin/CoreData/Shaders/SM2/${NAME}.vs2 ../../Bin/CoreData/Shaders/SM3/${NAME}.vs3)
+
+        set (ALL_SHADERS ${ALL_SHADERS} ../../Bin/CoreData/Shaders/SM2FB/${NAME}.vs2 ../../Bin/CoreData/Shaders/SM2/${NAME}.vs2 ../../Bin/CoreData/Shaders/SM3/${NAME}.vs3)
     endmacro ()
 endif ()
 

+ 1 - 0
Docs/GettingStarted.dox

@@ -114,6 +114,7 @@ Urho3D.exe understands the following command line options:
 -nosound    Disable sound output
 -noip       Disable sound mixing interpolation
 -sm2        Force SM2.0 rendering
+-fallback   Force fallback rendering (no MRT, no hardware shadows)
 \endverbatim
 
 (*) Only forward rendering supports hardware multisampling. In deferred rendering temporal antialiasing will be used instead.

+ 1 - 1
Docs/Reference.dox

@@ -376,7 +376,7 @@ When setting the initial screen mode, Graphics does a few checks:
 - For Direct3D9, it checks which shader model is supported. 2.0 is minimum, but 3.0 will be used if available. %Shader model 2.0 can be forced by calling \ref Graphics::SetForceSM2() "SetForceSM2()" before calling SetMode() for the first time.
 - For OpenGL, version 2.0 is required as a minimum. Shadows and deferred rendering additionally require the frame buffer object and packed depth stencil extensions.
 - Are multiple render targets supported? If not, only forward rendering will be available.
-- Are hardware shadow maps supported? Both ATI & NVIDIA style shadow maps can be used. If neither are available, shadow rendering will be disabled.
+- Are hardware shadow maps supported? Both ATI & NVIDIA style shadow maps can be used. If neither are available, a fallback mode with non-filtered shadows will be chosen instead.
 
 \section Rendering_Renderer Renderer
 

+ 4 - 0
Engine/Engine/Engine.cpp

@@ -81,6 +81,7 @@ bool Engine::Initialize(const String& windowTitle, const String& logName, const
     bool vsync = false;
     bool tripleBuffer = false;
     bool forceSM2 = false;
+    bool forceFallback = false;
     bool shadows = true;
     bool sound = true;
     bool stereo = true;
@@ -111,6 +112,8 @@ bool Engine::Initialize(const String& windowTitle, const String& logName, const
                 flush = false;
             else if (argument == "sm2")
                 forceSM2 = true;
+            else if (argument == "fallback")
+                forceFallback = true;
             else
             {
                 switch (tolower(argument[0]))
@@ -196,6 +199,7 @@ bool Engine::Initialize(const String& windowTitle, const String& logName, const
         Graphics* graphics = GetSubsystem<Graphics>();
         graphics->SetFlushGPU(flush);
         graphics->SetForceSM2(forceSM2);
+        graphics->SetForceFallback(forceFallback);
         graphics->SetWindowTitle(windowTitle);
         if (!graphics->SetMode(mode, width, height, fullscreen, vsync, tripleBuffer, multiSample))
             return false;

+ 1 - 1
Engine/Engine/GraphicsAPI.cpp

@@ -717,7 +717,7 @@ static void RegisterGraphics(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Graphics", "uint get_numPrimitives() const", asMETHOD(Graphics, GetNumPrimitives), asCALL_THISCALL);
     engine->RegisterObjectMethod("Graphics", "uint get_numBatches() const", asMETHOD(Graphics, GetNumBatches), asCALL_THISCALL);
     engine->RegisterObjectMethod("Graphics", "bool get_renderTargetSupport() const", asMETHOD(Graphics, GetRenderTargetSupport), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Graphics", "bool get_deferredSupport() const", asMETHOD(Graphics, GetDeferredSupport), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Graphics", "bool get_fallback() const", asMETHOD(Graphics, GetFallback), asCALL_THISCALL);
     engine->RegisterObjectMethod("Graphics", "bool get_sm3Support() const", asMETHOD(Graphics, GetSM3Support), asCALL_THISCALL);
     engine->RegisterObjectMethod("Graphics", "bool get_hardwareShadowSupport() const", asMETHOD(Graphics, GetHardwareShadowSupport), asCALL_THISCALL);
     engine->RegisterObjectMethod("Graphics", "bool get_hiresShadowSupport() const", asMETHOD(Graphics, GetHiresShadowSupport), asCALL_THISCALL);

+ 10 - 2
Engine/Graphics/Batch.cpp

@@ -283,8 +283,16 @@ void Batch::Prepare(Graphics* graphics, const HashMap<StringHash, Vector4>& shad
             float fadeEnd = light_->GetShadowDistance();
             if (fadeStart > 0.0f && fadeEnd > 0.0f && fadeEnd > fadeStart)
                 intensity = Lerp(intensity, 1.0f, Clamp((light_->GetDistance() - fadeStart) / (fadeEnd - fadeStart), 0.0f, 1.0f));
-            float pcfValues = (1.0f - intensity) * 0.25f;
-            graphics->SetShaderParameter(PSP_SHADOWINTENSITY, Vector4(pcfValues, intensity, 0.0f, 0.0f));
+            float pcfValues = (1.0f - intensity);
+            // In fallback mode, a single sample is used. Otherwise 4 samples are used, so divide the intensity of one sample
+            // Also, fallback mode requires manual depth biasing. We do not do proper slope scale biasing, instead just fudge the
+            // bias values together
+            if (!graphics->GetFallback())
+                pcfValues *= 0.25f;
+            float constantBias = graphics->GetDepthConstantBias();
+            float slopeScaledBias = graphics->GetDepthSlopeScaledBias();
+            graphics->SetShaderParameter(PSP_SHADOWINTENSITY, Vector4(pcfValues, intensity, constantBias + slopeScaledBias *
+                constantBias, 0.0f));
         }
         
         if (graphics->NeedParameterUpdate(PSP_SHADOWPROJ, light_))

+ 29 - 19
Engine/Graphics/Direct3D9/D3D9Graphics.cpp

@@ -170,6 +170,7 @@ Graphics::Graphics(Context* context) :
     hardwareShadowSupport_(false),
     hiresShadowSupport_(false),
     streamOffsetSupport_(false),
+    fallback_(false),
     hasSM3_(false),
     forceSM2_(false),
     queryIndex_(0),
@@ -281,10 +282,6 @@ bool Graphics::SetMode(RenderMode mode, int width, int height, bool fullscreen,
             return false;
     }
     
-    // Disable deferred  rendering if not supported
-    if (mode == RENDER_DEFERRED && !deferredSupport_)
-        mode = RENDER_FORWARD;
-
     // Note: GetMultiSample() will not reflect the actual hardware multisample mode, but rather what the caller wanted.
     // In deferred rendering mode, it is used to control temporal antialiasing
     multiSample_ = multiSample;
@@ -1829,6 +1826,12 @@ void Graphics::SetForceSM2(bool enable)
     forceSM2_ = enable;
 }
 
+void Graphics::SetForceFallback(bool enable)
+{
+    // Note: this only has effect before calling SetMode() for the first time
+    forceFallback_ = enable;
+}
+
 bool Graphics::IsInitialized() const
 {
     return impl_->window_ != 0 && impl_->GetDevice() != 0;
@@ -2083,13 +2086,6 @@ bool Graphics::CreateInterface()
     
     // Check supported features: Shader Model 3, deferred rendering, hardware depth texture, shadow map, dummy color surface,
     // stream offset
-    if (!forceSM2_)
-    {
-        if (impl_->deviceCaps_.VertexShaderVersion >= D3DVS_VERSION(3, 0) && impl_->deviceCaps_.PixelShaderVersion >=
-            D3DPS_VERSION(3, 0))
-            hasSM3_ = true;
-    }
-    
     if (impl_->CheckFormatSupport(D3DFMT_R32F, D3DUSAGE_RENDERTARGET, D3DRTYPE_TEXTURE))
     {
         if (impl_->CheckFormatSupport((D3DFORMAT)MAKEFOURCC('I', 'N', 'T', 'Z'), D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE))
@@ -2100,11 +2096,14 @@ bool Graphics::CreateInterface()
                 hardwareDepthSupport_ = true;
         }
         unsigned requiredRTs = hardwareDepthSupport_ ? 2 : 3;
-        if (impl_->deviceCaps_.NumSimultaneousRTs >= requiredRTs)
-            deferredSupport_ = true;
+        if (forceFallback_ || impl_->deviceCaps_.NumSimultaneousRTs < requiredRTs)
+        {
+            hardwareDepthSupport_ = false;
+            fallback_ = true;
+        }
     }
     
-    // Prefer NVIDIA style hardware depth Compared shadow maps if available
+    // Prefer NVIDIA style hardware depth compared shadow maps if available
     shadowMapFormat_ = D3DFMT_D16;
     if (impl_->CheckFormatSupport((D3DFORMAT)shadowMapFormat_, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE))
     {
@@ -2131,11 +2130,22 @@ bool Graphics::CreateInterface()
                 hiresShadowMapFormat_ = shadowMapFormat_;
         }
         else
-        {
-            // No depth texture shadow map support -> no shadows at all
-            shadowMapFormat_ = 0;
-            hiresShadowMapFormat_ = 0;
-        }
+            // No depth texture shadow map support, use fallback mode
+            fallback_ = true;
+    }
+    
+    if (fallback_)
+    {
+        shadowMapFormat_ = D3DFMT_A8R8G8B8;
+        hiresShadowMapFormat_ = D3DFMT_A8R8G8B8;
+        hardwareShadowSupport_ = false;
+    }
+    
+    if (!forceSM2_ && !fallback_)
+    {
+        if (impl_->deviceCaps_.VertexShaderVersion >= D3DVS_VERSION(3, 0) && impl_->deviceCaps_.PixelShaderVersion >=
+            D3DPS_VERSION(3, 0))
+            hasSM3_ = true;
     }
     
     // Check for Intel 4 Series with an old driver, enable manual shadow map compare in that case

+ 10 - 8
Engine/Graphics/Direct3D9/D3D9Graphics.h

@@ -207,6 +207,8 @@ public:
     void EndImmediate();
     /// %Set force Shader Model 2 flag. Needs to be set before setting initial screen mode to have effect.
     void SetForceSM2(bool enable);
+    /// %Set force fallback shaders flag. Needs to be set before setting initial screen mode to have effect.
+    void SetForceFallback(bool enable);
     
     /// Return whether rendering initialized.
     bool IsInitialized() const;
@@ -246,10 +248,10 @@ public:
     unsigned GetShadowMapFormat() const { return shadowMapFormat_; }
     /// Return 24-bit shadow map depth texture format, or 0 if not supported.
     unsigned GetHiresShadowMapFormat() const { return hiresShadowMapFormat_; }
-    /// Return whether texture render targets are supported.
-    bool GetRenderTargetSupport() const { return renderTargetSupport_; }
-    /// Return whether deferred rendering is supported.
-    bool GetDeferredSupport() const { return deferredSupport_; }
+    /// Return whether texture render targets are supported. Always true on Direct3D9
+    bool GetRenderTargetSupport() const { return true; }
+    /// Return whether fallback shaders are required.
+    bool GetFallback() const { return fallback_; }
     /// Return whether Shader Model 3 is supported.
     bool GetSM3Support() const { return hasSM3_; }
     /// Return whether the hardware depth buffer can be sampled.
@@ -415,10 +417,6 @@ private:
     bool deviceLost_;
     //! Use auto depth stencil flag
     bool systemDepthStencil_;
-    /// Texture render target support flag.
-    bool renderTargetSupport_;
-    /// Deferred rendering support flag.
-    bool deferredSupport_;
     /// Hardware depth sampling support flag.
     bool hardwareDepthSupport_;
     /// Hardware shadow map depth compare support flag.
@@ -427,10 +425,14 @@ private:
     bool hiresShadowSupport_;
     /// Stream offset support flag.
     bool streamOffsetSupport_;
+    /// Fallback shader mode flag.
+    bool fallback_;
     /// Shader Model 3 flag.
     bool hasSM3_;
     /// Force Shader Model 2 flag.
     bool forceSM2_;
+    /// Force fallback shaders flag.
+    bool forceFallback_;
     /// Query (used to flush the GPU command queue) issued flags.
     bool queryIssued_[NUM_QUERIES];
     /// Current query index

+ 4 - 0
Engine/Graphics/OpenGL/OGLGraphics.cpp

@@ -1819,6 +1819,10 @@ void Graphics::SetForceSM2(bool enable)
 {
 }
 
+void Graphics::SetForceFallback(bool enable)
+{
+}
+
 bool Graphics::IsInitialized() const
 {
     return impl_->window_ != 0;

+ 4 - 2
Engine/Graphics/OpenGL/OGLGraphics.h

@@ -200,6 +200,8 @@ public:
     void EndImmediate();
     /// Set force Shader Model 2 flag. No effect on OpenGL.
     void SetForceSM2(bool enable);
+    /// %Set force fallback shaders flag. No effect on OpenGL.
+    void SetForceFallback(bool enable);
     
     /// Return whether rendering initialized.
     bool IsInitialized() const;
@@ -241,8 +243,8 @@ public:
     unsigned GetHiresShadowMapFormat() const { return hiresShadowMapFormat_; }
     /// Return whether texture render targets are supported.
     bool GetRenderTargetSupport() const { return renderTargetSupport_; }
-    /// Return whether deferred rendering is supported.
-    bool GetDeferredSupport() const { return deferredSupport_; }
+    /// Return whether fallback shaders are required. Always false on OpenGL.
+    bool GetFallback() const { return fallback_; }
     /// Return whether Shader Model 3 is supported. Always false on OpenGL.
     bool GetSM3Support() const { return false; }
     /// Return whether the hardware depth buffer can be sampled. Always true on OpenGL.

+ 34 - 11
Engine/Graphics/Renderer.cpp

@@ -709,10 +709,11 @@ void Renderer::Initialize()
     }
     else
     {
-        shaderPath_ = "Shaders/SM2/";
+        shaderPath_ = graphics_->GetFallback() ? "Shaders/SM2FB/" : "Shaders/SM2/";
         vsFormat_ = ".vs2";
         psFormat_ = ".ps2";
     }
+    
     #else
     {
         shaderPath_ = "Shaders/GLSL/";
@@ -1296,23 +1297,45 @@ bool Renderer::CreateShadowMaps()
     #else
     // Create shadow maps and dummy color rendertargets
     unsigned size = shadowMapSize_;
+    bool fallback = graphics_->GetFallback();
     for (unsigned i = 0; i < NUM_SHADOWMAP_RESOLUTIONS; ++i)
     {
-        if (!colorShadowMaps_[i])
-            colorShadowMaps_[i] = new Texture2D(context_);
-        if (!colorShadowMaps_[i]->SetSize(size, size, dummyColorFormat, TEXTURE_RENDERTARGET))
-            return false;
-        
+        // Dummy color rendertargets are not required in fallback mode, as the shadows are rendered into a color texture
+        if (!fallback)
+        {
+            if (!colorShadowMaps_[i])
+                colorShadowMaps_[i] = new Texture2D(context_);
+            if (!colorShadowMaps_[i]->SetSize(size, size, dummyColorFormat, TEXTURE_RENDERTARGET))
+                return false;
+        }
+        else
+        {
+            // In fallback mode, create one depth stencil that is large enough for the largest shadow map
+            if (!i)
+            {
+                if (!colorShadowMaps_[i])
+                    colorShadowMaps_[i] = new Texture2D(context_);
+                if (!colorShadowMaps_[i]->SetSize(size, size, D3DFMT_D16, TEXTURE_DEPTHSTENCIL))
+                    return false;
+            }
+        }
         for (unsigned j = 0; j < shadowMaps_[i].Size(); ++j)
         {
             if (!shadowMaps_[i][j])
                 shadowMaps_[i][j] = new Texture2D(context_);
-            if (!shadowMaps_[i][j]->SetSize(size, size, shadowMapFormat, TEXTURE_DEPTHSTENCIL))
+            if (!shadowMaps_[i][j]->SetSize(size, size, shadowMapFormat, fallback ? TEXTURE_RENDERTARGET : TEXTURE_DEPTHSTENCIL))
                 return false;
-            shadowMaps_[i][j]->SetFilterMode(hardwarePCF ? FILTER_BILINEAR : FILTER_NEAREST);
-            
-            // Link the color rendertarget to depth rendertarget
-            shadowMaps_[i][j]->GetRenderSurface()->SetLinkedRenderTarget(colorShadowMaps_[i]->GetRenderSurface());
+            if (!fallback)
+            {
+                shadowMaps_[i][j]->SetFilterMode(hardwarePCF ? FILTER_BILINEAR : FILTER_NEAREST);
+                // Link the color rendertarget to depth rendertarget
+                shadowMaps_[i][j]->GetRenderSurface()->SetLinkedRenderTarget(colorShadowMaps_[i]->GetRenderSurface());
+            }
+            else
+            {
+                shadowMaps_[i][j]->SetFilterMode(FILTER_NEAREST);
+                shadowMaps_[i][j]->GetRenderSurface()->SetLinkedDepthBuffer(colorShadowMaps_[0]->GetRenderSurface());
+            }
         }
         size >>= 1;
     }

+ 19 - 7
Engine/Graphics/View.cpp

@@ -1091,12 +1091,12 @@ unsigned View::ProcessLight(Light* light)
     if (light->GetLightType() == LIGHT_SPOT && !light->GetShapeTexture())
         light->SetShapeTexture(renderer_->GetDefaultLightSpot());
     
-    // Split the light if necessary
+    // Split the light if using shadows
     if (isShadowed)
         numSplits = SplitLight(light);
     else
     {
-        // No splitting, use the original light
+        // No shadows and no splitting, use the original light
         splitLights_[0] = light;
         numSplits = 1;
     }
@@ -2334,19 +2334,31 @@ void View::RenderShadowMap(const LightBatchQueue& queue)
     
     Texture2D* shadowMap = queue.light_->GetShadowMap();
     
-    graphics_->SetColorWrite(false);
     graphics_->SetTexture(TU_SHADOWMAP, 0);
-    graphics_->SetRenderTarget(0, shadowMap->GetRenderSurface()->GetLinkedRenderTarget());
-    graphics_->SetDepthStencil(shadowMap);
-    graphics_->Clear(CLEAR_DEPTH);
+    if (!graphics_->GetFallback())
+    {
+        graphics_->SetColorWrite(false);
+        graphics_->SetRenderTarget(0, shadowMap->GetRenderSurface()->GetLinkedRenderTarget());
+        graphics_->SetDepthStencil(shadowMap);
+        graphics_->Clear(CLEAR_DEPTH);
+    }
+    else
+    {
+        graphics_->SetColorWrite(true);
+        graphics_->SetRenderTarget(0, shadowMap->GetRenderSurface());
+        graphics_->SetDepthStencil(shadowMap->GetRenderSurface()->GetLinkedDepthBuffer());
+        graphics_->Clear(CLEAR_COLOR | CLEAR_DEPTH, Color::WHITE);
+    }
     
-    // Set shadow depth bias. Adjust according to the global shadow map resolution
+    // Set shadow depth bias
     BiasParameters parameters = queue.light_->GetShadowBias();
+    // Adjust the light's constant depth bias according to global shadow map resolution
     unsigned shadowMapSize = renderer_->GetShadowMapSize();
     if (shadowMapSize <= 512)
         parameters.constantBias_ *= 2.0f;
     else if (shadowMapSize >= 2048)
         parameters.constantBias_ *= 0.5f;
+    
     graphics_->SetDepthBias(parameters.constantBias_, parameters.slopeScaledBias_);
     
     // Set a scissor rectangle to match possible shadow map size reduction by out-zooming

+ 31 - 26
SourceAssets/HLSLShaders/Lighting.hlsl

@@ -44,34 +44,39 @@ float GetSpecular(float3 normal, float3 worldPos, float3 lightDir, float specula
 
 float GetShadow(float4 shadowPos)
 {
-    // Take four samples and average them
-    float4 pcfValues = cShadowIntensity.x;
-    #ifdef SM3
-        float2 ofs = cSampleOffsets.xy;
-        float4 projShadowPos = float4(shadowPos.xyz / shadowPos.w, 0.0);
-        float4 inLight = float4(
-            tex2Dlod(sShadowMap, float4(projShadowPos.xy + float2(ofs.x, ofs.x), projShadowPos.zw)).r,
-            tex2Dlod(sShadowMap, float4(projShadowPos.xy + float2(ofs.y, ofs.x), projShadowPos.zw)).r,
-            tex2Dlod(sShadowMap, float4(projShadowPos.xy + float2(ofs.x, ofs.y), projShadowPos.zw)).r,
-            tex2Dlod(sShadowMap, float4(projShadowPos.xy + float2(ofs.y, ofs.y), projShadowPos.zw)).r
-        );
-        #ifdef HWSHADOW
-            return cShadowIntensity.y + dot(inLight, pcfValues);
+    #ifndef FALLBACK
+        // Take four samples and average them
+        float4 pcfValues = cShadowIntensity.x;
+        #ifdef SM3
+            float2 ofs = cSampleOffsets.xy;
+            float4 projShadowPos = float4(shadowPos.xyz / shadowPos.w, 0.0);
+            float4 inLight = float4(
+                tex2Dlod(sShadowMap, float4(projShadowPos.xy + float2(ofs.x, ofs.x), projShadowPos.zw)).r,
+                tex2Dlod(sShadowMap, float4(projShadowPos.xy + float2(ofs.y, ofs.x), projShadowPos.zw)).r,
+                tex2Dlod(sShadowMap, float4(projShadowPos.xy + float2(ofs.x, ofs.y), projShadowPos.zw)).r,
+                tex2Dlod(sShadowMap, float4(projShadowPos.xy + float2(ofs.y, ofs.y), projShadowPos.zw)).r
+            );
+            #ifdef HWSHADOW
+                return cShadowIntensity.y + dot(inLight, pcfValues);
+            #else
+                return cShadowIntensity.y + dot(inLight > projShadowPos.z, pcfValues);
+            #endif
         #else
-            return cShadowIntensity.y + dot(inLight > projShadowPos.z, pcfValues);
+            float2 projOfs = cSampleOffsets.xy * shadowPos.w;
+            float4 inLight = float4(
+                tex2Dproj(sShadowMap, float4(shadowPos.xy + float2(projOfs.x, projOfs.x), shadowPos.zw)).r,
+                tex2Dproj(sShadowMap, float4(shadowPos.xy + float2(projOfs.y, projOfs.x), shadowPos.zw)).r,
+                tex2Dproj(sShadowMap, float4(shadowPos.xy + float2(projOfs.x, projOfs.y), shadowPos.zw)).r,
+                tex2Dproj(sShadowMap, float4(shadowPos.xy + float2(projOfs.y, projOfs.y), shadowPos.zw)).r
+            );
+            #ifdef HWSHADOW
+                return cShadowIntensity.y + dot(inLight, pcfValues);
+            #else
+                return cShadowIntensity.y + dot((inLight * shadowPos.w) > shadowPos.z, pcfValues);
+            #endif
         #endif
     #else
-        float2 projOfs = cSampleOffsets.xy * shadowPos.w;
-        float4 inLight = float4(
-            tex2Dproj(sShadowMap, float4(shadowPos.xy + float2(projOfs.x, projOfs.x), shadowPos.zw)).r,
-            tex2Dproj(sShadowMap, float4(shadowPos.xy + float2(projOfs.y, projOfs.x), shadowPos.zw)).r,
-            tex2Dproj(sShadowMap, float4(shadowPos.xy + float2(projOfs.x, projOfs.y), shadowPos.zw)).r,
-            tex2Dproj(sShadowMap, float4(shadowPos.xy + float2(projOfs.y, projOfs.y), shadowPos.zw)).r
-        );
-        #ifdef HWSHADOW
-            return cShadowIntensity.y + dot(inLight, pcfValues);
-        #else
-            return cShadowIntensity.y + dot((inLight * shadowPos.w) > shadowPos.z, pcfValues);
-        #endif
+        float inLight = DecodeDepth(tex2Dproj(sShadowMap, shadowPos).rg);
+        return cShadowIntensity.y + cShadowIntensity.x * ((inLight * shadowPos.w) > shadowPos.z);
     #endif
 }

+ 12 - 0
SourceAssets/HLSLShaders/Samplers.hlsl

@@ -42,3 +42,15 @@ float ReconstructDepth(float hwDepth)
 {
     return cDepthReconstruct.y / (hwDepth - cDepthReconstruct.x);
 }
+
+float2 EncodeDepth(float depth)
+{
+    depth *= 255.0;
+    return float2(floor(depth) / 255.0, frac(depth));
+}
+
+float DecodeDepth(float2 depth)
+{
+    const float2 dotValues = float2(1.0, 1.0 / 255.0);
+    return dot(depth, dotValues);
+}

+ 15 - 1
SourceAssets/HLSLShaders/Shadow.hlsl

@@ -14,6 +14,9 @@ void VS(float4 iPos : POSITION,
         float2 iTexCoord : TEXCOORD0,
         out float2 oTexCoord : TEXCOORD0,
     #endif
+    #ifdef FALLBACK
+        out float4 oClipPos : TEXCOORD1,
+    #endif
     out float4 oPos : POSITION)
 {
     #if defined(SKINNED)
@@ -27,12 +30,18 @@ void VS(float4 iPos : POSITION,
     #ifdef ALPHAMASK
         oTexCoord = GetTexCoord(iTexCoord);
     #endif
+    #ifdef FALLBACK
+        oClipPos = oPos;
+    #endif
 }
 
 void PS(
     #ifdef ALPHAMASK
         float2 iTexCoord : TEXCOORD0,
     #endif
+    #ifdef FALLBACK
+        float4 iClipPos : TEXCOORD1,
+    #endif
     out float4 oColor : COLOR0)
 {
     #ifdef ALPHAMASK
@@ -40,5 +49,10 @@ void PS(
             discard;
     #endif
 
-    oColor = 1.0;
+    #ifdef FALLBACK
+        float depth = min(iClipPos.z / iClipPos.w + cShadowIntensity.z, 1.0);
+        oColor = float4(EncodeDepth(depth), 1.0, 1.0);
+    #else
+        oColor = 1.0;
+    #endif
 }

+ 1 - 1
SourceAssets/HLSLShaders/Uniforms.hlsl

@@ -33,7 +33,7 @@ uniform float4 cMatDiffColor : register(C15);
 uniform float3 cMatEmissiveColor : register(C16);
 uniform float2 cMatSpecProperties : register(C17);
 uniform float4 cSampleOffsets : register(C18);
-uniform float2 cShadowIntensity : register(C19);
+uniform float3 cShadowIntensity : register(C19);
 uniform float4x4 cShadowProjPS : register(C20);
 uniform float4x4 cSpotProjPS : register(C24);
 uniform float4x4 cViewProjPS : register(C24);