Unity系列文章目录
文章目录
Unity系列文章目录前言一、Unity Shader 概述二、使用步骤1.3.1.2 Unity 中的材质2.Unity 中的Shader3.Unity Shader 的基础:ShaderLab4.Unity Shader 的结构参考前言
通过前面的学习内容我们已经知道,Shader 并不是什么神秘的东西,它们其实就是渲染流水
线中的某些特定阶段,如顶点着色器阶段、片元着色器阶段等。
在没有Unity 这类编辑器的情况下,如果我们想要对某个模型设置渲染状态,可能需要类似
下面的代码:
// 初始化渲染设置void Initialization() {// 从硬盘上加载顶点着色器的代码string vertexShaderCode = LoadShaderFromFile(VertexShader.shader);// 从硬盘上加载片元着色器的代码string fragmentShaderCode = LoadShaderFromFile(FragmentShader.shader);// 把顶点着色器加载到GPU 中LoadVertexShaderFromString(vertexShaderCode);// 把片元着色器加载到GPU 中LoadFragmentShaderFromString(fragmentShaderCode);// 设置名为"vertexPosition"的属性的输入,即模型顶点坐标SetVertexShaderProperty("vertexPosition", vertices);// 设置名为"MainTex"的属性的输入,someTexture 是某张已加载的纹理SetVertexShaderProperty("MainTex", someTexture);// 设置名为"MVP"的属性的输入,MVP 是之前由开发者计算好的变换矩阵SetVertexShaderProperty("MVP", MVP);// 关闭混合Disable(Blend);// 设置深度测试Enable(ZTest);SetZTestFunction(LessOrEqual);// 其他设置…}// 每一帧进行渲染void OnRendering() {// 调用渲染命令DrawCall();// 当涉及多种渲染设置时,我们可能还需要在这里改变各种渲染设置...}
VertexShader.shader:
// 输入:顶点位置、纹理、MVP 变换矩阵in float3 vertexPosition;in sampler2D MainTex;in Matrix4x4 MVP;// 输出:顶点经过MVP 变换后的位置out float4 position;void main() {// 使用MVP 对模型顶点坐标进行变换position = MVP * vertexPosition;}
FragmentShader.shader:
// 输入:VertexShader 输出的position、经过光栅化程序插值后的该片元对应的positionin float4 position;// 输出:该片元的颜色值out float4 fragColor;void main() {// 将片元颜色设为白色fragColor = float4(1.0, 1.0, 1.0, 1.0);}
上述伪代码仅仅是简化后的版本,当渲染的模型数目、需要调整的着色器属性不断增多时,
上述过程将变得更加复杂和冗长。而且,当涉及透明物体等多物体的渲染时,如果没有编辑器的
帮助,我们要非常小心如渲染顺序等问题。
Unity 的出现改善了上面的状况。它提供了一个地方能够让开发者更加轻松地管理着色器代
码以及渲染设置(如开启/关闭混合、深度测试、设置渲染顺序等),而不需要像上面的伪代码一
样,管理多个文件和函数等。Unity 提供的这个“方便的地方”,就是Unity Shader。
一、Unity Shader 概述
那么如何充分利用Unity Shader 来为我们的游戏增光添彩呢?
3.1.1 一对好兄弟:材质和Unity Shader
总体来说,在Unity 中我们需要配合使用材质(Material)和Unity Shader 才能达到需要的效
果。一个最常见的流程是:
(1)创建一个材质;
(2)创建一个Unity Shader,并把它赋给上一步中创建的材质;
(3)把材质赋给要渲染的对象;
(4)在材质面板中调整Unity Shader 的属性,以得到满意的效果。
图3.1 显示了Unity Shader 和材质是如何一起工作来控制物体的渲染的。
可以发现,Unity Shader 定义了渲染所需的各种代码(如顶点着色器和片元着色器)、属性(如
使用哪些纹理等)和指令(渲染和标签设置等),而材质则允许我们调节这些属性,并将其最终赋
给相应的模型。
二、使用步骤
1.3.1.2 Unity 中的材质
Unity 中的材质需要结合一个GameObject 的Mesh 或者Particle Systems 组件来工作。它决定
了我们的游戏对象看起来是什么样子的(这当然也需要Unity Shader 的配合)。
为了创建一个新的材质,我们可以在Unity 的菜单栏中选择Assets -> Create -> Material 来创
建,也可以直接在Project 视图中右击 -> Create -> Material 来创建。当创建了一个材质后,就可
以把它赋给一个对象。这可以通过把材质直接拖曳到Scene 视图中的对象上来实现,或者在该对
象的Mesh Renderer 组件中直接赋值,如图3.2 所示。
在Unity 5.x 版本中,默认情况下,一个新建的材质将使用Unity 内置的Standard Shader,这
是一种基于物理渲染的着色器,我们将在第18 章中讲到。
对于美术人员来说,材质是他们十分熟悉的一种事物。Unity 的材质和许多建模软件(如Cinema
4D、Maya 等)中提供的材质功能类似,它们都提供了一个面板来调整材质的各个参数。这种可视化
的方法使得开发者不再需要自行在代码中设置和改变渲染所需的各种参数,如图3.3 所示。
2.Unity 中的Shader
为了和前面通用的Shader 语义进行区分,我们把Unity 中的Shader 文件统称为Unity Shader。
这是因为,Unity Shader 和我们之前提及的渲染管线的Shader 有很大不同,我们会在3.6.2 节中进
行更加详细的解释。
为了创建一个新的Unity Shader,我们可以在Unity 的菜单栏中选择Assets -> Create -> Shader 来
创建,也可以直接在Project 视图中右击 -> Create -> Shader 来创建。在Unity 5.2 及以上版本中,Unity
一共提供了4 种Unity Shader 模板供我们选择—Standard Surface Shader,Unlit Shader,Image Effect
Shader 以及Compute Shader。其中,Standard Surface Shader 会产生一个包含了标准光照模型(使用了Unity 5 中新添加的基于物理的渲染方法,详见第18 章)的表面着色器模板,Unlit Shader 则会产生一
个不包含光照(但包含雾效)的基本的顶点/片元着色器,Image Effect Shader 则为我们实现各种屏幕
后处理效果(详见第12 章)提供了一个基本模板。最后,Compute Shader 会产生一种特殊的Shader
文件,这类Shader 旨在利用GPU 的并行性来进行一些与常规渲染流水线无关的计算,而这不在本书
的讨论范围内,读者可以在Unity 手册的Compute Shader 一文(/Manual/
ComputeShaders.html)中找到更多的介绍。总体来说,Standard Surface Shader 为我们提供了典型的表
面着色器的实现方法,但本书的重点在于如何在Unity 中编写顶点/片元着色器,因此在后续的学习中,
我们通常会使用Unlit Shader 来生成一个基本的顶点/片元着色器模板。
一个单独的Unity Shader 是无法发挥任何作用的,它必须和材质结合起来,才能发生神奇的
“化学反应”!为此,我们可以在材质面板最上方的下拉菜单中选择需要使用的Unity Shader。当
选择完毕后,材质面板中就会出现该Unity Shader 可用的各种属性。这些属性可以是颜色、纹理、
浮点数、滑动条(限制了范围的浮点数)、向量等。当我们把材质赋给场景中的一个对象时,就可
以看到调整属性所发生的视觉变化。
Unity Shader 本质上就是一个文本文件。和Unity 中的很多外部文件类似,Unity Shader 也有
导入设置(Import Settings)面板,在Project 视图中选中某个Unity Shader 即可看到。在Unity 5.2
版本中,Unity Shader 的导入设置面板如图3.4 所示。
在该面板上,我们可以在Default Maps 中指定该Unity Shader 使用的默认纹理。当任何材质
第一次使用该Unity Shader 时,这些纹理就会自动被赋予到相应的属性上。在下方的面板中,Unity
会显示出和该Unity Shader 相关的信息,例如它是否是一个表面着色器(Surface Shader)、是否是
一个固定函数着色器(Fixed Function Shader)等,还有一些信息是和我们在Unity Shader 中的标
签设置(详见3.3.3 节)有关,例如是否会投射阴影、使用的渲染队列、LOD 值等。
对于表面着色器(详见3.4.1 节)来说,我们可以通过单击Show generated code 按钮来打开
一个新的文件,在该文件里将显示Unity 在背后为该表面着色器生成的顶点/片元着色器。这可以
方便我们对这些生成的代码进行修改(需要复制到一个新的Unity Shader 中才可保存)和研究。
同样地,如果该Unity Shader 是一个固定函数着色器,在Fixed function 的后面也会出现一个Show
generated code 按钮,来让我们查看该固定函数着色器生成的顶点/片元着色器。Compile and show
code 下拉列表可以让开发者检查该Unity Shader 针对不同图像编程接口(例如OpenGL、D3D9、
D3D11 等)最终编译成的Shader 代码,如图3.5 所示。直接单击该按钮可以查看生成的底层的汇
编指令。我们可以利用这些代码来分析和优化着色器。
除此之外,Unity Shader 的导入面板还可以方便地查看其使用的渲染队列(Render queue)、
是否关闭批处理(Disable batching)、属性列表(Properties)等信息。
3.Unity Shader 的基础:ShaderLab
“计算机科学中的任何问题都可以通过增加一层抽象来解决。”— 大卫·惠勒
学习和编写着色器的过程一直是一个学习曲线很陡峭的过程。通常情况下,为了自定义渲染
效果往往需要和很多文件和设置打交道,这些过程很容易消磨掉初学者的耐心。而且,一些细节
问题也往往需要开发者花费较多的时间去解决。
Unity 为了解决上述问题,为我们提供了一层抽象—Unity Shader。而我们和这层抽象打交
道的途径就是使用Unity 提供的一种专门为Unity Shader 服务的语言—ShaderLab。
什么是ShaderLab?
“ShaderLab is a friend you can afford.”—尼古拉斯·弗朗西斯(Nicholas Francis),Unity 前首
席运营官(COO)和联合创始人之一。
Unity Shader 是Unity 为开发者提供的高层级的渲染抽象层。图3.6 显示了这样的抽象。Unity
希望通过这种方式来让开发者更加轻松地控制渲染。
在Unity 中,所有的Unity Shader 都是使用ShaderLab 来编写的。ShaderLab 是Unity 提供的
编写Unity Shader 的一种说明性语言。它使用了一些嵌套在花括号内部的语义(syntax)来描述
一个Unity Shader 文件的结构。这些结构包含了许多渲染所需的数据,例如Properties 语句块中定
义了着色器所需的各种属性,这些属性将会出现在材质面板中。从设计上来说,ShaderLab 类似
于CgFX 和Direct3D Effects(.FX)语言,它们都定义了要显示一个材质所需的所有东西,而不仅
仅是着色器代码。
一个Unity Shader 的基础结构如下所示:
Shader "ShaderName" {Properties {// 属性}SubShader {// 显卡A 使用的子着色器}SubShader {// 显卡B 使用的子着色器}Fallback "VertexLit"}
Unity 在背后会根据使用的平台来把这些结构编译成真正的代码和Shader 文件,而开发者只
需要和Unity Shader 打交道即可。
4.Unity Shader 的结构
在上一节的伪代码中我们见到了一些ShaderLab 的语义,如Properties、SubShader、Fallback
等。这些语义定义了Unity Shader 的结构,从而帮助Unity 分析该Unity Shader 文件,以便进行正
确的编译。在下面,我们会解释这些基础的语义含义和用法。
3.3.1 给我们的Shader 起个名字
每个Unity Shader 文件的第一行都需要通过Shader
语义来指定该Unity Shader 的名字。这个名字由一个字
符串来定义,例如“MyShader”。当为材质选择使用的
Unity Shader 时,这些名称就会出现在材质面板的下拉列
表里。通过在字符串中添加斜杠(“/”),可以控制Unity
Shader 在材质面板中出现的位置。例如:
Shader “Custom/MyShader” { }
那么这个Unity Shader 在材质面板中的位置就是:
Shader -> Custom -> MyShader,如图3.7 所示。
3.3.2 材质和Unity Shader 的桥梁:Properties
Properties 语义块中包含了一系列属性(property),
这些属性将会出现在材质面板中。
Properties 语义块的定义通常如下:
Properties {Name ("display name", PropertyType) = DefaultValueName ("display name", PropertyType) = DefaultValue// 更多属性}
开发者们声明这些属性是为了在材质面板中能够方便地调整各种材质属性。如果我们需要在
Shader 中访问它们,就需要使用每个属性的名字(Name)。在Unity 中,这些属性的名字通常由
一个下划线开始。显示的名称(display name)则是出现在材质面板上的名字。我们需要为每个
属性指定它的类型(PropertyType),常见的属性类型如表3.1 所示。除此之外,我们还需要为每
个属性指定一个默认值,在我们第一次把该Unity Shader 赋给某个材质时,材质面板上显示的就
是这些默认值。
参考
Unity Shader入门精要
作者:冯乐乐