1. 程序员的烦恼:Debug

调试(debug),大概是所有程序员的噩梦。而不幸的是,对一个Shader进行调试更是噩梦中的噩梦。这也是造成Shader难写的原因之一——如果发现得到的效果不对,我们就可能花非常多的时间来找到问题所在。造成这种现状的原因就是在Shader中可以选择的调试方法非常有限,甚至连简单的输出都不行。

2. 最新利器:帧调试器

Unity5除了带来全新的UI系统外,还为我们带来了一个新的针对渲染的调试器——帧调试器(Frame Debugger)。与其它调试工具的复杂性相比,Unity原生的帧调试器非常简单快捷。我们可以用它来看到游戏图像的某一帧是如何一步步渲染出来的。

要使用帧调试器,我们首先需要在Window->Frame Debugger中打开帧调试器窗口,如下图所示:



帧调试器可以用于查看渲染该帧时进行的各种渲染事件(event),这些事件包含了Draw Call序列,也包括了类似清空缓存等操作。帧调试器窗口大致可分为3个部分:最上面的区域可以开启/关闭(单击Enable按钮)帧调试功能,当开启了帧调试时,通过移动窗口最上方的滑动条(或单击前进和后退按钮),我们就可以重放这些渲染事件;左侧区域显示了所有事件的树状图,在这个树状图中,每个叶子节点就是一个事件,而每个父节点的右侧显示了该节点下的事件数目。我们可以从事件的名字了解这个事件的操作,例如以Draw开头的事件通常就是一个Draw Call;当单击了某个事件时,在右侧的窗口就会显示该事件的细节,例如几何图形的细节以及使用了哪个Shader等。同时在Game视图中我们也可以看到它的效果。如果该事件是一个Draw Call并且对应了场景中的一个GameObject,那么这个GameObject也会在Hierarchy视图中被高亮显示出来,下图显示了单击渲染某个对象的深度图事件的结果。



如果被选中的Draw Call是一个对渲染纹理(RenderTexture)的渲染操作,那么这个渲染纹理就会显示在Game视图中。而且,此时右侧面板上方的工具栏中也会出现更多的选项,例如在Game视图中单独显示R、G、B和A通道。

Unity5提供的帧调试器实际上并没有实现一个真正的帧拾取(frame capture)的功能,而是仅仅使用了停止渲染的方法来查看渲染事件的结果。例如我们想要查看第4个Draw Call的结果,那么帧调试器就会在第4个Draw Call调用完毕后停止渲染。这种方法虽然简单,但得到的信息也有限。

3. 小心渲染平台的差异

我们以前提到过OpenGL和DirectX的屏幕空间坐标的差异,如下图所示:



需要注意的是,我们不仅可以把渲染结果输出到屏幕上,还可以输出到不同的渲染目标(Render Target)中。这时,我们需要使用渲染纹理(Render Texture)来保存这些渲染结果。我们将在后面学习如何实现这样的目的。

大多数情况下,这样的差异并不会对我们造成任何影响。但当我们要使用渲染到纹理技术,把屏幕图像渲染到一张渲染纹理时,如果不采取任何措施的话,就会出现纹理翻转的情况。幸运的是,Unity在背后为我们处理了这种翻转问题——当在DirectX平台使用渲染到纹理技术时,Unity会为我们翻转屏幕图像纹理,以便在不同平台上达到一致性。

在一种特殊情况下Unity不会为我们进行这个翻转操作,这种情况就是我们开启了抗锯齿(在Edit->Project Setting->Quality->Anti Aliasing中开启)并在此时使用了渲染到纹理技术。在这种情况下,Unity首先渲染得到屏幕图像,再由硬件进行抗锯齿处理后,得到一张渲染纹理来供我们进行后续处理。此时,在DirectX平台下,我们得到的输入屏幕图像并不会被Unity翻转,也就是说,此时对屏幕图像的采样坐标是需要符合DirectX平台规定的。如果我们的屏幕特效只需要处理一张渲染图像,我们仍然不需要在意纹理的翻转问题,这是因为我们在调用Graphics.Blit函数时,Unity已经为我们对屏幕图像的采样坐标进行了处理,我们只需要按照正常的采样过程处理屏幕图像即可。但如果我们需要同时处理多张渲染图像(前提是开启了抗锯齿),例如需要同时处理屏幕图像和法线纹理,这些图像在竖直方向的朝向可能是不同的(只有在DirectX这样的平台上才有这样的问题)。这种时候,我们就需要自己在顶点着色器中翻转某些渲染纹理(例如深度纹理或其它由脚本传递过来的纹理)的纵坐标,使之都符合DirectX平台的规则。例如:

#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y<0)
uv.y = 1- uv.y;
#endif

其中,UNITY_UV_STARTS_AT_TOP用于判断当前平台是否是DirectX类型的平台,而在这样的平台下开启了抗锯齿后,主纹理的纹理大小在竖直方向上会变成负值,以方便我们对主纹理进行采样。因此,我们可以通过判断_MainTex_TexelSize.y是否小于0来检验是否开启了抗锯齿。如果是,我们就需要对主纹理外的其他纹理的采样进行竖直方向上的翻转。

4. Shader的整洁之道

4.1 float、half还是fixed

我们使用Cg/HLSL来编写UnityShader中的代码。而在Cg/HLSL中,有三种精度的数值类型:float,half和fixed。这些精度将决定计算结果的数值范围。下表给出了这3种精度在通常情况下的数值范围。

上面的精度范围并不是绝对正确的,尤其是在不同平台和GPU上,它们的实际精度可能和上面给出的范围不一致。通常来讲:

(1)大多数现代的桌面GPU会把所有计算都按最高的浮点精度进行计算,也就是说,float、half、fixed在这些平台上实际是等价的。这意味着,我们在PC上很难看出因为half和fixed精度而带来的不同。

(2)但在移动平台GPU上,它们的确会有不同的精度范围,而且不同精度的浮点值的运算速度也会有所差异。因此,我们应该确保在真正的移动平台上验证我们的Shader。

(3)fixed精度实际上只在一些较旧的移动平台上有用,在现在大多数现代的GPU上,它们内部把fixed和half当成同精度来对待。

尽管由上面的不同,但一个基本的建议是,尽可能使用精度较低的类型,是因为这可以优化Shader的性能,这一点在移动平台上尤为重要。从它们大体的值域范围来看,我们可以使用fixed类型来存储颜色和单位矢量,如果要存储更大范围的数据可以选择half类型,最差的情况下再选择float。如果我们的目标平台是移动平台,一定要确保在真实的手机上测试我们的Shader,这一点非常重要。

4.2 避免不必要的计算

如果我们毫不节制的在Shader(尤其是片元着色器)中进行了大量计算,那么我们可能很快收到Unity的错误提示:

temporary register limit of 8 exceeded

Arithmetic  instruction limit of 64 exceeded;65  arithmetic instructions needed  to compile program

出现这些错误信息大多是因为我们在Shader中进行了过多的运算,使得需要的临时寄存器数目或指令数目超过了当前可支持的数目。读者需要知道,不同的Shader Target、不同的着色器阶段,我们可以使用的临时寄存器和指令数目都是不同的。

通常,我们可以通过制定更高级的Shader Target来消除这些错误。下表给出了Unity目前支持的一些

Shader Target。

需要注意的是,由于Unity版本的不同,Unity支持的Shader Target种类也不同,读者可以在官方手册上找到更为详细的介绍。

读者:什么是Shader Model呢?

我们:Shader Model是由微软提出的一套规范,通俗的理解就是它们决定了Shader中各个特性(feature)的能力(capability)。这些特性和能力体现在Shader能使用的运算指令数目、寄存器数目等各个方面。Shader Model等级越高,Shader的能力越大。

虽然更高等级的Shader Target可以让我们使用更多的临时寄存器和运算指令,但一个更好的方法是尽可能减少Shader中的运算,或者通过预计算的方式来提供更多的数据。

4.3 慎用分支和循环语句

在最开始,GPU是不支持在顶点着色器和片元着色器中使用流程控制语句的。随着GPU的发展,我们现在已经可以使用if-else、for和while这种流程控制指令了。大体来说,GPU使用了不同于CPU的技术来实现分支语句,在最坏的情况下,我们花在一个分支语句的时间相当于运行了所有分支语句的时间。因此,我们不鼓励在SubShader中使用流程控制语句,因为它们会降低GPU的并行处理操作。

如果我们在Shader中使用了大量的流程控制语句,那么这个Shader的性能会成倍下降。一个解决方法是,我们应该尽量把计算向流水线上方移动,例如把放在片元着色器中的计算放到顶点着色器中去,或者直接在CPU中进行预计算,再把结果传递给Shader。当然,有时我们不可避免的要使用分支语句来进行计算,那么一些建议是:

(1)分支判断语句中使用的条件变量最好是常数,即在Shader运行过程中不会发生变化;

(2)每个分支中包含的操作指令数尽可能少;

(3)分支的嵌套层数尽可能少。

第四章 开始Unity Shader学习之旅(3)的更多相关文章

  1. 第四章 开始Unity Shader学习之旅(2)

    目录 1. 强大的援手:Unity提供的内置文件和变量 1.1 内置的包含文件 1.2 内置的变量 2. Unity提供的Cg/HLSL语义 2.1 什么是语义 2.2 Unity支持的语义 2.3 ...

  2. 第四章 开始Unity Shader学习之旅(1)

    1. 一个最简单的顶点/片元着色器 现在,我们正式开始学习如何编写Unity Shader,更准确的说是,学习如何编写顶点/片元着色器 2.顶点/片元着色器的基本结构 我们在以前已经讲过了Unity ...

  3. Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅

    一个顶点/片元 着色器的结构大概如下: Shader "MyShaderName" { Properties { //属性 } SubShader { //针对显卡A的SubSha ...

  4. Unity Shader 学习之旅

    Unity Shader 学习之旅 unityshader图形图像 纸上学来终觉浅,绝知此事要躬行 美丽的梦和美丽的诗一样 都是可遇而不可求的——席慕蓉 一.渲染流水线 示例图 Tips:什么是 GP ...

  5. Unity Shader 学习之旅之SurfaceShader

    Unity Shader 学习之旅之SurfaceShader unity shader 图形图像  如果大地的每个角落都充满了光明 谁还需要星星,谁还会 在夜里凝望 寻找遥远的安慰——江河 官方文档 ...

  6. Unity Shader学习笔记-1

    本篇文章是对Unity Shader入门精要的学习笔记,插图大部分来自冯乐乐女神的github 如果有什么说的不正确的请批评指正 目录 渲染流水线 流程图 Shader作用 屏幕映射 三角形遍历 两大 ...

  7. 【Unity Shader学习笔记】Unity基础纹理-单张纹理

    1 单张纹理 1.1 纹理 使用纹理映射(Texture Mapping)技术,我们把一张图片逐纹素(Texel)地控制模型的颜色. 美术人员建模时,会在建模软件中利用纹理展开技术把纹理映射坐标(Te ...

  8. Unity shader学习之屏幕后期处理效果之高斯模糊

    高斯模糊,见 百度百科. 也使用卷积来实现,每个卷积元素的公式为: 其中б是标准方差,一般取值为1. x和y分别对应当前位置到卷积中心的整数距离. 由于需要对高斯核中的权重进行归一化,即使所有权重相加 ...

  9. 第四章:重构代码[学习Android Studio汉化教程]

    第四章 Refactoring Code The solutions you develop in Android Studio will not always follow a straight p ...

随机推荐

  1. PyQt图形化布局

    安装PyQt第三方库 pip install PyQt5 安装Qt Designer(Qt的布局工具) pip install PyQt5-tools PyChram设置Qt工具 配置Qt Desig ...

  2. 划艇:dp/组合数/区间离散化

    Description 在首尔城中,汉江横贯东西.在汉江的北岸,从西向东星星点点地分布着 N 个划艇学校,编号依次为 1 到 N.每个学校都拥有若干艘划艇.同一所学校的所有划艇颜色相同,不同的学校的划 ...

  3. 磁盘冗余阵列之RAID5的配置

    1988年由加利福尼亚大学伯克利分校发表的文章首次提到并定义了RAID,当今CPU性能每年可提升30%-50%但硬盘仅提升7%,渐渐的已经成为计算机整体性能的瓶颈,并且为了避免硬盘的突然损坏导致数据丢 ...

  4. CSP-S 46 题解

    改完题了,就稍写一下题解,顺便反思一下! 其实这次考试挺水的,然而我也挺水的,看了考试结束后的成绩,就吃-*了! T1 set 这个我考试的时候实在是没有想到如何去判断-1,然后我就觉得这神仙题没法解 ...

  5. 『题解』Codeforces220B Little Elephant and Array

    更好的阅读体验 Portal Portal1: Codeforces Portal2: Luogu Description The Little Elephant loves playing with ...

  6. Linux系统重启Oracle-12c步骤

    Linux系统重启Oracle-12c步骤 1. 使用oracle用户登录: [root@Oracle-12c /]# su – oracle 2. 登录oracle登陆管理员账号: [oracle@ ...

  7. 010.Kubernetes二进制部署kube-controller-manager

    一 部署高可用kube-controller-manager 1.1 高可用kube-controller-manager介绍 本实验部署一个三实例 kube-controller-manager 的 ...

  8. 【algo&ds】3.栈和队列

    1.堆栈 堆栈(Stack):具有一定操作约束的线性表(只在一端(栈顶,Top)做插入.删除) 先进后出特性 1.1堆栈的抽象数据类型描述 类型名称: 堆栈(Stack) 数据对象集:一个有0个或多个 ...

  9. Worktile正式发布全新研发产品!

    经过近一年时间的打磨,Worktile研发产品正式发布啦!和以往Worktile版本升级不同的是,这是一个全新的产品形态,目前已上线 Agile(敏捷开发).Pipe(持续交付).Testhub(测试 ...

  10. pat 1065 A+B and C (64bit)(20 分)(大数, Java)

    1065 A+B and C (64bit)(20 分) Given three integers A, B and C in [−2​63​​,2​63​​], you are supposed t ...