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#
- The article on Material Settings
- The article on Custom Materials
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). Let's add the new custom base material for the forward rendering pass (similar to deferred rendering pass material.
The material will have the following structure:
<?xml version="1.0" encoding="utf-8"?>
<base_material version="2.0" name="custom_forward_material" editable="0">
<!-- Enable the custom transparency preset -->
<options transparent="2"/>
<!-- States -->
<state name="ambient">1</state>
<!-- Forward (ambient) rendering shaders -->
<shader pass="ambient" node="object_mesh_static"
ambient="1"
defines="BASE_AMBIENT"
vertex="shaders/vertex/ambient.vert"
fragment="shaders/fragment/ambient.frag"/>
<!-- Bindings -->
<bind node="object_mesh_dynamic" to="object_mesh_static"/>
<bind node="object_mesh_skinned" to="object_mesh_static"/>
<!-- Textures -->
<texture unit="0" name="albedo" anisotropy="1" shader="fragment" pass="deferred,ambient">core/textures/common/white.dds</texture>
<texture unit="1" name="height" shader="all" pass="deferred,ambient">core/textures/common/white.dds</texture>
<texture unit="2" shader="fragment" type="opacity_depth"/>
<texture unit="3" shader="fragment" type="gbuffer_normal"/>
</base_material>
The key changes of the material are:
- Added a new ambient state for the custom material.
- Defined shaders for forward (ambient) rendering pass.
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 assets/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:
// 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
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)
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;
// 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
Save the shader as ambient.vert to the assets/shaders/vertex folder of your project.
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:
- Open a plain text editor, and write the following:
// 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
You should add a new line (press Enter) after closing the instruction (after MAIN_END command). - Save the shader file as ambient.frag to the assets/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.
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.
- Open UnigineEditor and launch your project.
- Create a primitive, for example, a sphere.
- 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.