Unity&Shader基础篇-“Hello Cg”

转载请注明出处凯尔八阿哥专栏

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”的颜色值。

猜你喜欢

转载自blog.csdn.net/zhangxiao13627093203/article/details/52850589