Unity Shader知识点(一) 典型Shader文件的结构

什么是Shader?

Shader又称着色器,科学定义是“可编程图形管线”,如果从Unity中的工程角度思考,写Shader可以理解为我们的项目或者其中一个场景定义渲染方式,而制作一个Shader文件,一般是这一过程中的一环,往往是为某个物件定义其独特的渲染效果。

Shader文件的结构

这里从Unity工程中典型的单一Shader文件出发,尝试以初学者视角快速理解其结构,即具体代码块的功能,以窥Shader的运作方式,文章可能有所详略,相关名词可自行了解。

Shader命名

事实上,当我们尝试在Unity里新建一个Shader文件时会发现,这里提供了几种不同的模板(如果读过那本很出名的入门精要,往往会觉得无所适从,因为Unity5.3之前的版本是只有一种Shader可以创建的)。不过可以说明的是,我个人学习时,是先创建包括了顶点着色器(Vertex Shader)和片元着色器(Fragment Shader)的Unlit Shader,而非Unity提供的标准表面着色器(那玩意甚至不能写PASS块),这样阅读起来会没有障碍。

如果不改变它的命名,创建文件后打开,可以发现开头已经有了默认的命名:归类在Unlit文件中的NewUnlitShader文件。具体的命名应该根据Shader的种类和工程实践的需求进行,这里不再赘述。

Shader "Unlit/NewUnlitShader"

在对材质赋予Shader时,也可以用同样的路径找到这个Shader文件:

Properties语义块

正如你可能在别的教程中看到的一样,Shader文件由一个个的语义块组成,而properties语义块定义一些我们需要控制的变量(被支持的变量类型有限,可以自行查询)类似于Unity脚本中的public变量,可以在交互界面中改变其属性,例如:

    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }

以上代码定义了一个2D图像属性的变量,初始值是一张白色贴图,其格式为:

变量名 (“检视面板里显示的名称”, 变量类型)= 默认值

你可以在Shader的Inspector面板中改变它:

 SubShader语义块

SubShader语义块就是我们需要编辑的主体了,在同一个Shader文件中,可以有多个SubShader语义块,但至少要有一个,你可以理解为:我撰写一个Shader,需要考虑到Unity在不同的平台该怎么渲染,并分别提供不同的SubShader块来帮助Unity完成渲染;如果我不想考虑这些事,但我的工程又可能在不同的环境中被使用,那么文件末尾还应当有这样的字段:

FallBack "Diffuse"

以上语句相当于指定了SubShader的缺省值,即如果没有找到可用的块,用Unity自带的Diffuse进行渲染。

Pass块

Pass块属于SubShader块,也是可以有多个同时存在,不过,我们可能需要更加详细地告诉Unity它在我们渲染中的角色:

Tags { "RenderType"="Opaque" }

这是Tags语句,指定Pass块在光照流水线中的作用,其具体含义暂不关心(学到这里还记不住,太多了)

CG代码片的开始、引用库和函数

终于要到我们Shader的关键部位:正式的CG代码片了,不过我们还需要做点事情:

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

CG代码片使用CGPEOGRAM和ENDCG来作为开始和结束标志(我没弄懂这有啥意义),之后,我们用#pragma指令指定顶点着色器和片元着色器的代码,这里分别是vert和frag函数(见下文);另外,为了使用一些很有用的Unity内置函数和变量,我们需要引入一些库文件,例如UnityCG文件。

声明变量

我们在CG代码片中声明变量,这包括我们需要用到的Properties块中的变量,应当重新声明;也包括我们CG代码计算中需要的局部变量,如这里重申了2D类型的_MainTex和一个float4类型的自定变量:

sampler2D _MainTex;
float4 _MainTex_ST;

定义顶点着色器结构体

我们应当定义两个结构体,用于顶点着色器的输入和输出(事实上输出结构体就是片元着色器的输入结构体,因此其中每个子类型都应当关注)

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

这些输入输出从哪来呢?在实际撰写中,这些结构体中的类型,几乎都来自Unity提供的变量,如标志渲染位置的POSITION;来自贴图纹理等的TEXCOORD;有些则是一些约定俗成的过程变量,如常用作顶点着色器输出和片元着色器输入的SV_POSITION。

顶点着色器函数

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

这是一个非常典型的顶点着色器函数(来自默认Unlit Shader模板),在初学阶段我们不关心每个语句的语义,而是尝试剖析其结构:它使用了我们前面定义的输入结构体类型appdata,中间经过了一些输入和内置变量之间的运算,最终返回了v2f类型给片元着色器。

片元着色器函数

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }

同样的,片元着色器拿到v2f类型的输入,经过Unity的自建函数UNITY_APPLY_FOG(用于处理雾效,暂不研究),得到输出col,它是一个fixed格式变量(实际上是颜色,整个Shader是一个不考虑灯光效果的漫反射Shader)

总结

好的,大功告成!现在阅读了一个完整的Shader,我们大概了解了写一个Shader需要做些什么,也能一窥Unity Shader应该学习的东西(包括常用变量类型及其数据结构,内建函数,约定俗成的写法,不同着色器间的交流等等)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值