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 Forward Rendering Pass

Since UNIGINE 2 rendering pipelines includes forward rendering pass, you are able to create custom shaders for forward rendering. Forward pass can be added to the already created custom material. This article shows you how to write a material with custom shaders for the forward rendering pass.

Prior Knowledge

See Also

Create a Material

To use shader, we should create a material with the forward pass. Another way (if you already created the material with deferred pass) is to add the forward pass to the already created custom material. Let's add the forward pass to the custom material that created for deferred shaders.

The material will have the following 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>
		<state name="ambient">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"/>

		<!-- forward rendering shaders -->
		<shader pass="ambient" object="mesh_static"
			ambient="1"
			defines="BASE_AMBIENT"
			vertex="<your project folder>/shaders/vertex/deferred.vert"
			fragment="<your project folder>/fragment/forward.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,ambient">core/textures/common/white.dds</texture>
		<texture unit="1" name="height" shader="vertex" pass="deferred,ambient">core/textures/common/white.dds</texture>
		<texture unit="1" shader="fragment" type="deferred_depth"/>
		<texture unit="2" shader="fragment" type="deferred_normal"/>
		
	</material>
	
</materials>

The key changes of the material are:

  • Added a new ambient state for the custom material.
  • Defined shaders for forward (ambient) rendering pass.
    Notice
    In UNIGINE we use "ambient pass" instead of "forward pass".
  • Added ambient pass to the textures attributes.
  • Added two new textures for the fragment shader of the ambient pass: deferred_depth and deferred_normal. These textures contain the information from the corresponding buffers of the deferred rendering pass.

Now the material library has the material with forward and deferred rendering passes.

Warning
You should relaunch the engine, if you change the material .mat file whilst the engine is running.

Create Vertex Shader

Since we write a simple shader example, let's use the vertex shader for the deferred rendering pass.

The vertex shader code is 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

// 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];
	
	// 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;
	
	// 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.
Notice
Use the shaders_reload command to reload shaders whilst the engine is running.

Create Fragment Shader

This section contains instructions on how to create a fragment shader (also known as pixel shader). Forward rendering pass doesn't use deferred buffers to render the final image.

Let's create the fragment shader with some interesting code. We took the current textures of deferred shaders buffers (normal buffer and depth buffer) and use them to form the output color. Here is a code of the fragment shader:

  1. Open a plain text editor, and write the following:
    Source code (UUSL)
    // Include the UUSL language header
    #include <core/shaders/common/fragment.h>
    
    // Adds texture samplers
    #define TEX_COLOR 0
    #define TEX_DEFERRED_DEPTH 1
    #define TEX_DEFERRED_NORMAL 2
    
    INIT_TEXTURE(TEX_COLOR)
    INIT_TEXTURE(TEX_DEFERRED_DEPTH)
    INIT_TEXTURE(TEX_DEFERRED_NORMAL)
    
    // Input data structure
    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
    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);
    	
    	// Get data from G-Buffer
    	float4 deferred_depth_buffer = TEXTURE_BIAS_ZERO(TEX_DEFERRED_DEPTH,IN_POSITION.xy * s_viewport.zw);
    	float4 deferred_normal_buffer = TEXTURE_BIAS_ZERO(TEX_DEFERRED_NORMAL,IN_POSITION.xy * s_viewport.zw);
    	
    	// Cut the sky from the buffer
    	float deferred_cut = sign(dot(deferred_depth_buffer,deferred_depth_buffer));
    	// Get the normal
    	float3 deferred_normal = normalize(getDeferredNormal(deferred_normal_buffer));
    	
    	// Set the output color
    	OUT_COLOR.rgb = FLOAT3(abs(dot(deferred_normal,N)) * deferred_cut);
    	OUT_COLOR.a = 1.0f;
    	
    	// For custom depth
    	CUSTOM_DEPTH_FRAGMENT_OUT
    	
    MAIN_END
    
    // end
    Warning
    You should add a new line (press Enter) after closing the instruction.
  2. Save the shader file with the forward.frag extension to the shaders/fragment folder.

Well, let's clarify what is under the hood of this fragment shader:

  • We defined 2 textures in material: deferred_depth (depth buffer texture) and deferred_normal (normal buffer texture) to use them in our fragment shader.
  • We defined the deferred_cut variable to cut out the sky.
  • We set the output color by using the dot product of normal vectors.
  • We didn't fill the G-buffer, because it's the fragment shader for ambient pass.
Notice
Use the shaders_reload command to reload shaders whilst the engine is running.

Editting the Material

After performing all these steps, you can see recently created shaders in use. Now you should open the UnigineEditor and assign the material.

  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. Create a new material by inheriting recently created.

  5. Go to the States tab of the material and turn off the Deferred pass.

    The sphere should disappear.

  6. Go to the Common tab and specify one of the transparency presents to activate the ambient pass. For example, we activated Additive preset.

After that you'll get the following result.

Well, that the whole story!

Last update: 2017-07-03