最近搞了下SSS(嘗試下手機能不能用)
參考了下這個GPU Gems,不過基本和之前的想法一致。
其實實時渲染裡的SSS,基本原理就是對光照圖做一次Blur,因為所謂的次表面散射,透進去然後再反射出來,外在的低精度表現就是這樣的。
就是光能夠到達的區域變大了
。我一開始自然想的是Bloom,但是考慮到光能守恆,Blur或許是更正確的。
原文中模糊光照圖的方法是將模型展開至UV空間,實際上就是把人皮給鋪開至平面,再在這裡完整計算一次光照,接著橫豎Blur進行高斯模糊,再重新繪製一次用UV重新對應回去。
好處很明顯:比較正確不會穿幫,可以進行低精度的繪製再利用硬體插值來輔助Blur,模糊的方法也很多。
但缺點也很明顯:即使是背面也同樣要繪製,而且其實需要額外的UV,因為人臉UV一般都是對稱重複利用的,這個UV分佈還需要儘可能均勻。嗯,還要處理好接縫。
我個人其實覺得原文的方法挺好的,實現需要依賴的東西比較少,雖然要繪製正反面,但是精度可以放肆地調低,未必比不降解析度的螢幕空間SSS費。
但實在不太容易找到符合要求的模型,這不是讓我重刷UV麼,我MAX都沒裝。
所以只能直接嘗試螢幕空間SSS,也就是直接繪製螢幕空間的光照圖。
然後在螢幕空間Blur
(本來這裡應該分通道Blur的,紅色通道的模糊會更強一些,但為了後面和法線塞在同一個圖裡就沒分)
最後如同想象那樣糊作一片,這也是當然的。
利用下螢幕空間法線資訊,順便加了點ddxddy做為輔助
fixed2(1 - abs(viewNormal。rg) - fixed2(ddx(i。pos。z),ddy(i。pos。z) * 100))
限定下xy方向的模糊步長
static const half curve4[4] = {0。324, 0。232, 0。0855, 0。0205};
half4 fragBlur8 ( v2f_withBlurCoords8 i ) : SV_Target
{
half2 uv = i。uv。xy;
half4 baseColor = tex2D(_MainTex, UnityStereoScreenSpaceUVAdjust(uv, _MainTex_ST));
half light = baseColor。b * curve4[0];
half4 frontColor = baseColor;
half4 backColor = baseColor;
half2 frontCord = uv;
half2 backCord = uv;
for (int l = 1; l <= 3; l++ )
{
frontCord += i。offs * frontColor。rg;
frontColor = tex2D(_MainTex, UnityStereoScreenSpaceUVAdjust(frontCord, _MainTex_ST));
backCord -= i。offs * backColor。rg;
backColor = tex2D(_MainTex, UnityStereoScreenSpaceUVAdjust(backCord, _MainTex_ST));
light += (frontColor。b + backColor。b) * curve4[l];
}
return half4(baseColor。rg,light,1);
}
就好了一些。
其實還是糊,我也只能這樣了。我個人其實更傾向在UV空間做這個
這裡高斯模糊的兩次Blur是可以用Stencil過濾的(也是為了過濾才選擇的這個橫豎Blur的方式),邊緣處會出現一點小瑕疵。不過Unity預設的Blit API對Stencil不友好真的挺煩人的(甚至可以稱之為腦殘了)
private void StencilBlit(RenderTexture source, RenderTexture destination, Material mat, int pass, RenderTexture stencil)
{
if (stencil == null)
{
Graphics。Blit(source, destination, mat, pass);
return;
}
Graphics。SetRenderTarget(destination。colorBuffer, stencil。depthBuffer);
GL。Clear(false, true, Color。clear);
GL。PushMatrix();
GL。LoadOrtho();
mat。mainTexture = source;
mat。SetPass(pass);
GL。Begin(GL。QUADS);
GL。TexCoord2(0。0f, 1。0f); GL。Vertex3(0。0f, 1。0f, 0。1f);
GL。TexCoord2(1。0f, 1。0f); GL。Vertex3(1。0f, 1。0f, 0。1f);
GL。TexCoord2(1。0f, 0。0f); GL。Vertex3(1。0f, 0。0f, 0。1f);
GL。TexCoord2(0。0f, 0。0f); GL。Vertex3(0。0f, 0。0f, 0。1f);
GL。End();
GL。PopMatrix();
}
具體模糊繪製過程(就是最常見的高斯模糊)
void LateUpdate ()
{
if (rt != null)
RenderTexture。ReleaseTemporary(rt);
rt = RenderTexture。GetTemporary(Screen。width, Screen。height, 24, RenderTextureFormat。ARGB32);
mCamera。targetTexture = rt;
mCamera。RenderWithShader(lightShader, “RenderType”);
mCamera。targetTexture = null;
if (enableBlur)
{
blurMaterial。SetFloat(“_Size”, size);
// vertical blur
RenderTexture rt2 = RenderTexture。GetTemporary(rt。width, rt。height, 0, rt。format);
rt2。filterMode = FilterMode。Bilinear;
StencilBlit(rt, rt2, blurMaterial, 0, rt);
// horizontal blur
RenderTexture rt3 = RenderTexture。GetTemporary(rt。width, rt。height, 0, rt。format);
rt3。filterMode = FilterMode。Bilinear;
StencilBlit(rt2, rt3, blurMaterial, 1, rt);
RenderTexture。ReleaseTemporary(rt);
RenderTexture。ReleaseTemporary(rt2);
rt = rt3;
}
Shader。SetGlobalTexture(“_SSSLightTexture”, rt);
}
光照圖精度還可以考慮單獨降低,但假如不對邊緣做處理,物體邊緣的模糊會更嚴重。這種修正做法本來就是螢幕空間節約光照計算的技巧,現階段我搞不了。
另外一般談到SSS,除了這個散射效果,還有一個特徵是其半透性質。
但這個在人體上並不明顯。
這個其實比上面的簡單多了,和ShadowMap的原理差不多,根據光照攝像機那邊獲得的深度圖可以確定視點“背面”的座標,再與當前的世界座標(視點正面)相減得出物體的“厚度”,再根據這個厚度算光照。
隨便在哪個空間做都是一樣的,利用的也是ShadowMap的資料,自身基本沒啥成本。
我這裡利用的是Unity自己的陰影系統,暫時只考慮手機平臺下的非螢幕空間ShadowMap,所以都打上了條件判斷
#if defined(SHADOWS_SCREEN) && defined(UNITY_NO_SCREENSPACE_SHADOWS)
當前螢幕座標在光照空間的位置很容易求得
float4 worldPos = mul(unity_ObjectToWorld, v。vertex);
o。shadowPos = mul(unity_WorldToShadow[0], worldPos);
然後在frag裡讀下陰影紋理,和座標的z相減就有資料了。
half photopermeability = UNITY_SAMPLE_DEPTH(_ShadowMapTexture。Sample(my_linear_clamp_sampler, i。shadowPos。xy)) - i。shadowPos。z;
ambient += saturate(exp(-photopermeability * 3000)) * i。photopermeabilityPow;
注意這個_ShadowMapTexture不帶sample,所以不能直接取樣,需要自己定義一個
SamplerState my_linear_clamp_sampler;
可以看unity幫助:Using sampler states
而且這個_ShadowMapTexture只有在SHADOWS_SCREEN等KeyWord開啟的時候才存在,不加條件判斷是取不到的。
搞明白Unity這個“因為多平臺和舊版本而特別噁心”的陰影系統設計稍微花了點時間,但假如還想繼續用Unity的自帶陰影,就必須瞭解這玩意兒。
背面打光才能看出一點來,人體上基本沒啥大用
而你們也看到了,這個效果在很多情況下都非常接近RimLight,尤其是對於透光率比較低的人體。
而之前的光照圖模糊,其實也確實有點接近Bloom。
早期的遊戲,也確實在用RimLight和Bloom來近似模擬人體。這種做法在卡通渲染上則……現在都在用。
這個圖取消Bloom效果後直接就是塑膠了= =
當然利用強高光來模擬“溼潤的人體”則是更常見的做法,因為人體在溼潤的時候,次表面散射特性被減弱了(光大多都先反射出去了),變得更容易模擬了,人眼也認可這樣的材質是人體。
然後就變成了“油膩的師姐”……
哈哈,高光且不說,Bloom和RimLight依然是做一些低配遊戲人體模擬的方向就是了。
檔案下載
https://
pan。baidu。com/s/1i4Bite
P
上一篇:提升文筆最有效途徑是什麼?
下一篇:入手碳纖維三腳架,我踩坑了