Unity3D自带功能:地形(Terrain)

Unity3D自带功能:地形(Terrain),第1张

Unity3D中的地形类似于Maya中使用置换贴图来变形高段数的多边形平面,其原理是自动创建一个中等多边形密度的mesh plane(多边形平面),然后指定一张16bit的灰阶图作为height map(高差图),并根据mesh各个顶点所对应的灰度数值沿着Y轴改变该顶点的高度,形成高低起伏的复杂地形。

Unity3D内置了一套简单的brush(笔刷)工具来方便用户对地形物体的编辑修改。

通过菜单 GameObject > 3D Object > Terrain 可以创建一个Terrain (地形)。

Terrain Settings是对Terrain整体的参数设置面板。

最上面的一排按钮分别用来对地形及植被进行“刷制”的工具面板切换,最右边的 Terrain Setting 面板是针对地形整体的设置,这里挑一些常用的重点的来讲:

按下鼠标左键开始提升地形高度,如果同时按住 shift 键,则降低地形高度直到0。

直接刷出特定高度,按住 shift 键获取某一点的高度。非常适合用来刷出非水平面高度的平整地形,比如山顶或山间的一块平整地面等等。同时如果场景中有水面,由于Terrain不能刷出水平面以下的地形(最低刷到水平面),可以首先将全部地形提升到一定高度,再将水域地形刷低。

相当于光滑笔刷工具

在地形表面上绘制贴图。

首先需要在Textures栏中点击 Edit Textures ,再选择 Add Texture 以添加一层贴图。

在这个“添加地形贴图”面板中我们可以设置一张颜色贴图和一张法线贴图,并指定贴图的重复度。如果检测出颜色贴图含有Alpha通道,就会将这个Alpha通道与作为光滑度的贴图,否则就会出现一个Smoothness参数供我们调节这一层表面的光滑度,此外还提供一个Metallic参数供我们调节这一层表面的金属颜色。

这里的贴图都需要处理成无缝贴图,否则场景中会出现明显的接缝。贴图不需要特别大,因为重复度会被设置得比较高,通常512或者1024分辨率的贴图就可以了。

第一层贴图会默认覆盖整个地形区域,从第二层(从左往右)开始,添加好贴图以后需要在地形上刷出该层贴图的出现范围。

需要编辑各层的贴图设置只需点击 Edit Textures > Edit Texture ,需要删除贴图层就点击 Edit Textures > Remove Texture 。

在地形上刷出树来。

按下鼠标左键开始刷树,按住 Shift 键再刷可以删除已经刷出来的树,按住 Ctrl 键再刷可以删除当前选择的特定类型的树。

在Trees栏点击 Edit Trees ,再选择 Add Tree ,可以添加一个GameObject作为“树”。这个物体可以是任意GameObject,并不一定非要是Tree Object。

然后就可以用鼠标在地形表面“刷”出这类树来了,同时也会出现具体的Settings参数。

我们可以添加多种type的树,然后一层层刷出复杂的森林结构。

同样的,我们也可以点击 Edit Trees > Edit Tree 给树更换GameObject,或点击 Edit Trees > Remove Tree 删除某一层的树。

在地形上刷出细节物体,通常用来做草。

按下鼠标左键开始刷细节物体,按住 Shift 键再刷可以删除已经刷出来的细节物体,按住 Ctrl 键再刷可以删除当前选择的特定类型的细节物体。

在Details栏点击 Edit Details ,再选择 Add Grass Texture ,打开Add Grass Texture面板设置新建“草贴图”的参数:

在Details栏点击 Edit Details ,再选择 Add Detail Mesh ,打开Add Detail Mesh面板设置新建“细节面片”的参数:

深度纹理实际就是一张渲染纹理,只不过它里面存储的像素值不是颜色值,而是一个高精度的深度值。由于被存储在一张纹理中,深度纹理里的深度值范围是[0, l], 而且通常是非线性分布的。这些深度值来自于顶点变换后得到的归一化的设备坐标 (Normalized Device Coordinates , 简称NDC) 。一个模型要想最终被绘制在屏幕上,需要把它的顶点从模型空间变换到齐次裁剪坐标系下,这是通过在顶点着色器中乘以 MVP 变换矩阵得到的 。在变换的最后一步,我们需要使用一个投影矩阵来变换顶点,当我们使用的是透视投影类型的摄像机时,这个投影矩阵就是非线性的。

下图 显示了Unity 中透视投影对顶点的变换过程。最左侧的图显示了投影变换前,即观察空间下视锥体的结构及相应的顶点位置,中间的图显示了应用透视裁剪矩阵后的变换结果,即顶点着色器阶段输出的顶点变换结果 ,最右侧的图则是底层硬件进行了透视除法后得到的归一化的设备坐标(NDC)。需要注意的是,这里的投影过程是建立在Unity对坐标系的假定上的,也就是说,针对的是观察空间为右手坐标系,使用列矩阵在矩阵右侧进行相乘,且变换到NDC后 z分量范围将在[-1, l] 之间的情况。而在类似 DirectX 这样的图形接口中,变换后z分量范围将在[0, 1]之间 。如果是在其他图形接口下,需要对一些计算参数做出相应变化。

下图 显示了在使用正交摄像机时投影变换的过程。同样,变换后会得到一个范围为 [-1, 1]的立方体。正交投影使用的变换矩阵是线性的。

在得到NDC后,深度纹理中的像素值就可以很方便地计算得到了,这些深度值就对应了NDC中顶点坐标的z分量的值。由于NDC中 z分量的范围在[-1, I], 为了让这些值能够存储在一张图像中,需要使用下面的公式对其进行映射:

其中,d对应了深度纹理中的像素值, 对应了NDC坐标中的z分量的值。

在Unity中,深度纹理可以直接来自于真正的深度缓存,也可以是由一个单独的 Pass 渲染而得,这取决于使用的渲染路径和硬件。通常来讲,当使用延迟渲染路径(包括遗留的延迟渲染路径)时,深度纹理理所当然可以访问到,因为延迟渲染会把这些信息渲染到 G-buffer 。而当无法直接获取深度缓存时,深度和法线纹理是通过一个单独的Pass渲染而得 。具体实现是Unity会使用着色器替换技术选择那些渲染类型(即 SubShader RenderType 标签)为 Opaque 的物体,判断它们使用的渲染队列是否小于等于2500(内置的 Background Geometry AlphaTest 渲染队列均在此范围内),如果满足条件,就把它渲染到深度和法线纹理中。因此,要想让物体能够出现在深度和法线纹理中,就必须在Shader中设置正确的RenderType标签。

在 Unity中,可以选择让一个摄像机生成一张深度纹理或是一张深度+法线纹理。当选择前者,即只需要 张单独的深度纹理时, Unity 会直接获取深度缓存或是按之前讲到的着色器替换技术,选取需要的不透明物体,并使用它投射阴影时使用的 Pass (即 LightMode 被设置为ShadowCaster Pass)来得到深度纹理。如果 Shader 中不包含这样一个 Pass, 那么这个物体就不会出现在深度纹理中(当然,它也不能向其他物体投射阴影)。深度纹理的精度通常24 位或 16 位,这取决于使用的深度缓存的精度。如果选择生成一张深度+法线纹理, Unity 创建一张和屏幕分辨率相同、精度为 32 位(每个通道为 位)的纹理,其中观察空间下的法线信息会被编码进纹理的 通道,而深度信息会被编码进 通道。法线信息的获取在延迟渲染中是可以非常容易就得到的, Unity 只需要合并深度和法线缓存即可。而在前向渲染中,默认情况下是不会创建法线缓存的,因此 Unity 底层使用了一个单独的 Pass 把整个场景再次渲染一遍来完成。这个 Pass 被包含在 Unity 内置的一个 Unity Shader 中,可以在内置的builtin_shaders-xxx/DefaultResources/Camera-DepthNormaITextureshader 文件中找到这个用于渲染深度和法线信息的 Pass。

获取深度纹理,先设置摄像机的 depthTextureMode:

然后在 Shader 中通过声明_CameraDepthNormalsTexture 变量来访问它。

同理,如果需要获取获取深度+法线纹理,设置摄像机的 depthTextureMode为:

然后在 Shader 中通过声明_CameraDepthN ormalsTexture 变量来访问它。

还可以组合这些模式,让一个摄像机同时产生一张深度和深度+法线纹理:

在 Shader 中访问到深度纹理_CameraDepthTexture 后,我们就可以使用当前像素的纹理坐标对它进行采样。绝大多数情况下,我们直接使用 tex2D 函数采样即可,但在某些平台(例如 PS3 PSP2) 上,我们需要 一些特殊 处理 Unity 为我们提供了一个统一的宏SAMPLE_DEPTH_TEXTURE, 用来处理这些由于平台差异造成的问题。而我们只需要在 Shader中使用 SAMPLE_DEPTH_TEXTURE 宏对深度纹理进行采样,例如:

其中,iscrPos是在顶点着色器中通过调用ComputeScreenPos(opos)得到的屏幕坐标。上述这些宏的定义,可以在Unity 内置的HLSLSupportcginc文件中找到。

当通过纹理采样得到深度值后,这些深度值往往是非线性的,这种非线性来自于透视投影使用的裁剪矩阵。然而,在我们的计算过程中通常是需要线性的深度值,也就是说,我们需要把投影后的深度值变换到线性空间下,例如视角空间下的深度值,我们只需要倒推顶点变换的过程即可。下面以透视投影为例,推导如何由深度纹理中的深度信息计算得到视角空间下的深度值。

当我们使用透视投影的裁剪矩阵 对视角空间下的一个顶点进行变换后,裁剪空间下顶点的z和w分量为:

其中,Far和Near分别是远近裁剪平面的距离。然后,我们通过齐次除法就可以得到NDC下的z分量:

而深度纹理中的深度值是通过下面的公式由NDC 计算而得的:

由上面的这些式子,可以推导出用d表示而得的 的表达式:

由于在Unity 使用的视角空间中,摄像机正向对应的z值均为负值,因此为了得到深度值的正数表示,我们需要对上面的结果取反,最后得到的结果如下:

它的取值范围就是视锥体深度范围,即[Near, Far]。如果我们想得到范围在[0, l]之间的深度值,只需要把上面得到的结果除以Far即可。这样,0就表示该点与摄像机位于同一位置,1表示该点位于视锥体的远裁剪平面上。结果如下:

其实,Unity提供了两个辅助函数来为我们进行上述的计算过程LinearEyeDepth 和LinearOlDepth。LinearEyeDepth 负责把深度纹理的采样结果转换到视角空间下的深度值,也就是我们上面得到的 。而Linear01Depth则会返回一个范围在[0, 1]的线性深度值,也就是我们上面得到的 。这两个函数内部使用了内置的_ZBufferParams变量来得到远近裁剪平面的距离。

如果需要获取深度+法线纹理,可以直接使用tex2D函数对_CameraDepthNormalsTexture 进行采样,得到里面存储的深度和法线信息。Unity提供了辅助函数来为我们对这个采样结果进行解码,从而得到深度值和法线方向。这个函数是DecodeDepthNormal,它在UnityCGcginc 里被定义为:

DecodeDepthNormal 的第一个参数是对深度+法线纹理的采样结果,这个采样结果是 Unity 深度和法线信息编码后的结果 它的 xy 分量存储的是视角空间下的法线信息 而深度信息被编码进了 zw 分量。通过调用 DecodeDepthNormal 函数对采样结果解码后, 我们就可 得到解码后的深度值和法线。这个深度值是范围在[O l] 的线性深度值(这与单独的深度纹理中存储 深度值不同),而得到的法线则是视角空间下的法线方向。同样也可以通过调用 DecodeFloatRG和DecodeViewNormalStereo 来解码深度+法线纹理中的深度和法线信息。

简介

在渲染阶段,引擎所做的工作是把所有场景中的对象按照一定的策略(顺序)进行渲染。最早的是画家算法,顾名思义,就是像画家画画一样,先画后面的物体,如果前面还有物体,那么就用前面的物体把物体覆盖掉,不过这种方式由于排序是针对物体来排序的,而物体之间也可能有重叠,所以效果并不好。所以目前更加常用的方式是z-buffer算法,类似颜色缓冲区缓冲颜色,z-buffer中存储的是当前的深度信息,对于每个像素存储一个深度值,这样,我们屏幕上显示的每个像素点都会进行深度排序,就可以保证绘制的遮挡关系是正确的。而控制z-buffer就是通过ZTest,和ZWrite来进行。但是有时候需要更加精准的控制不同类型的对象的渲染顺序,所以就有了渲染队列。今天就来学习一下渲染队列,ZTest,ZWrite的基本使用以及分析一下Unity为了Early-Z所做的一些优化。

Unity中的几种渲染队列

首先看一下Unity中的几种内置的渲染队列,按照渲染顺序,从先到后进行排序,队列数越小的,越先渲染,队列数越大的,越后渲染。

Unity中设置渲染队列也很简单,我们不需要手动创建,也不需要写任何脚本,只需要在shader中增加一个Tag就可以了,当然,如果不加,那么就是默认的渲染队列Geometry。比如我们需要我们的物体在Transparent这个渲染队列中进行渲染的话,就可以这样写:

我们可以直接在shader的Inspector面板上看到shader的渲染队列:

另外,我们在写shader的时候还经常有个Tag叫RenderType,不过这个没有Render Queue那么常用,这里顺便记录一下:

Opaque : 用于大多数着色器(法线着色器、自发光着色器、反射着色器以及地形的着色器)。

Transparent :用于半透明着色器(透明着色器、粒子着色器、字体着色器、地形额外通道的着色器)。

TransparentCutout : 蒙皮透明着色器(Transparent Cutout,两个通道的植被着色器)。

Background : 天空盒着色器。

Overlay : GUITexture,镜头光晕,屏幕闪光等效果使用的着色器。

TreeOpaque : 地形引擎中的树皮。

TreeTransparentCutout : 地形引擎中的树叶。

TreeBillboard : 地形引擎中的广告牌树。

Grass : 地形引擎中的草。

GrassBillboard : 地形引擎何中的广告牌草。

相同渲染队列中不透明物体的渲染顺序

拿出Unity,创建三个立方体,都使用默认的bump diffuse shader(渲染队列相同),分别给三个不同的材质(相同材质的小顶点数的物体引擎会动态合批),用Unity5带的Frame Debug工具查看一下Draw Call。(Unity5真是好用得多了,如果用4的话,还得用NSight之类的抓帧)

可以看出,Unity中对于不透明的物体,是采用了从前到后的渲染顺序进行渲染的,这样,不透明物体在进行完vertex阶段,进行Z Test,然后就可以得到该物体最终是否在屏幕上可见了,如果前面渲染完的物体已经写好了深度,深度测试失败,那么后面渲染的物体就直接不会再去进行fragment阶段。(不过这里需要把三个物体之间的距离稍微拉开一些,本人在测试时发现,如果距离特别近,就会出现渲染次序比较乱的情况,因为我们不知道Unity内部具体排序时是按照什么标准来判定的哪个物体离摄像机更近,这里我也就不妄加猜测了)

相同渲染队列中半透明物体的渲染顺序

透明物体的渲染一直是图形学方面比较蛋疼的地方,对于透明物体的渲染,就不能像渲染不透明物体那样多快好省了,因为透明物体不会写深度,也就是说透明物体之间的穿插关系是没有办法判断的,所以半透明的物体在渲染的时候一般都是采用从后向前的方法进行渲染,由于透明物体多了,透明物体不写深度,那么透明物体之间就没有所谓的可以通过深度测试来剔除的优化,每个透明物体都会走像素阶段的渲染,会造成大量的Over Draw。这也就是粒子特效特别耗费性能的原因。

我们实验一下Unity中渲染半透明物体的顺序,还是上面的三个立方体,我们把材质的shader统一换成粒子最常用的Particle/Additive类型的shader,再用Frame Debug工具查看一下渲染的顺序:

半透明的物体渲染的顺序是从后到前,不过由于半透相关的内容比较复杂,就先不在这篇文章中说了,打算另起一篇。

自定义渲染队列

Unity支持我们自定义渲染队列,比如我们需要保证某种类型的对象需要在其他类型的对象渲染之后再渲染,就可以通过自定义渲染队列进行渲染。而且超级方便,我们只需要在写shader的时候修改一下渲染队列中的Tag即可。比如我们希望我们的物体要在所有默认的不透明物体渲染完之后渲染,那么我们就可以使用Tag{“Queue” = “Geometry+1”}就可以让使用了这个shader的物体在这个队列中进行渲染。

还是上面的三个立方体,这次我们分别给三个不同的shader,并且渲染队列不同,通过上面的实验我们知道,默认情况下,不透明物体都是在Geometry这个队列中进行渲染的,那么不透明的三个物体就会按照cube1,cube2,cube3进行渲染。这次我们希望将渲染的顺序反过来,那么我们就可以让cube1的渲染队列最大,cube3的渲染队列最小。贴出其中一个的shader:

其他的两个shader类似,只是渲染队列和输出颜色不同。

通过渲染队列,我们就可以自由地控制使用该shader的物体在什么时机渲染。比如某个不透明物体的像素阶段 *** 作较费,我们就可以控制它的渲染队列,让其渲染更靠后,这样可以通过其他不透明物体写入的深度剔除该物体所占的一些像素。

PS:这里貌似发现了个问题,我们在修改shader的时候一般不需要什么其他 *** 作就可以直接看到修改后的变化,但是本人改完渲染队列后,有时候会出现从shader的文件上能看到渲染队列的变化,但是从渲染结果以及Frame Debug工具中并没有看到渲染结果的变化,重启Unity也没有起到作用,直到我把shader重新赋给材质之后,变化才起了效果(猜测是个bug,因为看到网上还有和我一样的倒霉蛋被这个坑了,本人的版本是532,害我差点怀疑昨天是不是喝了,刚实验完的结果就完全不对了)

ZTest(深度测试)和ZWrite(深度写入)

上一个例子中,虽然渲染的顺序反了过来,但是物体之间的遮挡关系仍然是正确的,这就是z-buffer的功劳,不论我们的渲染顺序怎样,遮挡关系仍然能够保持正确。而我们对z-buffer的调用就是通过ZTest和ZWrite来实现的。

首先看一下ZTest,ZTest即深度测试,所谓测试,就是针对当前对象在屏幕上(更准确的说是frame buffer)对应的像素点,将对象自身的深度值与当前该像素点缓存的深度值进行比较,如果通过了,本对象在该像素点才会将颜色写入颜色缓冲区,否则否则不会写入颜色缓冲。ZTest提供的状态较多。 ZTest Less(深度小于当前缓存则通过, ZTest Greater(深度大于当前缓存则通过),ZTest LEqual(深度小于等于当前缓存则通过),ZTest GEqual(深度大于等于当前缓存则通过),ZTest Equal(深度等于当前缓存则通过),ZTest NotEqual(深度不等于当前缓存则通过),ZTest Always(不论如何都通过)。注意,ZTest Off等同于ZTest Always,关闭深度测试等于完全通过。

下面再看一下ZWrite,ZWrite比较简单,只有两种状态, ZWrite On(开启深度写入)和ZWrite Off(关闭深度写入) 。当我们开启深度写入的时候,物体被渲染时针对物体在屏幕(更准确地说是frame buffer)上每个像素的深度都写入到深度缓冲区;反之,如果是ZWrite Off,那么物体的深度就不会写入深度缓冲区。但是,物体是否会写入深度,除了ZWrite这个状态之外,更重要的是需要深度测试通过,也就是ZTest通过,如果ZTest都没通过,那么也就不会写入深度了。就好比默认的渲染状态是ZWrite On和ZTest LEqual,如果当前深度测试失败,说明这个像素对应的位置,已经有一个更靠前的东西占坑了,即使写入了,也没有原来的更靠前,那么也就没有必要再去写入深度了。所以上面的ZTest分为通过和不通过两种情况,ZWrite分为开启和关闭两种情况的话,一共就是四种情况:

1深度测试通过,深度写入开启:写入深度缓冲区,写入颜色缓冲区;

2深度测试通过,深度写入关闭:不写深度缓冲区,写入颜色缓冲区;

3深度测试失败,深度写入开启:不写深度缓冲区,不写颜色缓冲区;

4深度测试失败,深度写入关闭:不写深度缓冲区,不写颜色缓冲区;

Unity中默认的状态(写shader时什么都不写的状态)是ZTest LEqual和ZWrite On,也就是说默认是开启深度写入,并且深度小于等于当前缓存中的深度就通过深度测试,深度缓存中原始为无限大,也就是说离摄像机越近的物体会更新深度缓存并且遮挡住后面的物体。如下图所示,前面的正方体会遮挡住后面的物体:

写几个简单的小例子来看一下ZTest,ZWrite以及Render Queue这几个状态对渲染结果的控制。

让绿色的对象不被前面的立方体遮挡,一种方式是关闭前面的蓝色立方体深度写入:

通过上面的实验结果,我们知道,按照从前到后的渲染顺序,首先渲染蓝色物体,蓝色物体深度测试通过,颜色写入缓存,但是关闭了深度写入,蓝色部分的深度缓存值仍然是默认的Max,后面渲染的绿色立方体,进行深度测试仍然会成功,写入颜色缓存,并且写入了深度,因此蓝色立方体没有起到遮挡的作用。

另一种方式是让绿色强制通过深度测试:

这个例子中其他立方体的shader使用默认的渲染方式,绿色的将ZTest设置为Always,也就是说不管怎样,深度测试都通过,将绿色立方体的颜色写入缓存,如果没有其他覆盖了,那么最终的输出就是绿色的了。

那么如果红色的也开了ZTest Always会怎么样?

在红色立方体也用了ZTest Always后,红色遮挡了绿色的部分显示为了红色。如果我们换一下渲染队列,让绿色在红色之前渲染,结果就又不一样了:

更换了渲染队列,让绿色的渲染队列+1,在默认队列Geometry之后渲染,最终重叠部分又变回了绿色。可见,当ZTest都通过时,上一个写入颜色缓存的会覆盖上一个,也就是说最终输出的是最后一个渲染的对象颜色。

再看一下Greater相关的部分有什么作用,这次我们其他的都使用默认的渲染状态,绿色的立方体shader中ZTest设置为Greater:

这个效果就比较好玩了,虽然我们发现在比较深度时,前面被蓝色立方体遮挡的部分,绿色的最终覆盖了蓝色,是想要的结果,不过其他部分哪里去了呢?简单分析一下,渲染顺序是从前到后,也就是说蓝色最先渲染,默认深度为Max,蓝色立方体的深度满足LEqual条件,就写入了深度缓存,然后绿色开始渲染,重叠的部分的深度缓存是蓝色立方体写入的,而绿色的深度值满足大于蓝色深度的条件,所以深度测试通过,重叠部分颜色更新为绿色;而与红色立方体重合的部分,红色立方体最后渲染,与前面的部分进行深度测试,小于前面的部分,深度测试失败,重叠部分不会更新为红色,所以重叠部分最终为绿色。而绿色立方体没有与其他部分重合的地方为什么消失了呢?其实是因为绿色立方体渲染时,除了蓝色立方体渲染的地方是有深度信息的,其他部分的深度信息都为Max,蓝色部分用Greater进行判断,肯定会失败,也就不会有颜色更新。

有一个好玩的效果其实就可以考ZTest Greater来实现,就是游戏里面经常出现的,当玩家被其他场景对象遮挡时,遮挡的部分会呈现出X-光的效果;其实是在渲染玩家时,增加了一个Pass,默认的Pass正常渲染,而增加的一个Pass就使用Greater进行深度测试,这样,当玩家被其他部分遮挡时,遮挡的部分才会显示出来,用一个描边的效果渲染,其他部分仍然使用原来的Pass即可。

Early-Z技术

传统的渲染管线中,ZTest其实是在Blending阶段,这时候进行深度测试,所有对象的像素着色器都会计算一遍,没有什么性能提升,仅仅是为了得出正确的遮挡结果,会造成大量的无用计算,因为每个像素点上肯定重叠了很多计算。因此现代GPU中运用了Early-Z的技术,在Vertex阶段和Fragment阶段之间(光栅化之后,fragment之前)进行一次深度测试,如果深度测试失败,就不必进行fragment阶段的计算了,因此在性能上会有很大的提升。但是最终的ZTest仍然需要进行,以保证最终的遮挡关系结果正确。前面的一次主要是Z-Cull为了裁剪以达到优化的目的,后一次主要是Z-Check,为了检查,如下图:

Early-Z的实现,主要是通过一个Z-pre-pass实现,简单来说,对于所有不透明的物体(透明的没有用,本身不会写深度),首先用一个超级简单的shader进行渲染,这个shader不写颜色缓冲区,只写深度缓冲区,第二个pass关闭深度写入,开启深度测试,用正常的shader进行渲染。其实这种技术,我们也可以借鉴,在渲染透明物体时,因为关闭了深度写入,有时候会有其他不透明的部分遮挡住透明的部分,而我们其实不希望他们被遮挡,仅仅希望被遮挡的物体半透,这时我们就可以用两个pass来渲染,第一个pass使用Color Mask屏蔽颜色写入,仅写入深度,第二个pass正常渲染半透,关闭深度写入。

关于Early-Z技术可以参考ATI的论文Applications of Explicit Early-Z Culling以及PPT,还有一篇Intel的文章。

Unity渲染顺序总结

如果我们先绘制后面的物体,再绘制前面的物体,就会造成over draw;而通过Early-Z技术,我们就可以先绘制较近的物体,再绘制较远的物体(仅限不透明物体),这样,通过先渲染前面的物体,让前面的物体先占坑,就可以让后面的物体深度测试失败,进而减少重复的fragment计算,达到优化的目的。Unity中默认应该就是按照最近距离的面进行绘制的,我们可以看一下Unity官方的文档中显示的:

从文档给出的流程来看,这个Depth-Test发生在Vertex阶段和Fragment阶段之间,也就是上面所说的Early-Z优化。

简单总结一下Unity中的渲染顺序: 先渲染不透明物体,顺序是从前到后;再渲染透明物体,顺序是从后到前

Alpha Test(Discard)在移动平台消耗较大的原因

从本人刚刚开始接触渲染,就开始听说移动平台Alpha Test比较费,当时比较纳闷,直接discard了为什么会费呢,应该更省才对啊?这个问题困扰了我好久,今天来刨根问底一下。还是跟我们上面讲到的Early-Z优化。正常情况下,比如我们渲染一个面片,不管是否是开启深度写入或者深度测试,这个面片的光栅化之后对应的像素的深度值都可以在Early-Z(Z-Cull)的阶段判断出来了;而如果开启了Alpha Test(Discard)的时候,discard这个 *** 作是在fragment阶段进行的,也就是说这个面片光栅化之后对应的像素是否可见,是在fragment阶段之后才知道的,最终需要靠Z-Check进行判断这个像素点最终的颜色。其实想象一下也能够知道,如果我们开了Alpha Test并且还用Early-Z的话,一块本来应该被剃掉的地方,就仍然写进了深度缓存,这样就会造成其他部分被一个完全没东西的地方遮挡,最终的渲染效果肯定就不对了。所以,如果我们开启了Alpha Test,就不会进行Early-Z,Z Test推迟到fragment之后进行,那么这个物体对应的shader就会完全执行vertex shader和fragment shader,造成over draw。有一种方式是使用Alpha Blend代替Alpha Test,虽然也很费,但是至少Alpha Blend虽然不写深度,但是深度测试是可以提前进行的,因为不会在fragment阶段再决定是否可见,因为都是可见的,只是透明度比较低罢了。不过这样只是权宜之计,Alpha Blend并不能完全代替Alpha Test。

关于Alpha Test对于Power VR架构的GPU性能的影响,简单引用一下官方的链接以及一篇讨论帖:

最后再附上两篇参考文章

>

一、生成物体之后,看Hierarchy 面板中那子物体叫什么,

Vector3 pos = GameObjectFind ("name")transformposition;

二、生成预知体的时候返回生成的transform,找子物体。

Transform _transform = Instantiate("预知体",position, Quaternionidentity) as Transform;

Vector3 pos2 = _transformFindChild("name")transformposition;

以上就是关于Unity3D自带功能:地形(Terrain)全部的内容,包括:Unity3D自带功能:地形(Terrain)、Unity Shader 获取深度纹理和法线纹理、Unity Shader-渲染队列,ZTest,ZWrite,Early-Z(转)等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9660191.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-30
下一篇 2023-04-30

发表评论

登录后才能评论

评论列表(0条)

保存