This page has been translated automatically.
Video Tutorials
Interface
Essentials
Advanced
How To
UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Working With Projects
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Lighting
Landscape Tool
Sandworm (Experimental)
Using Editor Tools for Specific Tasks
Extending Editor Functionality
Built-in Node Types
Nodes
Objects
Effects
Decals
Light Sources
Geodetics
World Objects
Sound Objects
Pathfinding Objects
Players
Programming
Fundamentals
Setting Up Development Environment
Usage Examples
UnigineScript
C++
C#
File Formats
Rebuilding the Engine Tools
GUI
Double Precision Coordinates
API
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
Warning! This version of documentation is OUTDATED, as it describes an older SDK version! Please switch to the documentation for the latest SDK version.
Warning! This version of documentation describes an old SDK version which is no longer supported! Please upgrade to the latest SDK version.

Creating a Custom Shader for Deferred Rendering Pass

In UNIGINE all the opaque geometry is rendered in the deferred rendering pass. This article explains how to create your own material with shaders (both vertex and fragment) for the deferred rendering pass.

In this article, we create a material for static, dynamic and skinned meshes. We define a heightmap for the vertex shader and an albedo texture for the fragment shader. Also, we calculate normals to ensure proper lighting for our mesh.

Prior Knowledge
This article assumes you have prior knowledge of the following topics. Please read them before proceeding:

See Also#

Create a Material#

The material is the essential thing for rendering: you specify shaders, states, bindings, textures in the material, thus, telling the engine how to render it.

Let's create our own ULON-based base material (for deferred pass only) and write the vertex and the fragment shaders for it. Perform the following steps to create the material:

  1. In your project folder open the data/materials folder.
  2. Create a new custom_mesh_material.basemat file and implement the following base material:
    Source Code (ULON)
    // describing the read-only custom mesh material to be used for static meshes, 
    // setting prefixes to be used in shaders to refer to textures and parameters 
    BaseMaterial custom_mesh_material <node=ObjectMeshStatic editable=false var_prefix=var texture_prefix=tex default=true>
    {
    	// enabling the deferred pass for our material and hiding it (will be invisible in the UnigineEditor)
    	State deferred=1	<internal=true>
    
    	//////////////////////////////////////////////////////////////////////////
    	// Passes
    	//////////////////////////////////////////////////////////////////////////
    
    	// describing the deferred pass with links to shaders to be used
    	Pass deferred <defines="BASE_DEFERRED">
    	{
    		Vertex   = "shaders/vertex/deferred.vert"
    		Fragment = "shaders/fragment/deferred.frag"
    	}
    	
    	// describing bindings for node types to which the material is to be applicable
    	Bind ObjectMeshStatic=ObjectMeshDynamic
    	Bind ObjectMeshStatic=ObjectMeshSkinned
    }
    • We defined a new custom_mesh_material base material with the deferred state (internal).
    • Also we defined shaders (fragment and vertex) for the deferred pass and set the paths to them (we will create both these shader files and a folder in the following sections).
    • Finally, we defined the bindings: types of meshes for which the material can be assigned.
  3. Save the changes.

Create a Vertex Shader#

In this section, we shall explain how to create the structure for shaders and how to create the custom vertex shader.

We're gonna need to create two shaders for our material: vertex and fragment. To create a vertex shader, perform the following:

  1. Create the shaders folder in your project's /data folder.
  2. Inside the shaders folder, create two folders: vertex for vertex shaders, fragment for fragment shaders.
  3. Open a plain text editor and write the following:
    Source code (UUSL)
    // Include Unified Unigine Shader Language (UUSL) header
    #include <core/shaders/common/common.h>
    
    // Input data struct
    STRUCT(VERTEX_IN)
    	INIT_ATTRIBUTE(float4,0,POSITION)	// Vertex position
    	INIT_ATTRIBUTE(float4,1,TEXCOORD0)	// Vertex texcoord (uv)
    	INIT_ATTRIBUTE(float4,2,TEXCOORD1)	// Vertex basis tangent
    	INIT_ATTRIBUTE(float4,3,TEXCOORD2)	// Vertex color
    	INIT_INSTANCE
    END
    
    // Our output vertex data struct
    STRUCT(VERTEX_OUT)
    	INIT_POSITION					// Out projected position
    	INIT_OUT(float4,0)				// Texcoord (uv)
    END
    
    MAIN_BEGIN(VERTEX_OUT,VERTEX_IN)
    	
    	// Get transform with scale and rotation (without translation)
    	float4x4 transform = getObjectTransform(IN_INSTANCE);
    	float4 row_0 = transform[0];
    	float4 row_1 = transform[1];
    	float4 row_2 = transform[2];
    	
    	// Get Modelview-space transform
    	float4 in_vertex = float4(IN_ATTRIBUTE(0).xyz,1.0f);
    	float4 position = mul4(row_0,row_1,row_2,in_vertex);
    	
    	// Set output UV
    	float4 texcoord = IN_ATTRIBUTE(1);
    	OUT_DATA(0) = texcoord;
    	
    	// Set output position
    	OUT_POSITION = getPosition(position);
    MAIN_END
    
    // end
    Warning
    You should add a new line (press Enter) after closing the instruction (after MAIN_END command).

    The code is pretty simple and clear. In the code above we

    • Include the UUSL header.
    • Define the input vertex data structure.
    • Define the output vertex data structure.
    • Start the main program of the shader:
      • Translate the object-space coordinates to clipping space coordinates.
      • Set the output UV.
      • Set the vertex position.
  4. Save the shader file with the deferred.vert name and extension inside the data/shaders/vertex folder.

Create a Fragment Shader#

This section contains instructions on how to create a fragment shader (also known as pixel shader).

To create a fragment shader, perform the following:

  1. Open a plain text editor and write the following:
    Source code (UUSL)
    // Include the UUSL language header
    #include <core/shaders/common/fragment.h>
    
    // Input data struct
    STRUCT(FRAGMENT_IN)
    	INIT_POSITION				// Projected position
    	INIT_IN(float4,0)			// Texcoord (uv)
    END
    
    MAIN_BEGIN_DEFERRED(FRAGMENT_IN)
    	
    	// Create the G-Buffer and set it
    	GBuffer gbuffer = GBufferDefault();
    	
    	setGBuffer(gbuffer);
    	
    MAIN_END
    
    // end
    Warning
    You should add a new line (press Enter) after closing the instruction (after MAIN_END command).

    Explanations on fragment shader:

    • We include the UUSL header for fragment shaders.
    • Define the input fragment data struct (where we receive the data from output structure of the vertex shader).
    • Start the main program of the shader, where we create a GBuffer instance for metalness workflow and set it.
  2. Save the shader file with the deferred.frag name and extension to the data/shaders/fragment folder.

Working with Material#

After performing all these steps you can see the recently created shaders in use. They are primitive, and don't provide a nice final image. Anyway, to check their work perform the following steps:

  1. Open your world by using UnigineEditor, The Editor will create all necessary resources in the data folder.
  2. Open the Materials Hierarchy window. You will see the recently created base material in the hierarchy.
  3. Assign the created material to the material ball by dragging it to the object. Or create a primitive (e.g., a sphere) and assign the material to the primitive.

After that you'll get the following result:

It doesn't look super good, but it works.

Let's go further and calculate the tangent basis for the mesh and assign textures to it.

Adding Textures to Shaders#

Let's add textures to both shaders: vertex and fragment. In the vertex shader we will use a texture as a displacement map and in the fragment as an albedo map.

Editing the Material#

First, let's edit our custom base material and add two texture slots and a parameter to control displacement.

  1. Open the custom_mesh_material.basemat file from the data/materials folder by using plain text editor.
  2. Modify it as follows:
    Source Code (ULON)
    // describing the read-only custom mesh material to be used for static meshes, 
    // setting prefixes to be used in shaders to refer to textures and parameters 
    BaseMaterial custom_mesh_material <node=ObjectMeshStatic editable=false var_prefix=var texture_prefix=tex default=true>
    {
    	// enabling the deferred pass for our material and hiding it (will be invisible in the UnigineEditor)
    	State deferred=1	<internal=true>
    	
    	// describing textures and parameters
    	Group "Base"
    	{
    		Texture2D albedo="core/textures/common/grain.dds" <unit=0 pass=[deferred] tooltip="Albedo texture">
    		Texture2D displacement="core/textures/common/white.dds" <unit=1 pass=[deferred] tooltip="Displacement map">
    		Slider displacement_scale=0 <min=-0.5 max=0.5 tooltip="Displacement scale">
    	}
    
    	//////////////////////////////////////////////////////////////////////////
    	// Passes
    	//////////////////////////////////////////////////////////////////////////
    
    	// describing the deferred pass with links to shaders to be used
    	Pass deferred <defines="BASE_DEFERRED">
    	{
    		Vertex   = "shaders/vertex/deferred.vert"
    		Fragment = "shaders/fragment/deferred.frag"
    	}
    	
    	// describing bindings for node types to which the material is to be applicable
    	Bind ObjectMeshStatic=ObjectMeshDynamic
    	Bind ObjectMeshStatic=ObjectMeshSkinned
    }

Now the textures and the parameter will be available for the shaders. We specified the name of the texture, rendering pass, and the path to the default texture. Our parameter (displacement_scale) shall be controlled by a slider with the specified minimum, maximum, and default values.

Editing the Vertex Shader#

Now we should edit the vertex shader code and add the texture to the shader.

The best way to add the texture in the shader (an example):

USL
// Initialize the texture slot for a texture named TEX_COLOR
INIT_TEXTURE(0, TEX_COLOR)

It makes changing the number of textures slots easy without correction of the whole shader's code. Let's do it in the vertex shader:

  1. Open the vertex shader deferred.vert file in the data/shaders/vertex folder by using plain text editor.
  2. Edit the source code in the following way:
    Source code (UUSL)
    // Include Unified Unigine Shader Language (UUSL) header
    #include <core/shaders/common/common.h>
    
    // Create a texture sampler (slot 0 is used in the fragment shader for the color texture)
    INIT_TEXTURE(1,TEX_HEIGHT)
    
    // Input data struct
    STRUCT(VERTEX_IN)
    	INIT_ATTRIBUTE(float4,0,POSITION)	// Vertex position
    	INIT_ATTRIBUTE(float4,1,TEXCOORD0)	// Vertex texcoord (uv)
    	INIT_ATTRIBUTE(float4,2,TEXCOORD1)	// Vertex basis tangent
    	INIT_ATTRIBUTE(float4,3,TEXCOORD2)	// Vertex color
    	INIT_INSTANCE
    END
    
    // Our output vertex data struct
    STRUCT(VERTEX_OUT)
    	INIT_POSITION						// Out projected position
    	INIT_OUT(float4,0)					// Texcoord (uv)
    	INIT_OUT(float3,1)					// Vertex TBN (X)
    	INIT_OUT(float3,2)					// Vertex TBN (Y)
    	INIT_OUT(float3,3)					// Vertex TBN (Z)
    END
    
    MAIN_BEGIN(VERTEX_OUT,VERTEX_IN)
    	
    	// Get transform with scale and rotation (without translation)
    	float4x4 transform = getObjectTransform(IN_INSTANCE);
    	float4 row_0 = transform[0];
    	float4 row_1 = transform[1];
    	float4 row_2 = transform[2];
    	
    	// Perform Modelview-space transform
    	float4 in_vertex = float4(IN_ATTRIBUTE(0).xyz,1.0f);
    	float4 position = mul4(row_0,row_1,row_2,in_vertex);
    	
    	// Set output UV
    	float4 texcoord = IN_ATTRIBUTE(1);
    	OUT_DATA(0) = texcoord;
    	
    	// Define tangent basis
    	float3 tangent,binormal,normal;
    	
    	// Get normal in object-space
    	getTangentBasis(IN_ATTRIBUTE(2),tangent,binormal,normal);
    	
    	// Transform object-space TBN into camera-space TBN
    	normal = normalize(mul3(row_0,row_1,row_2,normal));
    	tangent = normalize(mul3(row_0,row_1,row_2,tangent));
    	binormal = normalize(mul3(row_0,row_1,row_2,binormal));
    	
    	// Set output TBN matrix
    	OUT_DATA(1) = float3(tangent.x,binormal.x,normal.x);
    	OUT_DATA(2) = float3(tangent.y,binormal.y,normal.y);
    	OUT_DATA(3) = float3(tangent.z,binormal.z,normal.z);
    	
    	// Set the texture
    	float4 displacement = TEXTURE_BIAS_ZERO(TEX_HEIGHT,texcoord.xy);
    	
    	// Perform the displacement mapping using the Displacement Scale parameter of the material (with the "var" prefix")
    	position.rgb += normal * (displacement.r * var_displacement_scale);
    	
    	// Set output position
    	OUT_POSITION = getPosition(position);
    
    MAIN_END
    
    // end
    Warning
    You should add a new line (press Enter) after closing the instruction (after MAIN_END command).
  3. Save the file.

That's it! Our vertex shader now calculates normals, gets a texture and performs displacement mapping taking into account the scale value. We've added three new output semantics and now we should receive them in our fragment shader.

Notice
Note that your changes are not applied automatically (hot reload is disabled for shaders and materials). While in Editor, press F7 to reload materials.

Editing the Fragment Shader#

Now we should edit the fragment shader code and add the albedo texture to the shader.

  1. Open the fragment shader deferred.frag file in the data/shaders/fragment folder by using plain text editor.
  2. Edit the source code in the following way:
    Source code
    // Include the UUSL language header
    #include <core/shaders/common/fragment.h>
    
    // Adds a texture sampler  (slot 0)
    INIT_TEXTURE(0, TEX_COLOR)
    
    STRUCT(FRAGMENT_IN)
    	INIT_POSITION				// Projected position
    	INIT_IN(float4,0)			// Texcoord (uv)
    	INIT_IN(float3,1)			// Vertex TBN (X)
    	INIT_IN(float3,2)			// Vertex TBN (Y)
    	INIT_IN(float3,3)			// Vertex TBN (Z)
    END
    
    MAIN_BEGIN_DEFERRED(FRAGMENT_IN)
    	
    	// Get the UV coords
    	float4 texcoord = IN_DATA(0);
    
    	// Get the texture data
    	float4 texture_data = TEXTURE(TEX_COLOR,texcoord.xy);
    	
    	// Define the normal of a fragment in tangent-space
    	STATICVAR float3 tangentspace_normal = float3(0.0f,0.0f,1.0f);
    	
    	// Calculate the view-space normal
    	float3 viewspace_normal;
    	viewspace_normal.x = dot(IN_DATA(1),tangentspace_normal);
    	viewspace_normal.y = dot(IN_DATA(2),tangentspace_normal);
    	viewspace_normal.z = dot(IN_DATA(3),tangentspace_normal);
    	viewspace_normal = normalize(viewspace_normal);
    
    	// Fill G-Buffer: set the calculated normal and albedo color of the texture
    	GBuffer gbuffer = GBufferDefault();
    	gbuffer.albedo = texture_data.rgb;
    	gbuffer.normal = viewspace_normal;
    
    	setGBuffer(gbuffer);
    	
    MAIN_END
    
    // end
    Warning
    You should add a new line (press Enter) after closing the instruction (after MAIN_END command).
  3. Save the file.

Now our shaders are ready to work! Let's see them in action!

Editing the Material#

We've changed our materials and shaders, so we should reload them by using the relative console commands.

Open the console and execute the materials_reload command (or use the corresponding hotkey).

Now the result is different — we see the white lit sphere:

It looks much better! But let's go a bit further. Perform the following actions:

  1. Inherit a material from your custom base material (a child material named custom_mesh_material_0 will be created).
  2. Assign the created child material custom_mesh_material_0 to the sphere by dragging it to the object.
  3. Go to the Parameters window.
  4. Choose the Albedo and the Displacement textures for the material and adjust the Displacement Scale parameter.
    Notice
    You can also switch the parameter to the Expression mode and insert a script to animate the value, e.g.: sin(time)/2

Voila! We just created a new deferred material with own shaders which uses two textures for albedo and displacement mapping!

Last update: 2020-11-24
Build: ()