This is a guide on how to make sure your Unity shader can be used in VaM on skin and clothing items, without running into the T-Pose problem.
Please note this is not a guide on how to write Unity shader in general. For that there are already a good amount of resource online to learn from.
For context, with this plugin below, you can now load Unity shader from an assetbundle and replace the VaM default shader of skin/clothing items.
hub.virtamate.com
So you have a working shader implemented in Unity and it looks all good. But when it is replaced onto any person skin or clothing in VaM, you see this:
This T-Pose issue is due to the fact that VaM does it's own skinning which is the process of calculating positions of vertices. When the custom shader doesn't have access to the skinning result, it will use the default vertex positions, which is the T-Pose.
To resolve this, the shader code will need to use the 3 internal VaM buffers that contains the skinning result, instead of standard vertex input. Below is how to do it:
1. First, declare the 3 buffers in subshader:
2. Then, declare struct of vert input as below:
3. then in vert shader, instead of using pos/normal/tang data from vert input, fetch the data from corresponding buffer indexed by vertex id, and pass them to output
4. Then in frag shader, do normal shader stuff.
That is it. Once shader is implemented this way, when replaced on VaM person skin/clothing, you shouldn't see the T-Pose anymore.
I have attached a template shader file that have this setup. In the template it has some additional stuff I think should be helpful:
1. _UseStructuredBufferInput toggle property:
This show up as a toggle in Unity,
This is useful because when your shader is fetching from the buffers, in Unity your mesh won't show up since the buffers only exist in VaM. By unchecking this toggle you can revert back to use default vertex input so that you can do develop your shader in Unity as usual.
2. Native VaM properties
These property names matches what VaM uses internally. When the custom shader also implements these properties, VaM built-in skin/clothing material tab Ui can directly control these properties of the custom shader. This saves you the trouble of adding all these controls to the loader plugin and clog up the UI.
Some additional Q/A:
1. Can I use surface shader with this?
Sadly no, as far as I know structured buffers are not supported in surf shader. You'll need to use vertex/frag shader implementation.
2. How do I get the tileX/Y and offsetX/Y sliders in material tabs to work?
When you sample the texture, do something like this:
This will allow texture to be tile/offseted.
3. How to prepare the properties json file to use with the loader plugin?
The shade author should prepare a json file containing shader properties info. This file is for the plugin to read and spawn corresponding control UIs to configure shader properties at runtime. We have to do this due to Unity version limitation that shader properties are not accessible at runtime.
The json file should look like below:
Currently it supports float, range, vector, texture, color. I plan to add support for toggle boolean later
You should name the json file <Shader_Name>.json. put it at the same directory as the assetbundle. When the shader with the same name is loaded, the json file will be picked up and you should see property controls on the right.
You don't need to put all properties in the json, just the ones you want control over at run time.
If the assetbundle is in a var and/or you need to put the json file elsewhere, you can manually load the json file using the "Load Shader Properties" button
Please post any issue you run into in the discussion thread here, or you can ping me on discord.
Thanks
Please note this is not a guide on how to write Unity shader in general. For that there are already a good amount of resource online to learn from.
For context, with this plugin below, you can now load Unity shader from an assetbundle and replace the VaM default shader of skin/clothing items.

Custom Shader Loader - Plugins + Scripts -
This is a plugin that loads custom unity shader from assetbundle. It works on person atoms, clothing items (via @Stopper 's clothing plugin manager) and CUAs. How to use: Where do I get the shader assetbundles? I have one posted here...

So you have a working shader implemented in Unity and it looks all good. But when it is replaced onto any person skin or clothing in VaM, you see this:
This T-Pose issue is due to the fact that VaM does it's own skinning which is the process of calculating positions of vertices. When the custom shader doesn't have access to the skinning result, it will use the default vertex positions, which is the T-Pose.
To resolve this, the shader code will need to use the 3 internal VaM buffers that contains the skinning result, instead of standard vertex input. Below is how to do it:
1. First, declare the 3 buffers in subshader:
Code:
Shader "SHADERNAME" {
Properties {
// ... your properties
}
SubShader {
Pass {
// ....
StructuredBuffer<float3> verts;
StructuredBuffer<float3> normals;
StructuredBuffer<float4> tangents;
// ....
ENDCG
}
}
}
2. Then, declare struct of vert input as below:
Code:
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 uv : TEXCOORD0;
uint id : SV_VertexID;
uint inst : SV_InstanceID;
};
3. then in vert shader, instead of using pos/normal/tang data from vert input, fetch the data from corresponding buffer indexed by vertex id, and pass them to output
Code:
struct VertexOutput {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldTangent : TEXCOORD1;
float3 worldPos : TEXCOORD2;
float2 uv : TEXCOORD3;
};
VertexOutput vert(VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(float4(verts[v.id], 1.0f));
o.worldPos = mul(unity_ObjectToWorld, float4(verts[v.id], 1.0f)).xyz;
float3 rawNormal = normals[v.id];
float3 rawTangent = tangents[v.id];
o.worldNormal = UnityObjectToWorldNormal(rawNormal);
o.worldTangent = UnityObjectToWorldDir(rawTangent);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
4. Then in frag shader, do normal shader stuff.
Code:
float4 frag(VertexOutput i) : SV_Target {
// ... your shader logic
return color;
}
That is it. Once shader is implemented this way, when replaced on VaM person skin/clothing, you shouldn't see the T-Pose anymore.
I have attached a template shader file that have this setup. In the template it has some additional stuff I think should be helpful:
1. _UseStructuredBufferInput toggle property:
This show up as a toggle in Unity,
This is useful because when your shader is fetching from the buffers, in Unity your mesh won't show up since the buffers only exist in VaM. By unchecking this toggle you can revert back to use default vertex input so that you can do develop your shader in Unity as usual.
2. Native VaM properties
These property names matches what VaM uses internally. When the custom shader also implements these properties, VaM built-in skin/clothing material tab Ui can directly control these properties of the custom shader. This saves you the trouble of adding all these controls to the loader plugin and clog up the UI.
Code:
_MainTex ("Main Texture", 2D) = "white" {}
_Color ("Color", Color) = (1,1,1,1)
_SpecColor ("Specular Color", Color) = (1,1,1,1)
_AlphaAdjust ("Alpha Adjust", Range(-1,1)) = 0
_SpecTex ("Specular Texture", 2D) = "white" {}
_SpecInt ("Specular Intensity", Range(0,10)) = 1
_SpecOffset ("Specular Texture Offset", Float) = 0
_GlossTex ("Gloss Texture", 2D) = "white" {}
_Shininess ("Gloss", Range(2,8)) = 6
_GlossOffset ("Gloss Texture Offset", Float) = 0
_AlphaTex ("Alpha Texture", 2D) = "white" {}
[NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {}
_DiffuseBumpiness ("Normal Strength", Range(0,2)) = 1
_SpecularBumpiness ("Highlight Strength", Range(0,2)) = 1
_DecalTex ("Decal Texture", 2D) = "black" {}
_Fresnel ("Fresnel", Range(0,10)) = 0.8
_IBLFilter ("IBL Filter", Range(0,1)) = 1
Some additional Q/A:
1. Can I use surface shader with this?
Sadly no, as far as I know structured buffers are not supported in surf shader. You'll need to use vertex/frag shader implementation.
2. How do I get the tileX/Y and offsetX/Y sliders in material tabs to work?
When you sample the texture, do something like this:
Code:
float4 color = tex2D(_MainTex, TRANSFORM_TEX(i.uv, _MainTex)) * _Color;
This will allow texture to be tile/offseted.
3. How to prepare the properties json file to use with the loader plugin?
The shade author should prepare a json file containing shader properties info. This file is for the plugin to read and spawn corresponding control UIs to configure shader properties at runtime. We have to do this due to Unity version limitation that shader properties are not accessible at runtime.
The json file should look like below:
Code:
[
{
"name": "property_name",
"displayName": "Display Name",
"type": "color"
},
{
"name": "float_property",
"displayName": "Float Property",
"type": "float",
"defaultValue": 0.5,
"minValue": 0.0,
"maxValue": 1.0
},
{
"name": "range_property",
"displayName": "Range Property",
"type": "range",
"defaultValue": 0.5,
"minValue": 0.0,
"maxValue": 1.0
},
{
"name": "texture_property",
"displayName": "Texture Property",
"type": "texture"
},
{
"name": "vector_property",
"displayName": "Vector Property",
"type": "vector",
"defaultVector": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"minVector": {
"x": -1.0,
"y": -1.0,
"z": -1.0
},
"maxVector": {
"x": 1.0,
"y": 1.0,
"z": 1.0
}
}
]
Currently it supports float, range, vector, texture, color. I plan to add support for toggle boolean later
You should name the json file <Shader_Name>.json. put it at the same directory as the assetbundle. When the shader with the same name is loaded, the json file will be picked up and you should see property controls on the right.
You don't need to put all properties in the json, just the ones you want control over at run time.
If the assetbundle is in a var and/or you need to put the json file elsewhere, you can manually load the json file using the "Load Shader Properties" button
Please post any issue you run into in the discussion thread here, or you can ping me on discord.
Thanks