Dynamic Directional Shadows
How to set up dynamic directional shadow casting in Kanzi 3.9
Overview
This tutorial explains how to configure dynamic directional shadows in Kanzi 3.9. You will:
- Configure a material type to receive directional shadows.
- Create a shadow map render pass that renders the scene from the light's perspective.
- Set up the main render pass to project the shadow map onto shadow-receiving objects.
- Configure the scene camera and occluder objects.
Prerequisites
- A scene containing at least one mesh object and a floor plane.
- A directional light in the scene.
- A material type based on the PBR PhysicallyBased shader (or any shader that supports the KANZI_SHADER_RECEIVE_DIRECTIONAL_SHADOW preprocessor definition).
Tag Occluder Objects
- In the Library window, go to the Tags Section and Create a new tag and rename it “Occluder”
- Under the Rendering>Object Sources create a new “Tag Filter” and rename it “Occluder”
- On The Occluder Object Source add Occluder tag under the section for “Included Tags”
- Select each mesh in the scene that should cast or receive shadows.
In the Properties panel, add the Occluder tag to each mesh.
Part 1: Configure the Material Type
Enable shadow receiving on the material type by setting the appropriate preprocessor definition.
- In the Kanzi Studio Library, select your material type.
- In the Properties panel, scroll to Preprocessor Definitions.
| NOTE Preprocessor definitions control which shader features are compiled in. Toggle-style definitions accept 0 (off) or 1 (on). Count-style definitions accept a positive integer that sets the maximum number of supported items of that type. |
- Set the following definition:
| Property | Value / Setting |
|---|---|
| KANZI_SHADER_RECEIVE_DIRECTIONAL_SHADOW | 1 |
| KANZI_SHADER_NUM_DIRECTIONAL_LIGHTS | 1 (or more, if using multiple directional lights) |
- After editing the preprocessor definitions, the material type becomes invalid and any materials that use it turn red in the viewport. In the material type Properties panel, click Sync with Uniforms to recompile.
| NOTE If you also need spot or point light shadows, add KANZI_SHADER_RECEIVE_SPOT_SHADOW and KANZI_SHADER_RECEIVE_POINT_SHADOW, and set the corresponding KANZI_SHADER_NUM_SPOT_LIGHTS and KANZI_SHADER_NUM_POINT_LIGHTS counts. |
Part 2: Create the Shadow Map Render Pass
The shadow map render pass renders the scene depth from the directional light's point of view into a depth texture. The main render pass then reads this texture to determine which surfaces are in shadow.
2.1 Create the Render to Texture Pass
- In the Render Pass tree, create a Render to Texture Render Pass. Rename it DirectionalShadowPass.
- On the DirectionalShadowPass, set the following properties:
| Property | Value / Setting |
|---|---|
| Composition Target 0 | Remove |
| Pixel Format 0 | None |
| Depth Texture Format | D24_UNORM |
| Depth Compare Function | Less or Equal |
2.2 Create & Add Custom Properties to the DirectionalShadowPass
Create and Add the following custom properties to the DirectionalShadowPass. These properties are referenced by bindings elsewhere in the render graph.
*Optional: Add a category to easily organize the properties you are creating. This will organize them into a section on the nodes we edit later.
*Optional: Create a folder to organize the properties, the NameSpace and Name of the properties will be adjusted slightly to reflect this. This can keep the project library organized in large projects.
| Property | Value / Setting |
|---|---|
| ShadowCamera | Data Type: Project Item | Editor: Node dropdown |
| ShadowMapSize | Data Type: Integer | Editor: Slider | Affect Rendering: checked |Lower Bound: 16 |Upper Bound: 4096 |Step 16 |
| LightProjectionMatrix | Data Type: Matrix 4x4 | Editor: None |
| DirectionalShadowMap | Data Type: Project Item |Editor Texture dropdown (This property might have been created automatically by the previous step.) |
- Bind the DirectionalShadowMap texture property to the DirectionalShadowPass output: select Result Depth Texture in the Properties panel and drag it into the Bindings panel targeting the DirectionalShadowMap property.
- Binding Mode: ToSource
Push Target: ##Template
- Create two bindings that bind the Height and Width properties of the pass to the ShadowMapSize custom property. Use the following binding syntax,
{##Template/ShadowMapSize}
2.3 Configure the Clear Render Pass
The DirectionalShadowPass contains a Clear Render Pass child by default.
- Select the Clear Render Pass under DirectionalShadowPass.
- Remove the Clear Color 0 property. The shadow map only requires depth information; clearing color is unnecessary.
2.4 Create the Draw Occluders Pass
- Remove the Gather Lights Render Pass from under the DirectionalShadowPass. Lighting is not needed when rendering depth only.
- Create a Draw Objects with Material Render Pass under DirectionalShadowPass. Rename it DrawOccluders.
- On DrawOccluders, create a binding for the camera property:
Expression: {##Template/ShadowCamera}
Create a binding for LightProjectionMatrix on the DrawOccluders pass. This matrix combines the light's projection and view transforms:
Binding Mode: To Source
proj = {@./DrawObjectsRenderPass.ProjectionMatrix} view = {@./DrawObjectsRenderPass.CameraMatrix} proj * view
⚠ Potentially incorrect binding expression The binding is targeting the type of render pass not the exact name of the renderpass. |
2.5 Create the Depth-Only Material Type
The DrawOccluders pass requires a dedicated material type that writes only depth — no lighting or color output.
- Create a new material type named DepthOnlyProjectedMaterial.
- Assign the following vertex and fragment shaders:
Vertex shader (DepthOnly.vert):
#version 300 es
precision highp float;
in vec3 kzPosition;
uniform highp mat4 kzProjectionCameraWorldMatrix;
void main()
{
gl_Position = kzProjectionCameraWorldMatrix * vec4(kzPosition, 1.0);
}Fragment shader (DepthOnly.frag):
#version 300 es
void main()
{
// No color output. Depth is written automatically.
}
- Assign the DepthOnlyProjectedMaterial material type to the Material property of the DrawOccluders pass.
| NOTE The DrawOccluders pass and the DirectionalShadowPass render-to-texture pass together form a reusable render pass prefab. You can duplicate and repurpose them for spot light shadow maps. |
Part 3: Create the Main Render Pass Group
Wrap the shadow map pass and the main scene pass inside a group render pass so they execute in the correct order.
- Create a new Group Render Pass. Rename it ShadowExampleRenderPass.
- Add a Clear Render Pass as the first child of ShadowExampleRenderPass. This clears the framebuffer from the previous frame.
Move the DirectionalShadowPass to be a child of ShadowExampleRenderPass.
| NOTE: The DirectionalShadowPass dragged to be a child of the ShadowExampleRenderPass, will now be called the DirectionalShadowPass prefab, to denote the difference between the render to texture pass, and the render pass prefab. |
- Add a Gather Lights Render Pass as the next child of ShadowExampleRenderPass. This collects all lights in the scene.
- Under the Gather Lights Render Pass, add a Draw Objects Render Pass.
3.1 Bind Shadow Properties on the Draw Objects Pass
The Draw Objects pass must pass the shadow map and the light projection matrix to the material uniforms. Create the following bindings on the Draw Objects pass:
| Property | Value / Setting |
|---|---|
| DirectionalLightViewProjection | Drag the LightProjectionMatrix property from DirectionalShadowPass prefab into the binding expression field. |
| DirectionalShadowMap | Drag the DirectionalShadowMap texture property from DirectionalShadowPass prefab into the binding expression field. |
Part 4: Update Material Type Uniform Bindings
The material type uniforms must be bound to the render pass properties so that every object drawn with this material receives the correct shadow data.
- In the Kanzi Studio Library, select the PhysicallyBased material type.
- Expand the Bindings section.
- Update the following two bindings:
| Property | Value / Setting |
|---|---|
| DirectionalLightViewProjection | {##RenderPass/DirectionalLightViewProjection} |
| DirectionalShadowMap | {##RenderPass/DirectionalShadowMap} |
| NOTE ##RenderPass is a special Kanzi binding keyword. Inside a material type uniform binding it resolves to the render pass that is currently drawing the material, allowing the material to read properties from whichever pass invokes it. |
Part 5: Configure the Scene
- On the Viewport 2D apply the ShadowExampleRenderPass to the RenderPass Prefab property.
5.2 Create the Shadow Camera
- Add a Camera node to the scene and child it directly under the Directional Light node that casts shadows.
- Rename the camera DirectionalShadowCamera.
- Set the following properties on DirectionalShadowCamera:
| Property | Value / Setting |
|---|---|
| Projection Type | Orthographic |
| Orthographic Plane Size | Adjust to cover the area that needs to receive shadows (13 is a reasonable starting value for small scenes) |
- Select the Viewport 2D and then open the menu for Dictionaries.
- Click the +Create Resource Dictionary button in the Dictionaries window
- With the Viewport 2D selected drag and drop the DirectionalShadowCamera into the Dictionaries Window. This creates a resource reference that can be found by the Render Pass that exists on the Viewport 2D.
- Select the DirectionalShadowPass prefab (under the ShadowExampleRenderPass) and click the + for the ShadowCamer.
Change the property drop down to <Relative> and write in #DirectionalShadowCamera
5.3 Adjust Shadow Bias
Shadow bias prevents self-shadowing artifacts (shadow acne). On your material, set:
| Property | Value / Setting |
|---|---|
| DirectionalShadowBias | Start at 0.005 and reduce toward 0 until acne disappears without losing contact shadows. |
| NOTE Too low a bias value causes shadow acne. Too high a value causes Peter Panning (shadows detach from objects). Tune this value per scene. |
Comments
0 comments
Please sign in to leave a comment.