http://youxiputao.com/articles/11839 主要是參考該篇文章做一個微小的復盤。
漫反射與高光
文章中的漫反射與高光並不是類似於普通的
resultCol = Diffuse(...) + Specular(...)
而是更貼近實際的cg繪畫流程中的,基色+陰影色+高光的模式。
其中陰影色以正片疊底的方式疊加到基色上,之后再加上高光
這也是為什么文章中的DiffuseRamp跟SpecularRamp的兩張貼圖方向相反。因為Diffuse是作為陰影上色,因此其Ramp貼圖在照度低的左部顏色更亮。
同時這種上色也使得傳統的forward rendering中的復數光源用blend one one進行渲染變得不可能了(我猜的),因此只有一個主要的directional light作為光源。不過這樣也夠用了。
這里涉及到的正片疊底的模式,其實就是原有色與疊加色直接相乘。疊加色為1(白)時,結果不變,疊加色為0時變黑。文章中不止考慮了疊加的顏色,還考慮了疊加的強度(alpha通道)。此時疊加的代碼如下:
//_DiffuseLayer1是疊加色,diffuse是在diffuseRamp上采樣的結果
resultCol *= lerp(_DiffuseLayer1.rgb, fixed3(1, 1, 1), 1 - (_DiffuseLayer1.a * diffuse));
在cg繪畫流程中,陰影圖層往往不止一個,而是有多個來提升表現力。在文章中提到,DiffuseRamp的三個通道是分開使用進行上色的。設計師通過修改對應三個通道的Tint色來進行顏色的調整。此時代碼如下:
fixed3 diffuse = tex2D(_DiffuseRamp, float2(diffuseAtten,0));
fixed3 white = fixed3(1, 1, 1);
resultCol *= lerp(_DiffuseLayer1.rgb, white, 1 - (_DiffuseLayer1.a * diffuse.r));
resultCol *= lerp(_DiffuseLayer2.rgb, white, 1 - (_DiffuseLayer2.a * diffuse.g));
resultCol *= lerp(_DiffuseLayer3.rgb, white, 1 - (_DiffuseLayer3.a * diffuse.b));
specular的部分和這個類似,區別在於不是正片疊底,而是直接相加。
軟硬風格切換沒什么好說的,根據采樣到的頂點色去修改diffuseRamp的采樣位置即可。
文章中提到的降低面部陰影的操作,我猜測是用了pow函數,將亮度采樣整體提升,同時也可以保證亮度為0、1時不受干擾。
diffuseAtten = saturate(pow(diffuseAtten, 1 - 0.95 * _ShadowMask)); //so shadow will be less when shadowmask is high.
邊緣光
不同於普通的邊緣光,加入了對環境的采樣。代碼如下
half3 Fresnel(half3 normal, half3 viewDir,half exponent) {
//fresnel(rim light)
half fresnel = 1 - DotClamped(normal, viewDir);
fresnel = saturate(pow(fresnel, exponent));
half3 reflectionDir = reflect(-viewDir, normal);
float4 envSample = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, reflectionDir);
float3 envLight = DecodeHDR(envSample, unity_SpecCube0_HDR);
return envLight * fresnel;
}
眼睛折射
最簡單的眼球渲染的模型就是一個圓形。
但是考慮到人眼的虹膜部分並不是跟眼球一個表面,而是內凹的一個形狀,因此正確的渲染是虹膜部分向內部凹陷一定距離。
另外,虹膜到眼球表面的這段距離會造成一定的折射效果。但是這個折射效果跟凹陷效果其實不太容易分辨的出來,可以當做一種效果處理。
具體的實現就是根據視線方向去計算uv偏移。我們可以輸入一個深度貼圖,表示某個位置距離眼球表面的距離。
偏移的計算參考自 https://computergraphics.stackexchange.com/questions/4133/eye-parallax-refraction
//choose one to calculate uv offset(or use both)
float2 ReflectOffset(float2 ouv,half3 V) {
fixed depth = tex2D(_EyeDepthMask, ouv);
float2 offset = depth * mul((float2x3)unity_WorldToObject, V);
offset.y = -offset.y;
return ouv + offset * _EyeCenter.z;
}
float2 PBReflection(float2 ouv, half3 V, half3 frontW, half3 refractedW) {
half depth = tex2D(_EyeDepthMask, ouv).r * _EyeCenter.w;
float cosAlpha = dot(frontW, -refractedW);
float dist = depth/cosAlpha;
float3 offsetW = dist * refractedW;
float2 offsetL = mul((float2x3)unity_WorldToObject, offsetW);
offsetL.y = -offsetL.y;
return ouv + offsetL;
}
眼睛焦散
跟文章中提到的類似,根據反射后的lightDir計算一個diffuseterm,加上去就行了。
//caustic
half3 backLightDir = normalize(reflect(-lightDir, eyeForward));
half causticAtten = pow(DotClamped(backLightDir, normal),3);
half3 caustic = _EyeCausticTint * causticAtten * _LightColor0.rgb * s.EyeCaustic; //s.EyeCaustic是Caustic貼圖的采樣
resultCol += caustic;
頭發高光
同時頂部的文章中的圖片已經給出了代碼。需要注意的是根據uv方向的不同,tangent可能要換成bitangent。
加入JitterMap后做抖動:
float tangentShift = tex2D(_SpecularJitterMap, IN.uv_SpecularJitterMap);
o.tangent += tangentShift * IN.worldNormal * _SpecularJitterStrength;
o.tangent = normalize(o.tangent);
文章中提到了兩層高光,一層低頻一層高頻。可以配合SpecularRamp的兩個通道去做,這樣最終的效果會更貼近繪畫的風格。
參考: http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2012/10/Scheuermann_HairSketchSlides.pdf
鏡面反射
參考 http://gad.qq.com/article/detail/18544。
其中文末提到的bug可以通過斜投影解決,斜投影矩陣的計算可以參考http://gad.qq.com/article/detail/18612。
Unity內置了計算斜投影的函數,可以不用操心行列向量左右手坐標系之類瑣碎的事情,具體的實現可以參考http://wiki.unity3d.com/index.php/MirrorReflection4。
屏幕空間反射
個人覺得挺有意思的效果,從頭學了一下,重新實現了一遍,在 http://www.cnblogs.com/yangrouchuan/p/7574405.html
有個問題就是無法反射Forward rendering的物體。視頻中似乎是只在遠離人物的地方使用SSR。舞台平面都是用的鏡面反射。
剩下的日后慢慢更新,目前還太菜了。。。