绒毛/短毛渲染

refer:
腾讯游戏学堂
主要来自这里,感谢讲解!

  • 首先说一点,很多文章都把毛发和头发弄混,根本就不是一回事好吧,为了区分,我的两篇文章分别用了长毛短毛来区分

多层毛发模型

1

  • 关于这个模型,大部分文章都会引用这张图,图很好,但是原论文中单独的一张图放到这里,完全没有语境,让人看着非常费解,并且和Unity的实现有一点区别;
  • 这里我把原图的注释翻译并且重写了,希望能帮助你理解这个模型:

当然直接一张图是看不明白的,这里我来用通俗的话来讲明白这个多层毛发模型


2

首先,真实世界的毛发是这样的:

  1. 圆柱形,越靠近末端越细,直到尖尖
  2. 透光,越细越透光(不是因为其有间隙才透光,而是毛发本身就是半透明的)
  3. 虽然透光,但是不是完全透光,因此毛发的根部收到的光照更少

也就是如下这样:
在这里插入图片描述

现实中的毛发就是这样,但是我们不可能去真的一根根建模
于是讨巧的模拟毛发的算法便被提出:
纹理很好用,我们想把所有的毛发信息记录在纹理中,但是纹理只是2维的,如何记录三维的毛发信息呢?
不卖关子,做法就是用很多层纹理叠起来,把毛发分段记录下来:
在这里插入图片描述

  • 只不过这毛发不是实体的,而是分层的面片,侧面看会露馅,不过好解决,只要层数足够多,就可以掩盖这一点
    在这里插入图片描述

  • 将没有毛发的空袭部分alpha值记为0,就像是用透明度这个工具一点点雕刻,这样便可以雕刻出毛皮的样子

  • 至于具体怎么去雕刻毛发,去看下面的代码实现

  • 另外前面还有说到毛发是会透光的,那么我们在渲染的时候就可以修改其透明度,越靠近末梢越透明

  • 我还说到毛发根部受到的光更少,这个现象我们用环境光遮蔽来实现



3

这样一来,便将渲染毛发这种听起来不可思议的事情,转化成了渲染一堆叠起来的纹理了,这听起来就实际多了,不过这仍然会消耗大量的算力

尽管开销很大,但这也是当前渲染毛发最好的方法了(实时),别的开销更大
感兴趣可以看一下这位UP的案例,个人觉得超棒

因为开销很大,所有在性能调优方面就极为重要

  • 王者荣耀中妲己的角色展示界面,其尾巴就是用的这个模型,而其在绝大部分移动平台上都可以流程运行,效果还很棒,说明优化很到位
  • 如果每一层都调用一个pass去绘制的话太浪费了,应该使用实例化(GPU instance)来减少调用,实例化可以看learnopengl,讲的很好
  • 通过改变毛发的形状,可以在较少的层级下接近更多层级的效果:
  • 环境光遮蔽,添加与否的差别,个人觉得差距还挺大的

                 


代码实现

先放出本代码的最终效果

微观细节:

  • 可以看到是一片一片的组成的毛发
    在这里插入图片描述

一点补充说明:

  • 我用了很笨的方法,一个Pass挤出一层,所以我调用了非常多的Pass,性能开销很大,应该用实例化,但是还没学到,学到了来补
  • 由于大量的重复Pass,因此将着色器的主体写在了cginc中,调用即可
  • 另外上图是没有进行任何光照运算的结果(除了AO),本人在尝试用phong进行光照时,效果非常奇怪,推测是多层透明的影响,毕竟本文是讲毛发的,光照不再讨论了,这部分我以后再尝试不同的光照模型(主要试试各向异性的模型,如kajiya)




cginc

就不逐行写注释了,主要的要点如下:

  • v2f vert_fur(appdata v, float layer_offset)
    注意这个,cginc中的vert和frag可以传入参数,这也就是毛发模型能够在每一层细微调整的关键(每个pass的参数不一样,直接properties导入太多太复杂)
  • alpha = step(layer_offset, alpha);
    这个是雕刻毛发的关键,step函数是:step(a,x); x<a 返回0 x>=0 返回1;而step中的alpha是从噪声图中读取的
  • alpha *= (1-layer_offset);
    逐层进行透明度衰减
  • col.xyz *= pow(layer_offset, _AO ); 进行环境光遮蔽,AO具体可以看我的这篇文章(还没写,新坑)
#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
        }
    }

}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值