Created
March 10, 2020 03:50
-
-
Save stilllisisi/df902526a65aec5475276ccefecb1745 to your computer and use it in GitHub Desktop.
【游戏-渲染】移动端下雨系统的实现
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //我们需要设计一种tile方式的雨来处理摄像机看到雨的范围。水平方向上可以调整tile个数,垂直方向我可以根据控制雨下落的生命周期来控制。 | |
| //我们要计算出摄像机能看到的AABB盒,从而去做需要雨落下的物体的范围。 | |
| Vector3[] GetCorners(float distance) | |
| { | |
| Vector3[] corners = new Vector3[4]; | |
| float halfFOV = (Camera.main.fieldOfView * 0.5f) * Mathf.Deg2Rad; | |
| float aspect = Camera.main.aspect; | |
| float height = distance * Mathf.Tan(halfFOV); | |
| float width = height * aspect; | |
| Transform tx = Camera.main.transform; | |
| // UpperLeft | |
| corners[0] = tx.position - (tx.right * width); | |
| corners[0] += tx.up * height; | |
| corners[0] += tx.forward * distance; | |
| // UpperRight | |
| corners[1] = tx.position + (tx.right * width); | |
| corners[1] += tx.up * height; | |
| corners[1] += tx.forward * distance; | |
| // LowerLeft | |
| corners[2] = tx.position - (tx.right * width); | |
| corners[2] -= tx.up * height; | |
| corners[2] += tx.forward * distance; | |
| // LowerRight | |
| corners[3] = tx.position + (tx.right * width); | |
| corners[3] -= tx.up * height; | |
| corners[3] += tx.forward * distance; | |
| return corners; | |
| } | |
| //因为每个雨滴都是独立的,这次我们可以放心使用广告牌技术了。 | |
| v2f vert(appdata_full v) | |
| { | |
| v2f o; | |
| UNITY_SETUP_INSTANCE_ID(v); | |
| UNITY_TRANSFER_INSTANCE_ID(v, o); | |
| float3 centerOffs = v.vertex.xyz * float3(_RainX, 1, _RainZ); | |
| float4 worldPos = mul(unity_ObjectToWorld, v.vertex); | |
| float3 normalDir = _WorldSpaceCameraPos - worldPos; | |
| normalDir.y = 0; | |
| normalDir = normalize(normalDir); | |
| float3 upDir = float3(0, 1, 0); | |
| float3 rightdir = cross(upDir, normalDir); | |
| float3 pos1 = float3(unity_ObjectToWorld[0].w, unity_ObjectToWorld[1].w, unity_ObjectToWorld[2].w); | |
| worldPos.xyz = pos1 + rightdir * centerOffs.z + upDir * centerOffs.x + normalDir * centerOffs.y; | |
| o.pos = mul(UNITY_MATRIX_VP, float4(worldPos.xyz, 1)); | |
| o.uv.xy = TRANSFORM_TEX(v.texcoord.xy, _MainTex); | |
| return o; | |
| } | |
| //由于雨离摄像机太近会有点太大块的感觉,所以我建议将摄像机的nearPlane调整到1左右。 | |
| //增加雨滴打中地面的波纹。 | |
| //波纹我们参考的是这篇文章:https://seblagarde.wordpress.com/2013/01/03/water-drop-2b-dynamic-rain-and-its-effects/ | |
| //r通道代表离圆心的距离。gb通道代表法线方向,a通道代表随机强度值。 | |
| inline float2 ComputeRipple(float2 UV, float CurrentTime, float Weight) | |
| { | |
| float4 Ripple = tex2Dlod(_RippleTex, float4(UV, 0, 0)); | |
| Ripple += tex2Dlod(_RippleTex, float4(UV, 0, 1)); | |
| Ripple *= 0.5; | |
| Ripple.yz = Ripple.yz * 2 - 1; | |
| float DropFrac = frac(Ripple.w + CurrentTime); | |
| float TimeFrac = DropFrac - 1.0f + Ripple.x; | |
| float DropFactor = saturate(0.2f + Weight * 0.8f - DropFrac); | |
| float FinalFactor = DropFactor * Ripple.x * sin(clamp(TimeFrac * 9.0f, 0.0f, 3.0f) * 3.141592653589793); | |
| return Ripple.yz * FinalFactor; | |
| } | |
| inline float3 AddWaterRipples(float3 i_worldPos, float fadeOut) | |
| { | |
| float4 Weights = RainIntensity * 2 - float4(0, 0.25, 0.5, 0.75); | |
| Weights = saturate(Weights * 4); | |
| float animSpeed = _Time.y; | |
| float2 Ripple1 = ComputeRipple(float2(i_worldPos.xz * _RippleTile + float2(0.025f, 0.0f) * _Time.y), animSpeed, Weights.x); | |
| float2 Ripple2 = ComputeRipple(float2(i_worldPos.xz * _RippleTile + float2(-0.055f, 0.03f) * _Time.y), animSpeed * 0.71, Weights.y); | |
| float3 rippleNormal = float3(Weights.x * Ripple1.xy + Weights.y * Ripple2.xy, 1); | |
| return lerp(float3(0, 0, 1), rippleNormal, fadeOut); | |
| } | |
| //接下来要处理的是水导致物体本身的变化。一般颜色会稍变深,表面会变光滑,如果有水坑的话就会反光之类。其实就是pbr里面的smooth。但是手机用全套pbr性能有点耗,打算先把brdf3的一部分拿过来用。 | |
| float3 BRDF3_Direct1(float3 diffColor, float3 specColor, float rlPow4, float smoothness) | |
| { | |
| float LUT_RANGE = 16.0; // must match range in NHxRoughness() function in GeneratedTextures.cpp | |
| // Lookup texture to save instructions | |
| float specular = tex2D(unity_NHxRoughness, float2(rlPow4, SmoothnessToPerceptualRoughness1(smoothness))).UNITY_ATTEN_CHANNEL * LUT_RANGE; | |
| return diffColor + specular * specColor; | |
| } | |
| float3 BRDF3_Indirect1(float3 diffColor, float3 specColor, float grazingTerm, float fresnelTerm, float r) | |
| { | |
| float3 c = diffColor; | |
| r = min(r, 0.5); | |
| c += lerp(specColor, grazingTerm, min(r, fresnelTerm)); | |
| return c; | |
| } | |
| float4 BRDF3_Unity_PBS1(float3 diffColor, float3 specColor, float oneMinusReflectivity, float smoothness, | |
| float3 normal, float3 viewDir, float _Roughness1, float reflect1) | |
| { | |
| float3 reflDir = reflect(viewDir, normal); | |
| float nl = saturate(dot(normal, _WorldSpaceLightPos0.xyz)); | |
| float nv = saturate(dot(normal, viewDir)); | |
| // Vectorize Pow4 to save instructions | |
| float2 rlPow4AndFresnelTerm = Pow4(float2(dot(reflDir, _WorldSpaceLightPos0.xyz), 1 - nv)); // use R.L instead of N.H to save couple of instructions | |
| float rlPow4 = rlPow4AndFresnelTerm.x; // power exponent must match kHorizontalWarpExp in NHxRoughness() function in GeneratedTextures.cpp | |
| float fresnelTerm = rlPow4AndFresnelTerm.y; | |
| float grazingTerm = saturate(smoothness + (1 - oneMinusReflectivity)); | |
| float3 color = BRDF3_Direct1(diffColor, specColor, rlPow4, smoothness) * 0.7; | |
| color *= _LightColor0 * nl; | |
| float3 floatDir = normalize(_WorldSpaceLightPos0.xyz + viewDir); | |
| float d = max(0, dot(floatDir, normalize(float3(-normal.x, 2, -normal.z)))); | |
| //color += BRDF3_Indirect1(diffColor, specColor, grazingTerm, fresnelTerm, _Roughness1) * 0.15; | |
| color += color * pow(d, 128 * reflect1) * normal.y * 6 * reflect1; | |
| return float4(color, 1); | |
| } | |
| //然后就是要考虑被阻挡的部分,我打算再顶部放一个摄像机,用来记录物体的深度,然后整个系统都需要去判断深度,然后看是否被雨水阻挡,再做相应处理。实际操作分为这么几步: | |
| //1.顶部摄像机用深度shader替换去渲染物体,然后做一下高斯模糊,用来让边界过度的更加自然。 | |
| Shader.SetGlobalTexture("DepthTex", depthTexture); | |
| Matrix4x4 mat = GL.GetGPUProjectionMatrix(camera.projectionMatrix, true); | |
| mat = mat * camera.worldToCameraMatrix; | |
| Shader.SetGlobalMatrix("depthMat", mat); | |
| Matrix4x4 matV = mat.inverse; | |
| Shader.SetGlobalMatrix("depthMatV", matV); | |
| void OnRenderImage(RenderTexture src, RenderTexture dest) | |
| { | |
| // Copy the source Render Texture to the destination, | |
| // applying the material along the way. | |
| //Graphics.Blit(src, dest, mat); | |
| float widthMod = 1f / (1f * (1 << downSample)); | |
| Vector4 param = new Vector4(blurSize * widthMod, -blurSize * widthMod, 0f, 0f); | |
| blurMaterial.SetVector("_Parameter", param); | |
| src.filterMode = FilterMode.Bilinear; | |
| int rtW = src.width >> downSample; | |
| int rtH = src.height >> downSample; | |
| RenderTexture rt = RenderTexture.GetTemporary(rtW, rtH, 0, src.format); | |
| rt.filterMode = FilterMode.Bilinear; | |
| Graphics.Blit(src, rt, blurMaterial, 0); | |
| int pass = 0; | |
| for(int i = 0; i < iter; i++) | |
| { | |
| float iteroff = i * 1f; | |
| blurMaterial.SetVector("_Parameter", new Vector4(blurSize * widthMod + iteroff, -blurSize * widthMod - iteroff, 0f, 0f)); | |
| RenderTexture rt2 = RenderTexture.GetTemporary(rtW, rtH, 0, src.format); | |
| rt2.filterMode = FilterMode.Bilinear; | |
| Graphics.Blit(rt, rt2, blurMaterial, 1 + pass); | |
| RenderTexture.ReleaseTemporary(rt); | |
| rt = rt2; | |
| rt2 = RenderTexture.GetTemporary(rtW, rtH, 0, src.format); | |
| rt2.filterMode = FilterMode.Bilinear; | |
| Graphics.Blit(rt, rt2, blurMaterial, 2 + pass); | |
| RenderTexture.ReleaseTemporary(rt); | |
| rt = rt2; | |
| } | |
| Graphics.Blit(rt, dest); | |
| RenderTexture.ReleaseTemporary(rt); | |
| } | |
| //2.平面shader把世界坐标转到顶部摄像机裁剪空间坐标,算出uv去取深度值,然后根据矩阵反算出世界坐标的值,比较两个世界坐标的y,来看是否顶部有物体挡住,如果有的话,就对潮湿效果,反射效果,波纹效果进行弱化处理。 | |
| float2 depthuv = i.depthuv * 0.5 + 0.5; | |
| #if UNITY_REVERSED_Z | |
| depthuv.y = 1 - depthuv.y; | |
| #endif | |
| float depth = DecodeFloatRGBA(tex2D(DepthTex, depthuv)); | |
| #if UNITY_REVERSED_Z | |
| #else | |
| depth = depth * 2 - 1; | |
| #endif | |
| float4 proj = float4(depthuv * 2 - 1, depth, 1); | |
| float4 world = mul(depthMatV, proj); | |
| float3 nrml = UnpackNormal(tex2D(_Normal, i.normalUV.xy)); | |
| nrml += UnpackNormal(tex2D(_Normal, i.normalUV.zw)); | |
| float4 uv1 = i.ref; | |
| uv1.xy += nrml.xy * 0.1; | |
| float4 refl = tex2Dproj(_ReflectionTex, UNITY_PROJ_COORD(uv1)); | |
| if (i.worldPos.y < world.y / world.w - 1) | |
| { | |
| float diff = world.y / world.w - i.worldPos.y - 1; | |
| nrml *= max(0, 1 - diff); | |
| _refract *= max(0, 1 - diff); | |
| } | |
| float4 _Rough = tex2D(_RoughTex, i.uv * 0.5 + nrml.xy * _refract * 0.004); | |
| float wet = min(1,ComputeWater(_Rough.g, _Rough.ar, _refract) + _Rough.b * _Bump); | |
| float ref = (1 - wet) * 2.5; | |
| float4 col = tex2D(_MainTex, i.uv + nrml.xy * _refract * 0.004 * ref); | |
| float3 specColor = lerp(unity_ColorSpaceDielectricSpec.rgb, col.rgb, _Roughness); | |
| float oneMinusReflectivity = OneMinusReflectivityFromMetallic1(_Roughness); | |
| float3 diffColor = col.rgb * oneMinusReflectivity; | |
| float smoothness = 0.5; | |
| float dis = distance(_WorldSpaceCameraPos, i.worldPos); | |
| if (i.worldPos.y < world.y / world.w - 1) | |
| { | |
| float diff1 = world.y / world.w - i.worldPos.y - 1; | |
| dis += 999999*diff1; | |
| wet += 0.3 * diff1; | |
| wet = min(0.7, wet); | |
| } | |
| //3.反射效果就是用水面反射,增加一个反射摄像机,具体可以参考自带的水面反射。 | |
| private void OnWillRenderObject() | |
| { | |
| if(this.enabled == false) | |
| { | |
| return; | |
| } | |
| Camera cam = Camera.current; | |
| if(!cam || cam != Camera.main) | |
| { | |
| return; | |
| } | |
| if(insideWater) | |
| { | |
| return; | |
| } | |
| insideWater = true; | |
| Camera reflectCamera; | |
| CreateWaterObjects(cam, out reflectCamera); | |
| Vector3 pos = transform.position; | |
| Vector3 normal = transform.up; | |
| UpdateCameraModes(cam, reflectCamera); | |
| float d = -Vector3.Dot(normal, pos) - clipPlaneOffset; | |
| Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d); | |
| Matrix4x4 reflection = Matrix4x4.zero; | |
| CalculateReflectionMatrix(ref reflection, reflectionPlane); | |
| Vector3 oldpos = cam.transform.position; | |
| Vector3 newpos = reflection.MultiplyPoint(oldpos); | |
| reflectCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection; | |
| Vector4 clipPlane = CameraSpacePlane(reflectCamera, pos, normal, 1f); | |
| reflectCamera.projectionMatrix = cam.CalculateObliqueMatrix(clipPlane); | |
| reflectCamera.cullingMatrix = cam.projectionMatrix * cam.worldToCameraMatrix; | |
| reflectCamera.cullingMask = reflectLayers.value; | |
| reflectCamera.targetTexture = m_reflectionTexture; | |
| bool oldCulling = GL.invertCulling; | |
| GL.invertCulling = !oldCulling; | |
| reflectCamera.transform.position = newpos; | |
| Vector3 euler = cam.transform.eulerAngles; | |
| reflectCamera.transform.eulerAngles = new Vector3(-euler.x, euler.y, euler.z); | |
| reflectCamera.Render(); | |
| reflectCamera.transform.position = oldpos; | |
| GL.invertCulling = oldCulling; | |
| render.sharedMaterial.SetTexture("_ReflectionTex", m_reflectionTexture); | |
| insideWater = false; | |
| } | |
| //如果要整合到自己的项目中,还是需要做一些代码上的整合。例如水导致物体潮湿,可以把我的函数和贴图放到自己的shader中,也可以直接修改shader里面的高光,光滑度等这些参数。 | |
| //如果场景特别大的话,通过移动摄像机的话,边缘部分会产生抖动。所以我建议大场景要使用多个深度摄像机或者自己提前烘焙好深度数据。 |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://gameinstitute.qq.com/community/detail/128832