【Unity Shader】UnityShader基础

xiaoxiao2021-02-28  120

主要参考《Unity Shader入门精要》一书,外加自己的一些总结

前言

通过前面的学习内容可以知道,Shader并不是设么神秘的东西,它其实就是渲染流水线中的某些特定阶段,比如顶点着色器阶段、片元着色器阶段等。

什么是Unity Shader

Unity中的Shader和之前提到的渲染流水线的Shader有很大不同,我们把Unity中的Shader文件统称为Unity Shader。Unity Shader 实际上指的就是一个ShaderLab文件,硬盘上以.shader为文件后缀的一种文件。Unity Shader是Unity为开发者提供的高层次的渲染抽象层。 图1  Unity Shader为控制渲染过程提供了一层抽象。如果没有使用Unity Shader(左图),开发者需要和很多文件和设置打交道,才能让画面呈现出想要的效果;而在Unity Shader的帮助下(右图),开发者只需要使用ShaderLab来编写Unity Shader文件就可以完成所有的工作  Unity Shader(或者说ShaderLab文件)和传统意义上的Shader的不同: 1), 在传统的Shader中,我们仅可以编写特定类型的Shader,比如顶点着色器、片元着色器等。而在Unity Shader中,我们可以在同一个文件里同时包含需要的顶点着色器和片元着色器代码。 2), 在传统的Shader中,我们无法设置一些渲染设置,比如是否开启混合、深度测试等,这些是开发者在另外的代码中自行设置的。而在Unity Shader中,我们通过一行特定的指令就可以完成这些设置。 3), 在传统的Shader中,我们需要编写冗长的代码来设置着色器的输入和输出,要小心的处理这些输入输出的位置对应关系等。而在Unity Shader中,我们只需要在特定语句块中声明一些属性,就可以依靠材质来方便的改变这些属性。 Unity Shader提供了一种让开发者同时控制渲染流水线中多个阶段的一种方式,不仅仅是提供Shader代码。 当然,Unity Shader也有一些缺点,由于Unity Shader的高封装性,我们可以编写的Shader类型和语法都被限制了,并且对于一些类型的Shader,比如曲面细分着色器、几何着色器等,Unity 的支持就相对差一些。

Unity Shader概述

一个单独的Unity Shader是无法发挥任何作用的,Unity中我们需要配合使用材质(Material)和Unity Shader才能达到需要的效果。流程如下: a,创建一个材质; b,创建一个Unity Shader,并把它赋给上一步中的材质; c,把材质赋给要渲染的对象; d,在材质面板中调整Unity Shader的属性,以达到满意的效果。 图2 Unity Shader 和 材质   Unity提供了 4 种Unity Shader模板供我们选择——Standard Surface Shader、Unlit Shader、Image Effect Shader以及Compute Shader。其中Standard Surface Shader会产生一个包含了标准光照模型的表面着色器模板,Unlit Shader则会产生一个不包含光照但包含雾效的基本的顶点/片元着色器,Image Effect Shader 则会为我们实现各种屏幕后处理效果提供一个基本模板,Compute Shader会产生一种特殊的Shader文件,这类Shader旨在利用GPU的并行性来进行一些与常规渲染流水线无关的计算。

什么是ShaderLab

在Unity中,所有的Unity Shader 都是使用ShaderLab来编写的,ShaderLab是Unity提供的编写Unity Shader的一种说明性语言。它使用了一些嵌套在花括号内部的语义来描述一个Unity Shader 文件的结构。 一个 Unity Shader 的基础结构如下所示: Shader "ShaderName"{ Properties{ //属性 } SubShader{ //显卡A使用的子着色器 } SubShader{ //显卡B使用的子着色器 } FallBack "VertexLit" } Unity在背后会根据使用的平台来把这些结构编译成真正的代码和Shader文件,而开发者只需要和Unity Shader打交道即可。 在Unity 中,属性的名字通常由一个下划线开始,我们需要给每个属性指定它的类型,并指定默认值。 下面的代码给出了一个展示所有属性类型的例子: Shader "Custom/ShaderLabProperties" { Properties { // Numbers and Sliders _Int ("Int",Int) = 2 _Float("Float",Float) = 1.5 _Range("Range",Range(0.0,5.0)) = 3.0 // Colors and Vectors _Color ("Color",Color) = (1,1,1,1) _Vector ("Vector",Vector) = (2,5,3,1) //Textures _2D("2D",2D) = ""{} _Cube("Cube",Cube) = "white"{} _3D("3D",3D) = "black"{} } FallBack "Diffuse" } SubShader: 每一个Unity Shader 文件可以包含多个SubShader语义块,但至少要有一个。当Unity 需要加载这个Unity Shader 时,Unity会扫描所有的SubShader语义块,然后选择第一个能够在目标平台上运行的SubShader。如果都不支持的话,Unity 就会使用FallBack 语义指定的 unity Shader。 SubShader语义块包含的定义通常如下: SubShader{ //可选的 [Tags] //可选的 [RenderSetup] Pass{ } //oteher Passes } SubShader中定义了一系列Pass 以及可选的状态([RenderSetup]) 和标签([Tags])设置。每个Pass定义了一次完整的渲染流程,但如果Pass数目过多,会造成渲染性能的下降。SubShader中的标签设置是特定的,和Pass中使用的标签不一样。但对于状态来说,使用的语法是相同的,如果在SubShader进行了状态设置,那么将会用于所有的Pass。 ShaderLab提供了一系列 渲染状态的设置指令,这些指令可以设置显卡的各种状态,如下表: SubShader 的标签Tags: SubShader的标签是一个键值对(Key/Value Pair),它的键和值都是字符串类型。这些键值对是SubShader 和渲染引擎之间的沟通桥梁,标签的结构如下: Tags{ "TagName1" = "Value1" "TagName2" = "Value2"} 上面表里的标签仅可以在SubShader中声明,不可以在Pass块中声明,Pass的标签类型如下表: FallBack: 作用:告诉Unity,如果所有的SubShader在这显卡上不能运行,就使用这个最低级的Shader。 事实上,FallBack会影响阴影的投射。在渲染阴影纹理时,Unity会在每个Unity Shader中寻找一个阴影投射的Pass。通常情况下,我们不需要自己专门实现一个Pass,这是因为FallBack 使用的内置Shader中包含了这样一个通用的Pass,所以,为每个Unity Shader正确设置FallBack是非常重要的。

Unity Shader的形式

表面着色器(SurFace Shader): 表面着色器是Unity自己创造的一种着色器代码类型。可以理解成,表面着色器是Unity对顶点/片元着色器的更高一层的抽象,它存在价值在于,Unity为我们处理了很多光照细节。一个非常简单的表面着色器示例代码如下: Shader "Custom/Simple Surface Shader" { SubShader{ Tags{ "RenderType" = "Opaque"} CGPROGRAM #pragma surface surf Lambert struct Input { float4 color : COLOR; }; void surf(Input IN, inout SurfaceOutput o) { o.Albedo = 1; } ENDCG } FallBack "Diffuse" }表面着色器定义在SubShader语义块(非Pass语义块)中的CGPROGRAM和ENDCG之间,是使用CG/HLSL编写的。 顶点/片元着色器(Vertex/Fragment Shader): 一个简单的顶点/片元着色器示例代码如下: Shader "Custom/Simple VertexFragment Shader" { SubShader{ Pass{ CGPROGRAM #pragma vertex vert #pragma fragment frag float4 vert(float4 v : POSITION) : SV_POSITION{ return mul(UNITY_MATRIX_MVP,v); } float4 frag() : SV_Target{ return fixed4(1.0,0.0,0.0,1.0); } ENDCG } } FallBack "Diffuse" }顶点/片元着色器的代码也需要定义在CGPROGRAM和ENDCG之间,也是用CG/HLSL编写的,但不同的是,顶点/片元着色器是写在Pass语义块内,而非SubShader内的。 选择哪种Unity Shader 形式? a,除非有非常明确的需求必须使用固定函数着色器,比如需要在非常旧的设备上运行你的游戏,否则就使用可编程管线的着色器,即表面着色器或顶点/片元着色器。 b,如果你想和各种光源打交道,可以选择表面着色器,但需要注意它在移动平台的性能表现。 c,如果你需要使用的光照数目非常少,比如只有一个平行光,那么使用顶点/片元着色器是一个更好的选择。 c,如果你有非常多自定义的渲染效果,那么请选择顶点/片元着色器。

Unity Shader 和 CG/HLSL之间的关系

Unity Shader 是ShaderLab 语言编写的,但对于表面着色器和顶点/片元着色器,我们可以在ShaderLab内部嵌套CG/HLSL语言来编写这些着色器代码。这些CG/HLSL 代码是嵌套在CGPROGRAM 和 ENDCG 之间的,由于CG 和 HLSL 从写法上几乎是同一种语言,因此在Unity 里CG 和 HLSL 是等价的。 表面着色器在本质上就是顶点/片元着色器,他们看起来很不想是因为表面着色器是Unity 在顶点/片元着色器上层为开发者提供的一层抽象封装,但在背后,Unity 还是会把它转化成一个包含多Pass的顶点/片元着色器。 所以,从本质上来讲,Unity Shader 只有两种形式: 顶点/片元着色器和固定函数着色器(Unity5.2以后的版本,固定函数着色器也会在背后被转化成顶点/片元着色器,因此,Unity本质上只存在顶点/片元着色器)。 当然,Unity Shader 也可以用GLSL来写,但是这意味着你可以发布的目标平台就只有 Mac OS X、OpenGL ES 2.0或者Linux,而对于PC 、Xbox 360这样的仅支持DirectX的平台来说,就不行了。

转载请注明原文地址: https://www.6miu.com/read-56435.html

最新回复(0)