0
\$\begingroup\$

How do you have the clothing mesh hide the body mesh underneath, to prevent odd patches of body from sticking out in tighter modular clothing?

Is there some sort of shader trick that can have the clothing always render "in front of" the mesh?

\$\endgroup\$
2
\$\begingroup\$

The game I'm working on now uses a similar approach as described by Benjamin Danger Johnson, but we bake the information into the mesh to save a texture. 😉

Ahead of time, we charted out the different parts of the body we might want to hide (eg. forearms to the wrist for long-sleeve shirts, to the elbow for baseball shirts, upper arms just past the shoulder for T-shirts, no arm hidden for sleeveless tops...)

Each different region gets an integer code. We save that code in a vertex attribute on the mesh (like a vertex colour or texture coordinate channel).

Based on the combination of clothing worn at a time, we compute which of these numbered regions should still be visible (each clothing item stores which region IDs it covers), and we express that as a bitmask — where the nth bit is 1 if region n should show skin, or 0 otherwise. We send this bitmask to the GPU as just one uniform variable.

In the vertex shader, we compare the region ID for the current vertex against the bitmask. If it lines up with a 1 somewhere, the shader pipeline keeps running as normal. If it doesn't, we abort the vertex (if you output NAN for the vertex position, the GPU won't draw triangles using that vertex).

That lets us clip out the invisible geometry as early as possible in the pipeline, before rasterization or the expensive skin shader, and without extra overdraw, blending costs, or texture memory/sampling.

\$\endgroup\$
1
  • \$\begingroup\$ this is a bit low level for me - can you provide some snippets? \$\endgroup\$ – ina Jan 28 at 6:54
2
\$\begingroup\$

This is a pretty common problem many people run into and the solution depends largely on what you are trying to do in your game.

If your character is unable to change clothes in game, or has a very limited selection of clothing, what many people will do is flatten the character mesh and clothing meshes to a single character model. After doing so you can delete any geometry that would be inside the clothing that might risk clipping through. This should also give some small performance increases as there is less geometry to render overall. Since most games only offer 1-3 clothing options per character exporting one character model per clothing option usually isn't too bad.

If your character needs to be able to change clothes constantly (such as equipping armor in an MMO), what I would do is create a shader for your main character that, in addition to your usual textures, takes in a special clothing mask texture that controls the alpha channel of your character based on what they are wearing. For example if you want to equip a T-shirt to your character you masking texture would show all of the bodies UVs except the torso and shoulders as white and the rest as black. If you want to equip a three piece suit, show everything as black except the hands, neck and face. You may need to allocate more than one texture slot of clothes can be mixed and matched, but this should basically set everything that should be covered up as visible so you don't have to worry about clipping except in areas where the body should be visible (neck line and wrists).

Shadergraph Sample ShaderSome really cool stuff ehre

HLSL Surface Sample Shader

Shader "Ina's Cool Shaders/Lit/Character Shader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _ClothingMask("Clothing Mask", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags
        { 
            "IgnoreProjector" = "True"
            "RenderType" = "Transparent"
            "Queue" = "Transparent"
        }
        LOD 200
        Blend SrcAlpha OneMinusSrcAlpha

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows alpha

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _ClothingMask;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
        // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 mainColor = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = mainColor.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;

            // Masking
            fixed4 maskingData = tex2D(_ClothingMask, IN.uv_MainTex);
            o.Alpha = maskingData.r; // any channel is fine, if your mask is black and white rgb should all be the same values
        }

        ENDCG
    }
    FallBack "Diffuse"
}

HLSL Unlit Shader Sample (base code taken from https://answers.unity.com/questions/1098744/unlit-texture-transparent-shader.html)

 Shader "Ina's Cool Shaders/Unlit/Character Shader"
 {
     Properties 
     {
         _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
         _ClothingMask("Clothing Mask", 2D) = "white" {}
     }

     SubShader
     {
         Tags
         {
             "Queue"="Transparent"
             "IgnoreProjector"="True" 
             "RenderType"="Transparent"
         }

         LOD 100
 
         ZWrite Off
         Blend SrcAlpha OneMinusSrcAlpha 
 
         Pass     
         {  
             CGPROGRAM

             #pragma vertex vert
             #pragma fragment frag
             #pragma multi_compile_fog
         
             #include "UnityCG.cginc"

             struct appdata_t
             {
                 float4 vertex : POSITION;
                 float2 texcoord : TEXCOORD0;
             };

             struct v2f
             {
                 float4 vertex : SV_POSITION;
                 half2 texcoord : TEXCOORD0;
                 UNITY_FOG_COORDS(1)
             };

             sampler2D _MainTex;
             sampler2D _ClothingMask;
             float4 _MainTex_ST;
         
             v2f vert (appdata_t v)
             {
                 v2f o;
                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                 o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
                 UNITY_TRANSFER_FOG(o,o.vertex);
                 return o;
             }
         
             fixed4 frag (v2f i) : SV_Target
             {
                 fixed4 col = tex2D(_MainTex, i.texcoord);
                 UNITY_APPLY_FOG(i.fogCoord, col);

                 fixed4 clothingMask = tex2D(_ClothingMask, i.texcoord);
                 col.a = clothingMask.r;
                 return col;
             }

             ENDCG
         }
     }
 }
\$\endgroup\$
6
  • \$\begingroup\$ Can you provide a shader snippet? \$\endgroup\$ – ina Jan 27 at 7:48
  • \$\begingroup\$ Sure modifying the answer to show shadergraph and hlsl. Bear in mind they aren't properly tested so you might need to mess with stuff to get lighting right. \$\endgroup\$ – Benjamin Danger Johnson Jan 27 at 10:17
  • \$\begingroup\$ You might want to use alpha testing instead of blending to save the blending cost, if skin is only ever fully opaque or entirely absent. \$\endgroup\$ – DMGregory Jan 27 at 13:17
  • \$\begingroup\$ Curious why it's Transparent queue - also Unlit version? \$\endgroup\$ – ina Jan 28 at 6:53
  • \$\begingroup\$ I through it in the transparent queue so you could do blending if needed, honestly I don't write many shaders so this is just off the top of my head. I can quickly through together an unlit version if you need it but it's basically the same thing, you just need to sample the masking texture to see if the fragment at the current UV should be on or off. If you take @DMGregory's approach, you will want to check the bitmask against the fragments vertex group (I assume you could store this group ID in a second UV channel). \$\endgroup\$ – Benjamin Danger Johnson Jan 28 at 7:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.