【unity shader/风格化水表面渲染/基础笔记】urp代码版04-法线和顶点波浪动画

前言

上一节为水面上色,本节梳理水面的波浪生成。共分为法线和顶点两部分
- 涉及的知识点
抓屏
GerstnerWave算法

1 法线


由于该案例光照不参与计算,因此法线只扭曲水下的内容(抓屏实现)。这里采样两次法线,动画速度和tile都不同,以模拟波浪运动的无规律性。

1.1 获取切线和副法线

                o.tanW = normalize(TransformObjectToWorldDir(v.tangent.xyz));
                o.biW = normalize(cross(v.normal.xyz, v.tangent.xyz)
                                * v.tangent.w * unity_WorldTransformParams.w);

1.2 法线采样和混合

  • 法线混合函数_blendNormal
BlendedNormal = normalize( float3( A.xy + B.xy, A.z*B.z ).
  • 切线空间的法线
    本节只使用切线空间的法线信息。
        float3 _getNormalTS(float2 uv)
        {
    
    
            uv = uv / _NormalScale;
            float speed = _Time.y * _NormalSpeed * 0.1;
            real4 normalData1 =  SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, uv+speed);
            float3 normalT1 = UnpackNormalScale(normalData1, _NormalStr);
            real4 normalData2 =  SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, 
                                                    uv*0.2 + speed * (-0.35));
            float3 normalT2 = UnpackNormalScale(normalData2, _NormalStr);

            return  _blendNormal(normalT1, normalT2);

        }

在这里插入图片描述

1.3 抓屏

Grabpass在内置管线中的用法如下

GrabPass {
    
    "_GrabTex"}

urp没有直接的抓屏功能,所以本小节的目的是实现类似grabpass的功能,有几种方式可以实现,这里只列举最方便的方法,其他方法和细节可参看本人的urp语雀笔记。
回到管线设置,勾选Opaque Texture
在这里插入图片描述
可以看到这个生成的_CameraOpaqueTexture在半透明渲染之前,也就是说此数据不会存在半透明物体,这部分需要注意
在这里插入图片描述
现在使用_CameraOpaqueTexture来实现抓屏

float2 uvSS = screenPos.xy;
real4 grab_col = SAMPLE_TEXTURE2D(_CameraOpaqueTexture, sampler_CameraOpaqueTexture, uvSS);

在这里插入图片描述
采样坐标是经过齐次除法的屏幕空间坐标,以便能正确映射。
而后我们利用之前的切线空间的法线做扭曲

 uvSS += 0.01 * normalTS * _UWDistort;

在这里插入图片描述

1.4 与水表面颜色混合

水底扭曲只在水面mesh透明的部分,然后再添加岸边泡沫,因此代码如下

                // =========Mix Results=========== //
                base_col = lerp(grab_col, base_col, base_col.a);
                base_col = lerp(base_col, base_col+sinWave,  sinWave.a);

在这里插入图片描述

2 顶点动画


使用细分过的plane做顶点动画
在这里插入图片描述

GerstnerWave

这里创建GerstnerWave函数,调用三次
自定义的变量waveA,waveB,waveC为Vector数据类型,分量代表:
SpeedXY,Steepness,wavelength

        float3 GerstnerWave( float3 position, inout float3 tangent, inout float3 binormal, float4 wave )
		{
    
    
			float steepness = wave.z * 0.01;
			float wavelength = wave.w;
			float k = 2 * 3.14159 / wavelength;
			float c = sqrt(9.8 / k);
			float2 d = normalize(wave.xy);
			float f = k * (dot(d, position.xz) - c * _Time.y);
			float a = steepness / k;
						
			tangent += float3(
			-d.x * d.x * (steepness * sin(f)),
			d.x * (steepness * cos(f)),
			-d.x * d.y * (steepness * sin(f))
			);
			binormal += float3(
			-d.x * d.y * (steepness * sin(f)),
			d.y * (steepness * cos(f)),
			-d.y * d.y * (steepness * sin(f))
			);
			return float3(
			d.x * (a * cos(f)),
			a * sin(f),
			d.y * (a * cos(f))
			);
		}

水平面使用默认的世界切线和法线,我们利用 GerstnerWave函数修改模型空间的顶点和法线

        void _getVertexData(inout a2v v)
        {
    
    
            float3 tangent = float3( 1,0,0 );
			float3 binormal = float3( 0,0,1 );
            float3 posW = TransformObjectToWorld(v.vertex.xyz);
            float3 wave1 = GerstnerWave(posW, tangent,binormal, _WaveA);
            float3 wave2 = GerstnerWave(posW, tangent,binormal, _WaveB);
            float3 wave3 = GerstnerWave(posW, tangent,binormal, _WaveC);
            posW = posW + wave1 + wave2 + wave3;
            v.vertex.xyz = TransformWorldToObject(posW.xyz);
            v.vertex.w = 1;
            float3 normalW = normalize(cross(binormal, tangent));
            v.normal = mul( unity_WorldToObject, float4(normalW, 0 ) ).xyz;
        }

最后的调整结果
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43544518/article/details/128757913