// 简单的扭曲效果 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)。