Unity&Shader基础篇-“Hello Cg”
1.3.1、从简单的模板程序开始
1、打开Unity程序,在Project中选择Create 选择Shader>Unlit Shader。程序的名字最好和shader的用途有关联,让人一看就知道这个shader是用来做什么的。
2、将下面的模板程序复制到这个新建的Shader程序文件中,替换Unity自动生成的代码。程序注释部分给出了每一行代码的含义。
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 | 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中顶点程序的输入参数来自于一个物体的MeshRender组件,每一帧它会自动将物体的所有mesh数据发送给GPU,这个就是所谓的drawcall。mesh即物体模型的网格,如果网格数少,drawcall自然就会减少。网格上的数据包括位置、法线、颜色等信息。这些信息都有对应的语义词,可以通过语义词进行绑定。定义结构体的方法进行顶点程序输入语义绑定,如:1 2 3 4 5 6 7 8 9 10 | 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; // 颜色 }; |
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 | 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”。如下代码:
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 | 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代码替换成如下的代码可以得到不同的效果:
1 2 3 4 | 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通常都是用来修饰一些由应用程序传入的离散数据,如:
1 | uniform float4x4 Object2World; |
表示从外部传入一个四乘四的矩阵,如此,Shader程序就可以和外部的应用程序进行交互。在Unity中可以通过控制面板或者C#脚本来控制这些变量。
1、控制面板上控制uniform参数:在上面的模板程序的基础上添加一个属性,代码如下:
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 | 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,属性模块中变量的定义形式为:
1 | _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块中要使用都必须定1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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]:在控制面板上隐藏这个属性变量,直接在属性块中的变量前面添加即可,如:
1 2 3 4 | Properties{ [HideInInspector] _Color( "color" , Color) = (0.0, 1.0, 0.0, 1.0) } |
●[NoScaleOffset]:这个是针对贴图变量的,隐藏控制面板中的贴图变量的tilling和offset
●[Gamma]:将float和Vector类型的属性在控制面板上显示成类似颜色属性那样的UI交互面板,常用的类型有:
●Toggle]:定义float类型的属性变量,在控制面板上将显示一个勾选框如:
1 | [Toggle] _Invert( "Invert?" , Float) = 0 |
这个变量的值为1或0,勾选表示1,反之为0;
●[Enum]:定义float类型的属性变量,控制面板上显示一个下拉列表,下拉列表中可以选择自定义的数据,如:
1 | [Enum(One,1,Two,2)] _Num ( "Num Enum" , Float) = 1 |
在控制面板上将显示下拉列表,列表中有两个变量One和Two,它们的值分别为1和2。最大能枚举的列表量个数为7个;
●[KeywordEnum]:定义float类型的属性变量,在控制面板上显示一个下拉列表,里面可以枚举Shader的关键字。
●[PowerSlider]:定义Range类型的变量,Range变量本来就会在控制面板上显示滑动条,但是在拖动的时候变量的值是以线性进行增减的,而加上这个特性的Range变量会以指定的指数形式增减。如:
1 | [PowerSlider(2.0)] _Shin ( "Shin" , Range (0.01, 1)) = 0.1 |
[Space]:定义所有类型的变量,使得变量在控制面板上距离上一个变量保持一定的距离,如:其中50表示间隔的空间,可以不添加,也即为默认一个空格的距离
[Space(50)] _Prop ("Prop", Float) = 0
●[Header]:定义所有类型的变量,能在控制面板上显示自定义的变量标题,如:“Floatvariable”会出现在控制面板上变量的上方,不能为中文。
1 | [Header(Float variable)] _P ( "P" , Float) = 0 |
2、在代码中控制uniform参数:
要在代码中控制uniform的参数,可以不用在属性块中定义,只需要在Shader代码中使用关键字uniform定义变量。修改上述的Shader代码如下:
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 | 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代码运行在同一个物体上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using UnityEngine; using System.Collections; [ExecuteInEditMode] public class Propertey : MonoBehaviour { private Renderer _renderer; // Use this for initialization void Start () { _renderer=GetComponent } // Update is called once per frame void Update () { _renderer.sharedMaterial.SetColor( "_Color" , new Color(1, 1, 0, 0.5f)); } }
|
1 | _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程序文件中
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 56 57 58 59 60 61 62 63 | 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 } } } |
更多最新文章敬请关注凯尔八阿哥专栏点击打开