本篇介绍的是编写着色器的基础示例
最简单的顶点/片元着色器
hello world
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Shader "Unlit/Chapter5-SimpleShader" { SubShader{ pass{ CGPROGRAM //告诉Unity哪个函数包含了顶点着色器代码 #pragma vertex vert //告诉Unity哪个顶点包含了片元着色器代码 #pragma fragment frag //逐顶点执行,通过POSITION语义指定了输入v包含了顶点的位置,语义告诉系统用户需要哪些输入值和用户的输出 //SV_POSITION告诉Unity,顶点着色器的输出时裁剪空间中的顶点坐标 float4 vert(float4 v:POSITION):SV_POSITION{ return mul(UNITY_MATRIX_MVP,v); } //SV_TARGET语义告诉渲染器把用户的输出颜色存储到一个渲染目标中 fixed4 frag():SV_TARGET{ return fixed4(1.0,1.0,1.0,1.0); } ENDCG }
} }
|
获取更多数据
可以定义结构体,包含模型数据
格式:
1 2 3 4 5
| struct StructName{ Type Name: Semantic; Type Name: Semantic; ...... };
|
Unity支持的语义有:POSITION,TANGENT,NORMAL,TEXCOORD0,TEXCOORD1,TEXCOORD2,TEXCOORD3,COLOR等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| Shader "Unlit/Chapter5-SimpleShader" { SubShader{ pass{ CGPROGRAM //告诉Unity哪个函数包含了顶点着色器代码 #pragma vertex vert //告诉Unity哪个顶点包含了片元着色器代码 #pragma fragment frag
struct a2v{ //用模型空间的顶点填充vertex变量 float4 vertex:POSITION; //用模型空间的法线方向填充normal变量 float3 normal:NORMAL; //用模型的第一套纹理坐标填充texcoord变量 float4 texcoord:TEXCOORD0; } //逐顶点执行 //SV_POSITION告诉Unity,顶点着色器的输出时裁剪空间中的顶点坐标 float4 vert(a2v v):SV_POSITION{ return mul(UNITY_MATRIX_MVP,v.vertex); } //SV_TARGET语义告诉渲染器把用户的输出颜色存储到一个渲染目标中 fixed4 frag():SV_TARGET{ return fixed4(1.0,1.0,1.0,1.0); } ENDCG } } }
|
数据来源:使用该材质的Mesh Render组件,在每帧调用Draw Call的时候,Mesh Render会把它负责渲染的模型数据发送给Unity Shader。一个模型通常包含了一组三角面片,每个三角面片由3个顶点组成,每个顶点包含了一些数据:顶点位置、法线、切线、纹理坐标、顶点颜色等。
着色器通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| Shader "Unlit/Chapter5-SimpleShader" { SubShader{ pass{ CGPROGRAM //告诉Unity哪个函数包含了顶点着色器代码 #pragma vertex vert //告诉Unity哪个顶点包含了片元着色器代码 #pragma fragment frag
struct a2v{ //用模型空间的顶点填充vertex变量 float4 vertex:POSITION; //用模型空间的法线方向填充normal变量 float3 normal:NORMAL; //用模型的第一套纹理坐标填充texcoord变量 float4 texcoord:TEXCOORD0; } ; struct v2f{ //pos中包含了顶点在裁剪空间中的位置信息 float4 pos:SV_POSITION; //COLOR0语义可以用于存储颜色信息 fixed3 color:COLOR0; } ; //逐顶点执行 //SV_POSITION告诉Unity,顶点着色器的输出时裁剪空间中的顶点坐标 //顶点着色器的输出结构必须包含一个语义为SV_POSITION的变量,否则渲染器 //无法得到裁剪空间中的顶点坐标 v2f vert(a2v v){ v2f o; //声明输出结构 o.pos=mul(UNITY_MATRIX_MVP,v.vertex); //v.normal包含了顶点的法线方向,分量范围在【-1,1】 //现映射到【0,1】 //存到o.color中传到片元着色器 o.color=v.normal*0.5+fixed3(0.5,0.5,0.5); return o; } //SV_TARGET语义告诉渲染器把用户的输出颜色存储到一个渲染目标中 fixed4 frag(v2f i):SV_TARGET{ return fixed4(i.color,1.0); } ENDCG } } }
|
使用属性
在材质面板上展示,方便调整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| Shader "Unlit/Chapter5-SimpleShader" { Properties{ //声明一个Color类型的属性 _Color ("Color Tint", Color)=(1.0,1.0,1.0,1.0) } SubShader{
pass{ CGPROGRAM //告诉Unity哪个函数包含了顶点着色器代码 #pragma vertex vert //告诉Unity哪个顶点包含了片元着色器代码 #pragma fragment frag
//在CG代码中,我们需要定义一个与属性名称和类型都匹配的变量 uniform fixed4 _Color; struct a2v{ //用模型空间的顶点填充vertex变量 float4 vertex:POSITION; //用模型空间的法线方向填充normal变量 float3 normal:NORMAL; //用模型的第一套纹理坐标填充texcoord变量 float4 texcoord:TEXCOORD0; }; struct v2f{ //pos中包含了顶点在裁剪空间中的位置信息 float4 pos:SV_POSITION; //COLOR0语义可以用于存储颜色信息 fixed3 color:COLOR0; }; //逐顶点执行 //SV_POSITION告诉Unity,顶点着色器的输出时裁剪空间中的顶点坐标 v2f vert(a2v v){ v2f o; //声明输出结构 o.pos=mul(UNITY_MATRIX_MVP,v.vertex); //v.normal包含了顶点的法线方向,分量范围在【-1,1】 //现映射到【0,1】 //存到o.color中传到片元着色器 o.color=v.normal*0.5+fixed3(0.5,0.5,0.5); return o; } //SV_TARGET语义告诉渲染器把用户的输出颜色存储到一个渲染目标中 fixed4 frag(v2f i):SV_TARGET{ fixed3 c=i.color; //使用Color属性来控制输出颜色 c*=_Color.rgb; return fixed4(c,1.0); } ENDCG } } }
|
为了在CG代码中可以访问到定义的属性,必须在CG代码片段提前定义一个新的变量,这个变量的名称和类型必须与Properties语义块中的属性定义相匹配。
匹配关系
uniform关键词时CG中修饰变量和参数的一种修饰词,仅仅提供一些关于该变量的初始值时如何指定和存储的相关信息。可以省略(仅限unity)
Unity内置文件和变量
Unity内置了提前定义的函数、变量和宏辅助编程
内置的包含文件
#include 文件后缀是.cginc
CGincludes文件夹包含了所有的内置包含文件。
主要文件和用处:
UnityCG.cginc是最常接触的一个包含文件,可以使用其提供的结构体和函数为编写提供方便。
Unity还提供了用于访问时间、光照、雾效和环境光等目的的变量,这些变量大多位于UnityShaderVariables.cginc中,与光照有关的内置变量还会位于Lightning.cginc,AutoLight.cginc中
Unity提供的CG/HLSL语义
语义:一个赋给Shader输入和输出的字符串,可以让Shader知道从哪里读取数据,并将数据输出到哪里
系统数值语义(system-value semantics):以SV开头,带渲染流水线中有特殊的含义,不可以随便赋值
Unity支持的语义
一个语义可以使用的寄存器只能处理4个浮点值。
如果想定义矩阵变量就需要使用更多的空间。一种方法是拆分。
float4x4拆成4个float4类型的变量,每个变量存储了矩阵中的一行数据。
一些补充
注意语法差异: