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
Using Editor Tools for Specific Tasks
Extending Editor Functionality
Built-in Node Types
Nodes
Objects
Effects
Decals
Light Sources
Geodetics
World Nodes
Sound Objects
Pathfinding Objects
Players
Programming
Fundamentals
Setting Up Development Environment
Usage Examples
C++
C#
UnigineScript
Plugins
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
Content Creation
Content Optimization
Materials
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Tutorials
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 Forward Rendering Pass

Since Unigine rendering pipeline includes forward rendering pass for transparent objects, you are able to create custom shaders for the forward rendering pass. Forward pass can be added to the already created custom material or you can create new material for it. This article shows you how to write a material with custom shaders for the forward rendering pass.

See Also#

Create a Material#

First, we should create a material with the forward pass. Another way (if you already created the material with the deferred pass) is to add the forward pass to the already created custom base material). But for now let's add a new custom ULON-based base material for the forward rendering pass (similar to deferred rendering pass material.

The material will have the following structure:

Source Code (ULON)
// describing the read-only custom forward material to be used for static meshes, 
// setting prefixes to be used in shaders to refer to textures and parameters 
BaseMaterial custom_forward_material <node=ObjectMeshStatic editable=false var_prefix=var texture_prefix=tex default=true>
{
	// enabling the custom transparency preset
	Option transparent=2
	
	// enabling the ambient pass for our material and hiding it (will be invisible in the UnigineEditor)
	State ambient=1	<internal=true>

	// describing textures
	Group "Base"
	{
		Texture2D albedo="core/textures/common/white.dds" <unit=0 pass=[deferred || ambient] tooltip="Albedo texture">
		Texture2D height="core/textures/common/red.dds" <unit=1 pass=[deferred || ambient] tooltip="Displacement map">
		Texture2D              <unit=2 type=opacity_depth>
		Texture2D              <unit=3 type=gbuffer_normal>
	}
	
	//////////////////////////////////////////////////////////////////////////
	// Pass
	//////////////////////////////////////////////////////////////////////////
	
	//describing the ambient pass with links to shaders to be used
	Pass ambient <defines="AMBIENT">
	{
		Vertex   = "shaders/vertex/ambient.vert"
		Fragment = "shaders/fragment/ambient.frag"
	}
	
	// describing bindings for node types to which the material is to be applicable
	Bind ObjectMeshStatic=ObjectMeshDynamic
	Bind ObjectMeshStatic=ObjectMeshSkinned
}

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: opacity_depth and gbuffer_normal. These textures contain the information from the corresponding buffers of the deferred rendering pass.

Save the new material as custom_forward_material.basemat file to the data/materials folder.

Now, in the project, there are materials with forward and deferred rendering passes.

Create a 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>

// Create a texture sampler
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)				// TBN matrix
	INIT_OUT(float3,2)				// TBN matrix
	INIT_OUT(float3,3)				// TBN matrix
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;
	
	// 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 height = TEXTURE_BIAS_ZERO(TEX_HEIGHT,texcoord.xy);
	
	// Perform the displacement mapping
	position.rgb += normal * (height.r) * 0.2f;
	
	// 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).

Save the shader as ambient.vert to the data/shaders/vertex folder of your project.

Notice
Use the materials_reload command to reload shaders whilst the engine is running.

Create a 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
    INIT_TEXTURE(0,TEX_COLOR)
    INIT_TEXTURE(2,TEX_DEFERRED_DEPTH)
    INIT_TEXTURE(3,TEX_DEFERRED_NORMAL)
    
    // Input data structure
    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(FRAGMENT_OUT,FRAGMENT_IN)
    	
    	// Get the UV coords
    	float4 texcoord = IN_DATA(0);
    
    	// Calculate the screen UV
    	float2 screen_uv = IN_POSITION.xy * s_viewport.zw;
    	
    	// 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);
    	
    	// Create a Gbuffer
    	GBuffer gbuffer = GBufferDefault();
    
    	// Set normal and depth textures
    	loadGBufferNormal(gbuffer,TEXTURE_OUT(TEX_DEFERRED_NORMAL),screen_uv);
    	float deferred_depth = getLinearizedDepth(TEXTURE_OUT(TEX_DEFERRED_DEPTH),screen_uv);
    	
    	// Cut the sky from the buffer
    	float depth_cutout = deferred_depth >= s_depth_range.y ? 0.0f : 1.0f;
    	
    	// Set the output color by components
    	OUT_COLOR.r = texture_data.r*0.05 + abs(dot(viewspace_normal,gbuffer.normal)) * depth_cutout;
    	OUT_COLOR.g = texture_data.g*0.05 + abs(dot(viewspace_normal,gbuffer.normal)) * depth_cutout;
    	OUT_COLOR.b = texture_data.b*0.05 + abs(dot(viewspace_normal,gbuffer.normal)) * depth_cutout;
    	OUT_COLOR.a = 1.0f;
    	
    MAIN_END
    
    // end
    Warning
    You should add a new line (press Enter) after closing the instruction (after MAIN_END command).
  2. Save the shader file as ambient.frag to the data/shaders/fragment folder of your project.

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

  • We defined 2 textures in material: opacity_depth (depth buffer texture) and gbuffer_normal (normal buffer texture) to use them in our fragment shader.
  • We defined the depth_cutout variable to cut out the sky.
  • We set the output color by using our initial albedo texture and 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 materials_reload console command to reload shaders whilst the engine is running.

Editing 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 your project in the UnigineEditor.
  2. Create a primitive, for example, a sphere.
  3. Assign the created material by dragging it to the the primitive.

After that you'll get the following result.

Last update: 2021-12-13
Build: ()