Unity_shader
大体结构
- Shader:先是最大的一层外壳,写上shader再指定其在哪个目录下,名字是什么。
- Properties:在外壳中第一层结构是Properties所包裹的属性区域,里面定义写入一些属性。
- SubShader:在属性之后是SubShader,是shader的核心,主要执行里面的代码对物体进行渲染,SubShader可以有多个,一般写为性能逐级递减的多个,当计算机硬件读取到靠前的SubShader,发现其中有硬件不支持的操作,那么就会去读取执行下一个SubShader。
- Fallback:在Shader的最后写入,当计算机硬件不支持该Shader里的所有SubShader时,会读取Fallback之后跟的’备用‘shader,然后以其代替这个shader的位置。
基本语法
- 在unity中首先我们创建一个shader(做水面推荐unlit Shader),然后打开其代码,里面会有一些自动生成的代码大致如下:
Shader "Unlit/text"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
这里先提一下,我们在Unity中写shader用的语言是cg语言的Unityshader衍生版,cg语言是基于c语言上的一种语言,与c语言很像,但其主要是作用域GPU上的。(编辑器不支持cg语言识别,所以就直接贴普通代码了)
- 其中第一句话 Shader "Unlit/text"的意思为,一个shader,在Unlit列表中,命名为text(之后往材质上贴的时候会用到)。
- 再往下第一个区块
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
意为属性定义,在这个里面是得到其2d纹理。需要注意的是,在之后的区块中要定义部分变量时要与这里的变量命名完全一致。
- 再往下的一个区块(省略其内容)
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
ENDCG
}
}
在这之中,SubShader是子着色器列表,每一个shader至少得有一个子着色器,其中的pass是一个通道,在pass之中,CGPROGRAM意为插入cg代码开始,ENDCG意为插入cg代码结束。
然后再pass里面的#pragma格式为声明变量绑定从GPU上一个工位传来的值使用格式为:
#pragma 上一个工位传来的变量关键词 绑定命名;
在里面的#include "UnityCG.cginc"是指调用Unity内置的cg语言函数库。
水面波动示例
这里就用上面创建好的那个shader进行改写就行了
所用函数与变量简介
- distance:距离计算函数,得出点与点的距离,第一个参数我们这里使用上一个工位传来的坐标参数,第二个为正弦波发出位置坐标(我这里是平面中心点,平面放在了世界坐标原点)。
- POSITION:意指传递的坐标参数,从上一工位接受坐标绑定时使用这个,向下一工位传递坐标参数的时候也绑定这个。
- TEXCOORD0:第一套纹理的传递参数,与其相似的还有第二套第三套纹理等,仅仅是改变了最后一个数字。
- mul:乘法函数,在这里我们一般作为矩阵相乘使用。
- _Time:时间参数,其中有四个分量,这里我用了第三个,是游戏开始时间的二倍。四个分量分别为(t/20,t,t2,t3);
- sampler2D 2d纹理型变量。
- float4 四个浮点型的绑定类型变量
- unity_WorldToObject:世界坐标转局部坐标的矩阵,使用方法为让需要转换的目标矩阵左乘这个矩阵。
- unity_ObjectToWorld:局部坐标转世界坐标,用法同上。
具体实施
- 首先改一下shader的位置:将第一行代码改为Shader “Custom/Sinshader”,意为我这个Sinshader放在Custom目录下。
- 然后进入pass中修改cg代码:首先在俩个结构体 之前没什么好改的(但有一点点改动,自己看代码对比吧),因为主要实要在渲染上做手脚。 sampler2D _MainTex;这句话命名与之前的属性命名要完全一致(没改动)。之后主要修改vert函数。
- 修改vert函数:在其之中的内容基本都改动了。
为其传入结构体appdata的参数,作为从上一工位传来的坐标与纹理参数,在此基础上修改成为我们需要的v2f型结构体的参数,最后返回这个参数,让之后的函数调用。
用一些函数得到距离作为sin的初相位,在用_Time的分量作为变动参数实现正弦波动,在其中对整体进行调整可以改变振幅,对_Time的分量参数大小改动可以调整波动速率,调整初相位参数以改变波动密度。
完整代码
Shader "Custom/Sinshader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
//distance是一个计算距离的函数,第一个参数我们调用appdata结构体的xyz,
//第二个参数用float3型三个参数都为0即可,得到点到中心的距离
float dist = distance(v.vertex.xyz,float3(0,0,0));
//渲染坐标左乘_Object2World矩阵以变换为世界坐标
o.vertex = mul(unity_ObjectToWorld, v.vertex);
//取先前得到的距离和距离游戏开始的时间的第三个分量(2t)为sin的参数
//来得出正弦波的位置
float hight = sin(dist*3+_Time.z*2);
o.vertex.y = hight;
//渲染坐标左乘_World2Object以让其转回局部坐标
o.vertex = mul(unity_WorldToObject, o.vertex);
o.vertex = UnityObjectToClipPos(o.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}