Browse Source

Fixed component ID overwrite when copy-pasting scene nodes in the editor.
Fixed light's bounding box not updating on light type, range, fov or aspect ratio change.
Fixed possible light stencil volume clipping bug when approaching a point light.
Fall back to non-specular or non-shadowed shaders if the desired light shader variation does not exist.
Unified order of light vertex & pixel shader variations.
Added support for enum accessor attributes.
Added light stencil mask toggle in the editor.

Lasse Öörni 14 years ago
parent
commit
fd06d3cf4a

+ 4 - 4
Bin/Data/Scripts/Editor.as

@@ -96,13 +96,12 @@ void LoadConfig()
     {
         renderer.textureQuality = renderingElem.GetInt("texturequality");
         renderer.materialQuality = renderingElem.GetInt("materialquality");
-        if (renderingElem.HasAttribute("shadowresolution"))
-            SetShadowResolution(renderingElem.GetInt("shadowresolution"));
-        if (renderingElem.HasAttribute("shadowquality"))
-            renderer.shadowQuality = renderingElem.GetInt("shadowquality");
+        SetShadowResolution(renderingElem.GetInt("shadowresolution"));
+        renderer.shadowQuality = renderingElem.GetInt("shadowquality");
         renderer.maxOccluderTriangles = renderingElem.GetInt("maxoccludertriangles");
         renderer.specularLighting = renderingElem.GetBool("specularlighting");
         renderer.dynamicInstancing = renderingElem.GetBool("dynamicinstancing");
+        renderer.lightStencilMasking = renderingElem.GetBool("lightstencilmasking");
         engine.maxFps = renderingElem.GetBool("framelimiter") ? 200 : 0;
     }
 }
@@ -141,6 +140,7 @@ void SaveConfig()
     renderingElem.SetInt("maxoccludertriangles", renderer.maxOccluderTriangles);
     renderingElem.SetBool("specularlighting", renderer.specularLighting);
     renderingElem.SetBool("dynamicinstancing", renderer.dynamicInstancing);
+    renderingElem.SetBool("lightstencilmasking", renderer.lightStencilMasking);
     renderingElem.SetBool("framelimiter", engine.maxFps > 0);
 
     config.Save(File(configFileName, FILE_WRITE));

+ 10 - 0
Bin/Data/Scripts/Editor/EditorCamera.as

@@ -130,6 +130,9 @@ void UpdateEditorSettingsDialog()
     CheckBox@ dynamicInstancingToggle = settingsDialog.GetChild("DynamicInstancingToggle", true);
     dynamicInstancingToggle.checked = renderer.dynamicInstancing;
 
+    CheckBox@ lightStencilMaskingToggle = settingsDialog.GetChild("LightStencilMaskingToggle", true);
+    lightStencilMaskingToggle.checked = renderer.lightStencilMasking;
+
     CheckBox@ frameLimiterToggle = settingsDialog.GetChild("FrameLimiterToggle", true);
     frameLimiterToggle.checked = engine.maxFps > 0;
 
@@ -165,6 +168,7 @@ void UpdateEditorSettingsDialog()
         SubscribeToEvent(maxOccluderTrianglesEdit, "TextFinished", "EditMaxOccluderTriangles");
         SubscribeToEvent(specularLightingToggle, "Toggled", "EditSpecularLighting");
         SubscribeToEvent(dynamicInstancingToggle, "Toggled", "EditDynamicInstancing");
+        SubscribeToEvent(lightStencilMaskingToggle, "Toggled", "EditLightStencilMasking");
         SubscribeToEvent(frameLimiterToggle, "Toggled", "EditFrameLimiter");
         SubscribeToEvent(settingsDialog.GetChild("CloseButton", true), "Released", "HideEditorSettingsDialog");
         subscribedToCameraEdits = true;
@@ -327,6 +331,12 @@ void EditDynamicInstancing(StringHash eventType, VariantMap& eventData)
     renderer.dynamicInstancing = edit.checked;
 }
 
+void EditLightStencilMasking(StringHash eventType, VariantMap& eventData)
+{
+    CheckBox@ edit = eventData["Element"].GetUIElement();
+    renderer.lightStencilMasking = edit.checked;
+}
+
 void EditFrameLimiter(StringHash eventType, VariantMap& eventData)
 {
     CheckBox@ edit = eventData["Element"].GetUIElement();

+ 10 - 0
Bin/Data/UI/EditorSettingsDialog.xml

@@ -276,6 +276,16 @@
             <text value="Dynamic instancing" />
         </element>
     </element>
+    <element>
+        <fixedheight value="17" />
+        <layout mode="horizontal" spacing="8" />
+        <element type="CheckBox" name="LightStencilMaskingToggle">
+            <fixedsize value="16 16" />
+        </element>
+        <element type="Text">
+            <text value="Light stencil masking" />
+        </element>
+    </element>
     <element>
         <fixedheight value="17" />
         <layout mode="horizontal" spacing="8" />

+ 14 - 2
Engine/Core/Attribute.h

@@ -73,8 +73,8 @@ struct AttributeInfo
     }
     
     /// Construct offset enum attribute.
-    AttributeInfo(VariantType type, const char* name, unsigned offset, const String* enumNames, const Variant& defaultValue, unsigned mode) :
-        type_(type),
+    AttributeInfo(const char* name, unsigned offset, const String* enumNames, const Variant& defaultValue, unsigned mode) :
+        type_(VAR_INT),
         name_(name),
         offset_(offset),
         enumNames_(enumNames),
@@ -95,6 +95,18 @@ struct AttributeInfo
     {
     }
     
+    /// Construct accessor enum attribute.
+    AttributeInfo(const char* name, AttributeAccessor* accessor, const String* enumNames, const Variant& defaultValue, unsigned mode) :
+        type_(VAR_INT),
+        name_(name),
+        offset_(0),
+        enumNames_(enumNames),
+        accessor_(accessor),
+        defaultValue_(defaultValue),
+        mode_(mode)
+    {
+    }
+    
     /// Attribute type.
     VariantType type_;
     /// Name.

+ 20 - 10
Engine/Graphics/Batch.cpp

@@ -71,7 +71,7 @@ void Batch::CalculateSortKey()
         (((unsigned long long)material) << 16) | geometry;
 }
 
-void Batch::Prepare(Graphics* graphics, const HashMap<StringHash, Vector4>& shaderParameters, bool setModelTransform) const
+void Batch::Prepare(Graphics* graphics, Renderer* renderer, const HashMap<StringHash, Vector4>& shaderParameters, bool setModelTransform) const
 {
     if (!vertexShader_ || !pixelShader_)
         return;
@@ -362,15 +362,25 @@ void Batch::Prepare(Graphics* graphics, const HashMap<StringHash, Vector4>& shad
         if (shadowMap && graphics->NeedTextureUnit(TU_SHADOWMAP))
             graphics->SetTexture(TU_SHADOWMAP, shadowMap);
         if (graphics->NeedTextureUnit(TU_LIGHTRAMP))
-            graphics->SetTexture(TU_LIGHTRAMP, light->GetRampTexture());
-        if (graphics->NeedTextureUnit(TU_LIGHTSPOT))
-            graphics->SetTexture(TU_LIGHTSPOT, light->GetShapeTexture());
+        {
+            Texture* rampTexture = light->GetRampTexture();
+            if (!rampTexture)
+                rampTexture = renderer->GetDefaultLightRamp();
+            graphics->SetTexture(TU_LIGHTRAMP, rampTexture);
+        }
+        if (graphics->NeedTextureUnit(TU_LIGHTSHAPE))
+        {
+            Texture* shapeTexture = light->GetShapeTexture();
+            if (!shapeTexture && light->GetLightType() == LIGHT_SPOT)
+                shapeTexture = renderer->GetDefaultLightSpot();
+            graphics->SetTexture(TU_LIGHTSHAPE, shapeTexture);
+        }
     }
 }
 
-void Batch::Draw(Graphics* graphics, const HashMap<StringHash, Vector4>& shaderParameters) const
+void Batch::Draw(Graphics* graphics, Renderer* renderer, const HashMap<StringHash, Vector4>& shaderParameters) const
 {
-    Prepare(graphics, shaderParameters);
+    Prepare(graphics, renderer, shaderParameters);
     geometry_->Draw(graphics);
 }
 
@@ -392,7 +402,7 @@ void BatchGroup::SetTransforms(Renderer* renderer, void* lockedData, unsigned& f
     freeIndex += instances_.Size();
 }
 
-void BatchGroup::Draw(Graphics* graphics, VertexBuffer* instanceBuffer, const HashMap<StringHash, Vector4>& shaderParameters) const
+void BatchGroup::Draw(Graphics* graphics, Renderer* renderer, const HashMap<StringHash, Vector4>& shaderParameters) const
 {
     if (!instances_.Size())
         return;
@@ -408,14 +418,14 @@ void BatchGroup::Draw(Graphics* graphics, VertexBuffer* instanceBuffer, const Ha
     batch.lightQueue_ = lightQueue_;
     batch.vertexShaderIndex_ = vertexShaderIndex_;
     
-    Renderer* renderer = graphics->GetSubsystem<Renderer>();
     unsigned minGroupSize = renderer->GetMinInstanceGroupSize();
     unsigned maxIndexCount = renderer->GetMaxInstanceTriangles() * 3;
     
     // Draw as individual instances if below minimum size, or if instancing not supported
+    VertexBuffer* instanceBuffer = renderer->GetInstancingBuffer();
     if (!instanceBuffer || instances_.Size() < minGroupSize || geometry_->GetIndexCount() > maxIndexCount)
     {
-        batch.Prepare(graphics, shaderParameters, false);
+        batch.Prepare(graphics, renderer, shaderParameters, false);
         
         graphics->SetIndexBuffer(geometry_->GetIndexBuffer());
         graphics->SetVertexBuffers(geometry_->GetVertexBuffers(), geometry_->GetVertexElementMasks());
@@ -441,7 +451,7 @@ void BatchGroup::Draw(Graphics* graphics, VertexBuffer* instanceBuffer, const Ha
         else
             batch.vertexShader_ = vertexShaders[vertexShaderIndex_ + GEOM_INSTANCED];
         
-        batch.Prepare(graphics, shaderParameters, false);
+        batch.Prepare(graphics, renderer, shaderParameters, false);
         
         // Get the geometry vertex buffers, then add the instancing stream buffer
         // Hack: use a const_cast to avoid dynamic allocation of new temp vectors

+ 3 - 3
Engine/Graphics/Batch.h

@@ -62,9 +62,9 @@ struct Batch
     /// Calculate sort key, which consists of priority flag, light, pass and geometry.
     void CalculateSortKey();
     /// Prepare for rendering.
-    void Prepare(Graphics* graphics, const HashMap<StringHash, Vector4>& shaderParameters, bool setModelTransform = true) const;
+    void Prepare(Graphics* graphics, Renderer* renderer, const HashMap<StringHash, Vector4>& shaderParameters, bool setModelTransform = true) const;
     /// Prepare and draw.
-    void Draw(Graphics* graphics, const HashMap<StringHash, Vector4>& shaderParameters) const;
+    void Draw(Graphics* graphics, Renderer* renderer, const HashMap<StringHash, Vector4>& shaderParameters) const;
     
     /// Geometry.
     Geometry* geometry_;
@@ -138,7 +138,7 @@ struct BatchGroup
     /// Pre-set the instance transforms. Buffer must be big enough to hold all transforms.
     void SetTransforms(Renderer* renderer, void* lockedData, unsigned& freeIndex);
     /// Prepare and draw.
-    void Draw(Graphics* graphics, VertexBuffer* instanceBuffer, const HashMap<StringHash, Vector4>& shaderParameters) const;
+    void Draw(Graphics* graphics, Renderer* renderer, const HashMap<StringHash, Vector4>& shaderParameters) const;
     
     /// Geometry.
     Geometry* geometry_;

+ 2 - 2
Engine/Graphics/Direct3D9/D3D9Graphics.cpp

@@ -2281,8 +2281,8 @@ void Graphics::SetTextureUnitMappings()
     textureUnits_["EnvironmentMap"] = TU_ENVIRONMENT;
     textureUnits_["EnvironmentCubeMap"] = TU_ENVIRONMENT;
     textureUnits_["LightRampMap"] = TU_LIGHTRAMP;
-    textureUnits_["LightSpotMap"] = TU_LIGHTSPOT;
-    textureUnits_["LightCubeMap"]  = TU_LIGHTSPOT;
+    textureUnits_["LightSpotMap"] = TU_LIGHTSHAPE;
+    textureUnits_["LightCubeMap"]  = TU_LIGHTSHAPE;
     textureUnits_["ShadowMap"] = TU_SHADOWMAP;
     textureUnits_["FaceSelectCubeMap"] = TU_FACESELECT;
     textureUnits_["IndirectionCubeMap"] = TU_INDIRECTION;

+ 1 - 1
Engine/Graphics/GraphicsDefs.h

@@ -236,7 +236,7 @@ enum TextureUnit
     MAX_MATERIAL_TEXTURE_UNITS = 6,
     TU_SHADOWMAP = 6,
     TU_LIGHTRAMP = 7,
-    TU_LIGHTSPOT = 8,
+    TU_LIGHTSHAPE = 8,
     TU_FACESELECT = 9,
     TU_INDIRECTION = 10,
     MAX_TEXTURE_UNITS = 16

+ 16 - 13
Engine/Graphics/Light.cpp

@@ -69,6 +69,11 @@ void FocusParameters::Validate()
     minView_ = Max(minView_, SHADOW_MIN_VIEW);
 }
 
+template<> LightType Variant::Get<LightType>() const
+{
+    return (LightType)GetInt();
+}
+
 OBJECTTYPESTATIC(Light);
 
 Light::Light(Context* context) :
@@ -98,7 +103,7 @@ void Light::RegisterObject(Context* context)
 {
     context->RegisterFactory<Light>();
     
-    ENUM_ATTRIBUTE(Light, "Light Type", lightType_, typeNames, DEFAULT_LIGHTTYPE, AM_DEFAULT);
+    ENUM_ACCESSOR_ATTRIBUTE(Light, "Light Type", GetLightType, SetLightType, LightType, typeNames, DEFAULT_LIGHTTYPE, AM_DEFAULT);
     ATTRIBUTE(Light, VAR_COLOR, "Color", color_, Color(), AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(Light, VAR_FLOAT, "Specular Intensity", GetSpecularIntensity, SetSpecularIntensity, float, 0.0f, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(Light, VAR_FLOAT, "Range", GetRange, SetRange, float, 0.0f, AM_DEFAULT);
@@ -114,9 +119,9 @@ void Light::RegisterObject(Context* context)
     ACCESSOR_ATTRIBUTE(Light, VAR_FLOAT, "Shadow Fade Distance", GetShadowFadeDistance, SetShadowFadeDistance, float, 0.0f, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(Light, VAR_FLOAT, "Shadow Intensity", GetShadowIntensity, SetShadowIntensity, float, 0.0f, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(Light, VAR_FLOAT, "Shadow Resolution", GetShadowResolution, SetShadowResolution, float, 1.0f, AM_DEFAULT);
-    ATTRIBUTE(Light, VAR_FLOAT, "Near/Farclip Ratio", shadowNearFarRatio_, DEFAULT_SHADOWNEARFARRATIO, AM_DEFAULT);
-    ATTRIBUTE(Light, VAR_FLOAT, "Depth Constant Bias", shadowBias_.constantBias_, DEFAULT_CONSTANTBIAS, AM_DEFAULT);
-    ATTRIBUTE(Light, VAR_FLOAT, "Depth Slope Bias", shadowBias_.slopeScaledBias_, DEFAULT_SLOPESCALEDBIAS, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_BOOL, "Focus To Scene", shadowFocus_.focus_, true, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_BOOL, "Non-uniform View", shadowFocus_.nonUniform_, true, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_BOOL, "Auto-Reduce Size", shadowFocus_.autoSize_, true, AM_DEFAULT);
     ATTRIBUTE(Light, VAR_FLOAT, "CSM Split 1 End", shadowCascade_.splits_[0], M_LARGE_VALUE, AM_DEFAULT);
     ATTRIBUTE(Light, VAR_FLOAT, "CSM Split 2 End", shadowCascade_.splits_[1], M_LARGE_VALUE, AM_DEFAULT);
     ATTRIBUTE(Light, VAR_FLOAT, "CSM Split 3 End", shadowCascade_.splits_[2], M_LARGE_VALUE, AM_DEFAULT);
@@ -124,9 +129,9 @@ void Light::RegisterObject(Context* context)
     ATTRIBUTE(Light, VAR_FLOAT, "CSM Fade Start", shadowCascade_.fadeStart_, M_LARGE_VALUE, AM_DEFAULT);
     ATTRIBUTE(Light, VAR_FLOAT, "View Size Quantize", shadowFocus_.quantize_, DEFAULT_SHADOWQUANTIZE, AM_DEFAULT);
     ATTRIBUTE(Light, VAR_FLOAT, "View Size Minimum", shadowFocus_.minView_, DEFAULT_SHADOWMINVIEW, AM_DEFAULT);
-    ATTRIBUTE(Light, VAR_BOOL, "Focus To Scene", shadowFocus_.focus_, true, AM_DEFAULT);
-    ATTRIBUTE(Light, VAR_BOOL, "Non-uniform View", shadowFocus_.nonUniform_, true, AM_DEFAULT);
-    ATTRIBUTE(Light, VAR_BOOL, "Auto-Reduce Size", shadowFocus_.autoSize_, true, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Depth Constant Bias", shadowBias_.constantBias_, DEFAULT_CONSTANTBIAS, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Depth Slope Bias", shadowBias_.slopeScaledBias_, DEFAULT_SLOPESCALEDBIAS, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Near/Farclip Ratio", shadowNearFarRatio_, DEFAULT_SHADOWNEARFARRATIO, AM_DEFAULT);
     ATTRIBUTE(Light, VAR_INT, "View Mask", viewMask_, DEFAULT_VIEWMASK, AM_DEFAULT);
     ATTRIBUTE(Light, VAR_INT, "Light Mask", lightMask_, DEFAULT_LIGHTMASK, AM_DEFAULT);
 }
@@ -191,12 +196,7 @@ void Light::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 void Light::SetLightType(LightType type)
 {
     lightType_ = type;
-    
-    // Validate shape texture type: 2D for spot lights, cube for point lights. Change to null if wrong
-    if (lightType_ == LIGHT_SPOT && shapeTexture_ && shapeTexture_->GetType() != Texture2D::GetTypeStatic())
-        shapeTexture_ = 0;
-    if (lightType_ == LIGHT_POINT && shapeTexture_ && shapeTexture_->GetType() != TextureCube::GetTypeStatic())
-        shapeTexture_ = 0;
+    OnMarkedDirty(node_);
 }
 
 void Light::SetColor(const Color& color)
@@ -207,16 +207,19 @@ void Light::SetColor(const Color& color)
 void Light::SetRange(float range)
 {
     range_ = Max(range, 0.0f);
+    OnMarkedDirty(node_);
 }
 
 void Light::SetFov(float fov)
 {
     fov_ = Clamp(fov, 0.0f, M_MAX_FOV);
+    OnMarkedDirty(node_);
 }
 
 void Light::SetAspectRatio(float aspectRatio)
 {
     aspectRatio_ = Max(aspectRatio, M_EPSILON);
+    OnMarkedDirty(node_);
 }
 
 void Light::SetShadowNearFarRatio(float nearFarRatio)

+ 1 - 1
Engine/Graphics/Light.h

@@ -104,7 +104,7 @@ struct CascadeParameters
     float start_;
     /// Far clip values of the splits.
     float splits_[MAX_CASCADE_SPLITS];
-    /// The point relative to final split far clipwhere fade out begins. (0.0 - 1.0)
+    /// The point relative to final split far clip where fade out begins. (0.0 - 1.0)
     float fadeStart_;
 };
 

+ 2 - 2
Engine/Graphics/OpenGL/OGLGraphics.cpp

@@ -2068,8 +2068,8 @@ void Graphics::SetTextureUnitMappings()
     textureUnits_["EnvironmentMap"] = TU_ENVIRONMENT;
     textureUnits_["EnvironmentCubeMap"] = TU_ENVIRONMENT;
     textureUnits_["LightRampMap"] = TU_LIGHTRAMP;
-    textureUnits_["LightSpotMap"] = TU_LIGHTSPOT;
-    textureUnits_["LightCubeMap"]  = TU_LIGHTSPOT;
+    textureUnits_["LightSpotMap"] = TU_LIGHTSHAPE;
+    textureUnits_["LightCubeMap"]  = TU_LIGHTSHAPE;
     textureUnits_["ShadowMap"] = TU_SHADOWMAP;
     textureUnits_["FaceSelectCubeMap"] = TU_FACESELECT;
     textureUnits_["IndirectionCubeMap"] = TU_INDIRECTION;

+ 26 - 9
Engine/Graphics/Renderer.cpp

@@ -200,7 +200,7 @@ static const String lightVSVariations[] =
     "DirShadow",
     "SpotShadow",
     "PointShadow",
-    "SpcShadow",
+    "SpecShadow",
     "DirSpecShadow",
     "SpotSpecShadow",
     "PointSpecShadow"
@@ -209,21 +209,21 @@ static const String lightVSVariations[] =
 static const String lightPSVariations[] = 
 {
     "Dir",
-    "DirSpec",
     "Spot",
-    "SpotSpec",
     "Point",
-    "PointSpec",
     "PointMask",
+    "DirSpec",
+    "SpotSpec",
+    "PointSpec",
     "PointMaskSpec",
     "DirShadow",
-    "DirShadowSpec",
     "SpotShadow",
-    "SpotShadowSpec",
     "PointShadow",
-    "PointShadowSpec",
     "PointMaskShadow",
-    "PointMaskShadowSpec"
+    "DirSpecShadow",
+    "SpotSpecShadow",
+    "PointSpecShadow",
+    "PointMaskSpecShadow"
 };
 
 static const unsigned INSTANCING_BUFFER_MASK = MASK_INSTANCEMATRIX1 | MASK_INSTANCEMATRIX2 | MASK_INSTANCEMATRIX3;
@@ -1039,6 +1039,23 @@ void Renderer::SetBatchShaders(Batch& batch, Technique* technique, Pass* pass, b
             
             batch.vertexShader_ = vertexShaders[vsi];
             batch.pixelShader_ = pixelShaders[psi];
+            
+            // If specular or shadow variations do not exist, try without them
+            if ((!batch.vertexShader_ || !batch.pixelShader_) && (vsi & LVS_SPEC))
+            {
+                vsi &= ~LVS_SPEC;
+                psi &= ~LPS_SPEC;
+                batch.vertexShader_ = vertexShaders[vsi];
+                batch.pixelShader_ = pixelShaders[psi];
+            }
+            if ((!batch.vertexShader_ || !batch.pixelShader_) && (vsi & LVS_SHADOW))
+            {
+                vsi &= ~LVS_SHADOW;
+                psi &= ~LPS_SHADOW;
+                batch.vertexShader_ = vertexShaders[vsi];
+                batch.pixelShader_ = pixelShaders[psi];
+            }
+            
             batch.vertexShaderIndex_ = vsi;
         }
         else
@@ -1296,7 +1313,7 @@ void Renderer::CreateInstancingBuffer()
 
 bool Renderer::ResizeInstancingBuffer(unsigned numInstances)
 {
-    if (!instancingBuffer_)
+    if (!instancingBuffer_ || !dynamicInstancing_)
         return false;
     
     unsigned oldSize = instancingBuffer_->GetVertexCount();

+ 17 - 15
Engine/Graphics/Renderer.h

@@ -78,20 +78,20 @@ enum LightVSVariation
 enum LightPSVariation
 {
     LPS_NONE = 0,
-    LPS_SPEC,
     LPS_SPOT,
-    LPS_SPOTSPEC,
     LPS_POINT,
-    LPS_POINTSPEC,
     LPS_POINTMASK,
+    LPS_SPEC,
+    LPS_SPOTSPEC,
+    LPS_POINTSPEC,
     LPS_POINTMASKSPEC,
     LPS_SHADOW,
-    LPS_SHADOWSPEC,
     LPS_SPOTSHADOW,
-    LPS_SPOTSHADOWSPEC,
     LPS_POINTSHADOW,
-    LPS_POINTSHADOWSPEC,
     LPS_POINTMASKSHADOW,
+    LPS_SHADOWSPEC,
+    LPS_SPOTSHADOWSPEC,
+    LPS_POINTSHADOWSPEC,
     LPS_POINTMASKSHADOWSPEC,
     MAX_LIGHT_PS_VARIATIONS
 };
@@ -219,6 +219,8 @@ public:
     TextureCube* GetFaceSelectCubeMap() const { return faceSelectCubeMap_; }
     /// Return the shadowed pointlight indirection cube map.
     TextureCube* GetIndirectionCubeMap() const { return indirectionCubeMap_; }
+    /// Return the instancing vertex buffer
+    VertexBuffer* GetInstancingBuffer() const { return dynamicInstancing_ ? instancingBuffer_ : (VertexBuffer*)0; }
     /// Return a vertex shader by name.
     ShaderVariation* GetVertexShader(const String& name, bool checkExists = false) const;
     /// Return a pixel shader by name.
@@ -232,7 +234,15 @@ public:
     void Render();
     /// Add debug geometry to the debug graphics(s).
     void DrawDebugGeometry(bool depthTest);
-    
+    /// Return volume geometry for a light.
+    Geometry* GetLightGeometry(Light* light);
+    /// Return shadow map for a light. If shadow map reuse is disabled, a different map is returned each time.
+    Texture2D* GetShadowMap(Light* light, Camera* camera, unsigned viewWidth, unsigned viewHeight);
+    /// Get a shader program.
+    ShaderVariation* GetShader(const String& name, const String& extension, bool checkExists) const;
+    /// Choose shaders for a batch.
+    void SetBatchShaders(Batch& batch, Technique* technique, Pass* pass, bool allowShadows = true);
+
 private:
     /// Initialize when screen mode initially set.
     void Initialize();
@@ -242,14 +252,6 @@ private:
     bool AddView(RenderSurface* renderTarget, const Viewport& viewport);
     /// Return an occlusion buffer for use.
     OcclusionBuffer* GetOrCreateOcclusionBuffer(Camera* camera, int maxOccluderTriangles, bool halfResolution = false);
-    /// Return volume geometry for a light.
-    Geometry* GetLightGeometry(Light* light);
-    /// Return shadow map for a light. If shadow map reuse is disabled, a different map is returned each time.
-    Texture2D* GetShadowMap(Light* light, Camera* camera, unsigned viewWidth, unsigned viewHeight);
-    /// Get a shader program.
-    ShaderVariation* GetShader(const String& name, const String& extension, bool checkExists) const;
-    /// Choose shaders for a batch.
-    void SetBatchShaders(Batch& batch, Technique* technique, Pass* pass, bool allowShadows = true);
     /// Reload shaders.
     void LoadShaders();
     /// Reload shaders for a material technique.

+ 66 - 70
Engine/Graphics/View.cpp

@@ -436,15 +436,13 @@ void View::GetBatches()
             for (unsigned j = 0; j < litGeometries_.Size(); ++j)
             {
                 Drawable* drawable = litGeometries_[j];
-                
+                drawable->AddLight(light);
+
                 // If drawable limits maximum lights, only record the light, and check maximum count / build batches later
                 if (!drawable->GetMaxLights())
                     GetLitBatches(drawable, lightQueue);
                 else
-                {
-                    drawable->AddLight(light);
                     maxLightsDrawables_.Insert(drawable);
-                }
             }
             
             ++lightQueueCount;
@@ -547,6 +545,8 @@ void View::GetBatches()
 void View::GetLitBatches(Drawable* drawable, LightBatchQueue& lightQueue)
 {
     Light* light = lightQueue.light_;
+    Light* drawableFirstLight = drawable->GetLights()[0];
+    
     // Shadows on transparencies can only be rendered if shadow maps are not reused
     bool allowTransparentShadows = !renderer_->reuseShadowMaps_;
     unsigned numBatches = drawable->GetNumBatches();
@@ -564,7 +564,7 @@ void View::GetLitBatches(Drawable* drawable, LightBatchQueue& lightQueue)
         bool priority = false;
         
         // For the (first) directional light, check for lit base pass
-        if (light == lights_[0] && light->GetLightType() == LIGHT_DIRECTIONAL)
+        if (light == drawableFirstLight && light->GetLightType() == LIGHT_DIRECTIONAL)
         {
             if (!drawable->HasBasePass(i))
             {
@@ -764,12 +764,6 @@ unsigned View::ProcessLight(Light* light)
     
     LightType type = light->GetLightType();
     
-    // If light has no ramp textures defined, set defaults
-    if (type != LIGHT_DIRECTIONAL && !light->GetRampTexture())
-        light->SetRampTexture(renderer_->GetDefaultLightRamp());
-    if (type == LIGHT_SPOT && !light->GetShapeTexture())
-        light->SetShapeTexture(renderer_->GetDefaultLightSpot());
-    
     // Get lit geometries. They must match the light mask and be inside the main camera frustum to be considered
     litGeometries_.Clear();
     switch (type)
@@ -1047,52 +1041,61 @@ void View::OptimizeLightByScissor(Light* light)
 
 void View::OptimizeLightByStencil(Light* light)
 {
-    LightType type = light->GetLightType();
-    if (light && renderer_->GetLightStencilMasking() && type != LIGHT_DIRECTIONAL)
+    if (light && renderer_->GetLightStencilMasking())
     {
-        // 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.2f).DistanceFast(camera_->GetWorldPosition());
-        else
-            lightDist = light->GetFrustum().Distance(camera_->GetWorldPosition());
+        Geometry* geometry = renderer_->GetLightGeometry(light);
         
-        // 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)
+        if (geometry)
         {
-            graphics_->SetCullMode(CULL_CW);
-            graphics_->SetDepthTest(CMP_GREATER);
-        }
-        else
-        {
-            graphics_->SetCullMode(CULL_CCW);
-            graphics_->SetDepthTest(CMP_LESSEQUAL);
+            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_;
+            
+            return;
         }
-        
-        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());
-        renderer_->GetLightGeometry(light)->Draw(graphics_);
-        
-        graphics_->ClearTransformSources();
-        graphics_->SetColorWrite(true);
-        graphics_->SetStencilTest(true, CMP_EQUAL, OP_KEEP, OP_KEEP, OP_KEEP, lightStencilValue_);
-        ++lightStencilValue_;
     }
-    else
-        graphics_->SetStencilTest(false);
+    
+    graphics_->SetStencilTest(false);
 }
 
 const Rect& View::GetLightScissor(Light* light)
@@ -1478,8 +1481,9 @@ void View::PrepareInstancingBuffer()
     // If fail to set buffer size, fall back to per-group locking
     if (totalInstances && renderer_->ResizeInstancingBuffer(totalInstances))
     {
+        VertexBuffer* instancingBuffer = renderer_->GetInstancingBuffer();
         unsigned freeIndex = 0;
-        void* lockedData = renderer_->instancingBuffer_->Lock(0, totalInstances, LOCK_DISCARD);
+        void* lockedData = instancingBuffer->Lock(0, totalInstances, LOCK_DISCARD);
         if (lockedData)
         {
             baseQueue_.SetTransforms(renderer_, lockedData, freeIndex);
@@ -1492,7 +1496,7 @@ void View::PrepareInstancingBuffer()
                 lightQueues_[i].litBatches_.SetTransforms(renderer_, lockedData, freeIndex);
             }
             
-            renderer_->instancingBuffer_->Unlock();
+            instancingBuffer->Unlock();
         }
     }
 }
@@ -1536,10 +1540,6 @@ void View::CalculateShaderParameters()
 
 void View::RenderBatchQueue(const BatchQueue& queue, bool useScissor)
 {
-    VertexBuffer* instancingBuffer = 0;
-    if (renderer_->GetDynamicInstancing())
-        instancingBuffer = renderer_->instancingBuffer_;
-    
     if (useScissor)
         graphics_->SetScissorTest(false);
     graphics_->SetStencilTest(false);
@@ -1549,13 +1549,13 @@ void View::RenderBatchQueue(const BatchQueue& queue, bool useScissor)
         queue.priorityBatchGroups_.End(); ++i)
     {
         const BatchGroup& group = i->second_;
-        group.Draw(graphics_, instancingBuffer, shaderParameters_);
+        group.Draw(graphics_, renderer_, shaderParameters_);
     }
     // Priority non-instanced
     for (PODVector<Batch*>::ConstIterator i = queue.sortedPriorityBatches_.Begin(); i != queue.sortedPriorityBatches_.End(); ++i)
     {
         Batch* batch = *i;
-        batch->Draw(graphics_, shaderParameters_);
+        batch->Draw(graphics_, renderer_, shaderParameters_);
     }
     
     // Non-priority instanced
@@ -1565,7 +1565,7 @@ void View::RenderBatchQueue(const BatchQueue& queue, bool useScissor)
         const BatchGroup& group = i->second_;
         if (useScissor && group.lightQueue_)
             OptimizeLightByScissor(group.lightQueue_->light_);
-        group.Draw(graphics_, instancingBuffer, shaderParameters_);
+        group.Draw(graphics_, renderer_, shaderParameters_);
     }
     // Non-priority non-instanced
     for (PODVector<Batch*>::ConstIterator i = queue.sortedBatches_.Begin(); i != queue.sortedBatches_.End(); ++i)
@@ -1579,16 +1579,12 @@ void View::RenderBatchQueue(const BatchQueue& queue, bool useScissor)
             else
                 graphics_->SetScissorTest(false);
         }
-        batch->Draw(graphics_, shaderParameters_);
+        batch->Draw(graphics_, renderer_, shaderParameters_);
     }
 }
 
 void View::RenderLightBatchQueue(const BatchQueue& queue, Light* light)
 {
-    VertexBuffer* instancingBuffer = 0;
-    if (renderer_->GetDynamicInstancing())
-        instancingBuffer = renderer_->instancingBuffer_;
-    
     graphics_->SetScissorTest(false);
     graphics_->SetStencilTest(false);
     
@@ -1597,13 +1593,13 @@ void View::RenderLightBatchQueue(const BatchQueue& queue, Light* light)
         queue.priorityBatchGroups_.End(); ++i)
     {
         const BatchGroup& group = i->second_;
-        group.Draw(graphics_, instancingBuffer, shaderParameters_);
+        group.Draw(graphics_, renderer_, shaderParameters_);
     }
     // Priority non-instanced
     for (PODVector<Batch*>::ConstIterator i = queue.sortedPriorityBatches_.Begin(); i != queue.sortedPriorityBatches_.End(); ++i)
     {
         Batch* batch = *i;
-        batch->Draw(graphics_, shaderParameters_);
+        batch->Draw(graphics_, renderer_, shaderParameters_);
     }
     
     // All base passes have been drawn. Optimize at this point by both stencil volume and scissor
@@ -1615,13 +1611,13 @@ void View::RenderLightBatchQueue(const BatchQueue& queue, Light* light)
         queue.batchGroups_.End(); ++i)
     {
         const BatchGroup& group = i->second_;
-        group.Draw(graphics_, instancingBuffer, shaderParameters_);
+        group.Draw(graphics_, renderer_, shaderParameters_);
     }
     // Non-priority non-instanced
     for (PODVector<Batch*>::ConstIterator i = queue.sortedBatches_.Begin(); i != queue.sortedBatches_.End(); ++i)
     {
         Batch* batch = *i;
-        batch->Draw(graphics_, shaderParameters_);
+        batch->Draw(graphics_, renderer_, shaderParameters_);
     }
 }
 

+ 10 - 4
Engine/Scene/Node.cpp

@@ -936,10 +936,12 @@ Component* Node::CreateComponent(ShortStringHash type, unsigned id, CreateMode m
     
     components_.Push(newComponent);
     
-    // If zero ID specified, let the scene assign
+    // If zero ID specified, or the ID is already taken, let the scene assign
     if (scene_)
     {
-        newComponent->SetID(id ? id : scene_->GetFreeComponentID(mode));
+        if (!id || scene_->GetComponent(id))
+            id = scene_->GetFreeComponentID(mode);
+        newComponent->SetID(id);
         scene_->ComponentAdded(newComponent);
     }
     else
@@ -954,9 +956,13 @@ Node* Node::CreateChild(unsigned id, CreateMode mode)
 {
     SharedPtr<Node> newNode(new Node(context_));
     
-    // If zero ID specified, let the scene assign
+    // If zero ID specified, or the ID is already taken, let the scene assign
     if (scene_)
-        newNode->SetID(id ? id : scene_->GetFreeNodeID(mode));
+    {
+        if (!id || scene_->GetComponent(id))
+            id = scene_->GetFreeNodeID(mode);
+        newNode->SetID(id);
+    }
     else
         newNode->SetID(id);
     

+ 6 - 1
Engine/Scene/Scene.cpp

@@ -488,7 +488,12 @@ void Scene::ComponentAdded(Component* component)
     if (!component)
         return;
     
-    allComponents_[component->GetID()] = component;
+    unsigned id = component->GetID();
+    Map<unsigned, Component*>::Iterator i = allComponents_.Find(id);
+    if (i != allComponents_.End() && i->second_ != component)
+        LOGWARNING("Overwriting component with ID " + String(id));
+    
+    allComponents_[id] = component;
 }
 
 void Scene::ComponentRemoved(Component* component)

+ 2 - 1
Engine/Scene/Serializable.h

@@ -172,6 +172,7 @@ public:
 #define COPY_BASE_ATTRIBUTES(className, sourceClassName) context->CopyBaseAttributes<sourceClassName, className>()
 #define REMOVE_ATTRIBUTE(className, name) context->RemoveAttribute<className>(name)
 #define ATTRIBUTE(className, type, name, variable, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(type, name, offsetof(className, variable), defaultValue, mode))
-#define ENUM_ATTRIBUTE(className, name, variable, enumNames, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(VAR_INT, name, offsetof(className, variable), enumNames, defaultValue, mode))
+#define ENUM_ATTRIBUTE(className, name, variable, enumNames, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(name, offsetof(className, variable), enumNames, defaultValue, mode))
 #define ACCESSOR_ATTRIBUTE(className, type, name, getFunction, setFunction, typeName, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(type, name, new AttributeAccessorImpl<className, typeName>(&className::getFunction, &className::setFunction), defaultValue, mode))
+#define ENUM_ACCESSOR_ATTRIBUTE(className, name, getFunction, setFunction, typeName, enumNames, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(name, new AttributeAccessorImpl<className, typeName>(&className::getFunction, &className::setFunction), enumNames, defaultValue, mode))
 #define REF_ACCESSOR_ATTRIBUTE(className, type, name, getFunction, setFunction, typeName, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(type, name, new RefAttributeAccessorImpl<className, typeName>(&className::getFunction, &className::setFunction), defaultValue, mode))

+ 4 - 5
SourceAssets/GLSLShaders/Forward.xml

@@ -32,20 +32,19 @@
         </option>
         <variation name="" />
         <variation name="Skinned" define="SKINNED" />
-        <variation name="Billboard" define="BILLBOARD">
-            <exclude name="Normal" />
-        </variation>
+        <variation name="Billboard" define="BILLBOARD" exclude="Normal" />
     </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="SpecMap" define="SPECMAP" />
         <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>
@@ -82,8 +81,8 @@
             <define name="LIGHT" />
         </variation>
         <option name="Mask" define="CUBEMASK" require="POINTLIGHT" />
+        <option name="Spec" define="SPECULAR" require="LIGHT" />
         <option name="Shadow" define="SHADOW" require="LIGHT" />
-        <option name="Spec" define="SPECULAR" />
         <option name="LQ" define="LQSHADOW" require="SHADOW" />
     </shader>
 </shaders>

+ 3 - 4
SourceAssets/HLSLShaders/Forward.xml

@@ -33,9 +33,7 @@
         <variation name="" />
         <variation name="Skinned" define="SKINNED" />
         <variation name="Instanced" define="INSTANCED" />
-        <variation name="Billboard" define="BILLBOARD">
-            <exclude name="Normal" />
-        </variation>
+        <variation name="Billboard" define="BILLBOARD" exclude="Normal" />
     </shader>
     <shader name="Forward" type="ps">
         <option name="Diff" define="DIFFMAP" />
@@ -47,6 +45,7 @@
             <exclude name="Ambient" />
             <exclude name="Normal" />
             <exclude name="Shadow" />
+            <exclude name="Spec" />
             <exclude name="SpecMap" />
             <exclude name="Unlit" />
         </option>
@@ -83,8 +82,8 @@
             <define name="LIGHT" />
         </variation>
         <option name="Mask" define="CUBEMASK" require="POINTLIGHT" />
+        <option name="Spec" define="SPECULAR" require="LIGHT" />
         <option name="Shadow" define="SHADOW" require="LIGHT" />
-        <option name="Spec" define="SPECULAR" />
         <option name="LQ" define="LQSHADOW" require="HWSHADOW" />
         <option name="HW" define="HWSHADOW" require="SHADOW" />
         <option name="FB" define="FALLBACK">