UnityShader基础
UnityShader概述
材质和UnityShader
总的来说,在Unity中需要配合使用材质(Material)和 Unity Shader 才能达到需要的效果,常见流程为:
- 创建一个材质
- 创建一个 Unity Shader,并把它赋给上一步中创建的材质
- 把材质赋给需要渲染的对象
- 在材质面板中调整 Unity Shader 的属性,以得到满意的效果
Unity Shader定义了渲染所需的各种代码(如顶点着色器和片元着色器)、属性(如使用哪些纹理等)和指令(渲染和标签设置等)
材质允许调节这些属性,将其最终赋给对应的模型
Unity 中的材质
Unity 中的材质需要结合一个 GameObject 的 Mesh 或者 Particle systems 组件来工作,它决定了游戏对象看起来是什么样子的(也需要 Unity Shader 的配合)
创建一个新的材质,可以在 Unity 的菜单栏中选择 Assets -> Create -> Material 来创建,也可以直接在 Project 视图中右击 -> Create -> Material 来创建
当创建了一个材质后,就可以把它赋值给一个对象,这可以通过把材质直接拖拽到 Scene 视图中的对象来实现,或者在该对象的 Mesh Renderer 组件中直接赋值,如图:
在 Unity 的 2020.x 版本中,默认情况下,一个新建的材质将使用Unity内置的 Standard Shader,这是一种基于物理渲染的着色器
Unity的材质和许多建模软件(如 Cinema 4D、Maya 等)中提供的材质工鞥呢类似,他们都提供了一个面板来调整材质的各个参数,这种可视化的方法使得开发者不再需要自行在代码中设置和改变所需的各种参数,如图:
Unity 中的 Shader
创建一个新的 Unity Shader,可以在 Unity 的菜单栏中选择 Assets -> Create -> Shader 来创建,也可以直接在 Project 视图中右击 -> Create -> Shader 来创建,Unity 一共提供了5种 Unity Shader 模板供选择 —— Shader Surface Shader, Unlit Shader, Image Effect Shader, Compute Shader 以及 Ray Tracing Shader。
Standard Surface Shader 会产生一个包含了标准光照模型的表面着色器模板
Unlit Shader会产生一个不包含光照(但包含雾效)的基本的顶点/片元着色器
Image Effect Shader 为实现各种屏幕后处理效果提供了一个基本模板
Compute Shader 会产生一种特殊的 Shader 文件,这类 Shader 旨在利用 GPU 的并行性来进行一些常规渲染流水线无关的计算
Ray Tracing Shader是一种用于Unity中的实时光线追踪的Shader。光线追踪是一种通过追踪从相机到场景中各个像素的光线的技术,以模拟光线在真实世界中的传播和反射。Ray Tracing Shader可以帮助开发者实现更加逼真的光影效果,包括实时反射、折射、阴影和环境光照等效果。
一个单独的 Unity Shader 是无法发挥任何作用的,它必须和材质结合起来。
所以,可以在材质面板最上方的下拉菜单中选择需要使用的 Unity Shader,当选择完毕后,材质面板中就会出现该 Unity Shader 可用的各种属性。这些属性可以是颜色、纹理、浮点数、滑动条(限制了范围的浮点数)、向量等
当把材质赋给场景中的一个对象,就可以看到调整属性所发生的视觉变化
Unity Shader本质上就是一个文本文件,和 Unity 中的很多外部文件类似,Unity Shader 也有导入设置(Import Settings)面板,在 Project视图中选中某个 Unity Shader即可看到,如图:
在该面板上,可以在 Default Maps 中指定该 Unity Shader 使用的默认纹理。当任何材质第一次使用该 Unity Shader 时,这些纹理就会自动被赋予到相应的属性上。在下方的面板中,Unity 会显示出和该 Unity 相关的信息,例如它是否是一个表面着色器(Surface Shader)、是否是一个固定函数着色器(Fixed Function Shader)等,还有一些信息是和在 Unity Shader 中的标签设置有关,例如是否会投射阴影、使用的渲染队列、LOD值等
对于表面着色器来说,可以通过单击 Show generated code 按钮来打开一个新的文件,在该文件里将显示 Unity 在背后为该表面着色器生成的顶点/片元着色器。这可以方便对这些生成的代码进行修改(需要复制到一个新的 Unity Shader 中才可保存)和研究
如果 Unity Shader 使一个固定函数着色器,在 Fixed function 的后面也会出现一个 Show generated code 按钮,来查看该固定函数着色器生成的顶点/片元着色器。Compile and show code 下拉列表可以让开发者检查该 Unity Shader 针对不同图像编程接口(例如 OpenGL、D3D9、D3D11等)最终编译成的 Shader 代码,如图所示:
直接单击该按钮可以查看生成的底层的汇编指令。可以利用这些代码来分析和优化着色器
除此之外,Unity Shader 的导入面板还可以方便地查看其使用的渲染队列(Reder queue)、是否关闭批处理(Disable batching)、属性列表(Properties)等信息
Unity Shader 的基础:ShaderLab
什么是 ShaderLab
Unity Shader 是 Unity 为研发者提供的高层记得渲染抽象层,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"
}
UnityShader的结构
给Shader起名字
每个Unity Shader文件的第一行都需要通过 Shader 语义来制定该 Unity Shader 的名字,这个名字由一个字符串来定义
当为材质选择使用的 Unity Shader 时,这些名称就会出现在材质面板的下拉列表里,通过在字符串中添加斜杠(“/”),可以控制 Unity Shader 在材质面板中出现的位置,如下:
Shader "Unlit/001Shader"{}
那么,这个 Unity Shader在材质面板中的位置就是:Shader -> Unlit -> 001Shader,如图:
材质和 Unity Shader 的桥梁:Properties
Properties语义块中包含了一系列属性(property),这些属性将会出现在材质面板中
Properties语义块的定义通常如下:
Properties{Name("display name",PropertyType) = DefaultValueName("display name",PropertyType) = DefaultValue//更多属性
}
开发者声明这些属性是为了在材质面板中能够方便的调整各种材质属性
如果需要在 Shader 中访问它们,就需要使用每个属性的名字(Name),Unity中,这些属性的名字通常由一个下划线开始,显示的名称(display name)则是出现在材质面板上的名字,需要为每个属性指定它的类型(PropertyType),常见的属性如表:
属性类型 | 默认值的定义语法 | 例子 |
---|---|---|
Int | number | _Int(“Int”,Int) = 2 |
Float | number | _Float(“Float”,Float) = 1.5 |
Range(min,max) | number | _Range(“Range”,Range(0.0,5.0) = 3.0 |
Color | (number,number,number,number) | _Color(“Color”,Color) = (1,1,1,1) |
Vector | (number,number,number,number) | _Verctor(“Vector”,Vector) = (2,3,6,1) |
2D | “defaulttexture”{} | _2D(“2D”,2D) = “”{} |
Cube | “defaulttexture”{} | _Cube(“Cube”,Cube) = “white”{} |
3D | “defaulttexture”{} | _3D(“3D”,3D) = “black” |
除此之外,还需要为每个属性指定一个默认值,在第一次把该 Unity Shader 赋给某个素材时,菜盒子面板显示的就是这些默认值
对于 Int、Float、Range ,这些数字类型的属性,其默认值就是一个单独的数;
对于 Color 和 Vector 这类属性,默认值是用圆括号包围的一个四维向量;
对于 2D、Cube、3D 这3种纹理类型,默认值的类型稍微复杂,他们的默认值是通过一个字符串后跟一个花括号来指定的,其中,字符串要么是空的,要么是内置的纹理名称,如 “white”“black”“gray” 或者 “bump”。或括号的用处原本是用于制定一些纹理属性的。
如果需要控制固定管线的纹理坐标的生成,就需要再顶点着色器中编写计算相应纹理坐标的代码
如下方的代码:
Shader "Unlit/001Shader"
{Properties{_Int("Int",int) = 2_Float("Float",float) = 1.5_Range("Range",range(0.0,5.0)) = 1.0_Color("Color",Color) = (1,1,1,1)_Vector("Vector",Vector) = (2,3,6,1)_2D("2D",2D) = ""{}_Cube("Cube",Cube) = "white"{}_3D("3D",3D) = "black"{}}FallBack "diffuse"
}
材质面板中的显示结果,如图:
Unity 允许重载默认的材质编辑面板,以提供更多自定义的数据类型
为了在 Unity 中可以访问到这些属性,需要在 Cg 代码片中定义和这些属性类型相匹配的变量,需要说明的是,即使不在 Properties 语义块中生命这些属性,也可以直接在 Cg 代码片中定义变量
此时,可以通过脚本向 Shader 中换地这些属性,因此,Properties与一块的作用仅仅是为了让这些属性可以出现在材质面板中
SubShader
每一个 Unity Shader 文件可以包含多个 SubShader 语义块,但最少要有一个。
当 Unity 需要加载这个 Unity Shader 时,Unity 会扫描所有的 SubShader 语义块,然后选择第一个能够在目标平台运行的SubShader
如果都不支持的话,Unity 就会使用 Fallback 语义指定的 Unity Shader
Unity 提供这种语义的原因在于,不同的显卡具有不同的能力。例如:一些旧的显卡仅能支持一定数目的操作指令,而一些更高级的显卡可以支持更多的指令数
那么就希望在旧的显卡上使用计算复杂度低的着色器,在高级的显卡上使用计算复杂度较高的着色器,以便提供更出色的画面
SubShader 语义块中包含的定义通常如下
SubShader{//可选Tags//可选RenderSetupPass{}//Other Passes
}
SubShader 中定义了一系列 Pass 以及可选的状态(RenderSetup)和标签(Tags)设置。
每个 Pass 定义了一个完整的渲染流程,但如果 Pass 数目过多,往往会造成渲染性能的下降
因此,应该尽量使用最小数目的 Pass,状态和标签同样可以在 Pass 声明,不同的是,SubShader 中的一些标签是特定的
也就是说,这些标签设置和 Pass 中使用的标签是不一样的,对于状态设置来说,使用的语法是相同的
但是如果在 SubShader 进行了这些设置,呢么将会用于所有的 Pass
状态设置
ShaderLab 提供了一系列渲染状态的设置指令,这些指令可以设置显卡的各种状态,例如是否开启混合/深度测试等
ShaderLab 中常见的渲染状态设置选项如下表:
状态名称 | 设置指令 | 解释 |
---|---|---|
Cull | Cull Back|Front|Off | 设置剔除模式:剔除背面/正面/关闭剔除 |
ZTest | ZTest Less Greater|LEqqual|GEqual|Equal|NotEqual|Always | 设置深度测试时使用的函数 |
ZWrite | ZWrite On|Off | 开启/关闭深度写入 |
Blend | Blend SrcFactor DstFactor | 开启并设置混合模式 |
当在 SubShader 块中设置了上述渲染状态时,将会应用到所有的 Pass,如果不想这样(如在双面渲染中,希望第一个 Pass 中剔除正面来对背面进行渲染,在第二个 Pass 中剔除背面来对正面进行渲染),可以在 Pass 语义块中单独进行上面的设置
SubShader的标签
SubShader 的标签(Tags)是一个键值对(Key/Value Pair),它的键和值都是字符串类型,这些键值对是 SubShader 和渲染引擎之间的沟通桥梁。它们用来告诉 Unity 的渲染引擎希望怎样以及何时渲染这个对象:
标签的结构如下:
Tags{"TagName1" = "Value1" "TagName2" = "Value2"}
SubShader 的标签快支持的标签类型如表:
标签类型 | 说明 | 例子 |
---|---|---|
Queue | 控制渲染顺序,指定该物体属于哪一个渲染队列,通过这种方式可以保证所有的透明物体可以在所有不透明物体后面被渲染,也可以自定义使用的渲染队列来控制物体的渲染顺序 | Tags{“Queue” = “Transparent”} |
RenderTyep | 对着色器进行分类,例如这是一个不同明德着色器,或是一个透明的着色器等,这可以被用于着色器替换(Shader Replacement)功能 | Tags{“renderType”=“Opaque”} |
DisableBatching | 一些 SubShader 在使用 Unity 的批处理功能时会出现问题,例如使用了模型空间下的坐标进行顶点动画,这是可以通过该标签来直接指明是否对该 SubShader 使用批处理 | Tags{“DisableBatching” = “True” |
ForceNoShadowCasting | 控制使用该 SubShader 的物体是否会投射阴影 | Tags{“ForceNoShadowCasting”=“True” |
IgnoreProjector | 如果该标签为"True",那么使用该 SubShader 的物体将不会受 Projector 的影响,通常用于半透明物体 | Tags{“IgnoreProjector”=“True”} |
CanUseSpriteAtlas | 当该 SubShader 是用于精灵(sprite)时,将该标签设置为"False" | Tags{“CanUseSpriteAtlas”=“False” |
PreviewType | 知名材质面板将如何预览改材质。默认情况下,材质将显示为一个球形,可以通过把这些标签的值设为"Plane""SkyBox"来改变预览类型 | Tags{“PreviewType”=“Plane”} |
需要注意的是,上述标签仅可以在 SubShader 中声明,而不可以在 Pass 块中声明。Pass 块虽然也可以定义标签,但这些标签是不同于 SubShader 的标签类型
Pass 语义块
Pass 语义块包含的语义如下:
Pass{NameTagsRenderSetup
}
首先,可以在 Pass 中定义该 Pass 的名称,例如:
Name "MyPassName"
通过这个名称,可以使用 ShaderLab 的 UsePass 命令来直接使用其他 UnityShader 中的 Pass,例如:
UsePass "MyShader/MYPASSNAME"
这样可以提高代码的复用性,需要注意的是,由于 Unity 内部会把所有 Pass 的名称转换成大写字母的表示,因此,在使用 UsePass 命令时必须使用大写形式的名字
其次,可以对 Pass 设置渲染状态。SubShader 的状态设置同样适用于 Pass。除了上面提到的状态设置外,在 Pass 中我们还可以使用固定管线的着色器命令
Pass 同样可以设置标签,但它的标签不同于 SubShader 的标签,这些标签也是用于告诉渲染引擎希望怎样来渲染该物体
Pass 中使用的标签类型如下表:
标签类型 | 说明 | 例子 |
---|---|---|
LightMode | 定义该 Pass 在 Unity 的渲染流水线中的角色 | Tags{“LightMode”=“ForwardBase”} |
RequireOptions | 用于指定当满足某些条件时才渲染该 Pass,它的值是一个由空格分割的字符串。 | Tags{“RequireOptions”=“SoftVegetation”} |
除了上面普通的 Pass 定义外,UnityShader 还支持一些特殊的 Pass,以便进行代码复用或实现更复杂的效果:
UsePass:如之前提到的一样,可以使用该命令来复用其他 UnityShader 中的 Pass
GrabPass:该 Pass 负责抓取屏幕并将结果存储在一张纹理中,以用于后续的 Pass 处理
Fallback
紧跟在各个 SubShader 语义块后面的,可以是一个 Fallback 指令,它用于告诉 Unity,“如果上面所有的 SubShader 在这块显卡上都不能运行,那么就是用这个最低级的 Shader”
它的语义如下:
Fallback "name"
//或者
Fallback Off
如上所述,可以通过一个字符串来告诉 Unity 这个“最低级的 UnityShader”是谁,也可以关闭 Fallback 功能,但一旦这么做,那么意思就是“如果一块显卡跑不了上面所有的 Shader,那就不管他了”
例子如下:
Fallback "VertexLit"
事实上,Fallback还会影响阴影的投射
在渲染阴影纹理时,Unity 会在每个 UnityShader 中寻找一个阴影投射的 Pass,通常情况下,不需要自己专门实现一个 Pass,这是因为 Fallback 使用的内置 Shader 中包含了这样一个通用的 Pass,因此,为每个 UnityShader 正确设置 Fallback 是非常重要的
ShaderLab还有其他的语义吗
除了上述的语义,还有一些不常用到的寓意。
例如:如果不满足于 Unity 内置的属性类型,想要自定义材质面板的编辑界面,就可以使用 CustomEditor 语义来拓展编辑界面。还可以使用 Category 语义来对 UnityShader 中的命令进行分组。
UnitySahder 的形式
尽管 UnityShader 可以做的事情很多(例如设置渲染状态等),但其最重要的任务还是指定各种着色器所需的代码。这些着色器代码可以写在 SubShader 语义块中(表面着色器的做法),也可以写在 Pass 语义块中(顶点/片元着色器和固定函数着色器的做法)
在 Unity 中,可以使用下面 3 种形式来编写 UnityShader,而不管使用哪种形式,真正意义上的 Shader 代码都需要包含在 ShaderLab 语义块中,如下所示:
Shader "MyShader"{Properties{//所需的各种属性}SubShader{//真正意义上的 Shader 代码会出现在这里//表面着色器{Surface Shader}或者//顶点/片元着色器{Vertex/Fragment Shader}或者//固定函数着色器{Fixed Function Shader}}SubShader{//和上一个 SubShader 类似}
}
表面着色器
表面着色器(Surface Shader) 是 Unity 自己创造的一种着色器代码类型,他需要的代码量很少,Unity 在背后做了很多工作,渲染的代价比较大。
本质上和顶点/片元着色器是一样的,当给Unity提供一个便面着色器的时候,在背后仍旧需要把它装换成对应的顶点/片元着色器
它的价值在于:Unity 处理了很多光照细节,不需要去操心这些“烦人的事情”
示例代码如下:
Shader "Custom/Simple Surface Shader"{SubSHader{Tags{"RenderType"="Opaque"}CGPROGAM#pragma surface surf Lambertstruct Input{float4 color : COLOR;};void surf(Input IN,inout SurfaceOutput o){o.Albedo = 1;}ENDCG}Fallback "Diffuse"
}
表面着色器被定义在 SubShader 语义块(不是 Pass 语义块)中的 CGPROGRAM 和 ENDCG 之间
原因是表面着色器不需要开发者关心使用多少个 Pass,每个 Pass 如何渲染等问题,Unity 会在背后做好这些事情
CGPROGRAM 和 ENDCG 之间的代码是使用 Cg/HLSL 编写的,也就是说,需要把 Cg/HLSL 语言嵌套在 ShaderLab 语言中
这里的 Cg/HLSL 是 Unity 经封装后提供的,它的语法和标准的 Cg/HLSL 语法几乎一样,但还是有细微的不同
顶点/片元着色器
Unity 中可以使用 Cg/HLSL 语言来编写顶点/片元着色器(Vertex/Fragment Shader)。它们更加复杂,但灵活性更高
示例代码如下:
Shader "Custom/Simple VertexFragment Shader"{SubShader{Pass{CGPROGRAM#progama vetex vert#pragma fragment fragfloat4 vert(float4 v : POSITION) : SV_POSITON{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 代码,虽然可能需要编写更多的代码,但好处是灵活性很高,更重要的是,可以空时渲染的实现细节
固定函数着色器
对于一些较旧的设备(其GPU仅支持 DirectX 7.0、OpenGL 1.5 或 OpenGL ES 1.1),它们不支持可编程管线着色器,因此,这时候就需要使用固定函数着色器(Fixed Function Shader) 来完成渲染。这些着色器网网址可以完成一个非常简单的效果
示例代码如下:
Shader "Tutorial/Basic"{Properties{_Color("Main Color",Color} = {1,0.5,0.5,1}}SubShader{Pass{Material{Diffuse [_Color]}}}
}
固定着色器的代码被定义在 Pass 语义块中,这些代码相当于 Pass 中的一些渲染设置
对于固定函数着色器来说,需要完全使用 ShaderLab 的语法(使用 ShaderLab 的渲染设置命令)来编写,而不是使用 Cg/HLSL
由于现在绝大多数 GPU 都支持可编程的渲染管线,这种编程方式已经被逐渐抛弃,在新版的 Unity 中,所有固定函数着色器都会在背后被 Unity 编译成对应的顶点/片元着色器
选择哪种 UnityShader 形式
如果有非常明确的需求必须要使用固定函数着色器,否则使用可编程管线的着色器,即表面着色器或顶点/片元着色器
如果要和各种光源打交道,可能更需要使用表面着色器,但需要小新在移动平台中的性能表现
如果需要使用的光照数目非常少,那么使用顶点/片元着色器使一个更好的选择
如果有很多自定义的渲染效果,使用顶点/片元着色器
其他的一些问题
UnityShader != 真正的Shader
尽管UnityShader翻译过来就是Unity着色器,在Unity里,UnityShader实际上指的就是一个ShaderLab文件——硬盘上以.shader作为文件后缀的一种文件
在UnityShader里,可以做的事情远多于一个传统意义上的Shader
传统的Shader中,仅可以编写特定类型的Shader,例如顶点着色器、片元着色器等,在UnityShader中,可以在同一个文件里同时包含需要的顶点着色器和片元着色器代码
在传统的Shader中,无法设置一些渲染设置,例如是否开启混合、深度测试等,这些是开发者在另外的代码中自行设置的。而在UnityShader中,通过一行特定的指令就可以完成这些设置
在传统的Shader中,我们需要编写荣昌的代码来设置着色器的输入和输出,要小心的处理这些输入输出的位置对应关系等。而在UnityShader中,只需要在特定的语句块中生命一些属性,就可以依靠材质来方便的改变这些属性。而且对于模型自带的数据(如定点位置、纹理坐标、法线等),UnityShader也提供了直接访问的方法,不需要开发者自行编码来传给着色器
UnityShader出了上述这些优点外,也有一些缺点。
由于UnityShader的高度封装性,可以编写的Shader类型和语法都被限制了。对于一些类型的Shader,例如曲面细分着色器(Tessellation Shader)、几何着色器(Geometry Shader)等,Unity的支持就相对差一些。例如:Unity 4.x 仅在 DirectX 11平台下提供曲面细分着色器、几何着色器的相关功能,而对于OpenGL平台则没有这些支持。除此之外,一些高级的Shader语法UnityShader也不支持
可以说,UnityShader提供了一种让开发者同时控制渲染流水线中多个阶段的一种方式,不仅仅是提供Shader代码。作为开发者而言,绝大部分时候只需要和UnityShader打交道,而不需要关心渲染引擎底层的实现细节
UnityShader和Cg/HLSL之间的关系
UnityShader是用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//其他一些设置
}
在提供给编程人员这些便利的背后,Unity编辑器会把这些Cg片段编译成低级语言,如汇编语言等。同城,Unity会自动把这些诶Cg片段编译到所有相关平台(这里的平台是指不同的渲染平台,如Dirext3D 9、OpenGL、Direct3D 11、OpenGL ES等)上。这些编译过程比较复杂,Unity会使用不同的编译器来吧Cg转换成对应平台的代码。这样就不会在切换平台时再重新编译,而且如果代码在某些平台上发生错误就可以立刻得到错误信息。
可以在UnityShader的导入设置面板上查看这些编译后的代码,查看这些代码有助于进行Debug或优化等
如图:
但当发布游戏的时候,游戏数据文件中质保函目标平台需要的编译代码,而那些在目标平台上不需要的代码部分就会被移除。例如,当发布到Mac OS X平台上时,DirectX对应的代码部分就会被移除。