Unity Shader Example 4 (扭曲)

// 简单的扭曲效果

Shader "CGwell FX/Distortion Bump" {
	Properties {
		_BumpAmt  ("Distortion", range (0,128)) = 10
		_BumpMap ("Normalmap", 2D) = "bump" {}
	}

	Category {
		// We must be transparent, so other objects are drawn before this one.
		Tags { "Queue"="Transparent+100" "RenderType"="Opaque" }

		SubShader {
			
			ZWrite Off
			Cull Off
			Fog {Mode Off}
			Lighting Off

			// This pass grabs the screen behind the object into a texture.
			// We can access the result in the next pass as _GrabTexture
			GrabPass {							
				Name "BASE"
				Tags { "LightMode" = "Always" }
 			}
 		
 			// Main pass: Take the texture grabbed above and use the bumpmap to perturb it
 			// on to the screen
			Pass {
				Name "BASE"
				Tags { "LightMode" = "Always" }
			
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag
				#pragma fragmentoption ARB_precision_hint_fastest
				#include "UnityCG.cginc"

				struct appdata_t {
					float4 vertex : POSITION;
					float2 texcoord: TEXCOORD0;
				};

				struct v2f {
					float4 vertex : POSITION;
					float4 uvgrab : TEXCOORD0;
					float2 uvbump : TEXCOORD1;
				};

				float _BumpAmt;
				float4 _BumpMap_ST;

				v2f vert (appdata_t v)
				{
					v2f o;
					o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);

					/*
					#if UNITY_UV_STARTS_AT_TOP
					float scale = -1.0;
					#else
					float scale = 1.0;
					#endif
					o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
					o.uvgrab.zw = o.vertex.zw;
					*/

					//计算该模型顶点在屏幕坐标的纹理信息
					o.uvgrab = ComputeGrabScreenPos(o.vertex);

					o.uvbump = TRANSFORM_TEX( v.texcoord, _BumpMap );
					return o;
				}

				sampler2D _GrabTexture;
				float4 _GrabTexture_TexelSize;
				sampler2D _BumpMap;

				half4 frag( v2f i ) : COLOR
				{
					// calculate perturbed coordinates
					half2 bump = UnpackNormal(tex2D( _BumpMap, i.uvbump )).rg; // we could optimize this by just reading the x & y without reconstructing the Z

					// _GrabTexture_TexelSize 就是 _GrabTexture的大小
					float2 offset = bump * _BumpAmt * _GrabTexture_TexelSize.xy;

					// 扰动方式
					i.uvgrab.xy = offset * i.uvgrab.z + i.uvgrab.xy;
	
					//对_GrabTexture纹理进行取样
					//half4 col = tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(i.uvgrab));
					
					//也可以使用tex2D进行采样,为什么要除以i.uvgrab.w,
					float newUvX = i.uvgrab.x / i.uvgrab.w;
					float newUvY = i.uvgrab.y / i.uvgrab.w;

					half4 col = tex2D(_GrabTexture, float2(newUvX, newUvY));
					
					return col;
				}
				ENDCG
			}
		}

		// ------------------------------------------------------------------
		// Fallback for older cards and Unity non-Pro
	
		SubShader {
			Blend DstColor Zero
			Pass {
				Name "BASE"
				SetTexture [_MainTex] {	combine texture }
			}
		}
	}

}


1. 计算该模型顶点在屏幕坐标的纹理

Unity3D 封装了ComputeGrabScreenPos函数,做的事情就是上面注释掉的代码是一样的,在UnityCG.cginc中有源码:

inline float4 ComputeGrabScreenPos (float4 pos) {
	#if UNITY_UV_STARTS_AT_TOP
	float scale = -1.0;
	#else
	float scale = 1.0;
	#endif
	float4 o = pos * 0.5f;
	o.xy = float2(o.x, o.y*scale) + o.w;
	o.zw = pos.zw;
	return o;
}

理解:

ComputeGrabScreenPos函数的理解:

例如,一个顶点P(x,y,z,w), 经过MVP之后,变成了P'(x',y',z',w), 那就是从局部空间变换到了裁剪空间(因为一个CG顶点程序输出的顶点的位置是在剪裁空间中的,POSITION语义就是用来指明一个特效的顶点程序输出是裁剪空间的位置。)变换到了裁剪空间之后,其实,x‘,y',z’的范围就是,(-w <= x' <= w),对于y',z'也是一样的,那么,等到变换到裁剪空间之后,为了进行方便裁剪,就把 x‘,y',z’ 进行透视除法(显卡做的),对应OpenGL来说,就是把 x‘,y',z’ 变换到了(-1, 1)之间。

(这里需要注意的就是,因为把vertex声明为POSITION,所以,vertex会变换到裁剪空间,再进行透视除法,但是,如果是有东西声明了TEXCOORD0的话,就不会进行透视除法,透视除法只是为了POSITION而进行的)。


结合代码,

o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
o.uvgrab.zw = o.vertex.zw;

主要就是,把变化到裁剪空间的 P'(x',y',z',w), 把x',y', 从 [-w, w] 变换到[0, w], 现在的x'' = (x' + w) / 2 , 对应 y'' = (y' + w) / 2 。


2.  tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(i.uvgrab)) 的理解:

half4 col = tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(i.uvgrab));

这句代码可以用下面的替代

float newUvX = i.uvgrab.x / i.uvgrab.w;     

float newUvY = i.uvgrab.y / i.uvgrab.w;

为什么要除以w?

实际就是以为把 上一步的 [0, w] 变换到 [0, 1],刚好是屏幕UV坐标。

其实更深入去想的就是,因为上面提到,透视除法只是为了POSITION而进行的,那么对于其他的语义是不会进行的,所以,

当 o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5; 之后,

o.uvgrab 从vs传入ps,显卡都没有进行对o.uvgrab 进行透视除法,但是,o.uvgrab 是由 x',y' 构成的(x',y'只是进行

了MVP变换而已,在vs中可以看到),只有当x',y'进行了透视除法,o.uvgrab 得到的 值才是真正的 屏幕UV坐标值,所以,我们要手动进行透视除法。


3. 寻找UV的思路:

因为在vs中寻找当前position对应在屏幕的uv值,那么,就可以确定一个position对应 _GrabTexture 的UV值(因为_GrabTexture是屏幕的Texture)。




你可能感兴趣的:(unity3d)