1
\$\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\$

3 Answers 3

4
\$\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\$
8
  • \$\begingroup\$ Can you provide a shader snippet? \$\endgroup\$
    – ina
    Jan 27, 2021 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\$ Jan 27, 2021 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, 2021 at 13:17
  • \$\begingroup\$ Curious why it's Transparent queue - also Unlit version? \$\endgroup\$
    – ina
    Jan 28, 2021 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\$ Jan 28, 2021 at 7:36
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\$
2
  • \$\begingroup\$ this is a bit low level for me - can you provide some snippets? \$\endgroup\$
    – ina
    Jan 28, 2021 at 6:54
  • \$\begingroup\$ Not within my NDA, no. You can ask a clarifying question if there's something specific you need explained, but I cannot give you the code from that game directly. \$\endgroup\$
    – DMGregory
    Jun 29, 2023 at 11:44
0
\$\begingroup\$

"you can avoid the problem althogether by slicing the character into pieces instead of using shaders"

thi's how I arrange the textures: here's naked

enter image description here

here's clothed

enter image description here

This way you only need to apply skin color to one single texture or either to only one part of the texture without ever coloring by mistake the clothing if you are using masks.

and most importantly, if your game as transmog or the ability to color your clothing, you can do so without coloring your skin by mistake.

The end result looks like that, it also allows you to combine any piece of clothing with any other clothing or body part whilist having the least ammount of textures, the least ammount of vertices and if you are using some node logic 1 single material call for a character with multiple separated meshes... I don't personally do it, but I've seen a dude doing it unity, basically every single armor or clothing piece in his game was sharing one single material which is great for performance. enter image description here

and if you are worried about having to animate a thousand pieces of bodies and clothing so they all work well together...

just use mixamo...it automatically weight paints your characters, I've never had a single problem with clipping while using mixamo.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .