refer:
腾讯游戏学堂
主要来自这里,感谢讲解!
- 首先说一点,很多文章都把毛发和头发弄混,根本就不是一回事好吧,为了区分,我的两篇文章分别用了长毛短毛来区分
多层毛发模型
1
- 关于这个模型,大部分文章都会引用这张图,图很好,但是原论文中单独的一张图放到这里,完全没有语境,让人看着非常费解,并且和Unity的实现有一点区别;
- 这里我把原图的注释翻译并且重写了,希望能帮助你理解这个模型:
![](https://i-blog.csdnimg.cn/blog_migrate/62d8ebe584e0a40a9da0733fb028977d.jpeg#pic_center)
当然直接一张图是看不明白的,这里我来用通俗的话来讲明白这个多层毛发模型
2
首先,真实世界的毛发是这样的:
- 圆柱形,越靠近末端越细,直到尖尖
- 透光,越细越透光(不是因为其有间隙才透光,而是毛发本身就是半透明的)
- 虽然透光,但是不是完全透光,因此毛发的根部收到的光照更少
也就是如下这样:
现实中的毛发就是这样,但是我们不可能去真的一根根建模
于是讨巧的模拟毛发的算法便被提出:
纹理很好用,我们想把所有的毛发信息记录在纹理中,但是纹理只是2维的,如何记录三维的毛发信息呢?
不卖关子,做法就是用很多层纹理叠起来,把毛发分段记录下来:
-
只不过这毛发不是实体的,而是分层的面片,侧面看会露馅,不过好解决,只要层数足够多,就可以掩盖这一点
-
将没有毛发的空袭部分alpha值记为0,就像是用透明度这个工具一点点雕刻,这样便可以雕刻出毛皮的样子
-
至于具体怎么去雕刻毛发,去看下面的代码实现
-
另外前面还有说到毛发是会透光的,那么我们在渲染的时候就可以修改其透明度,越靠近末梢越透明
-
我还说到毛发根部受到的光更少,这个现象我们用环境光遮蔽来实现
3
这样一来,便将渲染毛发这种听起来不可思议的事情,转化成了渲染一堆叠起来的纹理了,这听起来就实际多了,不过这仍然会消耗大量的算力
尽管开销很大,但这也是当前渲染毛发最好的方法了(实时),别的开销更大
感兴趣可以看一下这位UP的案例,个人觉得超棒
因为开销很大,所有在性能调优方面就极为重要
- 王者荣耀中妲己的角色展示界面,其尾巴就是用的这个模型,而其在绝大部分移动平台上都可以流程运行,效果还很棒,说明优化很到位
- 如果每一层都调用一个pass去绘制的话太浪费了,应该使用实例化(GPU instance)来减少调用,实例化可以看learnopengl,讲的很好
- 通过改变毛发的形状,可以在较少的层级下接近更多层级的效果:
![](https://i-blog.csdnimg.cn/blog_migrate/f2809f24c502d49f0a41123ff9ef3bff.png#pic_center)
-
环境光遮蔽,添加与否的差别,个人觉得差距还挺大的
代码实现
先放出本代码的最终效果
微观细节:
- 可以看到是一片一片的组成的毛发
一点补充说明:
- 我用了很笨的方法,一个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
}
}
}