到目前为止,我们都是基于Surface Shaders来实现功能。Surface Shader的设计提供了一种简单的shader编码实现方法,给艺术家提供了十分有意义的工具。如果我们想要更进一步的了解Shader,是时候踏进Vertex(顶点)和Fragment(片元)着色器的领域了。
本章内容: + 明白顶点着色和片元着色的基本 + 使用Grab pass + 实现Grab 着色 + 实现水效果,2D游戏
相对于Surface Shader, 顶点和片元着色器少了物理着色功能,但换来的是更强大的能量,没有了物理束缚,可以实现很多非真实的渲染效果。本章节将着重介绍Grab Pass,她使shader可以模拟物体的形变。
学习vertex和Fragment着色器的最好方法是,亲自动手去实现一个。这一小节将实现一个简单的vertex,fragment着色器,简单的读取主纹理和颜色。
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' Shader "CookbookShaders/self/grab" { Properties { _MainTex("Base (RGB) Trans(A)", 2D) = "white"{} _Color("Color", Color) = (1, 1, 1, 1) } SubShader{ Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; float _Magnitude; float4 _Color; struct vertInput { float4 vertex: POSITION; float2 texcoord: TEXCOORD0; }; struct vertOutput { float4 vertex: POSITION; float2 texcoord: TEXCOORD0; }; vertOutput vert(vertInput v) { vertOutput o; o.vertex = UnityObjectToClipPos(v.vertex); o.texcoord = v.texcoord; return o; } half4 frag(vertOutput i):COLOR { half4 mainColor = tex2D(_MainTex, i.texcoord); return col * mainColor * _Color; } ENDCG } } FallBack "Diffuse" }+ 输出结构语义绑定
语义描述POSITION, SV_POSITION, HPOS顶点坐标,在摄像机坐标系内(clip space,范围0-1)COLOR, COLOR0, COL0, COL, SV_TARGET主颜色COLOR1, COL1第二颜色TEXCOORD0…TEXCOORDi纹理坐标WPOSThe position, in pixels, in the window (origin in the lower left corner)(不是很懂)虽说是语义绑定,但是其实要在结构里面存储什么样的数据都是开发者可以控制的。你完全可以用POSITION语义保存NORMAL的数据。
在第四章中为PBR材质添加了透明属性,了解到一个材质是如何实现透明的。虽然一个透明程度材质可以被绘制在场景中,但却不能改变场景中已经绘制的东西。这也就表示这些材质不能表现出玻璃或者水下的舞台形变。为了模拟这类形变,在这里介绍一种新的技术:Grab pass,这允许我们访问到当前绘制为止已经绘制在场景中的数据并使用他。
代码很简单,grab pass是Unity定义的一个特殊的pass,他将自动创建一个名字为TextureName的纹理,默认的名称为_GrabTexture,你需要在下一个pass中定义才能进行访问。通过对该纹理的采样就可以得到当前绘制之前的内容。计算采样的纹理坐标的思路是把顶点坐标转换成屏幕坐标,这里Unity为我们提供了内之爱函数:ComputeGrabScreenPos(o.vertex);,然后在片元着色器真正采样的时候使用宏UNITY_PROJ_COORD得到最后的结果,这个宏可以认为他就是返回了原本的值,查看他的定义可以知道只是根据PSP做了区分。
#if defined(SHADER_API_PSP2) #define UNITY_BUGGY_TEX2DPROJ4 #define UNITY_PROJ_COORD(a) (a).xyw #else #define UNITY_PROJ_COORD(a) a #endif GrabPass{"TextureName"}玻璃是一个比较复杂的材质,大部分玻璃材质并不完美,因为为了更完美需要使的透过玻璃看到的物体产生形变。思想是在顶点和片元着色器中利用Grab pass的texture稍微改变UV纹理坐标去构造形变。
可以看出透过球体能看到形变(不怎么透明,还是可以看出来的),二期从侧面看还有环境映射的效果。从片元着色器代码可以看出,我们沿法线方向改变了对Grab pass纹理的采样,而达到看上去的形变效果。
Shader "CookbookShaders/self/grab" { Properties { _MainTex("Base (RGB) Trans(A)", 2D) = "white"{} _BumpMap("Noise text", 2D) = "bump" {} _Magnitude("Magnitude", Range(0, 1)) = 0.05 _Color("Color", Color) = (1, 1, 1, 1) } SubShader{ Tags{ "Queue" = "Transparent" } GrabPass { "_GrabTexture" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _GrabTexture; sampler2D _MainTex; sampler2D _BumpMap; float _Magnitude; float4 _Color; struct vertInput { float4 vertex: POSITION; float2 texcoord: TEXCOORD0; }; struct vertOutput { float4 vertex: POSITION; float2 texcoord: TEXCOORD0; float4 uvgrab: TEXCOORD1; }; vertOutput vert(vertInput v) { vertOutput o; o.vertex = UnityObjectToClipPos(v.vertex); o.texcoord = v.texcoord; o.uvgrab = ComputeGrabScreenPos(o.vertex); return o; } half4 frag(vertOutput i):COLOR { half4 mainColor = tex2D(_MainTex, i.texcoord); half4 bump = tex2D(_BumpMap, i.texcoord); half2 distortion = UnpackNormal(bump).rg; i.uvgrab.xy += distortion * _Magnitude; fixed4 col = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab)); return col * mainColor * _Color; } ENDCG } } FallBack "Diffuse" }原理和上面的差不过,只不过增加了噪声贴图和引入sin函数,实现对形变的随时间的变化而改变。这里不过多叙述。
Shader "CookbookShaders/self/water" { Properties{ _NoiseTex("Base (RGB) Trans(A)", 2D) = "white"{} _Period("Period", Range(0, 50)) = 1 _Magnitude("Magnitude", Range(0, 1)) = 0.05 _Color("Color", Color) = (1, 1, 1, 1) _Scale("Scale", Range(0, 10)) = 1 } SubShader{ Tags{ "Queue" = "Transparent" } GrabPass { "_GrabTexture" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _GrabTexture; sampler2D _NoiseTex; float _Period; float _Magnitude; float4 _Color; float _Scale; struct vertInput { float4 vertex: POSITION; float2 texcoord: TEXCOORD0; float4 uvgrab: TEXCOORD2; }; struct vertOutput { float4 vertex: POSITION; fixed4 color : COLOR; float2 texcoord: TEXCOORD0; float4 worldPos: TEXCOORD1; float4 uvgrab: TEXCOORD2; }; vertOutput vert(vertInput v) { vertOutput o; o.vertex = UnityObjectToClipPos(v.vertex); o.texcoord = v.texcoord; o.worldPos = mul(unity_ObjectToWorld, v.vertex); o.uvgrab = ComputeGrabScreenPos(o.vertex); return o; } half4 frag(vertOutput i) :COLOR { float sinT = sin(_Time.w / _Period); float2 distortion = float2(tex2D(_NoiseTex, i.worldPos.xy / _Scale + float2(sinT, 0)).r - 0.5, tex2D(_NoiseTex, i.worldPos.xy / _Scale + float2(0, sinT)).r - 0.5 ); i.uvgrab.xy += distortion * _Magnitude; fixed4 col = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab)); col *= _Color; return col; } ENDCG } } FallBack "Diffuse" }从效率上来说,Grab Pass还是比较差的,每次都要渲染生成一张额外的texture
