到目前为止,我们都是基于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"
}
#pragma vertex vert
#pragma fragment frag
struct vertInput
{
float4 vertex: POSITION;
float2 texcoord: TEXCOORD0;
};
struct vertOutput
{
float4 vertex: POSITION;
float2 texcoord: TEXCOORD0;
};
语义 | 描述 |
---|---|
POSITION, SV_POSITION | 顶点的坐标,在模型空间内,float3/float4 |
NORMAL | 顶点的法向量,float3 |
TEXCOORD0 … TEXCOORDi | 纹理坐标,float2/float3/float4 |
TANGENT | 切向量,用于法线映射,float4 |
COLOR, COLOR0, DIFFUSE, SV_TARGET | 顶点颜色信息, float4 |
COLOR1 | 顶点存储的第二颜色,通常是高光颜色。float4 |
+ 输出结构语义绑定
语义 | 描述 |
---|---|
POSITION, SV_POSITION, HPOS | 顶点坐标,在摄像机坐标系内(clip space,范围0-1) |
COLOR, COLOR0, COL0, COL, SV_TARGET | 主颜色 |
COLOR1, COL1 | 第二颜色 |
TEXCOORD0…TEXCOORDi | 纹理坐标 |
WPOS | The 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