Bläddra i källkod

Expanded the lit base pass optimization to cover also the first spot or point light affecting a drawable.
Do not render light to stencil if camera is inside the light volume.
Added optimization for directional light shadow casters that are fully occluded by another shadow caster: do not render the lit pass for them.
Removed the alpha masking hint. Alpha test no longer gives the batch a lower priority.

Lasse Öörni 14 år sedan
förälder
incheckning
589c0ee141

+ 4 - 7
Docs/Reference.dox

@@ -484,8 +484,7 @@ A technique definition looks like this:
 
 \code
 <technique>
-    <pass name="base|litbase|light|extra|shadow"
-        vs="VertexShaderName" ps="PixelShaderName" alphamask="true|false"
+    <pass name="base|litbase|light|extra|shadow" vs="VertexShaderName" ps="PixelShaderName" 
         alphatest="true|false" blend="replace|add|multiply|alpha|addalpha|premulalpha|invdestalpha"
         depthtest="always|equal|less|lessequal|greater|greaterequal" depthwrite="true|false" />
     <pass ... />
@@ -493,17 +492,15 @@ A technique definition looks like this:
 </technique>
 \endcode
 
-"Alphamask" is not an actual renderstate. It is a hint that the pixel shader will do discard(), and should therefore be rendered after fully opaque geometry to ensure optimal depth buffer performance.
-
 The passes are:
 
 - base: forward rendering base pass. Renders the ambient light.
-- litbase: forward rendering lit base pass. Renders the ambient light and the first directional light affecting the object for optimization.
+- litbase: forward rendering lit base pass. Renders both the ambient light and the first light affecting the object for optimization.
 - light: forward rendering light pass. Renders one light's contribution additively.
 - extra: custom rendering pass. Rendered after the opaque geometry.
 - shadow: shadow map rendering pass. Renders depth only.
 
-Note that the technique does not need to enumerate shaders used for different geometry types (non-skinned, skinned, instanced, billboard) and light types (directional, point and spot, shadowed and non-shadowed.) Instead specific hardcoded shader variations are assumed to exist.
+Note that the technique does not need to enumerate shaders used for different geometry types (non-skinned, skinned, instanced, billboard) and light types (directional, point and spot, specular and no specular, shadowed and non-shadowed.) Instead specific hardcoded shader variations are assumed to exist.
 
 
 \page Lights Lights and shadows
@@ -536,7 +533,7 @@ Shadow rendering is easily the most complex aspect of using lights, and therefor
 
 - CascadeParameters: these have effect only for directional lights. They specify the far clip distance of each of the cascaded shadow map splits (maximum 4), and the fade start point relative to the maximum shadow range. Unused splits can be set to far clip 0.
 
-- FocusParameters: these have effect for directional and spot lights, and control techniques to increase shadow map resolution. They consist of focus enable flag (allows focusing the shadow camera on the visible shadow casters & receivers), nonuniform scale enable flag (allows better resolution), automatic resolution reduction flag (reduces shadow map resolution when the light is far away), and quantization & minimum size parameters for the shadow camera view.
+- FocusParameters: these have effect for directional and spot lights, and control techniques to increase shadow map resolution. They consist of focus enable flag (allows focusing the shadow camera on the visible shadow casters & receivers), nonuniform scale enable flag (allows better resolution), automatic size reduction flag (reduces shadow map resolution when the light is far away), and quantization & minimum size parameters for the shadow camera view.
 
 Additionally there are shadow fade distance, shadow intensity, shadow resolution and shadow near/far ratio parameters:
 

+ 1 - 1
Docs/ScriptAPI.dox

@@ -1406,7 +1406,6 @@ Properties:<br>
 Pass
 
 Properties:<br>
-- bool alphaMask
 - bool alphaTest
 - BlendMode blendMode
 - CompareMode depthTestMode
@@ -1507,6 +1506,7 @@ Methods:<br>
 - Variant GetAttribute(const String&)
 - void Remove()
 - void AddLine(const Vector3&, const Vector3&, const Color&, bool arg3 = true)
+- void AddNode(Node@, bool arg1 = true)
 - void AddBoundingBox(const BoundingBox&, const Color&, bool arg2 = true)
 - void AddFrustum(const Frustum&, const Color&, bool arg2 = true)
 - void AddPolyhedron(const Polyhedron&, const Color&, bool arg2 = true)

+ 0 - 2
Engine/Engine/GraphicsAPI.cpp

@@ -303,8 +303,6 @@ static void RegisterMaterial(asIScriptEngine* engine)
     engine->RegisterEnumValue("CullMode", "CULL_CW", CULL_CW);
     
     RegisterRefCounted<Pass>(engine, "Pass");
-    engine->RegisterObjectMethod("Pass", "void set_alphaMask(bool)", asMETHOD(Pass, SetAlphaMask), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Pass", "bool get_alphaMask() const", asMETHOD(Pass, GetAlphaMask), asCALL_THISCALL);
     engine->RegisterObjectMethod("Pass", "void set_alphaTest(bool)", asMETHOD(Pass, SetAlphaTest), asCALL_THISCALL);
     engine->RegisterObjectMethod("Pass", "bool get_alphaTest() const", asMETHOD(Pass, GetAlphaTest), asCALL_THISCALL);
     engine->RegisterObjectMethod("Pass", "void set_blendMode(BlendMode)", asMETHOD(Pass, SetBlendMode), asCALL_THISCALL);

+ 18 - 15
Engine/Graphics/Drawable.cpp

@@ -55,8 +55,8 @@ Drawable::Drawable(Context* context) :
     lodDistance_(0.0f),
     sortValue_(0.0f),
     viewFrameNumber_(0),
-    basePassFlags_(0),
     viewCamera_(0),
+    firstLight_(0),
     worldBoundingBoxDirty_(true),
     lodLevelsDirty_(true)
 {
@@ -185,28 +185,18 @@ void Drawable::SetSortValue(float value)
     sortValue_ = value;
 }
 
-void Drawable::ClearBasePass()
+void Drawable::ClearLights()
 {
     for (unsigned i = 0; i < basePassFlags_.Size(); ++i)
         basePassFlags_[i] = 0;
+    firstLight_ = 0;
     lights_.Clear();
 }
 
-void Drawable::SetBasePass(unsigned batchIndex)
-{
-    unsigned index = batchIndex << 5;
-    if (basePassFlags_.Size() <= index)
-    {
-        unsigned oldSize = basePassFlags_.Size();
-        basePassFlags_.Resize(index + 1);
-        for (unsigned i = oldSize; i <= index; ++i)
-            basePassFlags_[i] = 0;
-    }
-    basePassFlags_[index] |= (1 << (batchIndex & 31));
-}
-
 void Drawable::AddLight(Light* light)
 {
+    if (lights_.Empty())
+        firstLight_ = light;
     lights_.Push(light);
 }
 
@@ -227,6 +217,19 @@ void Drawable::LimitLights()
         lights_.Resize(maxLights_);
 }
 
+void Drawable::SetBasePass(unsigned batchIndex)
+{
+    unsigned index = batchIndex << 5;
+    if (basePassFlags_.Size() <= index)
+    {
+        unsigned oldSize = basePassFlags_.Size();
+        basePassFlags_.Resize(index + 1);
+        for (unsigned i = oldSize; i <= index; ++i)
+            basePassFlags_[i] = 0;
+    }
+    basePassFlags_[index] |= (1 << (batchIndex & 31));
+}
+
 bool Drawable::IsInView(unsigned frameNumber) const
 {
     return viewFrameNumber_ == frameNumber;

+ 10 - 6
Engine/Graphics/Drawable.h

@@ -142,14 +142,14 @@ public:
     void MarkInView(const FrameInfo& frame);
     /// Mark in a shadow camera view this frame. If an actual view is already set, does not override it. Called by View.
     void MarkInShadowView(const FrameInfo& frame);
-    /// Clear base pass flags. Also resets light vector.
-    void ClearBasePass();
-    /// %Set base pass flag for a batch.
-    void SetBasePass(unsigned batchIndex);
-    /// Add a light, for drawables that limit the maximum light count.
+    /// Clear lights and base pass flags for a new frame.
+    void ClearLights();
+    /// Add a light.
     void AddLight(Light* light);
     /// Sort and limit lights to maximum allowed.
     void LimitLights();
+    /// %Set base pass flag for a batch.
+    void SetBasePass(unsigned batchIndex);
     /// Return distance from camera.
     float GetDistance() const { return distance_; }
     /// Return LOD scaled distance from camera.
@@ -164,6 +164,8 @@ public:
     bool HasBasePass(unsigned batchIndex) const;
     /// Return lights.
     const PODVector<Light*>& GetLights() const { return lights_; }
+    /// Return the first added light.
+    Light* GetFirstLight() const { return firstLight_; }
     
 protected:
     /// Handle node being assigned.
@@ -215,8 +217,10 @@ protected:
     PODVector<unsigned> basePassFlags_;
     /// Last camera rendered from. Not safe to dereference.
     Camera* viewCamera_;
-    /// Lights affecting this drawable, when light count is limited.
+    /// Lights affecting this drawable.
     PODVector<Light*> lights_;
+    /// First light added this frame.
+    Light* firstLight_;
     /// Bounding box dirty flag.
     bool worldBoundingBoxDirty_;
     /// Lod levels dirty flag.

+ 1 - 1
Engine/Graphics/Light.cpp

@@ -364,7 +364,7 @@ void Light::SetIntensitySortValue(const Vector3& position, bool forDrawable)
     if (lightType_ == LIGHT_DIRECTIONAL)
         // If sorting lights for a drawable's maximum lights sorting, use the actual intensity
         // Else (when sorting lights globally for the view) ensure directional lights always have a fixed first priority
-        // so that the combined ambient + first light optimization works as expected
+        // so that the lit base pass optimization has the most benefit
         sortValue_ = forDrawable ? invIntensity : 0.0f;
     else
         sortValue_ = invIntensity * (1.0f + (GetWorldPosition() - position).LengthFast() / range_);

+ 2 - 2
Engine/Graphics/OcclusionBuffer.cpp

@@ -81,8 +81,8 @@ bool OcclusionBuffer::SetSize(int width, int height)
     width_ = width;
     height_ = height;
     // Reserve extra memory in case 3D clipping is not exact
-    fullBuffer_ = new int[width * (height + 2)];
-    buffer_ = fullBuffer_.Get() + 1 * width;
+    fullBuffer_ = new int[width * (height + 2) + 2];
+    buffer_ = fullBuffer_.Get() + width + 1;
     mipBuffers_.Clear();
     
     // Build buffers for mip levels

+ 0 - 9
Engine/Graphics/Technique.cpp

@@ -67,7 +67,6 @@ static const String CompareModeNames[] =
 
 Pass::Pass(PassType type) :
     type_(type),
-    alphaMask_(false),
     alphaTest_(false),
     blendMode_(BLEND_REPLACE),
     depthTestMode_(CMP_LESSEQUAL),
@@ -79,11 +78,6 @@ Pass::~Pass()
 {
 }
 
-void Pass::SetAlphaMask(bool enable)
-{
-    alphaMask_ = enable;
-}
-
 void Pass::SetAlphaTest(bool enable)
 {
     alphaTest_ = enable;
@@ -176,9 +170,6 @@ bool Technique::Load(Deserializer& source)
             if (passElem.HasAttribute("ps"))
                 newPass->SetPixelShader(passElem.GetString("ps"));
             
-            if (passElem.HasAttribute("alphamask"))
-                newPass->SetAlphaMask(passElem.GetBool("alphamask"));
-            
             if (passElem.HasAttribute("alphatest"))
                 newPass->SetAlphaTest(passElem.GetBool("alphatest"));
             

+ 0 - 6
Engine/Graphics/Technique.h

@@ -37,8 +37,6 @@ public:
     /// Destruct.
     ~Pass();
     
-    /// %Set alpha masking hint on/off.
-    void SetAlphaMask(bool enable);
     /// %Set alpha test on/off.
     void SetAlphaTest(bool enable);
     /// %Set blend mode.
@@ -56,8 +54,6 @@ public:
     
     /// Return pass type.
     PassType GetType() const { return type_; }
-    /// Return alpha masking hint.
-    bool GetAlphaMask() const { return alphaMask_; }
     /// Return alpha test mode.
     bool GetAlphaTest() const { return alphaTest_; }
     /// Return blend mode.
@@ -78,8 +74,6 @@ public:
 private:
     /// Pass type.
     PassType type_;
-    /// Alpha masking hint.
-    bool alphaMask_;
     /// Alpha test mode.
     bool alphaTest_;
     /// Blend mode.

+ 87 - 63
Engine/Graphics/View.cpp

@@ -305,7 +305,7 @@ void View::GetDrawables()
         unsigned flags = drawable->GetDrawableFlags();
         if (flags & DRAWABLE_GEOMETRY)
         {
-            drawable->ClearBasePass();
+            drawable->ClearLights();
             drawable->MarkInView(frame_);
             drawable->UpdateGeometry(frame_);
             
@@ -424,7 +424,6 @@ void View::GetBatches()
                         shadowBatch.camera_ = shadowCamera;
                         shadowBatch.distance_ = shadowCamera->GetDistance(drawable->GetWorldPosition());
                         shadowBatch.lightQueue_ = &lightQueue;
-                        shadowBatch.hasPriority_ = !pass->GetAlphaTest() && !pass->GetAlphaMask();
                         
                         renderer_->SetBatchShaders(shadowBatch, tech, pass);
                         shadowQueue.shadowBatches_.AddBatch(shadowBatch);
@@ -436,6 +435,11 @@ void View::GetBatches()
             for (unsigned j = 0; j < litGeometries_.Size(); ++j)
             {
                 Drawable* drawable = litGeometries_[j];
+                // In the occluded & shadowed directional light optimization, we null out pointers instead of removing entries
+                // from the lit geometry vector. Therefore must be prepared for a null pointer
+                if (!drawable)
+                    continue;
+                
                 drawable->AddLight(light);
                 
                 // If drawable limits maximum lights, only record the light, and check maximum count / build batches later
@@ -503,6 +507,7 @@ void View::GetBatches()
                 // Fill the rest of the batch
                 baseBatch.camera_ = camera_;
                 baseBatch.distance_ = drawable->GetDistance();
+                baseBatch.hasPriority_ = true;
                 
                 Pass* pass = 0;
                 
@@ -512,15 +517,10 @@ void View::GetBatches()
                 {
                     renderer_->SetBatchShaders(baseBatch, tech, pass);
                     if (pass->GetBlendMode() == BLEND_REPLACE)
-                    {
-                        baseBatch.hasPriority_ = !pass->GetAlphaTest() && !pass->GetAlphaMask();
                         baseQueue_.AddBatch(baseBatch);
-                    }
                     else
-                    {
-                        baseBatch.hasPriority_ = true;
                         transparentQueue_.AddBatch(baseBatch, true);
-                    }
+                    
                     continue;
                 }
                 else
@@ -529,7 +529,6 @@ void View::GetBatches()
                     pass = tech->GetPass(PASS_EXTRA);
                     if (pass)
                     {
-                        baseBatch.hasPriority_ = false;
                         renderer_->SetBatchShaders(baseBatch, tech, pass);
                         extraQueue_.AddBatch(baseBatch);
                     }
@@ -545,6 +544,7 @@ void View::GetBatches()
 void View::GetLitBatches(Drawable* drawable, LightBatchQueue& lightQueue)
 {
     Light* light = lightQueue.light_;
+    Light* firstLight = drawable->GetFirstLight();
     
     // Shadows on transparencies can only be rendered if shadow maps are not reused
     bool allowTransparentShadows = !renderer_->reuseShadowMaps_;
@@ -560,15 +560,14 @@ void View::GetLitBatches(Drawable* drawable, LightBatchQueue& lightQueue)
             continue;
         
         Pass* pass = 0;
-        bool priority = false;
         
-        // For the (first) directional light, check for lit base pass
-        if (light == lights_[0] && light->GetLightType() == LIGHT_DIRECTIONAL && !drawable->HasBasePass(i))
+        // Check for lit base pass. Because it uses the replace blend mode, it must be ensured to be the first light
+        if (light == firstLight && !drawable->HasBasePass(i))
         {
             pass = tech->GetPass(PASS_LITBASE);
             if (pass)
             {
-                priority = true;
+                litBatch.hasPriority_ = true;
                 drawable->SetBasePass(i);
             }
         }
@@ -584,7 +583,6 @@ void View::GetLitBatches(Drawable* drawable, LightBatchQueue& lightQueue)
         litBatch.camera_ = camera_;
         litBatch.distance_ = drawable->GetDistance();
         litBatch.lightQueue_ = &lightQueue;
-        litBatch.hasPriority_ = priority;
         
         // Check from the ambient pass whether the object is opaque or transparent
         Pass* ambientPass = tech->GetPass(PASS_BASE);
@@ -765,6 +763,7 @@ unsigned View::ProcessLight(Light* light)
     switch (type)
     {
     case LIGHT_DIRECTIONAL:
+        dirLightShadowCasters_.Clear();
         for (unsigned i = 0; i < geometries_.Size(); ++i)
         {
             if (geometries_[i]->GetLightMask() & light->GetLightMask())
@@ -868,6 +867,10 @@ unsigned View::ProcessLight(Light* light)
             OccludedFrustumOctreeQuery query(tempDrawables_, shadowCamera->GetFrustum(), buffer,
                 DRAWABLE_GEOMETRY, camera_->GetViewMask(), false, true);
             octree_->GetDrawables(query);
+            
+            // Store the raw list of possible shadow casters for a later optimization step
+            for (unsigned i = 0; i < tempDrawables_.Size(); ++i)
+                dirLightShadowCasters_.Insert(tempDrawables_[i]);
         }
         
         // Check which shadow casters actually contribute to the shadowing
@@ -876,11 +879,23 @@ unsigned View::ProcessLight(Light* light)
             hasShadowCasters = true;
     }
     
-    // If no shadow casters, the light can be rendered unshadowed. At this point we have not allocated a shadow map
-    // yet, so the only cost is the shadow camera setup & queries
+    // If no shadow casters, the light can be rendered unshadowed. At this point we have not allocated a shadow map yet, so the
+    // only cost has been the shadow camera setup & queries
     if (!hasShadowCasters)
         shadowSplits = 0;
     
+    // For a directional light queried with occlusion, can perform an optimization post-step: if a visible shadow caster has been
+    // rejected due to occlusion, the light does not need to be rendered on it either
+    if (shadowSplits && useOcclusion)
+    {
+        for (unsigned i = 0; i < litGeometries_.Size(); ++i)
+        {
+            Drawable* drawable = litGeometries_[i];
+            if (drawable->GetCastShadows() && !dirLightShadowCasters_.Contains(drawable))
+                litGeometries_[i] = 0;
+        }
+    }
+
     return shadowSplits;
 }
 
@@ -1040,58 +1055,67 @@ void View::OptimizeLightByStencil(Light* light)
     if (light && renderer_->GetLightStencilMasking())
     {
         Geometry* geometry = renderer_->GetLightGeometry(light);
+        if (!geometry)
+        {
+            graphics_->SetStencilTest(false);
+            return;
+        }
+        
+        LightType type = light->GetLightType();
+        Matrix3x4 view(camera_->GetInverseWorldTransform());
+        Matrix4 projection(camera_->GetProjection());
+        float lightDist;
         
-        if (geometry)
+        if (type == LIGHT_POINT)
+            lightDist = Sphere(light->GetWorldPosition(), light->GetRange() * 1.25f).DistanceFast(camera_->GetWorldPosition());
+        else
+            lightDist = light->GetFrustum().Distance(camera_->GetWorldPosition());
+        
+        // If the camera is actually inside the light volume, do not draw to stencil as it would waste fillrate
+        if (lightDist < M_EPSILON)
         {
-            LightType type = light->GetLightType();
-            
-            // If the stencil value has wrapped, clear the whole stencil first
-            if (!lightStencilValue_)
-            {
-                graphics_->Clear(CLEAR_STENCIL);
-                lightStencilValue_ = 1;
-            }
-            
-            Matrix3x4 view(camera_->GetInverseWorldTransform());
-            Matrix4 projection(camera_->GetProjection());
-            float lightDist;
-            if (type == LIGHT_POINT)
-                lightDist = Sphere(light->GetWorldPosition(), light->GetRange() * 1.25f).DistanceFast(camera_->GetWorldPosition());
-            else
-                lightDist = light->GetFrustum().Distance(camera_->GetWorldPosition());
-            
-            // If possible, render the stencil volume front faces. However, close to the near clip plane render back faces instead
-            // to avoid clipping the front faces.
-            if (lightDist < camera_->GetNearClip() * 2.0f)
-            {
-                graphics_->SetCullMode(CULL_CW);
-                graphics_->SetDepthTest(CMP_GREATER);
-            }
-            else
-            {
-                graphics_->SetCullMode(CULL_CCW);
-                graphics_->SetDepthTest(CMP_LESSEQUAL);
-            }
-            
-            graphics_->SetColorWrite(false);
-            graphics_->SetDepthWrite(false);
-            graphics_->SetStencilTest(true, CMP_ALWAYS, OP_REF, OP_KEEP, OP_KEEP, lightStencilValue_);
-            graphics_->SetShaders(renderer_->stencilVS_, renderer_->stencilPS_);
-            graphics_->SetShaderParameter(VSP_VIEWPROJ, projection * view);
-            graphics_->SetShaderParameter(VSP_MODEL, light->GetVolumeTransform());
-            
-            geometry->Draw(graphics_);
-            
-            graphics_->ClearTransformSources();
-            graphics_->SetColorWrite(true);
-            graphics_->SetStencilTest(true, CMP_EQUAL, OP_KEEP, OP_KEEP, OP_KEEP, lightStencilValue_);
-            ++lightStencilValue_;
-            
+            graphics_->SetStencilTest(false);
             return;
         }
+        
+        // If the stencil value has wrapped, clear the whole stencil first
+        if (!lightStencilValue_)
+        {
+            graphics_->Clear(CLEAR_STENCIL);
+            lightStencilValue_ = 1;
+        }
+        
+        // If possible, render the stencil volume front faces. However, close to the near clip plane render back faces instead
+        // to avoid clipping the front faces.
+        if (lightDist < camera_->GetNearClip() * 2.0f)
+        {
+            graphics_->SetCullMode(CULL_CW);
+            graphics_->SetDepthTest(CMP_GREATER);
+        }
+        else
+        {
+            graphics_->SetCullMode(CULL_CCW);
+            graphics_->SetDepthTest(CMP_LESSEQUAL);
+        }
+        
+        graphics_->SetColorWrite(false);
+        graphics_->SetDepthWrite(false);
+        graphics_->SetStencilTest(true, CMP_ALWAYS, OP_REF, OP_KEEP, OP_KEEP, lightStencilValue_);
+        graphics_->SetShaders(renderer_->stencilVS_, renderer_->stencilPS_);
+        graphics_->SetShaderParameter(VSP_VIEWPROJ, projection * view);
+        graphics_->SetShaderParameter(VSP_MODEL, light->GetVolumeTransform());
+        
+        geometry->Draw(graphics_);
+        
+        graphics_->ClearTransformSources();
+        graphics_->SetColorWrite(true);
+        graphics_->SetStencilTest(true, CMP_EQUAL, OP_KEEP, OP_KEEP, OP_KEEP, lightStencilValue_);
+        
+        // Increase stencil value for next light
+        ++lightStencilValue_;
     }
-    
-    graphics_->SetStencilTest(false);
+    else
+        graphics_->SetStencilTest(false);
 }
 
 const Rect& View::GetLightScissor(Light* light)

+ 2 - 0
Engine/Graphics/View.h

@@ -204,6 +204,8 @@ private:
     PODVector<Light*> lights_;
     /// Drawables that limit their maximum light count.
     HashSet<Drawable*> maxLightsDrawables_;
+    /// Directional light shadow caster drawables.
+    HashSet<Drawable*> dirLightShadowCasters_;
     /// Light queue indices of processed lights.
     Map<Light*, unsigned> lightQueueIndex_;
     /// View-global shader parameters.

+ 1 - 1
SourceAssets/GLSLShaders/Forward.frag

@@ -44,7 +44,7 @@ void main()
         diffColor *= vColor;
     #endif
 
-    #if defined(NORMALMAP) || defined(SPECMAP)
+    #ifdef NORMALMAP
         vec4 normalInput = texture2D(sNormalMap, vTexCoord);
     #endif
 

+ 18 - 21
SourceAssets/GLSLShaders/Forward.xml

@@ -36,47 +36,44 @@
     </shader>
     <shader name="Forward" type="ps">
         <option name="Diff" define="DIFFMAP" />
-        <option name="Normal" define="NORMALMAP" />
-        <option name="SpecMap" define="SPECMAP" />
+        <option name="Normal" define="NORMALMAP" require="LIGHT" />
+        <option name="SpecMap" define="SPECMAP" require="NORMALMAP" />
         <option name="VCol" define="VERTEXCOLOR" />
         <option name="Volume" define="VOLUMETRIC">
             <exclude name="Additive" />
-            <exclude name="Ambient" />
             <exclude name="Normal" />
             <exclude name="Shadow" />
             <exclude name="Spec" />
             <exclude name="SpecMap" />
             <exclude name="Unlit" />
         </option>
-        <variation name="Unlit" define="UNLIT">
-            <exclude name="Normal" />
-            <exclude name="Spec" />
-            <exclude name="SpecMap" />
+        <variation name="Unlit" define="UNLIT" />
+        <variation name="Additive" define="ADDITIVE" />
+        <variation name="Ambient" define="AMBIENT" />
+        <variation name="Dir">
+            <define name="DIRLIGHT" />
+            <define name="LIGHT" />
         </variation>
-        <variation name="Additive" define="ADDITIVE">
-            <exclude name="Normal" />
-            <exclude name="Spec" />
-            <exclude name="SpecMap" />
+        <variation name="Spot">
+            <define name="SPOTLIGHT" />
+            <define name="LIGHT" />
         </variation>
-        <variation name="Ambient" define="AMBIENT">
-            <exclude name="Normal" />
-            <exclude name="Spec" />
-            <exclude name="SpecMap" />
+        <variation name="Point">
+            <define name="POINTLIGHT" />
+            <define name="LIGHT" />
         </variation>
         <variation name="AmbientDir">
             <define name="AMBIENT" />
             <define name="DIRLIGHT" />
             <define name="LIGHT" />
         </variation>
-        <variation name="Dir">
-            <define name="DIRLIGHT" />
-            <define name="LIGHT" />
-        </variation>
-        <variation name="Spot">
+        <variation name="AmbientSpot">
+            <define name="AMBIENT" />
             <define name="SPOTLIGHT" />
             <define name="LIGHT" />
         </variation>
-        <variation name="Point">
+        <variation name="AmbientPoint">
+            <define name="AMBIENT" />
             <define name="POINTLIGHT" />
             <define name="LIGHT" />
         </variation>

+ 1 - 1
SourceAssets/HLSLShaders/Forward.hlsl

@@ -191,7 +191,7 @@ void PS(float2 iTexCoord : TEXCOORD0,
         diffColor *= iColor;
     #endif
 
-    #if defined(NORMALMAP) || defined(SPECMAP)
+    #ifdef NORMALMAP
         float4 normalInput = tex2D(sNormalMap, iTexCoord);
     #endif
 

+ 18 - 21
SourceAssets/HLSLShaders/Forward.xml

@@ -37,47 +37,44 @@
     </shader>
     <shader name="Forward" type="ps">
         <option name="Diff" define="DIFFMAP" />
-        <option name="Normal" define="NORMALMAP" />
-        <option name="SpecMap" define="SPECMAP" include="Spec" />
+        <option name="Normal" define="NORMALMAP" require="LIGHT" />
+        <option name="SpecMap" define="SPECMAP" require="NORMALMAP" />
         <option name="VCol" define="VERTEXCOLOR" />
         <option name="Volume" define="VOLUMETRIC">
             <exclude name="Additive" />
-            <exclude name="Ambient" />
             <exclude name="Normal" />
             <exclude name="Shadow" />
             <exclude name="Spec" />
             <exclude name="SpecMap" />
             <exclude name="Unlit" />
         </option>
-        <variation name="Unlit" define="UNLIT">
-            <exclude name="Normal" />
-            <exclude name="Spec" />
-            <exclude name="SpecMap" />
+        <variation name="Unlit" define="UNLIT" />
+        <variation name="Additive" define="ADDITIVE" />
+        <variation name="Ambient" define="AMBIENT" />
+        <variation name="Dir">
+            <define name="DIRLIGHT" />
+            <define name="LIGHT" />
         </variation>
-        <variation name="Additive" define="ADDITIVE">
-            <exclude name="Normal" />
-            <exclude name="Spec" />
-            <exclude name="SpecMap" />
+        <variation name="Spot">
+            <define name="SPOTLIGHT" />
+            <define name="LIGHT" />
         </variation>
-        <variation name="Ambient" define="AMBIENT">
-            <exclude name="Normal" />
-            <exclude name="Spec" />
-            <exclude name="SpecMap" />
+        <variation name="Point">
+            <define name="POINTLIGHT" />
+            <define name="LIGHT" />
         </variation>
         <variation name="AmbientDir">
             <define name="AMBIENT" />
             <define name="DIRLIGHT" />
             <define name="LIGHT" />
         </variation>
-        <variation name="Dir">
-            <define name="DIRLIGHT" />
-            <define name="LIGHT" />
-        </variation>
-        <variation name="Spot">
+        <variation name="AmbientSpot">
+            <define name="AMBIENT" />
             <define name="SPOTLIGHT" />
             <define name="LIGHT" />
         </variation>
-        <variation name="Point">
+        <variation name="AmbientPoint">
+            <define name="AMBIENT" />
             <define name="POINTLIGHT" />
             <define name="LIGHT" />
         </variation>