在Unity中实现基于粒子的水模拟(三:混合屏幕)
在Unity中實現基于粒子的水模擬(三:混合屏幕)
文章目錄
- 在Unity中實現基于粒子的水模擬(三:混合屏幕)
- 前言
- 一、著色算法介紹
- 1.折射
- 2.反射
- 二、準備紋理
- 1.獲取紋理
- 2.模糊紋理
- 2.混合主紋理上
- 總結
- 1.現有問題
前言
經過了前面的紋理,我們就到了最后的混合到屏幕階段了,這個階段的邏輯不是很難,本來是要基于這篇文章的公式來往上套的,不過由于本人水平問題并沒有看懂作者的具體含義,于是就大概的實現了一下,估計出入挺大的。
效果看圖:
項目鏈接:注意是在其中的master分支中
一、著色算法介紹
我們將顏色分為兩部分,也就是折射和反射。
1.折射
折射要有最好的效果應該是要根據當前像素執行SSR算法,也就是計算出折射法線后,在通過深度重建的世界坐標中,用最接近折射位置的那個像素的顏色值作為折射的顏色值,但是這種方式太昂貴,所以沒有采用這種方式。
本文用的方式是最簡單的折射采樣方式,根據法線以及液體寬度指進行uv偏移來獲取折射的顏色值(因為實在看不懂作者到底什么意思,所以就這樣簡單實現了 )。
2.反射
反射的計算比較簡單,直接傳遞一個Cube貼圖,然后根據反射方向進行紋理采樣即可。
最后將兩個顏色根據透光度進行混合就計算完成了。
二、準備紋理
1.獲取紋理
在SRP中可以通過設置CommandBuffer的SetRenderTarget將寬度、法線、深度圖專門用特定的ShaderTagId指定,這樣可以渲染兩次就得到所有3張紋理,因為法線可以和深度一次得到。
但是在Build-in中我沒有找到指定深度圖的方法,導致后面模糊時不能很好的將深度值提取出來模糊,不過因為我們這個計算方式只使用了xy的法線值,可以將深度值存儲在法線紋理的b通道中。
裁剪空間獲取深度的公式:
其中f是遠裁剪面、n是近裁剪面、z是視角空間z值。
代碼表示:
2.模糊紋理
模糊紋理使用的算法是雙邊濾波,這個算法可以讓球體的邊界混合起來,同時不會像高斯模糊一樣讓顏色值與周圍混合,導致顏色不能準確對應該像素。
核心代碼:
float CompareColor(float4 col1, float4 col2) {float l1 = LinearRgbToLuminance(col1.rgb);float l2 = LinearRgbToLuminance(col2.rgb);return smoothstep(_BilaterFilterFactor, 1.0, 1.0 - abs(l1 - l2)); }float4 BilateralFilterFragment (Varyings input) : SV_TARGET{float2 delta = _PostFxEffectSource_TexelSize.xy * _BlurRadius.xy;//采集Normal的顏色值float4 col = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV);float4 col0a = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV - delta);float4 col0b = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV + delta);float4 col1a = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV - 2.0 * delta);float4 col1b = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV + 2.0 * delta);float4 col2a = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV - 3.0 * delta);float4 col2b = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV + 3.0 * delta);float w = 0.37004405286;float w0a = CompareColor(col, col0a) * 0.31718061674;float w0b = CompareColor(col, col0b) * 0.31718061674;float w1a = CompareColor(col, col1a) * 0.19823788546;float w1b = CompareColor(col, col1b) * 0.19823788546;float w2a = CompareColor(col, col2a) * 0.11453744493;float w2b = CompareColor(col, col2b) * 0.11453744493;float3 result;result = w * col.rgb;result += w0a * col0a.rgb;result += w0b * col0b.rgb;result += w1a * col1a.rgb;result += w1b * col1b.rgb;result += w2a * col2a.rgb;result += w2b * col2b.rgb;result /= w + w0a + w0b + w1a + w1b + w2a + w2b;return float4(result, 1); }雙邊濾波也需要像高斯模糊一樣執行兩次,對垂直以及水平進行模糊,具體實現可以參考這篇文章。
2.混合主紋理上
這部分就和正常的后處理流程一樣了,具體后處理如何實現就不贅述了,這里只描寫著色的核心代碼。
首先第一步,進行深度對比,判斷該像素是否需要進行液體著色,也就是是否被遮擋。
if(currentDepth >= waterDepth)return float4(currentColor, 1); //返回原本像素顏色獲得折射以及反射的顏色值:
float3 viewDirection = normalize( _WorldSpaceCameraPos - worldPos );float3 reflectDir = normalize(-viewDirection + 2 * waterNormal); //反射方向//反射顏色float3 specular = SAMPLE_TEXTURECUBE_LOD( _WaterReflectCube, sampler_WaterReflectCube, reflectDir, 0 ).rgb;float2 ofssetUV = (-viewDirection - 0.2 * waterNormal).xy * waterWidth * 0.2 + input.screenUV;//折射顏色float3 refrColor = SAMPLE_TEXTURE2D_LOD(_PostFxEffectSource, sampler_linear_clamp, ofssetUV, 0).rgb;//混合液體顏色,transLight是透光度refrColor = lerp(refrColor * _WaterColor.rgb, refrColor, transLight);按照大佬文章的公式:
其中R1和R2分別是水和空氣的折射率。
代碼:
總結
導致這個粒子水系列就正式完成了,我覺得這個實現的水不適合作為“水”,不過如果拿來做流體模擬的話還是可以的,因為有深度、法線圖,用來實現牛奶等BSDF等渲染效果是很不錯的,畢竟BSDF的一個難點是寬度計算,有了寬度其他計算就和BRDF沒什么區別了。
1.現有問題
目前本場景的粒子并沒有進行軟粒子處理,如果有必要的話可以在渲染寬度時將深度圖傳入,在深度相近時進行透明,而且可以根據深度值進行Hi-z剔除,優化效果。
本項目的根據都是基于Unity的物理檢測的,這個部分是損耗最大的部分,也是最容易出bug的部分,如果之后場景需要的話建議直接設置固定的流動方向,不進行真正的時時物理檢測,刷新流動方向,這樣CPU占用太大了。
理論上這些粒子著色都是要用ComputeShader寫的,但是我在寫這個系列時還不懂這些,在最近研究SRP時才知道有這個東西。
不過由于模擬時數據量太大了,這種直接將數據傳遞到頂點的方式說不定更適合物理模擬,不過之前實現的粒子系統可能就真的需要更新了,之后應該會更新Compute Shader進行剔除的粒子系統,更加全面的實現Unity新版粒子系統。
總結
以上是生活随笔為你收集整理的在Unity中实现基于粒子的水模拟(三:混合屏幕)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CodeForces - 497D Ge
- 下一篇: 新天绿色能源与建投国融续签温室气体减排项