Programming
Fundamentials
Setting Up Development Environment
UnigineScript
High-Level Systems
C++
C#
File Formats
Rebuilding the Engine and Tools
GUI
Double Precision Coordinates
API
Bounds-Related Classes
Containers
Controls-Related Classes
Core Library
Engine-Related Classes
GUI-Related Classes
Node-Related Classes
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
Rendering-Related Classes
Utility Classes

Creating of Custom Shader for Deferred Rendering Pass

Unigine 2.0 renders all the opaque geometry in the deferred rendering pass. This article explains how to create your own material with shaders (both vertex and fragment) for the deferred pass.

In this article, we create a material for static, dynamic and skinned meshes. We define a heightmap for a vertex shader and a diffuse texture for fragment shader. Also, we calculate normals to make our mesh be lit properly.

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 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 (which located in the data folder) create the materials folder.
  2. Create the empty <your material name>.xml file.
  3. Open the material file in any plain text editor and create the following document structure:
    Source code(XML)
    <?xml version="1.0" encoding="utf-8"?>
    
    <materials version="2.0" editable="0">
    	<!-- custom mesh material -->
    	<material name="custom_mesh_material" editable="0">
    		
    		<!-- states -->
    		<state name="deferred">1</state>
    		
    		<!-- deferred rendering shaders -->
    		<shader pass="deferred" object="mesh_static"
    			deferred="1"
    			defines="BASE_DEFERRED"
    			vertex="<your project folder>/shaders/vertex/deferred.vert"
    			fragment="<your project folder>/shaders/fragment/deferred.frag"/>
    		
    		<!-- bindings -->
    		<bind object="mesh_dynamic" to="mesh_static"/>
    		<bind object="mesh_skinned" to="mesh_static"/>
    		
    	</material>
    	
    </materials>
    • In the code above, we defined a new custom material with the deferred state.
    • Also we defined shaders for the deferred state and set the paths to them (we will create these shader files and folder in further steps).
    • Finally, we defined the bindings: types of meshes for which the material can be assigned.
  4. Save the changes.

Create Vertex Shader

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

We assign two shaders in out material: vertex and fragment. To create the vertex shader, perform the following:

  1. Create the shaders folder in your project's folder.
  2. Inside recently created folder, create another 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
    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 direction
    	INIT_CUSTOM_DEPTH_VERTEX_OUT	// For custom depth
    END
    
    MAIN_BEGIN(VERTEX_OUT,VERTEX_IN)
    	
    	// Get transform with scale and rotation (without translation)
    	float4 row_0 = s_transform[0];
    	float4 row_1 = s_transform[1];
    	float4 row_2 = s_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 vertex direction
    	OUT_DATA(1) = position.xyz * s_depth_range.w;
    	
    	// Set output position
    	OUT_POSITION = getPosition(position);
    	// For custom depth
    	PERFORM_DEPTH_VERTEX_OUT
    MAIN_END
    
    // end
    Warning
    You should add a new line (press Enter) after closing the instruction.

    The code is pretty simple and clear:

    • Includes the UUSL header.
    • Defines the input vertex data structure.
    • Defines the output vertex data structure.
    • Starts the main program of the shader:
      • Translates the object-space coordinates to clipping space coordinates.
      • Sets the output UV.
      • Sets the vertex direction.
      • Sets the vertex position.
  4. Save the shader file with the deferred.vert extension inside the shaders/vertex folder.

Create Fragment Shader

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

To create the 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>
    
    STRUCT(FRAGMENT_IN)
    	INIT_POSITION				// Projected position
    	INIT_IN(float4,0)			// Texcoord (uv)
    	INIT_IN(float3,1)			// Vertex direction
    	INIT_CUSTOM_DEPTH_VERTEX_IN // For custom depth
    END
    
    MAIN_BEGIN(FRAGMENT_OUT,FRAGMENT_IN)
    	
    	// Fill G-Buffer
    	SET_DEPTH(length(IN_DATA(1)))
    	SET_NORMAL(float3(0.0f,0.0f,1.0f))
    	SET_DIFFUSE(float3_one)
    	SET_ALPHA(01.0f)
    	SET_SPECULAR(float3(0.0f,0.0f,0.0f))
    	SET_GLOSS(0.0f)
    	SET_DECAL_MASK(0)
    	SET_TRANSLUCENT(0.0f)
    	SET_VELOCITY(float3(0.0f,0.0f,1.0f),float3(0.0f,0.0f,1.0f))
    	SET_MICROFIBER(0.0f)
    	SET_OCCLUSION(1.0f)
    	SET_LIGHT_MAP(float3_zero)
    
    	// For custom depth
    	CUSTOM_DEPTH_FRAGMENT_OUT 
    	
    MAIN_END
    
    // end
    Warning
    You should add a new line (press Enter) after closing the instruction.

    Some explanations:

    • We include the UUSL header.
    • Define the input fragment data struct.
    • Start the main program of the shader:
      • Fill the G-Buffer:
        • SET_DEPTH command calculates the depth to the fragment.
        • SET_NORMAL command sets the normal vector.
        • SET_DIFFUSE command sets the diffuse value.
        • SET_ALPHA command sets the alpha value
        • SET_SPECULAR command sets the specular value.
        • SET_GLOSS command sets the gloss value.
        • SET_DECAL_MASK command sets the decal mask value.
        • SET_TRANSLUCENT command sets the translucent value.
        • SET_VELOCITY command fills the velocity buffer.
        • SET_MICROFIBER command sets the microfiber value.
        • SET_OCCLUSION command sets the occlusion value.
        • SET_LIGHT_MAP command sets the light map value.
  2. Save the shader file with the deferred.frag extension to the shaders/fragment folder.

Working with Material

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

  1. Open UnigineEditor and launch your project.
  2. Go the Materials settings and add a new material, which you recently created.
  3. Create a primitive, for example, a sphere.
  4. Assign the custom material.

After that you'll get the following result.

It doesn't look 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 the both shaders: vertex and fragment. In vertex shader we will use texture as a heightmap and in the fragment as a diffuse map.

Editting the Material

First, let's edit the custom material and add texture slots.

  1. Open the custom material .mat file by using plain text editor.
  2. Edit it in the following way:
    Source code(XML)
    <?xml version="1.0" encoding="utf-8"?>
    
    <materials version="2.0" editable="0">
    	<!-- custom mesh material -->
    	<material name="custom_mesh_material" editable="0">
    		
    		<!-- states -->
    		<state name="deferred">1</state>
    		
    		<!-- deferred shaders -->
    		<shader pass="deferred" object="mesh_static"
    			deferred="1"
    			defines="BASE_DEFERRED"
    			vertex="<your project folder>/shaders/vertex/deferred.vert"
    			fragment="<your project folder>/shaders/fragment/deferred.frag"/>
    		
    		<!-- bindings -->
    		<bind object="mesh_dynamic" to="mesh_static"/>
    		<bind object="mesh_skinned" to="mesh_static"/>
    
    		<!-- textures -->
    		<texture unit="0" name="diffuse" anisotropy="1" shader="fragment" pass="deferred">core/textures/common/white.dds</texture>
    		<texture unit="1" name="height" shader="vertex" pass="deferred">core/textures/common/white.dds</texture>
    		
    	</material>
    	
    </materials>
Warning
After changing the material file you should relaunch the engine!

Now the textures will be available in the shaders. We specified the name of the texture, rendering pass, and the path to the default texture.

Editting the Vertex Shader

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

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

USL
// Define the name of the texture
#define TEX_COLOR 0

// Initialize the texture slot
INIT_TEXTURE(TEX_COLOR)

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

  1. Open the vertex shader deferred.vert file 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>
    
    // 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
    END
    
    // Create a texture sampler
    #define TEX_HEIGHT 1
    INIT_TEXTURE(TEX_HEIGHT)
    
    // Our output vertex data struct
    STRUCT(VERTEX_OUT)
    	INIT_POSITION		// Out projected position
    	INIT_OUT(float4,0)	// Texcoord (uv)
    	INIT_OUT(float3,1)	// Vertex direction
    	INIT_OUT(float3,2)	// Vertex TBN (X)
    	INIT_OUT(float3,3)	// Vertex TBN (Y)
    	INIT_OUT(float3,4)	// Vertex TBN (Z)
    
    	INIT_CUSTOM_DEPTH_VERTEX_OUT // For custom depth
    END
    
    MAIN_BEGIN(VERTEX_OUT,VERTEX_IN)
    	
    	// Get transform with scale and rotation (without translation)
    	float4 row_0 = s_transform[0];
    	float4 row_1 = s_transform[1];
    	float4 row_2 = s_transform[2];
    	
    	// Perfrom 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 vertex direction
    	OUT_DATA(1) = position.xyz * s_depth_range.w;
    	
    	// Define tangent basis
    	float3 tangent,binormal,normal;
    	
    	// Getting 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));
    	
    	// Add this to make the two-sided material
    	normal *= s_polygon_front;
    	
    	// Set output TBN matrix
    	OUT_DATA(2) = float3(tangent.x,binormal.x,normal.x);
    	OUT_DATA(3) = float3(tangent.y,binormal.y,normal.y);
    	OUT_DATA(4) = float3(tangent.z,binormal.z,normal.z);
    	
    	// Set the texture
    	float4 height = TEXTURE_BIAS_ZERO(TEX_HEIGHT,texcoord.xy);
    	
    	// Perform the displacement mapping
    	position.rgb += normal * (height.r) * 0.2f;
    	
    	OUT_POSITION = getPosition(position);
    
    	PERFORM_DEPTH_VERTEX_OUT
    
    MAIN_END
    
    // end
    Warning
    You should add a new line (press Enter) after closing the instruction.
  3. Save the file.

That's it! Our vertex shader now calculates the normals, gets the texture and performs the displacement mapping. We add three new output semantics and now we should receive them in our fragment shader.

Editting the Fragment Shader

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

  1. Open the fragment shader deferred.frag file 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
    #define TEX_COLOR 0
    
    INIT_TEXTURE(TEX_COLOR)
    
    STRUCT(FRAGMENT_IN)
    	INIT_POSITION				// Projected position
    	INIT_IN(float4,0)			// Texcoord (uv)
    	INIT_IN(float3,1)			// Vertex direction
    	INIT_IN(float3,2)			// Vertex TBN (X)
    	INIT_IN(float3,3)			// Vertex TBN (Y)
    	INIT_IN(float3,4)			// Vertex TBN (Z)
    	INIT_CUSTOM_DEPTH_VERTEX_IN // for custom depth
    END
    
    MAIN_BEGIN(FRAGMENT_OUT,FRAGMENT_IN)
    	
    	// get the UV coords
    	float4 texcoord = IN_DATA(0);
    	// get the texture data
    	float4 texture_data = TEXTURE(TEX_COLOR,texcoord.xy);
    	
    	// Normal of a fragment in tangent-space
    	STATICVAR float3 ts_normal = float3(0.0f,0.0f,1.0f);
    	
    	float3 N;
    	N.x = dot(IN_DATA(2),ts_normal);
    	N.y = dot(IN_DATA(3),ts_normal);
    	N.z = dot(IN_DATA(4),ts_normal);
    	N = normalize(N);
    	
    	// Fill G-Buffer: set the calculated normal and diffuse of the texture
    	SET_DEPTH(length(IN_DATA(1)))
    	SET_NORMAL(N)
    	SET_DIFFUSE(texture_data.rgb)
    	SET_ALPHA(1.0f)
    	SET_SPECULAR(float3(0.0f,0.0f,0.0f))
    	SET_GLOSS(0.0f)
    	SET_DECAL_MASK(0)
    	SET_TRANSLUCENT(0.0f)
    	SET_VELOCITY(float3(0.0f,0.0f,1.0f),float3(0.0f,0.0f,1.0f))
    	SET_MICROFIBER(0.0f)
    	SET_OCCLUSION(1.0f)
    	SET_LIGHT_MAP(float3_zero)
    
    	// For custom depth
    	CUSTOM_DEPTH_FRAGMENT_OUT 
    	
    MAIN_END
    
    // end
    Warning
    You should add a new line (press Enter) after closing the instruction.
  3. Save the file.

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

Editting the Material

We changed the material and shader, so we should relaunch the engine.

Notice
When you change only shader files you can just use shaders_reload console command.

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

It looks much better! So perform the following:

  1. Create a new material by inheriting recently created.

  2. Go to the textures tab of the material.

  3. Choose the diffuse and the displacement maps for the material.

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

Last update: 2017-07-03