转载请注明出处凯尔八阿哥专栏
1.3.1、从简单的模板程序开始
1、打开Unity程序,在Project中选择Create 选择Shader>Unlit Shader。程序的名字最好和shader的用途有关联,让人一看就知道这个shader是用来做什么的。
2、将下面的模板程序复制到这个新建的Shader程序文件中,替换Unity自动生成的代码。程序注释部分给出了每一行代码的含义。
Shader "Cg Minimal shader" { // 定义Shader的名字,这个名字胡出现控制面 //板上作为材质的shader程序索引,还可以通过加“/”来将shader程序归类
//如:Custom/Cg Minimal shader
SubShader { // 一个Shader有多个SubShader,Unity会自动选择最合适的
Pass { // 一个SubShader有多个Pass块
CGPROGRAM // Unity的Cg程序预编译命令
#pragma vertex vert
// 定义一个顶点着色器程序名字为vert
#pragma fragment frag
// 定义一个片段着色器程序名字为frag
float4 vert(float4 vertexPos : POSITION) : SV_POSITION
// 顶点着色器程序,参数进行语义绑定
//POSITION为输入绑定,SV_POSITION为输出绑定
{
return mul(UNITY_MATRIX_MVP, vertexPos);
// 用内置的矩阵UNITY_MATRIX_MVP转变顶点着色器程序的输入数据 //vertexPos并返回这个转变之后的数据,之后它将作为片段
// 着色器程序的输入参数
}
float4 frag(void) : COLOR // 片段着色器,它的输入语义这里用了Void
//表示接受所有的类型,你还可以换成和顶点、 //程序的输入一样的语义
//float4 vertexPos : POSITION(SV_POSITION)
//用COLOR进行输出语义绑定
{
return float4(1.0, 0.0, 0.0, 1.0);
}
ENDCG // 结束Cg程序的预编译命令
}
}
}
3、创建一个材质球,Unity中有多种方式来对这个材质球进行shader程序绑定,推荐使用一个简便的方式,可以在创建材质球的时候,选择你要绑定的shader程序,然后点Create>Material。这是最方便直接的方式,不仅名字会保持和shader文件名一样,同时还将此shader同材质进行了绑定,其他的方式都比较繁琐。
4、创建一个Cube并将这个材质球赋给这个Cube,Cube显示为完全的红色,如果不是则说明这个Shader程序没有被编译,此时只需要关闭并重新打开即可。
注:本文中规定Shader即为顶点着色器和片段着色器,在别的文章中Shader只是顶点着色器和片段着色器中的一个。1.3.2、Unity中顶点和片段程序的输入输出
1、顶点程序的输入参数绑定:在上一小节中顶点程序vert中通过SV_POTION进行了输出语义绑定,返回的值即为片段程序的输入参数,那么顶点程序的输入参数是从哪里来的呢?在Unity中顶点程序的输入参数来自于一个物体的Mesh Render组件,每一帧它会自动将物体的所有mesh数据发送给GPU,这个就是所谓的drawcall。mesh即物体模型的网格,如果网格数少,draw call自然就会减少。网格上的数据包括位置、法线、颜色等信息。这些信息都有对应的语义词,可以通过语义词进行绑定。定义结构体的方法进行顶点程序输入语义绑定,如:struct vertexInput {
float4 vertex : POSITION; // 模型空间的位置
float4 tangent : TANGENT;
// 模型表面的切线
float3 normal : NORMAL; // 模型表面的法线,它通常是单位长度
float4 texcoord : TEXCOORD0; // 第0套贴图坐标
// (也称为UV坐标,范围在0~1之间)
float4 texcoord1 : TEXCOORD1; // 第1套贴图坐标,
fixed4 color : COLOR; // 颜色
};
2、Unity中内置的输入参数结构体:Unity中内置了一些顶点程序的输入参数的结构体,在Unity>Editor>Data>CGInclude>UnityCG.cginc文件中可找到,
struct appdata_base {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct appdata_tan {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct appdata_full {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
fixed4 color : COLOR;
// 多个贴图UV坐标并不是在每一个显卡上都支持
};
struct appdata_img {
float4 vertex : POSITION;
half2 texcoord : TEXCOORD0;
};
要在代码中使用这些内置结构体一定要添加命令#include “UnityCG.cginc”。如下代码:
Shader "Cg shader parameters" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : TEXCOORD0;
};
vertexOutput vert(appdata_full input)
{
vertexOutput output;
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
output.col = input.texcoord;
return output;
}
float4 frag(vertexOutput input) : COLOR
{
return input.col;
}
ENDCG
}
}
}
3、更改顶点程序的输出参数:将顶点程序中的output.col = input.texcoord代码替换成如下的代码可以得到不同的效果:
output.col = float4(input.texcoord.x, 0.0, 0.0, 1.0);
//output.col = float4(0.0, input.texcoord.y, 0.0, 1.0);
//output.col = float4(
(input.normal + float3(1.0, 1.0, 1.0)) / 2.0, 1.0);
1.3.3、Unity中控制Shader变量方法
Uniform参数的应用:Uniform通常都是用来修饰一些由应用程序传入的离散数据,如:
<span style="font-size:18px;">uniform float4x4 Object2World;</span>
表示从外部传入一个四乘四的矩阵,如此,Shader程序就可以和外部的应用程序进行交互。在Unity中可以通过控制面板或者C#脚本来控制这些变量。
1、控制面板上控制uniform参数:在上面的模板程序的基础上添加一个属性,代码如下:
Shader "Unlit/Propertey"
{
//添加一个属性块,在属性块中定义一个变量
Properties{
_Color("color", Color) = (0.0, 1.0, 0.0, 1.0)
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//在Pass块中使用该属性块中的变量必须要使用
//uniform关键字重新定义该变量
uniform float4 _Color;
float4 vert(float4 vertexPos : POSITION) : SV_POSITION
{
return mul(UNITY_MATRIX_MVP, vertexPos);
}
float4 frag(void) : COLOR
{
return _Color;
}
ENDCG
}
}
}
在SubShader上面添加了一个属性模块,属性块中定义了一个颜色变量_Color,属性模块中变量的定义形式为:
_ParamName("Display Name",ParamType)=defaultValue[{options}]
●_ParamName:该属性变量的名字,Shader代码中即使用这个变量名字来索引;
●Display Name:在控制面板上显示该属性的名字,在Unity中可以是中文;
●ParamType:该属性变量的类型,这个类型并不是Cg语言的数据类型,而是Unity中特有的属性类型,在Unity中属性变量的类型以及各自对应的Cg数据类型为:
●数值类型:
●Range(min,max):会在控制面板上显示一个滑动条,最小值为min,最 大值为max,值的类型为浮点类型;
●Flaot:浮点类型数据,注意此处第一个字母大写;
●Int:整数类型数据,注意此处第一个字母的大写;
●向量类型:
●Color:颜色属性,也是一个四维的向量,值的范围为0~1;
●Vector:四维向量,值可以任意指定;
●贴图类型:
●2D:2的阶数(如256,1024等)大小的贴图,它的坐标UV范围(0~1,0~1);
●Rect:非2的阶数的大小的贴图,它的坐标UV的范围(0~1,0~1);
●Cube:立方体贴图,即六张2D贴图的组合;
●3D:3D纹理贴图,Unity的脚本和Shader中支持3D贴图的使用和创建, 但是3D贴图的使用不能像2D贴图那样直接。它的坐标UVW的范围 (0~1,0~1,0~1),常见的应用如,火焰、烟雾以及光线等。
定义了属性块中的变量必须要在Pass块中使用uniform关键字重新定义这个变量才能在该Pass块的Shader代码中使用,如果有多个Pass块都会使用这个变量就必须要在每一个Pass块中都重新定义这个变量。在上面的代码中的第一个Pass块中使用uniform定义了变量_Color,在之后的Pass块中要使用都必须定Pass{
CGPROGRAM
...
#pragma fragment frag
uniform float4 _Color;
...
ENDCG
}
//如果有多个Pass块使用这个变量,需要在每一个Pass中都定义这个变量
Pass{
CGPROGRAM
...
uniform float4 _Color;
...
ENDCG
}
...
义这个变量。uniform变量的定义必须在CGPROGRAM~ENDCG之间,否则会编译报错,最好的习惯是将变量的定义在紧跟CGPROGRAM之后。
●option:这个选项只对2D、Rect以及Cube贴图有用,它是在没有选中用户自己的贴图时候默认使用Unity内置的贴图来填充,这些贴图的类型可以是 “white”,“black”, “gray” 以及 “bump”中的一种,也可以为空。
属性的个性显示:
●[HideInInspector]:在控制面板上隐藏这个属性变量,直接在属性块中的变量前面添加即可,如:
Properties{
[HideInInspector]
_Color("color", Color) = (0.0, 1.0, 0.0, 1.0)
}
●[NoScaleOffset]:这个是针对贴图变量的,隐藏控制面板中的贴图变量的tilling和offset
●[Gamma]:将float和Vector类型的属性在控制面板上显示成类似颜色属性那样的UI交互面板,常用的类型有:
●Toggle]:定义float类型的属性变量,在控制面板上将显示一个勾选框如:
[Toggle] _Invert("Invert?", Float) = 0
这个变量的值为1或0,勾选表示1,反之为0;
●[Enum]:定义float类型的属性变量,控制面板上显示一个下拉列表,下拉列表中可以选择自定义的数据,如:
[Enum(One,1,Two,2)] _Num ("Num Enum", Float) = 1
在控制面板上将显示下拉列表,列表中有两个变量One和Two,它们的值分别为1和2。最大能枚举的列表量个数为7个;
●[KeywordEnum]:定义float类型的属性变量,在控制面板上显示一个下拉列表,里面可以枚举Shader的关键字。
●[PowerSlider]:定义Range类型的变量,Range变量本来就会在控制面板上显示滑动条,但是在拖动的时候变量的值是以线性进行增减的,而加上这个特性的Range变量会以指定的指数形式增减。如:
[PowerSlider(2.0)] _Shin ("Shin", Range (0.01, 1)) = 0.1
在控制面板上拖动这个变量将会以2次曲线的形式进行增减。
[Space]:定义所有类型的变量,使得变量在控制面板上距离上一个变量保持一定的距离,如:其中50表示间隔的空间,可以不添加,也即为默认一个空格的距离
[Space(50)] _Prop ("Prop", Float) = 0
●[Header]:定义所有类型的变量,能在控制面板上显示自定义的变量标题,如:“Floatvariable”会出现在控制面板上变量的上方,不能为中文。
[Header(Float variable)] _P ("P", Float) = 0
2、在代码中控制uniform参数:
要在代码中控制uniform的参数,可以不用在属性块中定义,只需要在Shader代码中使用关键字uniform定义变量。修改上述的Shader代码如下:
Shader "Unlit/Propertey"
{
//添加一个属性块,在属性块中注释掉刚刚定义的变量
Properties{
//_Color("color", Color) = (0.0, 1.0, 0.0, 1.0)
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//必须使用uniform关键字定义该变量才能在C#脚本 //中调用
uniform float4 _Color;
float4 vert(float4 vertexPos : POSITION) : SV_POSITION
{
return mul(UNITY_MATRIX_MVP, vertexPos);
}
float4 frag(void) : COLOR
{
return _Color;
}
ENDCG
}
}
}
创建一个C#脚本,将下面的代码复制到新建的C#脚本中,并将这个脚本拖到上述Shader的物体上,保证C#脚本和Shader代码运行在同一个物体上。
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class Propertey : MonoBehaviour {
private Renderer _renderer;
// Use this for initialization
void Start () {
_renderer=GetComponent<Renderer>();
}
// Update is called once per frame
void Update () {
_renderer.sharedMaterial.SetColor("_Color", new Color(1, 1, 0, 0.5f));
}
}
本段代码中在类的前面添加了[ExecuteInEditMode],保证脚本可以在编辑状态下就被运行。通过代码
_renderer.sharedMaterial.SetColor("_Color", new Color(1, 1, 0, 0.5f));
对shader中的“_Color”变量进行设置,你可以通过更改代码中的值类浏览变换的效果。
sharedMaterial和material的区别:代码中可以将sharedMaterial替换成material,编译会报错并提示我们使用sharedMaterial,此处是因为在编辑模式下运行,如果去掉编辑模式,直接运行得到的效果也是一样的。material是针对当前的材质,在当前的材质基础上再实例化一个材质,使用material的时候,如图1.5所示,会在该材质球的后面添加了“(Instance)”,表示这个脚本
在使用material控制是只对当前的材质球有效,对其他使用该材质球的物体无
图1.5C#脚本中使用material控制uniform参数
效。而在使用sharedMaterial的时候是没有“(Instance)”,在C#脚本中对该材质球进行修改会影响到其他使用该材质球的物体。如此,我们必须要在使用脚本控制的时候区别对待。
脚本控制的常用方法:
Color GetColor(string propertyName):通过属性名字获取颜色属性值,并使用这个值作为返回值;
float GetFloat(string PropertyName):通过属性名字获取一个浮点类型的属性值,并使用值作为返回值;
int GetInt(string PropertyName):通过属性名字获取一个整数类型的属性值,并将这个值作为返回值;
TextureGetTexture(string PropertyName):通过属性名字来获取一个贴图,并将这个贴图作为返回值;
对应Color、float、int以及Texture的还有设置方法,都是通过属性名字来对相应的属性进行设置,更多的方法请参阅Unity官网文档,网址为:点击打开链接1.3.4、Unity中写Shader程序应用
新建一个Shader程序,将下面的代码复制到这个Shader程序文件中
Shader "UnityCg/worldSpaceColor"
{
Properties{
_Point("原点", Vector) = (0., 0., 0., 1.0)
_DistanceNear("阈值距离", Float) = 5.0
_ColorNear("离原点小于阈值距离的点的颜色", Color) = (0.0, 1.0, 0.0, 1.0)
_ColorFar("离原点大于阈值距离的点的颜色", Color) = (0.3, 0.3, 0.3, 1.0)
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
//使用关键字uniform再定义属性中的变量
uniform float4 _Point;
uniform float _DistanceNear;
uniform float4 _ColorNear;
uniform float4 _ColorFar;
struct vertexInput {
float4 vertex : POSITION;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 position_in_world_space : TEXCOORD0;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
//_Object2World是Unity内置的四乘四矩阵,使用了#include "UnityCG.cginc" 命令就可以直接使用,不用再使用uniform关键字进行定义
output.position_in_world_space =
mul(_Object2World, input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
//计算原点和这个物体的片段点之间的距离
float dist = distance(input.position_in_world_space,
_Point);
if (dist < _DistanceNear)
{
return _ColorNear;
}
else
{
return _ColorFar;
}
}
ENDCG
}
}
}
这个程序实现的效果即:物体在世界坐标系的位置与预设的原点位置“Point”的距离小于阈值“DistanceNear”部分就显示“ColorNear”的颜色值,大于阈值“DistanceNear”部分就显示“ColorFar”的颜色值。