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