AnyPortrait > Script > Custom Shader
AnyPortrait provides a variety of commonly used shaders, but depending on the project, you may need to write a custom shader with special effects.
You can easily write a custom shader to create great rendering results by following just a few rules of AnyPortrait.
This page explains how AnyPortrait's shader basics are structured.
Based on this explanation, we hope you will write and apply a custom shader.
When you want to apply shaders you wrote, you can use the Material Library or change the properties of the mesh.
Please check the following manuals for how to change shaders.
- Material Library
- Shader for meshes
Note
This page does not cover how to write shaders.
For more information, please refer to the Unity documentation, forums, and related sites!
AnyPortrait does not use a single shader, but rather uses a set of shaders that are appropriate for the situation.
A total of 17 shaders are applied as a set based on three factors from which shaders are derived. (Related page)
The factors are as follows.
1. Color Space
The shader is determined based on the color space of the project.
It is written based on Gamma Space, and the color calculation formula changes in the case of Linear Space.
2. Blending
Meshes are rendered using one of four blending methods.
The default is Alpha Blend, with Additive, Soft Additive, and Multiplicative slightly modifying the blending properties and the code related to alpha calculations.
3. Clipping
The mesh being clipped receives mask texture and performs additional alpha calculations.
There is also one shader that generates the mask, but it is a functional shader that performs a fixed role.
The above factors are combined to apply a total of 17 shaders to the character.
However, when you want to write a custom shader, you don't necessarily have to write all of them.
Just check the following and write only the shaders that fit the situation of your project.
- Write only one type of shader, either Gamma or Linear, depending on the color space of the project.
- If you do not use the blending option, you only need to write the Alpha Blend shader as default.
- If there is no clipped mesh, you do not need to write a clipping shader.
- The Alpha Mask shader, which creates a clipping mask, only performs a fixed role, so you do not need to create it as a custom shader.
This page first explains the rules and method for writing an "Alpha Blend" shader in the default "Gamma Space".
Then it explains the parts that need to be modified depending on the factors that determine the shader.
It might also be helpful to check out the shader codes built into the AnyPortrait package.
The shader assets are located by default in "Assets/AnyPortrait/Assets/Shaders".
It might be convenient to duplicate the provided shaders and modify the necessary parts.
If you want to create a custom shader with Shader Graph, you can write it in the format of a Shader Graph by following the rules described on this page.
For more information, see the Related page
AnyPortrait's default shader is not much different from Unity's default Transparent shader.
The following code is the default shader for "Alpha Blend" in "Gamma Space", and you can add your own custom code based on it.
Note
AnyPortrait supports both Surface Shader, Fragment Shader, and Shader Graph.
This page explains based on the code of Fragment Shader, and you can apply the rules explained here appropriately according to the situation.
Shader "Sample Shader/Gamma Space - Normal - AlphaBlend"
{
Properties
{
_Color ("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
_MainTex ("Main Texture (RGBA)", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
LOD 200
Pass
{
Tags { "LightMode" = "ForwardBase" }
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 2.0f;
col.a *= _Color.a;
return col;
}
ENDCG
}
Pass
{
Tags { "LightMode" = "ShadowCaster" }
ZWrite On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
V2F_SHADOW_CASTER;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
v2f vert(appdata_base v)
{
v2f o;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
float4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.a *= _Color.a;
if(col.a < 0.05f)
{
discard;
}
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
}
Let's take a look at the main parts of the shader code.
1. Properties
_Color ("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
_MainTex ("Main Texture (RGBA)", 2D) = "white" {}
These are the properties used in the default Transparent shader of Unity and the properties used in AnyPortrait.
In general cases where the mesh is not clipped, only these properties are used.
Therefore, in custom shaders, all codes related to these two properties must be written as is.
Also, since the AnyPortrait system controls these properties, you should not modify these properties with other scripts.
2. Basic Pass
Pass
{
Tags { "LightMode" = "ForwardBase" }
ZWrite Off
...
: Here is the code for the basic rendering pass.
The basic pass is specified as above because there is a separate pass for shadow casting.
If you do not write a shadow pass, you can omit the "Tags { "LightMode" = "ForwardBase" }" part.
Since we do not do Z Write, we set this value to Off.
3. Blending
Blend SrcAlpha OneMinusSrcAlpha
: Blending code for the "Alpha Blend" shader.
4. Color calculation
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 2.0f;
col.a *= _Color.a;
return col;
: AnyPortrait's color operation formula is "2X Multiply".
So RGB needs to be multiplied by 2 additionally.
This code changes depending on blending.
5. Shadow Pass
Pass
{
Tags { "LightMode" = "ShadowCaster" }
ZWrite On
...
}
: For the "Alpha Blend" mesh, you need to be able to generate shadows depending on the option.
Therefore, you need to write a "ShadowCaster" pass that generates shadows.
This pass only plays a role in generating shadows, so you can write it as in the example above.
If you write it in the Surface Shader way, you don't need to write this pass.
The above example is shader code in Gamma Space, if the color space of the project is Linear Space, you need to modify the "Color calculation" part.
In the above basic code, let's modify the frag function of the basic pass ("ForwardBase") as follows.
...
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 4.595f;
col.rgb = pow(col.rgb, 2.2f);
col.a *= _Color.a;
return col;
}
...
In Linear Space, if a value raised to the power of 2.2 is applied to the color compared to Gamma Space, it appears closer to the original image color.
So, even though it is the "2X Multiply" operation rule as above, we multiply it by "4.595" instead of "2", and the final RGB is also raised to the power of "2.2".
This change should be applied regardless of the blending type.
By changing the mesh options, you can render in the "Alpha Blend", "Additive", "Soft Additive", and "Multiplicative" methods in addition to the default blending method.
You need to write a shader for each blending method, but you can easily write it by slightly modifying the code of "Alpha Blend" above.
Blending options other than "Alpha Blend" are mainly for visual effects, so it is recommended not to write a pass that generates shadows.
For the three types of blending, you just need to modify some codes in the shader, "Blend" and "frag function".
Also, unlike Alpha Blend, we omit the shadow generation pass, so you can delete the "LightMode" designation code.
The following example includes the codes for all three types of blending, so you can check the comments and compile the necessary codes.
...
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
// --- Select and write according to the blending type ---
Blend One One // Additive
Blend OneMinusDstColor One // Soft Additive
Blend DstColor SrcColor // Multiplicative
LOD 200
Pass
{
// --- Delete Pass Tags ---
// Tags { "LightMode" = "ForwardBase" }
ZWrite Off
CGPROGRAM
...
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 2.0f;
col.a *= _Color.a;
// --- Additive 또는 Soft Additive ---
col.rgb *= col.a;
col.a = 1.0f;
// ---------------------------------
// --- Multiplicative ---
col.rgb = col.rgb * (col.a) + float4(0.5f, 0.5f, 0.5f, 1.0f) * (1.0f - col.a);
col.a = 1.0f;
// ----------------------
return col;
}
ENDCG
}
// Delete Shadow Generation Pass
}
}
1. Blending Options
Blend One One // Additive
Blend OneMinusDstColor One // Soft Additive
Blend DstColor SrcColor // Multiplicative
: Depending on your mesh's blending options, you can choose one of the above codes.
(Soft Additive may not be supported if you use Shader Graph.)
2. Delete Multi Pass
// Tags { "LightMode" = "ForwardBase" }
: Since the shadow generation pass is gone, you can delete the code related to the multi-pass.
Also delete the shadow generation pass code.
3. Modify Color calculation
(1) Additive or Soft Additive
col.rgb *= col.a;
col.a = 1.0f;
(2) Multiplicative
col.rgb = col.rgb * (col.a) + float4(0.5f, 0.5f, 0.5f, 1.0f) * (1.0f - col.a);
col.a = 1.0f;
: Change the final color to match the blending, and change the Alpha value to 1.
The color calculation formulas for Additive and Soft Additive are the same.
The Clipped meshes are rendered by a separate shader.
The shader for clipping receives the rendering result of the mesh that creates the clipping mask in texture format and adds an operation to prevent some of it from being rendered.
You can add the clipping property and the clipping operation to the basic shader.
Also, the clipping shader does not need to generate shadows in Alpha Blend, so you can write it as a single pass.
This is because it is included in the shadow of the mask mesh.
The following example is a shader for Alpha Blend in Gamma Space, and can be modified to match the explanation above.
Shader "Sample Shader/Gamma Space - Clipped - AlphaBlend"
{
Properties
{
_Color ("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
_MainTex ("Main Texture (RGBA)", 2D) = "white" {}
// --- Clipping properties ---
_MaskTex ("Mask Texture (A)", 2D) = "white" {}
_MaskScreenSpaceOffset ("Mask Screen Space Offset (XY_Scale)", Vector) = (0, 0, 0, 1)
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
// --- Screen Position for Clipping Operations ---
float4 screenPos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
// --- Property variables for Clipping ---
sampler2D _MaskTex;
float4 _MaskScreenSpaceOffset;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// --- Calculating Screen Position for Clipping ---
o.screenPos = ComputeScreenPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 2.0f;
col.a *= _Color.a;
// --- Set Alpha channel by Clipping mask ---
float2 screenUV = i.screenPos.xy / max(i.screenPos.w, 0.0001f);
screenUV -= float2(0.5f, 0.5f);
screenUV.x *= _MaskScreenSpaceOffset.z;
screenUV.y *= _MaskScreenSpaceOffset.w;
screenUV.x += _MaskScreenSpaceOffset.x * _MaskScreenSpaceOffset.z;
screenUV.y += _MaskScreenSpaceOffset.y * _MaskScreenSpaceOffset.w;
screenUV += float2(0.5f, 0.5f);
col.a *= tex2D(_MaskTex, screenUV).r;
return col;
}
ENDCG
}
}
}
1. Properties
_MaskTex ("Mask Texture (A)", 2D) = "white" {}
_MaskScreenSpaceOffset ("Mask Screen Space Offset (XY_Scale)", Vector) = (0, 0, 0, 1)
: Add a mask texture to the property for clipping mask processing.
If you add a property like the above, AnyPortrait will automatically handle clipping rendering.
_MaskScreenSpaceOffset is a property to apply the clipping mask based on the screen coordinate system.
sampler2D _MaskTex;
float4 _MaskScreenSpaceOffset;
: You also need to add variables to match the added properties.
2. Screen Space Position Semantic
float4 screenPos : TEXCOORD1;
: Clipping masks are processed in screen space.
Therefore, the position of the vertex in screen space must be calculated in the vertex shader and passed to the fragment shader.
o.screenPos = ComputeScreenPos(o.vertex);
: Add code to calculate the position in screen coordinates in the vertex shader.
3. Setting alpha channel value by mask
float2 screenUV = i.screenPos.xy / max(i.screenPos.w, 0.0001f);
screenUV -= float2(0.5f, 0.5f);
screenUV.x *= _MaskScreenSpaceOffset.z;
screenUV.y *= _MaskScreenSpaceOffset.w;
screenUV.x += _MaskScreenSpaceOffset.x * _MaskScreenSpaceOffset.z;
screenUV.y += _MaskScreenSpaceOffset.y * _MaskScreenSpaceOffset.w;
screenUV += float2(0.5f, 0.5f);
col.a *= tex2D(_MaskTex, screenUV).r;
: This is the formula for calculating the clipping mask and applying it to the alpha channel.
Please write it as it is, as it is a unique formula used in the AnyPortrait system.
AnyPortrait v1.6.0 has greatly improved the mask function.
In order to support the added mask function, the clipping shader and alpha mask shader must be changed.
For the mask function, please refer to Related Page.
The v16 Material Set provided by default from AnyPortrait v1.6.0 has changed a lot of clipping-related code compared to the previous version.
Check out the clipping shader and alpha mask shader code in v16 Material Set and use them when writing custom shaders.
Shader "AnyPortrait/Unlit (v16)/AlphaBlend Clipped"
{
Properties
{
_Color ("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
_MainTex ("Main Texture (RGBA)", 2D) = "white" {}
// Basic Clipping Properties
_MaskTex ("Mask Texture (A)", 2D) = "white" {}
_MaskScreenSpaceOffset ("Mask Screen Space Offset (XY_Scale)", Vector) = (0, 0, 0, 1)
_MaskRatio ("Mask Ratio", Range(0, 1)) = 0
// 4 Channels for Alpha Masks
_MaskRatio_1 ("Mask Ratio Ch1", Range(0, 1)) = 0
_MaskTex_1 ("Mask Texture Ch1", 2D) = "black" {}
_MaskScreenSpaceOffset_1 ("Mask Screen Space Offset Ch1", Vector) = (0, 0, 0, 1)
_MaskOp_1 ("Mask Operation Ch1", Range(0, 3)) = 0
_MaskRatio_2 ("Mask Ratio Ch2", Range(0, 1)) = 0
_MaskTex_2 ("Mask Texture Ch2", 2D) = "black" {}
_MaskScreenSpaceOffset_2 ("Mask Screen Space Offset Ch2", Vector) = (0, 0, 0, 1)
_MaskOp_2 ("Mask Operation Ch2", Range(0, 3)) = 0
_MaskRatio_3 ("Mask Ratio Ch3", Range(0, 1)) = 0
_MaskTex_3 ("Mask Texture Ch3", 2D) = "black" {}
_MaskScreenSpaceOffset_3 ("Mask Screen Space Offset Ch3", Vector) = (0, 0, 0, 1)
_MaskOp_3 ("Mask Operation Ch3", Range(0, 3)) = 0
_MaskRatio_4 ("Mask Ratio Ch4", Range(0, 1)) = 0
_MaskTex_4 ("Mask Texture Ch4", 2D) = "black" {}
_MaskScreenSpaceOffset_4 ("Mask Screen Space Offset Ch4", Vector) = (0, 0, 0, 1)
_MaskOp_4 ("Mask Operation Ch4", Range(0, 3)) = 0
// See-Through Effect
_SeeThroughRatio ("See-Through Ratio", Range(0, 1)) = 0.0
_SeeThroughTex ("See-Through Texture", 2D) = "black" {}
_SeeThroughScreenSpaceOffset ("See-Through Screen Space Offset (XY_Scale)", Vector) = (0, 0, 0, 1)
_SeeThroughAlpha ("See-Through Alpha", Range(0, 1)) = 0.0
}
SubShader
{
Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
// Screen Position for Clipping Operations
float4 screenPos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
// Basic Clipping Property variables
sampler2D _MaskTex;
float4 _MaskScreenSpaceOffset;
float _MaskRatio;
// Property variables corresponding to the 4 channels for the masks
float _MaskRatio_1;
sampler2D _MaskTex_1;
float4 _MaskScreenSpaceOffset_1;
float _MaskOp_1;
float _MaskRatio_2;
sampler2D _MaskTex_2;
float4 _MaskScreenSpaceOffset_2;
float _MaskOp_2;
float _MaskRatio_3;
sampler2D _MaskTex_3;
float4 _MaskScreenSpaceOffset_3;
float _MaskOp_3;
float _MaskRatio_4;
sampler2D _MaskTex_4;
float4 _MaskScreenSpaceOffset_4;
float _MaskOp_4;
// Property variables for see-through effect
float _SeeThroughRatio;
sampler2D _SeeThroughTex;
float4 _SeeThroughScreenSpaceOffset;
float _SeeThroughAlpha;
// Mask function: Compute Clipping Mask Alpha
half GetMaskAlpha (float alpha, float ratio)
{
return saturate((alpha * ratio) + (1.0f * (1.0f - ratio)));
}
// Mask function: Calculate per-channel mask based on operator
half GetMaskAlphaByOp (float prevMask, float alpha, float ratio, float op)
{
float opWeight_And = saturate(1.0f - abs(op - 0.0f));//AND
float opWeight_Or = saturate(1.0f - abs(op - 1.0f));//OR
float opWeight_InvAnd = saturate(1.0f - abs(op - 2.0f));//Inverse AND
float opWeight_InvOr = saturate(1.0f - abs(op - 3.0f));//Inverse OR
float inverseAlpha = 1.0f - alpha;
float nextAlpha_And = saturate(prevMask * alpha);//Multiply
float nextAlpha_Or = saturate(prevMask + (alpha * (1.0f - prevMask)));//Add Blended
float nextAlpha_InvAnd = saturate(prevMask * inverseAlpha);//Multiply (Inverse)
float nextAlpha_InvAOr = saturate(prevMask + (inverseAlpha * (1.0f - prevMask)));//Add Blended (Inverse)
float resultMask = (nextAlpha_And * opWeight_And) + (nextAlpha_Or * opWeight_Or) + (nextAlpha_InvAnd * opWeight_InvAnd) + (nextAlpha_InvAOr * opWeight_InvOr);
return saturate((resultMask * ratio) + (prevMask * (1.0f - ratio)));
}
// Mask Function: Compute the UVs of the mask render texture
float2 GetMaskScreenUV (float2 screenUV, float4 offset)
{
float2 result = screenUV - float2(0.5f, 0.5f);
result.x *= offset.z;
result.y *= offset.w;
result.x += offset.x * offset.z;
result.y += offset.y * offset.w;
result += float2(0.5f, 0.5f);
return result;
}
// Mask Function: Calculate See-Through Effect
half3 GetSeeThroughColor (half3 mainColor, half4 seeThroughColor)
{
float stAlpha = saturate(seeThroughColor.a * _SeeThroughAlpha * _SeeThroughRatio);
return (mainColor * (1.0f - stAlpha)) + (seeThroughColor.rgb * stAlpha);
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// Calculating Screen Position for Clipping
o.screenPos = ComputeScreenPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// Screen Position for Clipping Mask
float2 screenUV = i.screenPos.xy / max(i.screenPos.w, 0.0001f);
// See-Through Effect
half4 seeThroughColor = tex2D(_SeeThroughTex, GetMaskScreenUV(screenUV, _SeeThroughScreenSpaceOffset));
col.rgb = GetSeeThroughColor(col.rgb, seeThroughColor);
#if UNITY_COLORSPACE_GAMMA
// Color calculation (Gamma Space)
col.rgb *= _Color.rgb * 2.0f;
#else
// Color calculation (Linear Space)
col.rgb *= _Color.rgb * 4.595f;
col.rgb = pow(col.rgb, 2.2f);
#endif
col.a *= _Color.a;
// Alpha Mask Calculation Code
float maskResult = 1.0f;
// 1. Clipping Mask
half maskClipped = tex2D(_MaskTex, GetMaskScreenUV(screenUV, _MaskScreenSpaceOffset)).r;
maskResult *= GetMaskAlpha(saturate(maskClipped), saturate(_MaskRatio));
// 2. 4 Channel Masks
float maskCh1 = tex2D(_MaskTex_1, GetMaskScreenUV(screenUV, _MaskScreenSpaceOffset_1)).r;
float maskCh2 = tex2D(_MaskTex_2, GetMaskScreenUV(screenUV, _MaskScreenSpaceOffset_2)).r;
float maskCh3 = tex2D(_MaskTex_3, GetMaskScreenUV(screenUV, _MaskScreenSpaceOffset_3)).r;
float maskCh4 = tex2D(_MaskTex_4, GetMaskScreenUV(screenUV, _MaskScreenSpaceOffset_4)).r;
maskResult = GetMaskAlphaByOp(maskResult, saturate(maskCh1), saturate(_MaskRatio_1), _MaskOp_1);
maskResult = GetMaskAlphaByOp(maskResult, saturate(maskCh2), saturate(_MaskRatio_2), _MaskOp_2);
maskResult = GetMaskAlphaByOp(maskResult, saturate(maskCh3), saturate(_MaskRatio_3), _MaskOp_3);
maskResult = GetMaskAlphaByOp(maskResult, saturate(maskCh4), saturate(_MaskRatio_4), _MaskOp_4);
col.a *= maskResult;
return col;
}
ENDCG
}
(Shadow Caster Pass is omitted)
}
}
The shader above is the basic code of the clipping shader included in v16 material set.
Many mask-related properties have been added compared to before.
Also, a pass that generates shadows has been added.
(The explanation of the shadow generation pass is omitted here.)
1. Properties
_MaskTex ("Mask Texture (A)", 2D) = "white" {}
_MaskScreenSpaceOffset ("Mask Screen Space Offset (XY_Scale)", Vector) = (0, 0, 0, 1)
_MaskRatio ("Mask Ratio", Range(0, 1)) = 0
_MaskRatio_1 ("Mask Ratio Ch1", Range(0, 1)) = 0
_MaskTex_1 ("Mask Texture Ch1", 2D) = "black" {}
_MaskScreenSpaceOffset_1 ("Mask Screen Space Offset Ch1", Vector) = (0, 0, 0, 1)
_MaskOp_1 ("Mask Operation Ch1", Range(0, 3)) = 0
: The properties for alpha mask calculation have increased compared to the previous version.
In addition to the existing _MaskTex, _MaskScreenSpaceOffset, the _MaskRatio property has been added, which determines whether to use the mask for the corresponding channel (clipping and 4 additional channels).
In addition, the 4 channels have been given additional calculation options, allowing combinations of masks such as AND and OR.
Please also check the code related to the _MaskOp_n property for this.
_SeeThroughRatio ("See-Through Ratio", Range(0, 1)) = 0.0
_SeeThroughTex ("See-Through Texture", 2D) = "black" {}
_SeeThroughScreenSpaceOffset ("See-Through Screen Space Offset (XY_Scale)", Vector) = (0, 0, 0, 1)
_SeeThroughAlpha ("See-Through Alpha", Range(0, 1)) = 0.0
: See-Through feature added.
Similar to alpha mask, but uses both RGBA channels and has the property of overlapping RGB channel values.
2. Mask calculation functions
half GetMaskAlpha (float alpha, float ratio)
: This is a function that calculates the clipping mask.
It adjusts the degree of Alpha application according to _MaskRatio.
half GetMaskAlphaByOp (float prevMask, float alpha, float ratio, float op)
: This function calculates the mask for each of the four channels.
Different formulas are applied depending on the calculation options.
float2 GetMaskScreenUV (float2 screenUV, float4 offset)
: This function performs UV operations to reference masks that utilize Screen Space.
Since each mask has a separate UV optimization value (MaskScreenSpaceOffset), you must use this function to convert UVs.
half3 GetSeeThroughColor (half3 mainColor, half4 seeThroughColor)
: This is the function that computes the See-Through effect.
Shader "AnyPortrait/Unlit (v16)/AlphaMask"
{
Properties
{
_Color ("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
_MainTex ("Main Texture (RGBA)", 2D) = "white" {}
// Clipping properties according to Mask Chain
_MaskTex ("Mask Texture (A)", 2D) = "white" {}
_MaskScreenSpaceOffset ("Mask Screen Space Offset (XY_Scale)", Vector) = (0, 0, 0, 1)
_MaskRatio ("Mask Ratio", Range(0, 1)) = 0
// 4 Mask Channels according to Mask Chain
_MaskRatio_1 ("Mask Ratio Ch1", Range(0, 1)) = 0
_MaskTex_1 ("Mask Texture Ch1", 2D) = "black" {}
_MaskScreenSpaceOffset_1 ("Mask Screen Space Offset Ch1", Vector) = (0, 0, 0, 1)
_MaskOp_1 ("Mask Operation Ch1", Range(0, 3)) = 0
_MaskRatio_2 ("Mask Ratio Ch2", Range(0, 1)) = 0
_MaskTex_2 ("Mask Texture Ch2", 2D) = "black" {}
_MaskScreenSpaceOffset_2 ("Mask Screen Space Offset Ch2", Vector) = (0, 0, 0, 1)
_MaskOp_2 ("Mask Operation Ch2", Range(0, 3)) = 0
_MaskRatio_3 ("Mask Ratio Ch3", Range(0, 1)) = 0
_MaskTex_3 ("Mask Texture Ch3", 2D) = "black" {}
_MaskScreenSpaceOffset_3 ("Mask Screen Space Offset Ch3", Vector) = (0, 0, 0, 1)
_MaskOp_3 ("Mask Operation Ch3", Range(0, 3)) = 0
_MaskRatio_4 ("Mask Ratio Ch4", Range(0, 1)) = 0
_MaskTex_4 ("Mask Texture Ch4", 2D) = "black" {}
_MaskScreenSpaceOffset_4 ("Mask Screen Space Offset Ch4", Vector) = (0, 0, 0, 1)
_MaskOp_4 ("Mask Operation Ch4", Range(0, 3)) = 0
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Off
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
// Screen Position for Clipping Operations
float4 screenPos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
// Basic Clipping Property variables
sampler2D _MaskTex;
float4 _MaskScreenSpaceOffset;
float _MaskRatio;
// 4 Mask Channel Property variables
float _MaskRatio_1;
sampler2D _MaskTex_1;
float4 _MaskScreenSpaceOffset_1;
float _MaskOp_1;
float _MaskRatio_2;
sampler2D _MaskTex_2;
float4 _MaskScreenSpaceOffset_2;
float _MaskOp_2;
float _MaskRatio_3;
sampler2D _MaskTex_3;
float4 _MaskScreenSpaceOffset_3;
float _MaskOp_3;
float _MaskRatio_4;
sampler2D _MaskTex_4;
float4 _MaskScreenSpaceOffset_4;
float _MaskOp_4;
// Mask Function: Compute Clipping Mask Alpha
half GetMaskAlpha (float alpha, float ratio)
{
return saturate((alpha * ratio) + (1.0f * (1.0f - ratio)));
}
// Mask Function: Calculate per-channel mask based on operator
half GetMaskAlphaByOp (float prevMask, float alpha, float ratio, float op)
{
float opWeight_And = saturate(1.0f - abs(op - 0.0f));//AND
float opWeight_Or = saturate(1.0f - abs(op - 1.0f));//OR
float opWeight_InvAnd = saturate(1.0f - abs(op - 2.0f));//Inverse AND
float opWeight_InvOr = saturate(1.0f - abs(op - 3.0f));//Inverse OR
float inverseAlpha = 1.0f - alpha;
float nextAlpha_And = saturate(prevMask * alpha);//Multiply
float nextAlpha_Or = saturate(prevMask + (alpha * (1.0f - prevMask)));//Add Blended
float nextAlpha_InvAnd = saturate(prevMask * inverseAlpha);//Multiply (Inverse)
float nextAlpha_InvAOr = saturate(prevMask + (inverseAlpha * (1.0f - prevMask)));//Add Blended (Inverse)
float resultMask = (nextAlpha_And * opWeight_And) + (nextAlpha_Or * opWeight_Or) + (nextAlpha_InvAnd * opWeight_InvAnd) + (nextAlpha_InvAOr * opWeight_InvOr);
return saturate((resultMask * ratio) + (prevMask * (1.0f - ratio)));
}
// Mask Function: Compute the UVs of the mask render texture
float2 GetMaskScreenUV (float2 screenUV, float4 offset)
{
float2 result = screenUV - float2(0.5f, 0.5f);
result.x *= offset.z;
result.y *= offset.w;
result.x += offset.x * offset.z;
result.y += offset.y * offset.w;
result += float2(0.5f, 0.5f);
return result;
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// Calculating Screen Position for Clipping
o.screenPos = ComputeScreenPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb = fixed3(1.0f, 1.0f, 1.0f);
col.a = saturate(col.a * _Color.a);
// Mask operations for Mask Chains
float2 screenUV = i.screenPos.xy / max(i.screenPos.w, 0.0001f);
float maskResult = 1.0f;
// 1. Clipping Mask
half maskClipped = tex2D(_MaskTex, GetMaskScreenUV(screenUV, _MaskScreenSpaceOffset)).r;
maskResult *= GetMaskAlpha(saturate(maskClipped), saturate(_MaskRatio));
// 2. 4 Channel Masks
float maskCh1 = tex2D(_MaskTex_1, GetMaskScreenUV(screenUV, _MaskScreenSpaceOffset_1)).r;
float maskCh2 = tex2D(_MaskTex_2, GetMaskScreenUV(screenUV, _MaskScreenSpaceOffset_2)).r;
float maskCh3 = tex2D(_MaskTex_3, GetMaskScreenUV(screenUV, _MaskScreenSpaceOffset_3)).r;
float maskCh4 = tex2D(_MaskTex_4, GetMaskScreenUV(screenUV, _MaskScreenSpaceOffset_4)).r;
maskResult = GetMaskAlphaByOp(maskResult, saturate(maskCh1), saturate(_MaskRatio_1), _MaskOp_1);
maskResult = GetMaskAlphaByOp(maskResult, saturate(maskCh2), saturate(_MaskRatio_2), _MaskOp_2);
maskResult = GetMaskAlphaByOp(maskResult, saturate(maskCh3), saturate(_MaskRatio_3), _MaskOp_3);
maskResult = GetMaskAlphaByOp(maskResult, saturate(maskCh4), saturate(_MaskRatio_4), _MaskOp_4);
col.a *= maskResult;
return col;
}
ENDCG
}
}
}
The shader above is the basic code of the alpha mask shader of v16 Material Set.
Previously, it simply performed the role of calculating only the alpha channel on white, but mask-related code was added in AnyPortrait v1.6.0.
This is to support the Mask Chain function. (Related Page)
The clipping-related code is the same as the clipping shader, and the See-Through effect that is not related to the alpha channel is excluded from this shader.
You can use keywords to compile a single shader code into multiple versions.
You can use keywords in shaders using the features added in AnyPortrait v1.5.1.
You can see related information about keywords in the following official Unity documentation.
- Unity Manual
Here's an example shader where color operations change based on the keyword "SPECIAL_COLOR".
Shader "Shader Example/Gamma Space - Normal - AlphaBlend (Keyword)"
{
Properties
{
_Color ("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
_MainTex ("Main Texture (RGBA)", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
LOD 200
Pass
{
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile __ SPECIAL_COLOR
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 2.0f;
col.a *= _Color.a;
#ifdef SPECIAL_COLOR
col.rgb *= fixed3(0.0f, 1.0f, 0.0f);
#endif
return col;
}
ENDCG
}
}
}
1. Declaring keywords
#pragma multi_compile __ SPECIAL_COLOR
: Declare keywords using the #pragma multi_compile syntax.
In this example, a new keyword called "SPECIAL_COLOR" is declared.
You can use keywords in Unity using the "multi_compile" and "shader_feature" directives that generate shader variants, or the "dynamic_branch" directive that generates dynamic branches.
However, since AnyPortrait generates materials dynamically, you cannot use "shader_feature" that compiles shader variants based on assets.
2. Writing code branches
#ifdef SPECIAL_COLOR
col.rgb *= fixed3(0.0f, 1.0f, 0.0f);
#endif
: Write to behave differently depending on the keyword.
If you use "multi_compile", use the "#ifdef, #else, #elif, #endif" directives, and if you use "dynamic_branch", use the "if ( )" statement.
In this example, if the "SPECIAL_COLOR" keyword is enabled, only the color of the green channel is rendered.
To use keywords, you need to set them in Material Library.
For more information, please refer to the Related page.
(1) Open the Material Library.
(2) Add a property option named "SPECIAL_COLOR", set it to "Keyword" type, and set it to "Enable".
If you bake and run it, you can see that the rendering result changes depending on whether the keyword is activated or not, as shown above.