Fuzz/Short Fuzz Rendering

refer:
Tencent Game Academy
is mainly from here, thanks for the explanation!

  • First of all, a lot of articles confuse hair with hair, which is not the same thing. Well, in order to distinguish, my two articles use long hair and short hair to distinguish

Multi-Layer Hair Model

1

  • Regarding this model, most articles will refer to this picture. The picture is very good, but a single picture in the original paper is put here. ;
  • Here I translated and rewritten the annotations of the original picture, hoping to help you understand this model:

Of course, you can’t understand it directly from a picture. Here I will explain this multi-layer hair model in layman’s terms.


2

First, real world hair looks like this:

  1. Cylindrical, tapering towards the end, until pointy
  2. Translucent, the thinner the more translucent (not because there are gaps, but the hair itself is translucent)
  3. Although light is transmitted, it is not completely transparent, so the root of the hair receives less light

That is as follows:
insert image description here

This is the case with hair in reality, but it is impossible for us to model one by one,
so a clever algorithm for simulating hair is proposed:
texture is very useful, we want to record all hair information in texture, but texture is only 2D, how to record 3D hair information?
It’s not a secret, the method is to use many layers of textures to stack up and record the hair in sections:
insert image description here

  • It's just that the hair is not solid, but a layered patch, which will be exposed when viewed from the side, but it is easy to solve, as long as there are enough layers, this can be covered up
    insert image description here

  • Record the alpha value of the air strike part without hair as 0, just like using the transparency tool to sculpt a little bit, so that you can sculpt the appearance of fur

  • As for how to sculpt hair, see the following code implementation

  • In addition, it was mentioned earlier that hair is transparent, so we can modify its transparency when rendering. The closer to the end, the more transparent it is.

  • I also said that the roots of the hair get less light, which we achieve with ambient occlusion



3

In this way, rendering hair, which sounds incredible, is transformed into rendering a stack of textures, which sounds much more practical, but it still consumes a lot of computing power

Although the overhead is high, this is currently the best way to render hair (real-time), and others are more expensive. If you
are interested, you can take a look at the case of this UP . I personally think it is awesome.

Because of the high overhead, it is extremely important in terms of performance tuning

  • Daji’s character display interface in Glory of the King uses this model for its tail, and it can run smoothly on most mobile platforms, and the effect is very good, indicating that the optimization is in place
  • If each layer calls a pass to draw, it is too wasteful. You should use instantiation (GPU instance) to reduce calls. For instantiation, you can see learnopengl, which is very good.
  • By changing the shape of the fur, it is possible to approximate more layers with fewer layers:
  • Ambient light occlusion, the difference between adding or not, personally think the gap is quite big

                 


Code

Release the final effect of this code first

Micro details:

  • It can be seen that the hair is composed of pieces
    insert image description here

A little additional explanation:

  • I used a very stupid method, one Pass squeezes out one layer, so I called a lot of Pass, the performance overhead is very high, it should be instantiated, but I haven’t learned it yet, I learned it to make up for it
  • Due to a large number of repeated passes, the main body of the shader is written in cginc, just call
  • In addition, the above picture is the result of no lighting calculation (except AO). When I tried to use phong for lighting, the effect was very strange. It is speculated that it is the effect of multiple layers of transparency. After all, this article is about hair, and lighting will not be discussed anymore. In this part, I will try different lighting models in the future (mainly try anisotropic models, such as kajiya)




kitchen

I won’t write comments line by line. The main points are as follows:

  • v2f vert_fur(appdata v, float layer_offset)
    Pay attention to this, the vert and frag in cginc can pass in parameters, which is the key to fine-tuning the hair model in each layer (the parameters of each pass are different, too many direct properties are too complicated to import)
  • alpha = step(layer_offset, alpha);
    This is the key to sculpting hair, the step function is: step(a,x); x<a 返回0 x>=0 返回1; and the alpha in the step is read from the noise map
  • alpha *= (1-layer_offset);
    Transparency decay layer by layer
  • col.xyz *= pow(layer_offset, _AO );Perform ambient light occlusion, AO details can be seen in my这篇文章(还没写,新坑)
#ifndef FUR_INCLUDE
#define FUR_INCLUDE

#include "UnityCG.cginc"
#include "Lighting.cginc"

struct appdata
{
    
    
    float3 normal : NORMAL;
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
};

struct v2f
{
    
    
    float2 uv : TEXCOORD0;
    float2 uv_layer : TEXCOORD1;
    float4 vertex : SV_POSITION;
};

float _Length;
sampler2D _MainTex;
sampler2D _LayerMap;
float4 _MainTex_ST;
float4 _LayerMap_ST;
float _AO;

v2f vert_fur(appdata v, float layer_offset)
{
    
    
    v2f o;
    v.vertex.xyz += v.normal * _Length * layer_offset;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    o.uv_layer = TRANSFORM_TEX(v.uv, _LayerMap);

    return o;
}

fixed4 frag_fur(v2f i, float layer_offset) 
{
    
    
    float alpha = tex2D(_LayerMap, i.uv_layer).r;//读取layer纹理
    
    alpha = step(layer_offset, alpha); //雕刻毛发
    alpha *= 1-layer_offset; //透明度衰减计算
    fixed4 col = fixed4(tex2D(_MainTex, i.uv).rgb, alpha);//应用上述得到的透明度
    
    col.xyz *= pow(layer_offset, _AO ); //AO计算

    return col;
}

#endif




shader

Shader "Unlit/fur"
{
    
    
    Properties
    {
    
    
        _MainTex ("Texture", 2D) = "white" {
    
    }
        _LayerMap ("Layer map", 2D) = "white"{
    
    }
        _Length ("fur length", range(0,1)) = 0.5
        _AO ("AO", range(0,1)) = 0.5
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Transparent" "Queue" = "Transparent"}
        Blend SrcAlpha OneMinusSrcAlpha


        Cull off



        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert0
            #pragma fragment frag0
            #include "layers.cginc"

            v2f vert0(appdata v){
    
    return vert_fur(v,0);}
            fixed4 frag0(v2f i):SV_TARGET{
    
    return frag_fur(i,0);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert1
            #pragma fragment frag1
            #include "layers.cginc"

            v2f vert1(appdata v){
    
    return vert_fur(v,0.01);}
            fixed4 frag1(v2f i):SV_TARGET{
    
    return frag_fur(i,0.01);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert2
            #pragma fragment frag2
            #include "layers.cginc"

            v2f vert2(appdata v){
    
    return vert_fur(v,0.02);}
            fixed4 frag2(v2f i):SV_TARGET{
    
    return frag_fur(i,0.02);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert3
            #pragma fragment frag3
            #include "layers.cginc"

            v2f vert3(appdata v){
    
    return vert_fur(v,0.03);}
            fixed4 frag3(v2f i):SV_TARGET{
    
    return frag_fur(i,0.03);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert4
            #pragma fragment frag4
            #include "layers.cginc"

            v2f vert4(appdata v){
    
    return vert_fur(v,0.04);}
            fixed4 frag4(v2f i):SV_TARGET{
    
    return frag_fur(i,0.04);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert5
            #pragma fragment frag5
            #include "layers.cginc"

            v2f vert5(appdata v){
    
    return vert_fur(v,0.05);}
            fixed4 frag5(v2f i):SV_TARGET{
    
    return frag_fur(i,0.05);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert6
            #pragma fragment frag6
            #include "layers.cginc"

            v2f vert6(appdata v){
    
    return vert_fur(v,0.06);}
            fixed4 frag6(v2f i):SV_TARGET{
    
    return frag_fur(i,0.06);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert7
            #pragma fragment frag7
            #include "layers.cginc"

            v2f vert7(appdata v){
    
    return vert_fur(v,0.07);}
            fixed4 frag7(v2f i):SV_TARGET{
    
    return frag_fur(i,0.07);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert8
            #pragma fragment frag8
            #include "layers.cginc"

            v2f vert8(appdata v){
    
    return vert_fur(v,0.08);}
            fixed4 frag8(v2f i):SV_TARGET{
    
    return frag_fur(i,0.08);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert9
            #pragma fragment frag9
            #include "layers.cginc"

            v2f vert9(appdata v){
    
    return vert_fur(v,0.09);}
            fixed4 frag9(v2f i):SV_TARGET{
    
    return frag_fur(i,0.09);}

            ENDCG
        }
       Pass{
    
    
            CGPROGRAM
            #pragma vertex vert10
            #pragma fragment frag10
            #include "layers.cginc"

            v2f vert10(appdata v){
    
    return vert_fur(v,0.10);}
            fixed4 frag10(v2f i):SV_TARGET{
    
    return frag_fur(i,0.1);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert11
            #pragma fragment frag11
            #include "layers.cginc"

            v2f vert11(appdata v){
    
    return vert_fur(v,0.11);}
            fixed4 frag11(v2f i):SV_TARGET{
    
    return frag_fur(i,0.11);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert12
            #pragma fragment frag12
            #include "layers.cginc"

            v2f vert12(appdata v){
    
    return vert_fur(v,0.12);}
            fixed4 frag12(v2f i):SV_TARGET{
    
    return frag_fur(i,0.12);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert13
            #pragma fragment frag13
            #include "layers.cginc"

            v2f vert13(appdata v){
    
    return vert_fur(v,0.13);}
            fixed4 frag13(v2f i):SV_TARGET{
    
    return frag_fur(i,0.13);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert14
            #pragma fragment frag14
            #include "layers.cginc"

            v2f vert14(appdata v){
    
    return vert_fur(v,0.14);}
            fixed4 frag14(v2f i):SV_TARGET{
    
    return frag_fur(i,0.14);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert15
            #pragma fragment frag15
            #include "layers.cginc"

            v2f vert15(appdata v){
    
    return vert_fur(v,0.15);}
            fixed4 frag15(v2f i):SV_TARGET{
    
    return frag_fur(i,0.15);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert16
            #pragma fragment frag16
            #include "layers.cginc"

            v2f vert16(appdata v){
    
    return vert_fur(v,0.16);}
            fixed4 frag16(v2f i):SV_TARGET{
    
    return frag_fur(i,0.16);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert17
            #pragma fragment frag17
            #include "layers.cginc"

            v2f vert17(appdata v){
    
    return vert_fur(v,0.17);}
            fixed4 frag17(v2f i):SV_TARGET{
    
    return frag_fur(i,0.17);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert18
            #pragma fragment frag18
            #include "layers.cginc"

            v2f vert18(appdata v){
    
    return vert_fur(v,0.18);}
            fixed4 frag18(v2f i):SV_TARGET{
    
    return frag_fur(i,0.18);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert19
            #pragma fragment frag19
            #include "layers.cginc"

            v2f vert19(appdata v){
    
    return vert_fur(v,0.19);}
            fixed4 frag19(v2f i):SV_TARGET{
    
    return frag_fur(i,0.19);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert20
            #pragma fragment frag20
            #include "layers.cginc"

            v2f vert20(appdata v){
    
    return vert_fur(v,0.20);}
            fixed4 frag20(v2f i):SV_TARGET{
    
    return frag_fur(i,0.20);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert21
            #pragma fragment frag21
            #include "layers.cginc"

            v2f vert21(appdata v){
    
    return vert_fur(v,0.21);}
            fixed4 frag21(v2f i):SV_TARGET{
    
    return frag_fur(i,0.21);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert22
            #pragma fragment frag22
            #include "layers.cginc"

            v2f vert22(appdata v){
    
    return vert_fur(v,0.22);}
            fixed4 frag22(v2f i):SV_TARGET{
    
    return frag_fur(i,0.22);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert23
            #pragma fragment frag23
            #include "layers.cginc"

            v2f vert23(appdata v){
    
    return vert_fur(v,0.23);}
            fixed4 frag23(v2f i):SV_TARGET{
    
    return frag_fur(i,0.23);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert24
            #pragma fragment frag24
            #include "layers.cginc"

            v2f vert24(appdata v){
    
    return vert_fur(v,0.24);}
            fixed4 frag24(v2f i):SV_TARGET{
    
    return frag_fur(i,0.24);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert25
            #pragma fragment frag25
            #include "layers.cginc"

            v2f vert25(appdata v){
    
    return vert_fur(v,0.25);}
            fixed4 frag25(v2f i):SV_TARGET{
    
    return frag_fur(i,0.25);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert26
            #pragma fragment frag26
            #include "layers.cginc"

            v2f vert26(appdata v){
    
    return vert_fur(v,0.26);}
            fixed4 frag26(v2f i):SV_TARGET{
    
    return frag_fur(i,0.26);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert27
            #pragma fragment frag27
            #include "layers.cginc"

            v2f vert27(appdata v){
    
    return vert_fur(v,0.27);}
            fixed4 frag27(v2f i):SV_TARGET{
    
    return frag_fur(i,0.27);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert28
            #pragma fragment frag28
            #include "layers.cginc"

            v2f vert28(appdata v){
    
    return vert_fur(v,0.28);}
            fixed4 frag28(v2f i):SV_TARGET{
    
    return frag_fur(i,0.28);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert29
            #pragma fragment frag29
            #include "layers.cginc"

            v2f vert29(appdata v){
    
    return vert_fur(v,0.29);}
            fixed4 frag29(v2f i):SV_TARGET{
    
    return frag_fur(i,0.29);}

            ENDCG
        }
        Pass{
    
    
            CGPROGRAM
            #pragma vertex vert30
            #pragma fragment frag30
            #include "layers.cginc"

            v2f vert30(appdata v){
    
    return vert_fur(v,0.3);}
            fixed4 frag30(v2f i):SV_TARGET{
    
    return frag_fur(i,0.3);}

            ENDCG
        }
    }

}


Guess you like

Origin blog.csdn.net/dogman_/article/details/129886440