一个最简单的顶点/片元着色器
顶点/片元着色器的基本结构
Shader "MyShaderName"{
Properties{
//属性
}
SubShader{
//针对显卡A的SubShader
Pass{
//设置渲染状态和标签
//开始Cg代码片段
CGPROGRAM
//该代码片段的编译指令,例如
#pragma vertex vert
#pragma fragment frag
//Cg代码写这
ENDCG
//其他设置
}
//其他需要的Pass
}
SubShader{
//针对显卡B的SubShader
}
//上述SubShader都失败后用于回调的Unity Shader
Fallback "VertexLit"
}
其中,最重要的是Pass语义块,我们绝大部分代码都写在这个语义块里面。
Shader "Unity Shaders Book/Chapter 5/Simple Shader"{
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert (float4 v : POSITION) : SV_POSITION {
return UnityObjectToClipPos (v);
}
fixed4 frag() : SV_Target {
return fixed4 (1.0, 1.0, 1.0, 1.0);
}
ENDCG
}
}
}
第一行定义了Shader的名字,然后声明了SubShader和Pass语句块。接着就是CGPROGRAM和ENDCG所包围的CG代码片段
#pragma vertex vert
#pragma fragment frag
它们告诉Unity,哪个函数包含了顶点着色器代码,哪个函数包含了片元着色器代码。
#pragma vertex name
#pragma fragment name
name是我们指定的函数名,可以是任意自定义合法函数名,但一般使用vert和frag两个函数
float4 vert (float4 v : POSITION) : SV_POSITION {
return UnityObjectToClipPos (v);
}
顶点着色器代码,逐顶点执行的。vert 函数的输入v包含了这个顶点的位置,通过POSITION语义指定的。返回值是一个float4 类型的变量,它是该顶点在裁剪空间中的位置,POSITION 和 SV_POSITION都是Cg/HLSL中的语义,不可省略,POSITION将告诉Unity,把模型的顶点坐标填充到输入参数 v 中,SV_POSITION将告诉Unity,顶点着色器输出的是裁剪空间中的顶点坐标。这些语义告诉系统用户需要哪些输入值,以及用户输出什么。
顶点着色器只包含了一行代码,UNITY_MATRIX_MVP被UnityObjectToClipPos替代(unity自动转换)。这一步就是把顶点坐标从模型空间转换到裁剪空间中。
Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
自动转换信息如上
fixed4 frag() : SV_Target {
return fixed4 (1.0, 1.0, 1.0, 1.0);
}
本例frag函数没有任何输入,它输出的是一个fixed4类型的变量,并且使用了SV_Target语义进行限定。SV_Target也是HLSL的一个系统语义,它告诉渲染器,把用户的输出颜色存储到一个渲染目标中,这里将输出到默认的帧缓存中。片元着色器代码返回了一个表示白色的fixed4x4类型的变量。片元着色器输出的颜色的每个分量范围在[0, 1],其中(0, 0, 0)表示黑色,而(1, 1, 1)表示白色。
模型数据从哪来
现在,我们要得到模型上每个顶点的纹理坐标和法线方向。因此,我们需要为顶点着色器定义一个新的输入参数,这个参数不再是一个简单的数据类型,而是一个结构体。
Shader "Unity Shaders Book/Chapter 5/Simple Shader"{
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//使用一个结构体来定义顶点着色器的输入
struct a2v {
//POSITION 语义告诉Unity,用模型空间的顶点坐标填充 vertex 变量
float4 vertex : POSITION;
//NORMAL 语义告诉Unity,用模型空间的法线方向填充 normal 变量
float3 normal : NORMAL;
//T