先放出結果圖片。。。由於網上下的模型是拼的,所以眼皮,臉頰,嘴唇看起來像存在裂痕,解決方式是加入曲面細分和置換貼圖 進行一定隆起,但是博主試了一下fragment shader的曲面細分,雖然細分成功了但是着色效果變的很奇怪,這里就不用曲面細分了,大家如果有在fragment shader上用曲面細分的好辦法,可以的話請告訴我
參數設置1

參數設置2

細致到毛孔的高光

次表面散射的耳朵

人皮渲染是十多年的課題了,人們想盡一切辦法想讓其變得真實可信,大型3A級次時代游戲近來做的又來越真實了如《羅馬之子》,他們的皮膚自稱已經超過了NVIDIA的例子

這是在2005年SIGGRAPH的多層皮膚渲染,他們的參數都是經過精密的醫學上的測量的,而且渲染花費了5分鍾的時間。。。
研究這個找了許多資料,在結合之前的知識弄出了一個看起來還算入眼的人皮
本例做到了以下幾點
1. 次表面散射
2. 基於物理的渲染
包括specular和brdf等等,brdf我用了一張貼圖調整曲率來代替,specular在之前這篇文章有詳細講解 鏈接在此
3. 法線模糊
等等之類。。。
為什么皮膚渲染這么難?
1. 大多數的漫反射光來自次表面散射
2. 皮膚顏色主要來自上表皮
3. 粉或紅色主要為真皮中的血液
此圖為人皮的組成模擬,人有好多層表皮,這就說明在真實情況下要進行數次折射與反射,這就更難達到真實

光的折射與反射

上圖直觀的表明了光線“被怎么樣了”
光線接觸到皮膚時,有大約96%被皮膚各層散射了,只有大約4%被反射
再說specular,人的皮膚會出油,所以就會有反射,但是人的皮膚不能像鏡子那樣反射,因為人的皮膚是粗糙的,在這種情況下用基於物理的(physically based)方法就最好不過了,沒了解過physically based的看官們可以先了解下這篇文章和上面的一樣

使用了之前試出效果最好的的方法,也就是使命召喚2中用到的的方法,同時試了下beckmann的方法,但是效果並不好,phong等方法也沒有試
這里,我們的實現方法是這樣的:
<span style="font-size:14px;"> /* *this part is compute Physically-Based Rendering *the method is in the ppt about "ops2" */ float _SP = pow(8192, _GL); float d = (_SP + 2) / (8 * PIE) * pow(dot(n2, H), _SP); float f = _SC + (1 - _SC)*pow(2, -10 * dot(H, lightDir)); float k = min(1, _GL + 0.545); float v = 1 / (k* dot(viewDir, H)*dot(viewDir, H) + (1 - k)); float all = d*f*v; float3 refDir = reflect(-viewDir, n2); float3 ref = texCUBElod(_Cubemap, float4(refDir, _nMips - _GL*_nMips)).rgb;</span>
然后發現盡管gloss調到最大也沒有達到我們預期的那種效果,
又進行了 “智能補光”
也就是常規的求高光的方式,我們在此加入了高光貼圖,不讓不該高光的地方(如眉毛)產生高光
float specBase = max(0, dot(n2, H)); float spec = pow(specBase, 10) *(_GL + 0.2); spec = lerp(0, 1.2, spec); float3 spec3 = spec * (tex2D(_SpecularTex, i.uv_MainTex) - 0.1); spec3 *= Luminance(diff); spec3 = saturate(spec3); spec3 *= _SpecularPower;

光經過哪,就帶一部分那里的顏色,可以發現光從入射到出射,位置和方向都變了
光走的路徑數量是無窮大,光反射回來的都為漫反射,油脂表面的透明度也都是不一樣的,
這就產生了次表面散射

NVIDIA在GDC2007年的演講中提到把圖像blur個六遍達到柔和的次表面散射效果
每次blur都是在不同的顏色通道以不同的范圍和程度進行blur

由於我們的貼圖是這樣的“高配”

用在本例上會丟失少許本來貼圖上的細節,但是確實有一定的次表面散射效果,各位看官自行取舍,而且千萬不要只做高斯模糊,這樣的話細節會丟失更多,而且沒有什么次表面散射的感覺
為了節省花銷,省去了ppt中的rendering時blur,直接在ps上做了6張高斯模糊的貼圖放入material,並線性混合

float3 c = tex2D(_MainTex, i.uv_MainTex) * 128; c += tex2D(_BlurTex1, i.uv_MainTex) * 64; c += tex2D(_BlurTex2, i.uv_MainTex) * 32; c += tex2D(_BlurTex3, i.uv_MainTex) * 16; c += tex2D(_BlurTex4, i.uv_MainTex) * 8; c += tex2D(_BlurTex5, i.uv_MainTex) * 4; c += tex2D(_BlurTex6, i.uv_MainTex) * 2; c /= 256;
我們同時也起到重要作用的是邊緣光rim和brdf,
使用了BRDF最明顯的好處是,Brdf貼圖間接控制了明暗交界線的顏色,可通過曲率控制,模擬了光與陰影交界處光對皮膚的反射與折射,如果全黑的話說明光只是普通的漫反射。
而且使人皮有了次表面散射的質感

/* *this part is to add the sss *used front rim,back rim and BRDF */ float3 rim = (1 - dot(viewDir, n2))*_RimPower * _RimColor *tex2D(_RimTex, i.uv_MainTex); float3 frontrim = (dot(viewDir, n2))*_FrontRimPower * _FrontRimColor *tex2D(_FrontRimTex, i.uv_MainTex); float3 sss = (1 - dot(viewDir, n2)) / 50 * _SSSPower; sss = lerp(tex2D(_SSSFrontTex, i.uv_MainTex), tex2D(_SSSBackTex, i.uv_MainTex), sss * 20)*sss; fixed atten = LIGHT_ATTENUATION(i); float curvature = length(fwidth(mul(_Object2World, float4(normalize(i.normal), 0)))) / length(fwidth(i.worldpos)) * _CurveScale; float3 brdf = tex2D(_BRDFTex, float2((dot(normalize(i.normal), lightDir) * 0.5 + 0.5)* atten, curvature)).rgb;
對於rim的話添加了前向rim和后向rim其實本質上還是rim,后向rim使用了白色的圖片產生一種玉質的感覺(好吧其實更像羊羹),前向rim使用了紅色的圖片,相當於添加了光線在血液層的散射,讓人的臉蛋有了真實的血色
這是次表面散射的結果:

光源在嘴里

像不像把手指或者耳朵放在手電筒前面的那種效果?那就是次表面散射
需要一張Intense strips貼圖來混合原有顏色,方法就是在點光源情況下,求出當前點與點光源的距離,距離越近就越亮
關於法線,
用了一種新的混合方式,這樣能保有更多法線細節,
這里簡單講解一下法線混合,
float3 n1 = tex2D(texBase, uv).xyz*2 - 1; float3 n2 = tex2D(texDetail, uv).xyz*2 - 1; float3 r = normalize(n1 + n2); return r*0.5 + 0.5;
大家可能用過這種方式來混合兩個法線貼圖,這種線性的方式折中了兩個貼圖,得到的細節權重是平均的,效果並不好,得到的是這樣的結果

改進了一下,變成了覆蓋混合
float3 n1 = tex2D(texBase, uv).xyz; float3 n2 = tex2D(texDetail, uv).xyz; float3 r = n1 < 0.5 ? 2*n1*n2 : 1 - 2*(1 - n1)*(1 - n2); r = normalize(r*2 - 1); return r*0.5 + 0.5;
就是法線1的法線比較深的地方,就多一些權重,比較淺的地方就被法線2適當覆蓋,但是這樣效果還是不夠真實

在GDC2012的Mastering DX11 with unity中講到了一種官方的辦法如下:
float3x3 nBasis = float3x3(
float3(n1.z, n1.y, -n1.x), //繞着y軸+90度旋轉
float3(n1.x, n1.z, -n1.y),// 繞着x軸-90度旋轉
float3 (n1.x, n1.y, n1.z ));
n = normalize (n2.x*nBasis[0] + n2.y*nBasis[1] + n2.z*nBasis[2]);
得到的結果是這樣的,是不是好了許多?

雙方的細節程度都有很多提升,
他們用了一個basis來變換第二法線。具體可以看 這篇文章—鏈接
用 AutoLight.cginc里定義的一個函數LIGHT_ATTENUATION求出光的衰減atten,atten在directional light中固定是1,在點光源中才有衰減效果,因為directional light在unity中是沒有位置區別的,在哪里都一樣。
fixed atten = LIGHT_ATTENUATION(i);
對於細節方面,如毛孔,在本例的貼圖和法線貼圖都很細致,已經包括毛孔和皮膚的紋路,如果貼圖精度低還想要高細節的話,可以再貼上細節

全部可設置變量:



全部代碼已共享至GitHub鏈接
---- by wolf96
