將頂點依法線向外偏移(非後處理雪效果)。c#
float _Height; vert { if(積雪斷定){ vertex.xyz += normal * _Height; } }
https://www.patreon.com/posts/15944770,dot判斷,半蘭伯特+漸變紋理(卡通着色)less
https://blog.theknightsofunity.com/make-it-snow-fast-screen-space-snow-shader,後處理雪效果ide
以上的積雪斷定是沒法判斷室內外,遮擋物下的物體也會有積雪效果。post
判斷遮擋的積雪斷定須要一個在天空上面向地面的正交相機渲染深度圖,渲染時反推出當前正交相機座標系下的深度值,而後與深度圖中所在的深度值做比較。spa
深度圖的精度要求不高,且同一地點通常只須要更新一次,能夠根據主相機的移動來分段更新。3d
// C# RenderTexture depthTexture; Camera skyDepthCam; Shader replacementShader = null; start { // skyDepthCam所在的game object最好使用掛載在主相機的腳本中生成,這樣不須要跨對象獲取參數,解耦 // skyDepthCamObj = new GameObject("skyDepthCamObj"); // skyDepthCam = skyDepthCamObj.AddComponent<Camera>(); var skyDepthCam = GetComponent<Camera>(); gameObject.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy; skyDepthCam.enabled = false; skyDepthCam.renderingPath = RenderingPath.Forward; skyDepthCam.orthographic = true; skyDepthCam.clearFlags = CameraClearFlags.SolidColor; skyDepthCam.allowMSAA = false; skyDepthCam.backgroundColor = new Color(1f, 0f, 0f, 0f); skyDepthCam.nearClipPlane = 1f; skyDepthCam.orthographicSize = 256f; // 相機渲染大小 skyDepthCam.transform.rotation = Quaternion.Euler(90, 0, 0); // 面垂直朝下 if (depthTexture == null) { var res = 512; // 深度圖精度,最好是orthographicSize的一倍 depthTexture = new RenderTexture(res, res, 24, RenderTextureFormat.RGFloat, RenderTextureReadWrite.Linear); depthTexture.hideFlags = HideFlags.DontSave; depthTexture.filterMode = FilterMode.Bilinear; depthTexture.wrapMode = TextureWrapMode.Clamp; depthTexture.Create(); } skyDepthCam.targetTexture = depthTexture; // 使用替換shader渲染深度圖 replacementShader = Shader.Find("RenderDepthSh"); if (replacementShader == null) { Debug.LogError("could not find 'RenderDepth' shader"); } skyDepthCam.RenderWithShader(replacementShader, null); } // Shader,RenderDepthSh,替換shader渲染深度圖 vert { o.depth = COMPUTE_DEPTH_01; } frag { return i.depth; }
主相機渲染須要世界座標才能獲取到正交相機所對應的深度值。code
正交相機默認高寬比是1:1(camera.aspect),着色器中經過世界座標xz值減去左下點的值在除以寬度(2*orthographicSize)便可獲取uv正交相機的深度圖的UV值,以此獲取正交相機座標系下積雪點的深度值。世界座標系正交相機的y減去當前頂點的y值即爲當前頂點在正交相機座標系下的深度值,若是大於積雪點的深度值,則當前頂點在遮擋物下面。orm
判斷結果,白色積雪點,黑色未障礙物下對象
經過深度反推的世界座標沒法使用(效果很差且不穩定,猜想精度不夠),因此使用向前渲染路徑中的世界座標。在每一個shader中加上積雪效果不現實並且也不兼容延遲渲染路徑。解決方法是拷貝當前相機,使用替換shader渲染積雪效果得出渲染貼圖。而後在後處理中與渲染結果合併。blog
// c# // 設置用於渲染雪的相機 var currentCam = GetComponent<Camera>(); var snowCamObj = new GameObject("snowCamObj"); snowCamObj.hideFlags = HideFlags.HideAndDontSave; var snowCam = snowCam.AddComponent<Camera>(); snowCam.enabled = false; snowCam.CopyFrom(currentCam); snowCam.renderingPath = RenderingPath.Forward; snowCam.depthTextureMode = DepthTextureMode.None; var snowedSceneTexture = RenderTexture.GetTemporary(snowCam.pixelWidth, snowCam.pixelHeight, 24, RenderTextureFormat.ARGB32); snowCam.backgroundColor = new Color(0, 0, 0, 0); snowCam.clearFlags = CameraClearFlags.SolidColor; snowCam.allowMSAA = false; snowCam.allowHDR = false; snowCam.targetTexture = snowedSceneTexture; snowCam.rect = new Rect(0, 0, 1f, 1f); // 設置渲染用參數 Shader.SetGlobalTexture("_SkyDepthTexture", depthTexture); // 正交相機渲染出的深度貼圖 Shader.SetGlobalVector("_SkyCamPos", new Vector4(skyDepthCam.transform.position.x, skyDepthCam.transform.position.y, skyDepthCam.transform.position.z, skyDepthCam.orthographicSize)); var replacementShader = Shader.Find("SnowEffectSh"); if (replacementShader == null) { Debug.LogError("could not find 'SnowEffectSh' shader"); } snowCam.RenderWithShader(replacementShader, "RenderType");
// Shader vert { worldPos = mul(unity_ObjectToWorld, vertex).xyz; } frag { float4 depUV = float4(worldPos.xz - (_SkyCamPos.xz-_SkyCamPos.w), 0, 0); depUV /= (_SkyCamPos.w*2.0); float dep1 = tex2Dlod(_SkyDepthTexture, depUV).r; // 遮擋物的深度值 float dep2 = max(_SkyCamPos.y - worldPos.y, 0.001); // 當前位置在正交相機座標系下的深度值 float minDep = min(dep1, dep2); o.Alpha = saturate( ((minDep / dep2) - 0.9875) * 110.0); // 偏差修正,0:非積雪出,1:積雪處 if(o.Alpha <= 0) { return; } 。。。 // 獲取積雪顏色 SnowColor o.Albedo = SnowColor; }