您當前的位置:首頁 > 攝影

最近搞了下SSS(嘗試下手機能不能用)

作者:由 flashyiyi 發表于 攝影時間:2017-12-14

參考了下這個GPU Gems,不過基本和之前的想法一致。

其實實時渲染裡的SSS,基本原理就是對光照圖做一次Blur,因為所謂的次表面散射,透進去然後再反射出來,外在的低精度表現就是這樣的。

最近搞了下SSS(嘗試下手機能不能用)

就是光能夠到達的區域變大了

。我一開始自然想的是Bloom,但是考慮到光能守恆,Blur或許是更正確的。

最近搞了下SSS(嘗試下手機能不能用)

原文中模糊光照圖的方法是將模型展開至UV空間,實際上就是把人皮給鋪開至平面,再在這裡完整計算一次光照,接著橫豎Blur進行高斯模糊,再重新繪製一次用UV重新對應回去。

好處很明顯:比較正確不會穿幫,可以進行低精度的繪製再利用硬體插值來輔助Blur,模糊的方法也很多。

但缺點也很明顯:即使是背面也同樣要繪製,而且其實需要額外的UV,因為人臉UV一般都是對稱重複利用的,這個UV分佈還需要儘可能均勻。嗯,還要處理好接縫。

我個人其實覺得原文的方法挺好的,實現需要依賴的東西比較少,雖然要繪製正反面,但是精度可以放肆地調低,未必比不降解析度的螢幕空間SSS費。

但實在不太容易找到符合要求的模型,這不是讓我重刷UV麼,我MAX都沒裝。

所以只能直接嘗試螢幕空間SSS,也就是直接繪製螢幕空間的光照圖。

最近搞了下SSS(嘗試下手機能不能用)

然後在螢幕空間Blur

(本來這裡應該分通道Blur的,紅色通道的模糊會更強一些,但為了後面和法線塞在同一個圖裡就沒分)

最近搞了下SSS(嘗試下手機能不能用)

最後如同想象那樣糊作一片,這也是當然的。

最近搞了下SSS(嘗試下手機能不能用)

利用下螢幕空間法線資訊,順便加了點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);

}

最近搞了下SSS(嘗試下手機能不能用)

最近搞了下SSS(嘗試下手機能不能用)

就好了一些。

最近搞了下SSS(嘗試下手機能不能用)

其實還是糊,我也只能這樣了。我個人其實更傾向在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,除了這個散射效果,還有一個特徵是其半透性質。

最近搞了下SSS(嘗試下手機能不能用)

但這個在人體上並不明顯。

這個其實比上面的簡單多了,和ShadowMap的原理差不多,根據光照攝像機那邊獲得的深度圖可以確定視點“背面”的座標,再與當前的世界座標(視點正面)相減得出物體的“厚度”,再根據這個厚度算光照。

最近搞了下SSS(嘗試下手機能不能用)

隨便在哪個空間做都是一樣的,利用的也是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的自帶陰影,就必須瞭解這玩意兒。

最近搞了下SSS(嘗試下手機能不能用)

背面打光才能看出一點來,人體上基本沒啥大用

而你們也看到了,這個效果在很多情況下都非常接近RimLight,尤其是對於透光率比較低的人體。

而之前的光照圖模糊,其實也確實有點接近Bloom。

早期的遊戲,也確實在用RimLight和Bloom來近似模擬人體。這種做法在卡通渲染上則……現在都在用。

最近搞了下SSS(嘗試下手機能不能用)

這個圖取消Bloom效果後直接就是塑膠了= =

當然利用強高光來模擬“溼潤的人體”則是更常見的做法,因為人體在溼潤的時候,次表面散射特性被減弱了(光大多都先反射出去了),變得更容易模擬了,人眼也認可這樣的材質是人體。

然後就變成了“油膩的師姐”……

哈哈,高光且不說,Bloom和RimLight依然是做一些低配遊戲人體模擬的方向就是了。

檔案下載

https://

pan。baidu。com/s/1i4Bite

P

標簽: 0f  RT  GL  RenderTexture  Blur