零基础入门Unity Shader(五)
Technical Art,TA交流群:763506271
前言
上一篇中我们讲解了Properties,主要是为了在材质面板中去暴露一些参数给用户来调节。但是到目前为止,虽然我们学会了暴露参数,但是调节起来还没有任何效果,在这篇呢,我们就来讲一讲如何让我们的参数来反馈表现,对于初学者来说,这篇内容至关重要,能否准确清晰地理解直接影响是否能迈入Shader的大门。
为了能够由浅而深的学习Shader,在讲解的时候并不会把所有的参数都罗列出来一一讲一遍,而是有选择先讲一些重要的点,以避免过早地陷入细节而产生不必要的困惑和造成学习上的盲目性。在后续章节会再慢慢扩展讲解全面。也希望初学者能够跟着我的节奏走,循序渐近一步一步的吸收学习。
先实个小目标
先让我们实现一个只有颜色属性可调节的简单材质效果,如下图所示:
模型大家可以随意找个,默认的几何体也可以,在材质面板中只有一个颜色属性,当我们点开拾色器进行颜色选择时,对应的模型也会自动发生变化。
让我们继续拿之前的MyFirstShader来完善。
Pass
在零基础入门Unity Shader(三)中我们大致了解到了Shader的组织框架。
在一个Shader中,可以有多个SubShader以及一个SubShader中也可以有多个Pass,但是一个Shader中必须要至少有一个SubShader,并且这个SubShader中也必须至少有一个Pass。
SubShader我们已经知道是什么了,那么Pass是什么呢?
Pass的意思就是渲染一次模型!
而具体要怎么渲染就需要我们在Pass中添加Cg/HLSL代码片断来实现了,这段代码片断是由CGPROGRAM开始,由ENDCG结束。也就是说我们必须要添加CGPROGRAM和ENDCG,然后在它们中间去写实现效果的代码。
好,我们先按Unity规定的要求摆好姿势。
Shader "Unlit/MyFirstShader"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
既然是顶点片断着色器,那么就要先定义好顶点着色器与片断着色器(也可以叫做像素着色器),告诉Unity分别在哪里去执行它们。
#pragma是Unity内置的编绎指令用的命令,在Pass中我们就利用此命令来声明所需要的顶点着色器与片断着色器。
- #pragma vertexname
定义顶点着色器为name,通常情况下会起名为vert。
- #pragma fragmentname
定义片断着色器为name,通常情况下会起名为frag。
顶点色着色器
声明完后,我们就需要来实现具体的内容了。
故名思义,顶点着色器就是处理顶点的着色器,每个顶点都会执行一次顶点着色器。
首先呢,我们先来解释下顶点这个函数的结构。
- 顶点着色器函数的名称,在上面我们已经指定了顶点着色器的名称就是vert,所以这里我们必须要用vert作为名称。
- 其中float4 vertex是我们自己定义的一个四维向量,名字叫vertex(名字我们可以随便起),仅仅定义一个四维向量并不能使它拥有我们模型的顶点信息,所以这里我们需要为它指定一个语义——POSITION,POSITION就是代表着模型的顶点位置信息。此时变量vertex就表示着我们模型的顶点位置。
- 在顶色着色器中最主要的事情就是将顶点从模型坐标转换到裁剪坐标(说白了就是将模型显示在二维显示器上时需要做的一些矩阵转换)。不会矩阵转换怎么办,没关系,Unity已经为我们准备好现成的命令了,只需调用UnityObjectToClipPos即可,后面括号中加上我们的顶点位置变量就可以了。
- 然后呢,在后面片断着色器中我们需要顶点着色器中的输出结果,所以3中需要加上return来将转换后的顶点返回,float4就是用来定义我们返回的是四维向量。
- 经过变换后返回的顶点位置,我们也需要利用语义来标记一下,以便片断着色器可以知道哪个是从顶点着色器输出过来的顶点位置信息。所以我们在函数的后面加上: SV_POSITION。
简单地说POSITION语义是用于顶点着色器,用来指定模型的顶点位置,是在变换前的顶点的本地空间坐标。SV_POSITION语义则用于像素着色器,用来标识经过顶点着色器变换之后的顶点坐标。
在顶点着色器中处理顶点时,我们首先需要获取到模型的顶点数据(比如顶点位置、法线信息、顶点颜色等等),那么这些数据都是直接存储在模型中的,我们在Shader中只需要通过标识语义就可以自动获得。
此时我们的Shader还不能正常编译,因为了除了顶点着色器外,还们需要一个片断着色器。
片断着色器
片断着色器也被称作像素着色器,主要是处理最终显示在屏幕上的像素结果。
经过顶点着色器的处理,我们已经得到了最终显示在屏幕上的顶点矩阵,内部会自动进行插值计算,以获得当前模型的所有片断像素,然后每个像素都会执行一次片断着色器,得到最终每个像素的颜色值。
- 片断着色器的函数名,其中()中是空的,因为在这个简单的示例中我们并不需要额外的数据传过来,所以暂时为空。
- 在Cg/HLSL中使用Properties中的变量前还需要在Cg/HLSL中再重新声明一次,名称要求一致。这是死规则,我们只能按照要求来执行。float、half、fixed,这三都是浮点数的表示,只是分别对应的精度不一样,主要用此可以进行更进一步的优化。
- 去们直接返回_Color,也就是直接返回我们在材质面板中定义的颜色,这也是我们这个小例子想要的效果。
- 同样,返回的值是个四维向量,我们用float4来表示,如果想优化的话就用fixed4来表示,关于精度问题我们后面会专门讲解,这里不是重点。
- SV_TARGET是系统值,表示该函数返回的是用于下一个阶段输出的颜色值,也就是我们最终输出到显示器上的值。
最终的代码如下:
Shader "Unlit/MyFirstShader"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
fixed4 _Color;
float4 vert ( float4 vertex : POSITION ) : SV_POSITION
{
return UnityObjectToClipPos(vertex);
}
fixed4 frag () : SV_Target
{
return _Color;
}
ENDCG
}
}
}
此时我们已经可以正常编译并实时更换颜色得到反馈了。
结语
顶点着色器与片断着色器的执行并不是1:1的,举个例子,一个三角面片,只有三个顶点,顶点着色器只需执行3次,而片断着色器由最终的像素数来决定,执行几百上千都是很正常的。所以从性能的角度来考虑,我们要尽量把计算放在顶点着色器中去执行。其次在片断着色器中也要尽量的简化算法,节省开支。
这一节的内容对于初学者来说学习曲线有点陡,如果没有理解也没有关系,先照着做,就当是固定规则好了,在后续我们会慢慢的进行扩展介绍,到时自然会清晰明了。