Before this version, when the user wanted to customize some aspect of the rasterizer, blending or the depth states, we needed to create by code a new Layer class, registering it manually and then acceding it by its type. And you needed to implement the SetDevice and RestoreDevice methods, changint the RenderState structure.
Initially we just wanted to integrated them into the Visual Editor, but then we realised that we weren’t allowing the users to use the full potential of the modern graphical cards, so we redesigned it.
So the RenderLayer concept was born, a Visual Editor friendly specification of how the geometry and shaders are presented into the final frame. Our most important goal was to keep the simplicity of the previous implementation but adding the full potential of the modern graphic cards.
Render Layer Description
In Wave Engine Killer Whale 2.5.0, the old Layer class has been removed and substituted for RenderLayer sealed class, which means it can’t be inherited. The way you customize its behavior is through the RenderLayerDescription class, which defines the configuration of the Render Layer.
State Desciption
The RenderLayerDescription class contains different structs which sets different aspects of the render layer: The Rasterizer State, Blend State and Depth & Stencil State Descriptions. Those struct fields and properties are almost the same of the DirectX API, but also similar to the OpenGL ones. The main advantage of having a description object is that Wave Engine can sets an entire state just with one GPU call, instead of having one call per parameter, reducing the overhead and performance.
Rasterizer State
The Rasterizer State Description defines the behavior of the Rasterizer Stage in the graphic pipeline. To avoid setting lot of properties and simplifying things, you can adjust the entire RasterizerStateDescription struct with one of the following presets:
- CullFront
- CullBack
- None
- WireframeCullFront
- WireframeCullBack
- WireframeCullNone
However, you can also customize it by your own. Its main members are:
- FillMode: Can be set to Wireframe or Solid (default).
- CullMode: Defines what side of the triangles are culled and discarded. Can be None, Front and Back (default).
- Depth Bias: Defines the z-bias to avoid coplanar triangles and z-fighting.
Blend State
The Blend State defines the blending operations to be made after the pixel shader output (or fragment shader). It defines how the pixels are mixed with the previously rendered elements. Its basic presets are:
- Opaque
- AlphaBlend
- Additive
- Multiplicative
- NonPremultiplied
For customizing, lot of properties can be adjustes. Its most important properties are the following:
- BlendEnable: If the blending operations are enabled. false by default.
- SourceBlendColor: Defines the source blending factor for the rendered color.
- DestinationBlendColor: Specifies the destination blending factor for the destination color.
- BlendOperationColor: Sets the blending operation.
- SourceBlendAlpha: Specifies the source alpha blending factor of the rendered color.
- DestinationBlendAlpha: Specifies the destination alpha blending factor of the destination color.
- BlendOperationAlpha: Specifies the alpha blending operation.
- ColorWriteChannels: Allow specifying in which color channel the pixel will be rendered. Can be Red, Green, Blue, Alpha, All (default) and None.
Using these properties you can achieve a wide range of blending effects. To learn how to create them, we should know the blending equation.
The Blending Equation
How does blending combine the two pixel colors? It uses a very simple equation:
1 2 3 4 |
Final color = ( SourcePixelColor * SourceBlendColor) BlendOperationColor (+/-/*) ( DestinationPixelColor * DestinationBlendColor) |
The SourcePixelColor is the recently drawn pixel, and the Destination Pixel Color is the already rendered pixel in the backbuffer. Analogously, there are also the Alpha properties. The Blend Operation is a basic mathematical operation like addition or subtraction, and the two blend factors are set by user. The next diagram shows a basic operation for an additive blend.

Here are some basic blending examples:
Alpha Blending
- BlendEnable: true
- BlendOperationColor: Add
- SourceBlendColor: One (Uses premultipled alpha)
- DestinationBlendColor: InverseSourceAlpha
- BlendOperationAlpha: Add
- SourceBlendAlpha: One (Uses premultipled alpha)
- DestinationBlendAlpha: InverseSourceAlpha
Additive Blending
- BlendEnable: true
- BlendOperationColor: Add
- SourceBlendColor: One
- DestinationBlendColor: One
- BlendOperationAlpha: Add
- SourceBlendAlpha: One
- DestinationBlendAlpha: One
Depth Stencil State
The Depth Stencil State controls how depth-stencil testing is performed by the output-merger stage.
Depth Testing
When a pixel color makes it out of the pixel shader, the OM (Output Merger) compares the z value (depth value) of that pixel color with the z value of the pixel currently on the render target.
Depth testing will cause objects that are closer to the camera to show up in front of objects that are further away from the camera, no matter what was the rendering order.
Stencil Testing
The Stencil Testing uses another buffer, the Stencil Buffer, usually with 8bits per pixel. And it is allowed to be updated while rendering elements, and using its values to test using operations and discard pixels.
The DepthStencilState basic presets are:
- None: Doesn’t perform depth testing.
- Write: Writes on the depth buffer (for opaque items, for example)
- Read: Doesn’t write on the depth buffer, but still makes the depth testing (for additive and alpha blending, for example).
For customizing this state, you can alternatively set lot of variables. The most important ones are:
- DepthEnable: Checks if the depth testing is enabled.
- DepthWriteMask: boolean that specifies when the depth buffer must be updated with the z value of the rendered elements.
- DepthFunction: The function used for the depth test. Less is by default (Discarding the farest elements).
- StencilEnable: Enables/disables the stencil test. false by default.
- StencilReadMax: Identifies a portion of the depth-stencil buffer for reading stencil data.
- StencilWriteMax: Identifies a portion of the depth-stencil buffer for writing stencil data.
Depth Range
The Depth Range sets the Minimum and Maximum Depth when rendering the in render layer. This is helpful when you want to render some objects at the background (like the Skybox elements, for example).
Its members are:
- MinDepth: 0 by default
- MaxDepth: 1 by default
Using in Visual Editor
The Render Layers can be now defined in the VisualEditor. They are stored in the Project Settings (Edit / Project Settings…).
By default there are 6 render layers in every project.
- Opaque
- Skybox
- Alpha
- Additive
- GUI
- Debug
These layers can’t be removed, renamed or reordered. The rest of the user defined render layers can be reordered, renamed or deleted.
A render layer can be added clicking in the button. A popup will appear, asking for a base template.
Once created we can set its values as we specified before.
By default we only can sets the presets for the Rasterizer, Blend and DepthStencil States (or Modes, as defined in the VisualEditor). To fully customize its values, just sets the Custom value and a new property will be shown with all the values for that State.
Once the render layers are customized, we can use them in multiple places, like Materials or Sprites.
Using in Code
From code we can access the Render Layers defined in Visual Editor or create our own ones.
The defined Render Layers can be referenced by its Id in the WaveContants class.
This is an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public sealed class RenderLayers { /// <summary>Id of Opaque</summary> public const int Opaque = 0; /// <summary>Id of Skybox</summary> public const int Skybox = 1; /// <summary>Id of Alpha</summary> public const int Alpha = 2; /// <summary>Id of Additive</summary> public const int Additive = 3; /// <summary>Id of GUI</summary> public const int GUI = 4; /// <summary>Id of Debug</summary> public const int Debug = 5; } |
We can get a defined Render Layer through RenderManager.
1 |
var layerDesc = this.RenderManager.FindLayer(WaveContent.RenderLayers.Opaque); |
We can alternatively create our own Render Layer and register it through the RenderManager.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Defines the Render Layer Description var myLayerDesc= new RenderLayerDescription() { RasterizeState = RasterizerStates.CullBack, BlendState = BlendStates.AlphaBlend, DepthStencilState = DepthStencilStates.Read }; // A Render Layer can be created using a RenderLayerDescription var myLayer = new RenderLayer(myLayerDesc); // Registers the layer and adds it at the end of the render layer list. this.RenderManager.RegisterLayer(myLayer); |
The Render Layer presets are defined as static properties in the RasterizerStates, BlendStates and DepthStencilStates classes, as you can see above.
At last, we can assign a Render Layer to a Material by its LayerId, instead of its Type as before.
1 2 3 4 5 |
var mat = new StandardMaterial() { DiffuseColor = Color.Green, LayerId = WaveContent.RenderLayers.Additive }; |