1、引言
这一篇我们俩了解下Cg语言中的函数,这里涉及到的知识点包括以下内容:
- 定制函数
- 函数的前项声明
- 函数的传参
- Cg include
- Cg中的内建函数
2、示例
这我们将一一举例说明!开始之前我们先搭建好场景,便于演示。创建一个场景,一个cube,一个材质球和一个顶点片元shader。然后该cube使用新建的材质,选择刚才闯将的shader,修改代码如下:
Shader "lxt610/NewSurfaceShaderTest3" {
SubShader
{
Pass
{
CGPROGRAM
#pragma fragment frag
#pragma vertex vert
void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
{
pos = float4(objPos,0,1);
col = float4(1,0,0,1);//红色
//col = pos;
}
void frag(inout float4 col:COLOR0)
{
}
ENDCG
}
}
}
运行场景后显示红色。
2.1、函数
函数的语法格式:
返回值类型 函数名称([参数列表])
{
//语句块;
}
下面是一个示例
Shader "lxt610/NewSurfaceShaderTest3" {
SubShader
{
Pass
{
CGPROGRAM
#pragma fragment frag
#pragma vertex vert
void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
{
pos = float4(objPos,0,1);
col = float4(1,0,0,1);//红色
}
void frag(inout float4 col:COLOR0)
{
func();
}
void func()
{
}
ENDCG
}
}
}
写完回到unity,编译完毕发现异常了,提示函数未定义!Cg中需要把声明的函数放在使用的位置之前,否则就异常了。这时只需要改为:
void func()
{
}
void frag(inout float4 col:COLOR0)
{
func();
}
就可以了!然而有时候我们就想在后面声明怎么办?
2.2、函数的前项声明
此时可以考虑函数的前项声明。使用方式就是:在被调用的函数的位置前面声明一个前置函数,其函数名返回值类型及参数列表和声明的函数相同就可以了。eg:
Shader "lxt610/NewSurfaceShaderTest3" {
SubShader
{
Pass
{
CGPROGRAM
#pragma fragment frag
#pragma vertex vert
void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
{
pos = float4(objPos,0,1);
col = float4(1,0,0,1);//红色
}
void func();
void frag(inout float4 col:COLOR0)
{
func();
}
void func()
{
}
ENDCG
}
}
}
此时我们发现编译通过!
2.3、函数的参数传递
我们再次修改代码
Shader "lxt610/NewSurfaceShaderTest3" {
SubShader
{
Pass
{
CGPROGRAM
#pragma fragment frag
#pragma vertex vert
void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
{
pos = float4(objPos,0,1);
col = pos;
}
void func(float4 col);
void frag(inout float4 col:COLOR0)
{
func(col);
}
void func(float4 col)
{
col = float4(0,1,0,1);
}
ENDCG
}
}
}
这里我们发现颜色没有变化为想要的颜色,这时由于Cg中这里的值进行了值Copy,并不是操作的原来的值!要想改变可以使用out关键字:
Shader "lxt610/NewSurfaceShaderTest3" {
SubShader
{
Pass
{
CGPROGRAM
#pragma fragment frag
#pragma vertex vert
void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
{
pos = float4(objPos,0,1);
col = pos;
}
void func(out float4 col);
void frag(inout float4 col:COLOR0)
{
func(col);
}
void func(out float4 col)
{
col = float4(0,1,0,1);
}
ENDCG
}
}
}
编译通过后我们发现颜色变为了绿色。我们可以知道这里的out参数只是把数值传递进来,把值做了改变,并传回去了。这个out类似于C#中的ref关键字。当我们不需要Copy一个变量,需要这个值得时候可以使用out参数。
当我们需要传递一个数组时,不可以像其他语言一样,需要这样写:
float func2(float arr[3])
{
float sum = 0;
for(int i = 0; i < arr.Length; i++)
{
sum += arr[i];
}
return sum;
}
void frag(inout float4 col:COLOR0)
{
//func(col)
float arr[2] = {0.5,0.5};
col.x = func2(arr);
}
这里需要注意的是:
- 声明的函数的参数列表中如果是数组时,我们需要指定这个函数的长度,我们同时在调用的时候也必须传递给这个函数相同长度的数组
- 同时声明数组时如果我们是ES3.0需要指定数组的长度,eg:
float arr[2] = {0.5,0.5};
- 我们注意到Cg中数组有一个关键字Length,用来表示数组的长度。
最终的代码和效果如下:
Shader "lxt610/NewSurfaceShaderTest3" {
SubShader
{
Pass
{
CGPROGRAM
#pragma fragment frag
#pragma vertex vert
void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
{
pos = float4(objPos,0,1);
col = pos;
}
void func(out float4 col);
float func2(float arr[2])
{
float sum = 0;
for(int i = 0; i < arr.Length; i++)
{
sum += arr[i];
}
return sum;
}
void frag(inout float4 col:COLOR0)
{
//func(col)
float arr[2] = {0.5,0.5};
col.x = func2(arr);
}
void func(out float4 col)
{
col = float4(0,1,0,1);
}
ENDCG
}
}
}
同样这里的数组也是一个值Copye。因此在Cg中参数的传递是通过值Copy的形式来传递的,需要使用这个参数时可以使用out关键字。
2.4、Cg include
当我们写了很多shader之后,我们发现不同的shader在使用同样的函数,有没有什么办法解决这样的问题呢?这里就需要使用Cg当中的include指令。此时我们不妨打开Unity\Unity2017.4.27f1\Editor\Data\CGIncludes
目录,可以看到有很多.cginc文件,我们打开UnityCG.cginc,我们发现有很多函数和数据类型,我们可以使用include把这个文件包含进来,就可以使用这里面的函数和数据类型了。
那么当我们自己写shader的时候可不可以使用呢?当然也是可以的了。这里我们创建一个.cginc文件命名为lmy225.cginc,用来存储自己的一些函数。为了方便查找同时不和官方的cginc混淆这里我专门创建了一个名为lmy225的文件夹用来存储自己的cginc文件,此时把lmy225.cginc放在这个文件夹中,这里我们可以把:
float func2(float arr[2])
{
float2 sum = 0;
for(int i = 0; i < arr.Length;i++)
{
sum += arr[i];
}
return sum;
}
void func(out float4 c)
{
c = float4(0,1,0,1);
}
放在cginc中,然后回到shader删除前项声明,使用下面的include指令:
#include "路径"
eg:
Shader "lxt610/NewSurfaceShaderTest3" {
SubShader
{
Pass
{
CGPROGRAM
#pragma fragment frag
#pragma vertex vert
#include "lmy225/lmy225.cginc"
void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
{
pos = float4(objPos,0,1);
col = pos;
}
void frag(inout float4 col:COLOR0)
{
//func(col)
float arr[2] = {0.5,0.5};
col.x = func2(arr);
}
ENDCG
}
}
}
编译运行后我们发现实现了相同的效果!
这里include指令后面的路径可以使用\或/,也可以混合使用!
当然这里的.cginc后缀也可以是别的自己喜欢的后缀。只是改为其他后缀之后,关键字没有高亮提示了。
2.5、Cg中的内建函数
在cg的内建函数中主要有以下四类:
- 数学函数
- 几何函数
- 纹理函数
- 导数函数
这里我们给出Cg标准函数的文档,需要的同学,可以点击这里下载。当然除了上面的还有debug函数,由于功能比较单一,只是把数值通过颜色语义来显示出来!所以很少使用!
3、结束语
The End
好了,今天的分享就到这里,如有不足之处,还望大家及时指正,随时欢迎探讨交流!!!
喜欢的朋友们,请帮顶、点赞、评论!您的肯定是我写作的不竭动力!