Shader
应该写在首行,他表示了这个shader在选择界面的名字,后接一个大括号
Shader “MyShader/Shader1”
{
Properties{xxxx}
SubShader{xxxx}
}
//一个shader的完整写法
Shader "MyShader"{
Properties{
_Int ("int",Int) = 1
}
SubShader{
Pass{
CGPROGRAM
//do something
ENDCG
}
}
}
properties
应该包含在Shader的大括号内。主要是的作用是声明属性在面板上,也就是说即使不在这声明,我们也可以在具体的代码片段里面来声明所需要的变量,他的格式如下
Properties
{
__PropertiesName ("Name",Type) = "DefaultValue"
//PropertiesName 用于声明变量的名字,在后面代码中使用,
//Name 是在显示窗口设置使用。
//Type 用于声明变量的类型,大致有Int,Float,Texture2D等。
// DefaultValue 为默认值
}
//一些基本变量的例子
{
_Int ("Int",Int) = 1
_Float ("Float",Float) =1.2
_Range ("Range",Range(0.0,1.0)) =0.5
_Color ("Color",Color) = (1,1,1,1)
_Vector ("Vetor",Vector) = (1,1,1,1)
_2D ("2D",2D) = "" ()
_3D ("3D",3D) ="white" ()//引号内是默认值,一般为颜色,括号内可以为贴图
}
SubShader
shader代码的主体,我们需要在里面写好内容。最终只会有一个SubShader块被执行,但是我们可以写多个SubShader,以便不同平台的适配功能。unity会加载所有的SubShader,最终选出一个可以使用的。如果都不能使用,那么就会执行Fallback中指明的Shader。严格来说是某个渲染功能,我们并没有在自己所写下的SubShader块中实现,那他就会去找FallBack所指定的Shader,其实可以简单理解为在渲染流水线中我们会来自己写下的Shader来找相应的功能块,然后看看有没有,有的话便执行,没有的话便去执行FallBack的Shader,在阴影的例子中便是这样,我们在15的例子中没有写关于阴影的代码,所以他会去执行FallBack的例子。而寻找判断的标准便是我们在Tags中设置的值。比如
“LightMode” = “ForwardBase” //用于计算第一个逐像素光源和逐顶点和SH光源
“LightMode” = “ForwardAdd”//用于计算接下来的逐像素光源
“LightMode” = “ShadowCaster” //用于处理阴影
组成
一个SubShader由 Tags,RenderSetUp,Pass块 组成。其中前两个是可选项,Pass块则是必写。
每一个Pass都定义了一个完整的渲染流程。但是数量过多会导致性能下降,因此最好使用较少的Pass数量
Tag 和 RenderSetUp是可以在Pass内声明的。
Tag: 为一组键值对,是SubShader与引擎的桥梁。用于告知引擎希望怎么以及何时渲染这个对象,声明在SubShader中的标签和pass中的会有一些不同,
Tags {"TagName" ="ValueName" "TagName2" = "ValueName2"}
SubShader{
Pass{
CGPROGRAM
xxx
ENDCG
}
}
语义
语义是的本质是一种解释,她负责告诉我们的shader一些参数的输入输出,比如我们需要获得的顶点位置,以及我们想要输出的内容给谁。对于SV_POSITION.他在某些意义上与POSITION是等价的。但是在不同的平台上又是不等价的,因此我们最好还是要严格区分。
- 使用SV_POSITION来描述顶点着色器的输出顶点位置。使用SV_Target来描述片元着色器的颜色输出,
其他
Tags
Tags主要用于告知流水线一些信息,比如渲染模式,该有哪个Tag标记的Pass块渲染之类的信息。比如LightMode中的设置不同就可以让所写的Pass来渲染特定的内容。
像是类似于Tags,Blend等信息都应该写在 CGPROGRAM外。
程序给顶点着色器输入信息,顶点着色器给片元着色器传递信息
由于是很多的信息集合体,因此我们需要使用一个结构体来传递,其中变量需要跟上语义,且语义是不可以缺少的。
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD;
};
而对于顶点着色器与片元着色器的传输我们依然可以通过一个结构体来实现,然后让我们的顶点着色器返回值变为返回结构体。但需要注意的是我们仍然需要在返回的结构体中指定顶点着色器的返回值,即
struct v2f{
float4 pos :SV_POSITION;
fixed3 color:COLOR0;
};
其中的pos即是我们的返回值。也就是裁剪空间下的顶点坐标。由于在渲染流水线中我们是先执行了顶点着色器,因此数据以及获得了,我们在片元着色器中也就可以直接写下参数了。在片元着色器中得到的结果其实是经过了插值以后的结果。即
fixed3 frag(v2f i){
return i.color;
}
一些写法的建议
- 在代码中我们会有三种精度的类型,float,half,fixed。他们的精确度以此降低。在大多数情况下,他们三者都是按照最高精度来使用,完全等价,但在移动端,他们就会有一些细微的性能差距,因此在使用类型上,应该尽可能的使用精度较低的类型
- 如果可以,不要在shader中使用流程控制语句,因为他们会对性能造成较大的影响。
- 记住不要除以0,因为这会带来一些不确定的bug,如果非要除以,那建议除以一个很小的小数。
关于内置函数
UnityCg.cginc
函数名 | 描述 |
---|---|
float3 WorldSpaceViewDir(float4 v) | 输入一个模型空间下的顶点位置,返回世界空间下从该点到摄像机的观察方向 |
float3 UnityWorldSpaceViewDir(float4 v) | 输入一个世界空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向 |
float3 ObjSpaceViewDir(float4) | 输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察位置 |
float3 UnityObjectToWorldNormal(float3 norm) | 把法线方向从模型空间变换到世界空间 |
float3 UnityObjectToWorldDir(float3 dir) | 把方向矢量从模型空间转化到世界空间 |
float3 UnityWorldToObject(float3 dir) | 把方向矢量从世界空间转化到模型空间 |
float4 UnityObjectToClipPos(float4 vertex) | 把模型空间下的顶点转化到世界空间 |
float3 WorldSpaceLightDir(float4 v) | 仅用于前向渲染,输入一个模型空间顶点,返回世界空间中该点到光源的光照方向,没有被归一化 |
float3 UnityWorldSpaceLightDir(float4 v) | 仅用于前向渲染,输入一个世界空间的顶点位置,返回世界空间中从该点到光源的光照方向,没有被归一化 |
float3 ObjSpaceLightDir(float4 v) | 仅用于前向渲染,输入一个模型空间的顶点位置,返回模型空间中该点到光源的光照方向,没有被归一化 |