学习Shader Unity Shader 基础

1、如何充分利用 Unity Shader 来为我们的游戏增光添彩?

材质和 Unity Shader;

在Unity中,我们需要配合使用材质(Material)和 Unity Shader 才能达到需要的效果。一个最常用的流程是:

(1)创建一个材质;

(2)创建一个 Unity Shader,并把它赋给上一步创建的材质;

(3)把材质赋给要渲染的对象;

(4)在材质面板中调整 Unity Shader 的属性,以得到满意的效果。

2、Unity 中的材质

Unity 中的材质需要结合一个 GameObject 的 Mesh 或者 Particle Systems 组件来工作。它决定了我们的游戏对象看起来是怎么样的(这当然也需要 Unity Shader 的配合)。

为了创建一个材质,我们可以在 Unity 的菜单栏中选择 Assets->Create->Material 来创建,也可以直接在 Projrct 视图中右击->Create->Material来创建。当创建了材质后,就可以把它赋给一个对象。

3、Unity 中的 Shader

(1)为了和前面通用的 Shader 语义进行区分,我们把 Unity 中的 Shader 文件统称为 Unity Shader。Unity Shader 和我们之前提及的渲染管线的Shader有很大不同。

为了创建一个新的 Unity Shader,我们可以在unity的菜单栏中选择 Assets->Create->Shader来创建,也可以直接在 Project 视图中右击->Create->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的并行性来进行一些与常规渲染流水线无关的计算。总的来说,Standard Surface Shader 为我们提供了典型的表面着色器的实现方法,我们重于如何在 Unity 中编写顶点/片元着色器,我们通常会使用 Unlit Shader 来生成一个基本的顶点/片元着色器模板。

一个单独的 Unity Shader 是无法发挥任何作用的,它必须和材质结合起来,才能发生神奇的“化学反应”!为此,我们可以在材质面板最上方的下拉菜单中选择需要使用的 Unity Shader。当选择完毕后材质面板中就会出现该 Unity Shader 可用的各种属性。这些属性可以是颜色、纹理、浮点数、滑动条、向量等。当我们把材质赋给场景中的一个对象时,就可以看到调整属性所发生的视觉变化。

Unity Shader 本质上就是一个文本文件。和Unity中很多外部文件类似,Unity Shader 也有导入设置(Import Settings)面板,在Project视图中选中某个 Unity Shader 即可看到。

在该面板上,我们可以在 Default Maps 中指定该 Unity 使用的默认纹理。当任何材质第一次使用该 Unity Shader 时,这些纹理就会自动赋予到相应的属性上。在下面的面板中,Unity 会显示出该Unity Shader 相关的信息,例如他是否是一个表面着色器(Surface Shader)、是否是一个固定函数着色器(Fixed Function Shader)等,还有一些是信息是和我们在 Unity Shader 中的标签设置有关,例如是否会投射阴影、使用的渲染队列、LOD值等。

除此之外,Unity Shader 的导入面板还可以方便的查看其使用的渲染队列(Render queue)、是否关闭批处理(Disable batching)、属性列表(Properties)等信息。

(2)Unity Shader 的基础:ShaderLab

"计算机科学中的任何问题都可以通过增加一层抽象来解决。"-----大卫·惠勒

学习和编写着色器的过程一直是一个学习曲线很陡峭的过程。通常情况下,为了自定义渲染效果往往需要和很多文件和设置打交道,这些过程很容易消磨掉初学者的耐心。而且,一些细节问题也往往需要开发者花费较多的时间去解决。

Unity 为了解决上诉问题,为我们提供了一层抽象--Unity Shader。而我们和这层抽象打交道的途近就是使用Unity提供的一种专门为 Unity Shader 服务的语言--ShaderLab。

什么是 ShaderLab?

Unity Shader 是 Unity 为开发者提供的高层及的渲染抽象层。Unity 希望通过这种方式来让开发者更加轻松地控制渲染。

在 Unity 中,所有地 Unity Shader 都是使用 ShaderLab 来编写地。ShaderLab 是 Unity 提供地编写 Unity Shader 的一种说明性语言。它使用了一些嵌套在花括号内部的语义(syntax)来描述一个Unity Shader 文件的结构。这些结构包含了许多渲染所需的数据,例如 Properties 语句块中定义了着色器所需的各种属性,这些属性将会出现在材质面板中。从设计上来说,ShaderLab 类似于CgFX 和 Direct3D Effects 语言,它们都定义了要显示一个材质所需的所有东西,而不仅仅是着色器代码。

一个 Unity Shader 的基础结构如下所示:

Shader "ShaderName"{
    Properties{
        //属性
    }
    SubShader{
        //显卡A使用的子着色器
    }
    SubShader{
        //显卡B使用的子着色器
    }
    Fallback "VertexLlit"
}

Unity 在背后会根据使用的平台来把这些结构编译成真正的代码和 Shader 文件,而开发者只需要和 Unity Shader 打交道即可。

Unity Shader 的结构

上面我们见到了一些 ShaderLab 的语义,如 Properties、SubShader、Fallback 等。这些语义定义了 Unity Shader 的结构,从而帮助 Unity 分析该 Unity Shader 文件,以便进行正确的编译。下面,我们会解释这些基础的语义含义和用法。

给我们的 Shader 起个名字

每个 Unity Shader 文件的第一行都需要通过 Shader 语义来指定该 Unity Shader 的名字。这个名字由一个字符串来定义,例如 “MyShader”。当为材质选择使用的 Unity Shader 时,这些名称就会出现在材质面板的下拉列表中。通过字符串中添加斜杠(“/”),可以控制 Unity Shader 在材质面板中出现的位置。例如:
Shader "Custom/MyShader"{    }

那么这个 Unity Shader 在材质面板中的位置就是:Shader ->Custom ->MyShader

材质和 Unity Shader 的桥梁:Properties

Properties 语义块中包含了一系列熟悉(property),这些属性将会出现在材质面板中。

Properties 语义块的定义通常如下:

Properties {

        Name("display name", PropertyType) = DefaultValue

        Name("display name", PropertyType) = DefaultValue

        //更多属性

}

开发者们声明这些属性是为了在材质面板中能够方便地调整各种材质属性。如果我们需要在Shader 中访问它们,就需要使用每个属性地名字(Name)。在 Unity 中,这些属性地名字通常由一个下划线开始。显示地名称(display name)则是出现在材质面板上地名字。我们需要为每个属性指定它地类型(PropertyType),常见地属性类型如表3.1所示。除此之外,我们还需要为每个属性指定一个默认值,在我们第一次把该 Unity Shader 赋给某个材质时,材质面板上显示地就是这些默认值。

 对于 Int、Float、Range 这些数字类型地属性后,其默认值就是一个单独地数字;对于 Color 和Vector 这类属性,默认值是圆括号包围地一个四维向量;对于 2D、Cube、3D 这3种纹理类型,默认值地定义稍微复杂,它们的默认值是通过一个字符串后跟一个花括号来指定的,其中,字符串要么是空的,要么是内置的纹理名称,如 “white”,"black","gray"或者"bump"。花括号的用处原本是用于指定一些纹理属性的,我们可以通过 TexGen CubeReflect、TexGen CubeNormal 等选项来控制固定管线的纹理坐标的生成。但在 Unity5.0 以后的版本中,这些选项被移除了,如果我们需要类似的功能,就需要自己在顶点着色器中编写计算相应纹理坐标的代码。

下面的代码给出了一个展示所有属性类型的例子:

Shader "Custom/ShaderLabProperties"{
    Properties{
        //Numbers and  Sliders
        _Int ("Int",Int) =2
        _Float ("Float",Float) = 1.5
        _Range ("Range",Range(0.0,0.5)) = 3.0
        //Colors and Vectors
        _Color ("Color", Color) = (1,1,1,1)
        _Vector ("Vector", Vector) = (2,3,6,1)
        //Textures
        _2D ("2D",2D) = "" {}
        _Cube ("Cube", Cube) = "white" {}
        _3D ("3D", 3D) = "black" {}
    }
    FallBack "Diffuse"
}

为了在 Shader 中可以访问到这些属性,我们需要在CG代码片中定义和这些属性类型相匹配的变量。需要说明的是,即使我们不在 Properties 语义块中声明这些属性,也可以直接在CG代码片中定义变量。此时,我们可以通过脚本向 Shader 中传递这些属性。因此,Properties 语义块的作用仅仅是为了让这些属性可以出现在材质面板中。

(3)重量级成员:SubShader

每一个 Unity Shader 文件可以包含多个 SubShader 语义块,但至少要又一个。当 Unity 需要加载在这个 Unity Shader 时,Unity 会扫描所有的 SubShader 语义块,然后选择第一个能够在目标平台上运行的 SubShader。如果都不支持的话,Unity 就会使用Fallback语法指定的 Unity Shader。

Unity 提供这种语义的原因在于,不同的显卡具有不同的能力 。例如,一些旧的显卡仅能支持一定数目的操作指令,而一些更高级的显卡可以将支持更多的指令数,那么我们希望在旧的显卡上使用计算复杂较低的着色器,而在高级的显卡上使用计算复杂度较高的着色器,一边提供更出色的画面。

SubShader 语义块中包含的定义通常如下:

SubShader{
    //可选的
    [Tags]

    //可选的
    [RenderSetup]

    Pass{
    }
    //other Passes
}

SubShader 中定义了一系列 Pass 以及可选的状态([RenderSetup])和标签([Tags])设置。每个Pass定义了一次完整的 渲染流程,但如果Pass的数目过多,往往会造成渲染性能的下降。因此,我们应尽量使用最小数目的 Pass。状态和标签同样可以在 Pass 生命。不同的是,SubSshader 中的一些标签是特定的。也就是说,这些标签设置和 Pass 中使用的标签是不一样的。而对于状态设置来说,其使用的语法是相同的。但是,如果我们在 SubShader 进行了这些设置,那么将会用于所有的 Pass 。

状态设置

ShaderLab 提供了一系列的渲染状态的设置指令,这些指令可以设置显卡的各种状态,例如是否 开启混合/深度测试等。表3.2给出了 ShaderLab 中常见的渲染状态设置选项。

当在 SubShader 块中设置了上诉状态渲染时,将会应用到所有的 Pass。如果我们不想这样(例如在双面渲染中,我们希望在第一个 Pass 中剔除正面来对北面进行渲染,在第二个 Pass 中剔除背面来对正面进行渲染),可以在 Pass 语义块中单独进行上面的设置。

 SubShader 的标签

SubShader 的标签(Tags)是一个键值对(Key/Value Pair),它的键和值都是字符串类型。这些键值对是 SubShader 和渲染引擎之间的沟通桥梁。他们用来告诉Unity的渲染引擎:SubShader我希望怎样以及何时渲染这个对象。

标签的结构如下:

Tags{"TagName1" = "Value" "TagName2" = "Value2" }

SubShader 的标签块支持的标签类型如表3.3所示。

需要注意的是,上述标签仅可以在 SubShader 中生命,而不可以在 Pass 块中声明。Pass 块也可以定义标签,但这些标签是不同于 SubShader 的标签类型。这是我们下面将要讲到的。

Pass 语义块

Pass 语义块包含的语义如下:

Pass{
    [Name]
    [Tags]
    [RenderSetup]
    //other code
}

 首先,我们可以在 Pass 中定义该Pass的名称,例如:

Name "MyPassName"

通过这个名称,我们可以使用 ShaderLab 的 UsePass 命令来直接使用其他 Unity Shader 中Pass。例如:

UsePass "MyShader/MYPASSNAME"

这样可以提高代码的复用性。需要注意的是,由于 Unity 内部会把所有 Pass 的名称转换成大写字母的表示,因此,在使用 UsePass 命令时必须使用大写形式的名字。

其次,我们可以对 Pass 设置渲染状态。SubShader 的状态设置同样适用于 Pass。除了上面提到的状态设置外,在 Pass 中我们还可以使用固定管线的着色器命令。

Pass 同样可以设置标签,但它的标签不同于 SubShader 的标签。这些标签也时用于告诉渲染引擎我们希望怎样来渲染该物体。表3.4给出了 Pass 中使用的标签类型。

除了上面普通的 Pass 定义外,Unity Shader 还支持一些特殊的 Pass,以便进行代码复用或实现更复杂的效果。

UsePass:如我们之前提到的一样,可以使用该命令来复用其他 Unity Shader 中的 Pass;

GrabPass:该 Pass 负责抓取屏幕并将结果存储在一张纹理中,以用于后续的 Pass 处理。

(4)留一条后路:Fallback

紧跟在各个 SubShader 语义块后面的,可以时一个 Fallback 指令。 它用于告诉 Unity,“如果上面所有的 SubShader 在这块显卡上都不能运行,那么就使用这个最低级的 Shader 吧!“

它的语义 如下:

Fallback "name"

//或者

Fallback off

如上所述,我们可以通过一个字符串来告诉 Unity 这个“最低级的 Unity Shader”是谁。我们也可以任性地关闭 Fallback 功能,但一旦你这么做,你的意思大概就是:“如果一块显卡跑不了上面所有地 SubShader,那就不要管它了!”

下面给出了一个使用 Fallback 语句地例子:

Fallback "VertexLit"

事实上,Fallback 还会影响阴影地投射。在渲染阴影纹理时,Unity 会在每个 Unity Shader 中寻找一个阴影投射地 Pass。通常情况下,我们不需要自己专门实现一个 Pass,这是因为 Fallback 使用的内置 Shader 中包含了这样一个通用的 Pass。因此,为每个 Unity Shader 正确设置 Fallback是非常重要的。

(5)ShaderLab 还有其他的语义吗

除了上述的语义,还有一些不常用到的语义。例如,如果我们不满足于 Unity 内置的属性类型,想要自定义材质面板的编辑界面,就可以使用 CustomEditor 语义来扩展编辑界面。我们还可以使用Category 语义来对 Unity Shader 中的命令进行分组。

4、Unity Shader 的形式

上面,我们讲了 Unity Shader 文件的结构以及 ShaderLab 的语法。尽管 Unity Shader 可以做的事情非常多(例如设置渲染状态等),但其最重要的任务还是指定各种着色器所需的代码。这些着色器代码可以写在 SubShader 语义块中(表面着色器的做法),也可以写在 Pass 语义块中(顶点/片元着色器和固定函数着色器的做法)。

在 Unity中,我们可以使用下面3种形式来编写 Unity Shader。而不管使用哪种形式,真正意义上的 Shader 代码都需要包含在 ShaderLab 语义块中,如下所示:

Shader "MyShader" {
    Properties{
        //所需的各种属性    
    }
    SubShader{
        //真正意义上的Shader代码会出现在这里
        //表面着色器(Surface Shader)或者
        //顶点/片元着色器(Vertex/Fragment Shader)或者
        //固定函数着色器(Fixed Function Shader)
    }
    SubShader{
        //和上一个SubShader类似
    }
}

(1)Unity 的宠儿:表面着色器

表面着色器(Surface Shader)是 Unity 自己创造的一种着色器代码类型。它需要的代码量很少,Unity 在背后做了很多工作,但渲染的代价比较大。它在本质上和下面要讲到的顶点/片元着色器是一样的。也就是说,当给 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 之间。原因是,表面着色器不需要开发者关心使用多少个 Pass、每个Pass 如何让渲染等问题,Unity 会在背后为我们做好这些事情。我们要做的只是告诉它:“嘿,使用这些纹理去填充颜色,使用这个法线纹理去填充法线,使用 Lambert 光照模型 ,其他的不要来烦我!”。

CGPROGRAM 和 ENDCG 之间的代码是使用 CG/HLSL 编写的,也就是说,我们需要把CG/HLSL 语言嵌套在 ShaderLab 语言中。值得注意的是,这里的 CG/HLSL 是 UnIty 经封装后提供的,它的语法和标准的 CG/HLSL 语言几乎一样,但还是有细微的不同,例如有些原生的函数和用法 Unity 并没有提供支持。

(2)最聪明的孩子:顶点/片元着色器

在 Unity 中我们可以使用 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);
            }
            
            fixed4 frag():SV_Target {
                return fixed4(1.0,0.0,0.0,1.0);
            }
            ENDCG
        }
    }
}

和表面着色器类似,顶点/片元着色器的代码也需要定义在 CGPROGRAM 和 ENDCG 之间,但不同的是,顶点/片元着色器是写在 Pass 语义块内,而非 SubShader 内。原因是,我们需要自己定义每个Pass需要使用的 Shader 代码。虽然我们可能需要编写更多的代码,但带来的好处是灵活性很高。更重要的是,我们可以控制渲染的实现细节。同样,这里的 CGPROGRAM 和 ENDCG 之间的代码也是使用 CG/HLSL 编写的。

(3)被抛弃的角落:固定函数着色器

上面两种 Unity Shader 形式都使用了可编程管线。而对于一些较旧的设备(其 GPU 仅支持DirectX 7.0、OpenGL 1.5 或 OpenGL ES 1.1),例如 iPhone 3,它们不支持可编程管线着色器,因此,这时候我们就需要使用固定函数着色器(Fixed Function Shader)来完成渲染。这些着色器往往只可以完成一些非常简单的效果。

一个非常简单的固定函数着色器示例代码如下:

Shader "Tutorial/Basic" {
    Properties {    
        _Color ("Main color",Color ) = (1,0.5,0.5,1)
    }
    SubShader {
        Pass {
            Material {
                Diffuse [_Color]
            }
            Lighting On
        }
    }
}
    

可以看出,固定函数着色器的代码被定义在 Pass 语义块中,这些代码相当于 Pass 中的一些渲染设置,正如我们之前在之前提到的一样。

对于固定函数着色器来说,我们需要完全使用 ShaderLab 的语法(即使用 ShaderLab 的渲染设置i命令)来编写,而非使用 CG/HLSL。

由于现在绝大多数 GPU 都支持可编程的渲染管线,这种固定管线的编程方式已经逐渐被抛弃。实际上,在 Unity5.2 中,所有固定函数着色器都会在背后被Unity编译成对应的顶点/片元着色器,因此真正意义上的固定函数着色器已经不存在了。

(4)选择哪种 Unity Shader 形式

那么,我们究竟当选哪一种来进行 Unity Shader 的编写呢?这里给出了一些建议。

除非你有非常明确的需求必须要使用固定函数着色器,例如需要在非常旧的设备上运行你的游戏(这些设备非常少见),否则请使用可编程管线的着色器,即表面着色器或顶点/片元着色器。

如果你想和各种光源打交道,你可能更喜欢使用表面着色器,但需要小心它在移动平台的性能表现。

如果你需要使用的光照数目非常少,例如只有一个平行光,那么使用顶点/片元着色器是一个更好的选择。

最重要的是,如果你有很多自定义的渲染效果,那么请选择顶点/片元着色器。

5、答疑解惑

尽管在之前的内容中涵盖了很多基础内容,这里仍给出一些初学者常见的困惑之处,并给予说明和解释。

(1)Unity Shader != 真正的 Shader

需要注意的是,Unity Shader 并不等于之前的 Shader,尽管 Unity Shader 翻译过来就是 Unity 着色器。在 Unity 里,Unity Shader 实际上指的就是一个 ShaderLab 文件--硬盘上以.shader 作为文件后缀的一种文件。

在 Unity Shader (或者说是 ShaderLab 文件)里,我们可以做的事情远多于一个传统意义上的Shader.

  • 在传统的 Shader 中,我们仅可以编写特定类型的 Shader,例如顶点着色器、片元着色器等。而在 Unity Shader 中,我们可以在同一个文件里同时包含需要的顶点着色器和片元着色器代码。
  • 在传统的 Shader 中,我们无法设置一些渲染设置 ,例如是否开启混合、深度测试等,这些是开发者在另外的代码中自行设置的。而在 Unity Shader 中,我们通过一行特定的指令就可以完成这些设置。
  • 在传统的 Shader 中,我们需要编写冗长的代码来设置着色器的输入和输出,要小心地处理这些输入输出地位置对应关系等。而在 Unity Shader 中 ,我们只需要在特定语句块中声明一些属性,就可以依靠材质来方便地改变这些属性。而且对于模型自带地数据(如顶点位置、纹理坐标、发现等),Unity Shader 也提供了直接访问地方法,不需要开发者自行编码来传给着色器。

当然,Unity Shader 除了上诉这些优点外,也有一些缺点。由于 Unity Shader 的高度封装性,我们可以编写的 Shader 类型和语法都被限制了。对于一些类型的 Shader,例如曲面细分着色器(Tessellation  Shader)、几何着色器(Geometry Shader)等,Unity 的支持就贤惠差一些。一些高级的 Shader 语法 Unity Shader 也不支持。

可以说,Unity Shader 提供了一种让开发者同时控制渲染流水线中多个阶段的一种方式,不仅仅是提供 Shader 代码。作为开发者而言,我们绝大部分时候只需要和 Unity Shader 打交道,而不需关心渲染引擎底层的实现细节。

(2)Unity Shader 和 CG/HLSL 之间的关系

正如我们之前所将,Unity Shader 是用 ShaderLab 语言编写的,但对于表面着色器和顶点/片元着色器,我们可以在 ShaderLab 内部嵌套 CG/HLSL 语言来编写这些着色器代码。这些 CG/HLSL 代码是嵌套在 CGPROGRAM 和 ENDCG 之间的,正如我们之前看到的示例代码一样。由于 CG 和 DX9 风格的 HLSL 从写法上来说几乎是同一种语言,因此在 Unity 里 CG 和 HLSL 是等价的。我们可以说, CG/HLSL 代码是区别于 ShaderLab 的另一个世界。

通常,CG 的代码片段是位于 Pass 语义块内部的,如下所示:

Pass {
    //Pass 的标签和状态设置

    CGPROGRAM
    //编译指令 ,例如
    #pragma vertex vert
    #pragma fragment frag
    
    // CG代码
    
    ENDCG
    //其他一些设置
}

可能会有疑问:“之前不是说在表面着色器中,CG/HLSL 代码是写在 SubShader 语义块内吗?而不是 Pass 块内。”的确,在表面着色器中,CG/HLSL 代码是写在 SubShader 语义块内,但是读者应该还记得,表面着色器在本质上就是顶点/片元着色器,它们看起来很不像是因为表面着色器是 Unity 在顶点/片元着色器上层为开发者提供的一层抽象封装,但在背后,Unity 还是会把它转化成一个包含多 Pass 的顶点/片元着色器。我们可以在 Unity Shader 的导入设置面板中单击 Show generated code 按钮来查看生成的真正的顶点/片元着色器代码。可以说,从本质上来讲,Unity Shader 只有两种形式:顶点/片元着色器和固定函数着色器(在 Unity 5.2 以后的版本中,固定函数着色器也会在背后被转化成顶点/片元着色器,因从本质上来说 Unity 中只存在顶点/片元着色器)。

在提供给编程人员这些便利的背后,Unity 编辑器会把这些 CG 片段编译成低级语言,如汇编语言等。通常,Unity 会自动把这些 CG 片段编译到所有相关平台(这里的平台是指不同的渲染平台,例如 Direct3D 9、OpenGL、Direct3D 11、OpenGL ES等)上。这些编译过程比较复杂,Unity 会使用不同的编译器来把 CG 转化成对应平台的代码。这样就不会在转换平台时再重新编译,而且如果代码在某些平台上发生错误就可以立刻得到错误信息。

但当发布游戏的时候 ,游戏数据文件中只包含目标平台需要的编译代码,而那些在目标平台上不需要的代码部分救护被移除。例如,当发布到 Mac OS X 平台上时,DirectX 对应的代码部分就会被移除。

(3)我可以使用GLSL来写吗

当然可以。如果你坚持说:“我就是不想用 CG/HLSL 来写!就是要使用 GLSL 来写!”,但是这意味着你可以发布的目标平台就只有 Mac OS X、OpenGL ES 2.0 或者 Linux,而对于 PC、Xbox 360 这样的仅支持 DirectX 的平台来说,你就放弃它们了。

建立在你坚持要用 GLSL 来写 Unity Shader 的意愿上,你可以怎么写呢?和 CG/HLSL 需要嵌套在 CGPROGRAM 和 ENDCG 之间类似, GLSL 的代码需要嵌套在 GLSLPROGRAM 和 ENDCG 之间。

参考书籍《Unity Shader入门精要》

猜你喜欢

转载自blog.csdn.net/weixin_43418880/article/details/127097320