Customize Shader in Substance Painter

Why learn to customize Shader in Substance Painter?
Answer: The rendering effect in the engine and Substance Painter must be consistent, and the material configuration must be consistent. What you see is what you get.

Basic overview

First, in the shader settings, we can view the shader used for current rendering.

If there is no shader settings window, you can open it here

Click on the shader name to switch the currently owned shader.

The corresponding built-in shader can be found in the installation directory

The corresponding Shader api document can be opened in the help drop-down menu

The corresponding web page will open.

The web page address is /resources/shader-doc/index.html, you can also search it directly in the directory

The official website also has corresponding documents at

Next, follow the official tutorial and first create a color.glsl file in the shaders folder.

Then write in it

void shade(V2F inputs) {<!-- -->
  diffuseShadingOutput(vec3(0.0, 1.0, 1.0));

After completion, click to reselect the shader and you will find the shader you wrote.

After switching, the color of the model becomes a single color, and the first step, custom shader, is implemented.

Update shader

If you follow the official method, every time you modify the code, you have to restart the editor to update the shader, which would be too painful. Is there a way to update without restarting? The answer is yes, let’s see how to implement it.
First create a shader outside the official shader folder, mainly for differentiation. I created a directory to store the shader in the Resources directory of the editor.

The next part is to reference resources. We reference through resource management and choose to import resources.

Or select the + sign above the resource window

Select Add Resources and add the shader you just created.

After adding, the current resources will be displayed in the list

Next, you also need to set where to import your resources.

Current session: It takes effect until the editor is closed and is destroyed after closing.
Project: Only available in the current project
Library: available to all projects
I chose to import into the project.
After the import is successful, the shader you imported will be displayed in the resource.

If you only look at the customized resources in the project, you will find that it contains the shader you imported, or you can just search for the name.

In this case, if your shader is updated, you only need to right-click on the resource to update the resource.

Custom configuration parameters

The next step is to implement custom configuration items. First customize a color. The part starting with //: is the effect called by the editor to be displayed in the editor.

//: param custom { "default": 1, "label": "Color", "widget": "color" }
uniform vec3 color;

void shade(V2F inputs) {<!-- -->
  diffuseShadingOutput(vec3(1.0, 1.0, 1.0) * color);

After restarting the editor, open it again and you will have the ability to adjust the color parameters.

Color configuration

//: param custom { "default": 0, "label": "Color RGB", "widget": "color" }
uniform vec3 u_color_float3;
//: param custom { "default": 1, "label": "Color RGBA", "widget": "color" }
uniform vec4 u_color_float4;

Vector and floating point settings

//: param custom { "default": 0, "label": "Int spinbox" }
uniform int u_spin_int1;
//: param custom { "default": 0, "label": "Int2 spinbox" }
uniform ivec2 u_spin_int2;
//: param custom { "default": 0, "label": "Int3 spinbox" }
uniform ivec3 u_spin_int3;
//: param custom { "default": 0, "label": "Int4 spinbox" }
uniform ivec4 u_spin_int4;
//: param custom { "default": 0, "label": "Float spinbox" }
uniform float u_spin_float1;
//: param custom { "default": 0, "label": "Float2 spinbox" }
uniform vec2 u_spin_float2;
//: param custom { "default": 0, "label": "Float3 spinbox" }
uniform vec3 u_spin_float3;
//: param custom { "default": 0, "label": "Float4 spinbox" }
uniform vec4 u_spin_float4;


//: param custom { "default": 0, "label": "Int slider", "min": 0, "max": 10 }
uniform int u_slider_int1;
//: param custom { "default": 0, "label": "Int slider", "min": 0, "max": 10, "step": 2 }
uniform int u_slider_int1_stepped;
//: param custom { "default": 0, "label": "Int2 slider", "min": 0, "max": 10 }
uniform ivec2 u_slider_int2;
//: param custom { "default": 0, "label": "Int3 slider", "min": 0, "max": 10 }
uniform ivec3 u_slider_int3;
//: param custom { "default": 0, "label": "Int4 slider", "min": 0, "max": 10 }
uniform ivec4 u_slider_int4;
//: param custom { "default": 0, "label": "Float slider", "min": 0.0, "max": 1.0 }
uniform float u_slider_float1;
//: param custom { "default": 0, "label": "Float2 slider", "min": 0.0, "max": 1.0 }
uniform vec2 u_slider_float2;
//: param custom { "default": [0.2, 0.5, 0.8], "label": "Float3 slider", "min": 0.0, "max": 1.0 }
uniform vec3 u_slider_float3;
//: param custom { "default": 0, "label": "Float4 slider", "min": 0.0, "max": 1.0, "step": 0.02 }
uniform vec4 u_slider_float4_stepped;

Check box

//: param custom { "default": false, "label": "Boolean" }
uniform bool u_bool;

Texture configuration
There is another sentence: The texture is defined by its name in the shelf and must be in the Textures or Environments category.
It means to declare whether its use is a normal texture or an environment texture. The difference between a normal texture and an environment texture is that when selecting a texture, the selectable textures are different.

//: param custom { "default": "", "default_color": [1.0, 1.0, 0.0, 1.0], "label": "Texture1" }
uniform sampler2D u_sampler1;
//: param custom { "default": "texture_name", "label": "Texture2" }
uniform sampler2D u_sampler2;
//: param custom { "default": "texture_name", "label": "Texture3", "usage": "texture" }
uniform sampler2D u_sampler3;
//: param custom { "default": "texture_name", "label": "Texture4", "usage": "environment" }
uniform sampler2D u_sampler4;

drop down box

//: param custom {
//: "default": -1,
//: "label": "Combobox",
//: "widget": "combobox",
//: "values": {
//: "Value -1": -1,
//: "Value 0": 0,
//: "Value 10": 10
//: }
//: }
uniform int u_combobox;

Use textures

The picture above is the official tutorial. Next, I will implement it manually.
Import the library:

import lib-sparse.glsl

Declare the texture, two lines, the first line is the texture channel to be linked, the second line is the texture name

//: param auto texture_bent_normals
uniform SamplerSparse basecolor_tex;

Param auto is followed by the channel name. texture_bent_normals is the baked texture, which can be used.
Texture settings channel
channel_ambientocclusion channel_anisotropyangle channel_anisotropylevel channel_basecolor channel_blendingmask channel_diffuse channel_displacement channel_emissive channel_glossiness channel_height channel_ior channel_metallic channel_normal channel_opacity channel_reflection channel_roughness channel_scattering channel_specular channel_specularlevel channel_transmissive

user channel
channel_user0 channel_user1 channel_user2 channel_user3 channel_user4 channel_user5 channel_user6 channel_user7

Model map
texture_ambientocclusion : Ambient Occlusion map
texture_curvature : Curvature map
texture_id : ID map
texture_normal : Tangent space normal map
texture_normal_ws : World space normal map
texture_position : World space position map
texture_thickness : Thickness map
texture_height : Height map
texture_bent_normals : Bent normals map
texture_opacity : Opacity map

The texture name used in the shader can be customized, but the corresponding channel cannot be customized.
two special channels
texture_blue_noise blue disturbance map
texture_environment environment map, mipmap map, you need to introduce the lib-env.glsl library

Get texture color

vec4 baseColor = textureSparse(basecolor_tex, inputs.sparse_coord);

Successfully obtained stickers
Complete code:

import lib-sparse.glsl

//: param auto channel_basecolor
uniform SamplerSparse basecolor_tex;

//: param custom { "default": 1, "label": "Color", "widget": "color" }
uniform vec3 color;

void shade(V2F inputs) {<!-- -->
  vec4 baseColor = textureSparse(basecolor_tex, inputs.sparse_coord);
  vec3 diffuse = baseColor.rgb * color;

The effect is as follows, the basic texture of the model is successfully displayed.

Fragment output

First, let’s take a look at the data structure transmitted from the vertex to the fragment. All the required content is transferred to the fragment. Variable names with [] after them indicate that they are currently arrays, and the numbers inside represent the length of the array.

struct V2F {<!-- -->
  vec3 normal; // normal
  vec3 tangent; // Tangent line
  vec3 bitangent; // Bitangent
  vec3 position; // position coordinates
  vec4 color[1]; // vertex color (color0)
  vec2 tex_coord; // Texture coordinates (uv0)
  SparseCoord sparse_coord; // sparse texture coordinates sample the texture through the textureSparse() function
  vec2 multi_tex_coord[8]; // Texture coordinate array (uv0-uv7)

Then it can be used within the shade function. The simplest way is to directly return a single value and return a four-dimensional vector.

vec4 shade(V2F inputs)
{<!-- -->
  // We simply return the value of the RGB color picker
  return vec4(u_color_float3, 1.0);

But now it will prompt that the current method has been discarded

Then it is commonly used now, through the function output diffuseShadingOutput

void shade(V2F inputs) {<!-- -->
  vec4 baseColor = textureSparse(basecolor_tex, inputs.sparse_coord);
  vec3 diffuse = baseColor.rgb * color;

It only outputs a three-dimensional vector, if transparency alphaOutput function is needed

void shade(V2F inputs) {<!-- -->
  vec4 baseColor = textureSparse(basecolor_tex, inputs.sparse_coord);
  vec3 diffuse = baseColor.rgb * color;



Related output function definitions

//Transparency. default value: 1.0
void alphaOutput(float);
// Diffuse reflection. default value: vec3(0.0)
void diffuseShadingOutput(vec3);
//Specular reflection. default value: vec3(0.0)
void specularShadingOutput(vec3);
// Self-luminous. default value: vec3(0.0)
void emissiveColorOutput(vec3);
// Fragment color. default value: vec3(1.0)
void albedoOutput(vec3);
// Subsurface scattering, see lib-sss.glsl for details. default value: vec4(0.0)
void sssCoefficientsOutput(vec4);

The most basic rendering: emissiveColor + albedo * diffuseShading + specularShading

Rendering status settings

Cull back side

//: state cull_face on

Draw front and back

//: state cull_face off

blend mode

no mixing

//: state blend none

Standard blending mode, draw order from back to front

//: state blend over

Standard blending mode, paint order from back to front. Assume the color is pre-multiplied by alpha:

//: state blend over_premult

Additive blending mode:

//: state blend add

Multiplicative blending mode

//: state blend multiply

Shader sampling locality
By default, document channels are sampled using untransformed texture coordinates for rendering optimizations during painting.
If artifacts appear set the nonlocal state to on .

The translation is roughly

Shader sampling locality
By default, document channels are sampled during draw using unconverted texture coordinates for rendering optimization.
If artifacts occur, set the non-local state to on.

//: state nonlocal on

After understanding the basics, I will look at the built-in library and how the built-in pbr is implemented. If I update it later, I will update how to implement unity’s built-in standard in sp.