Unity&Shader基础篇-“Hello Cg”

发表于2016-11-18
评论1 2.7k浏览

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; // 颜色
      };
2、Unity中内置的输入参数结构体:Unity中内置了一些顶点程序的输入参数的结构体,在Unity>Editor>Data>CGInclude>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
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
在控制面板上拖动这个变量将会以2次曲线的形式进行增减。
[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));
     
    }
}
本段代码中在类的前面添加了[ExecuteInEditMode],保证脚本可以在编辑状态下就被运行。通过代码
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
    }
    }
}
这个程序实现的效果即:物体在世界坐标系的位置与预设的原点位置“Point”的距离小于阈值“DistanceNear”部分就显示“ColorNear”的颜色值,大于阈值“DistanceNear”部分就显示“ColorFar”的颜色值。
更多最新文章敬请关注凯尔八阿哥专栏点击打开


如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引