元数据

Unity Shader入门精要

  •  Unity Shader入门精要|200
  • 书名: Unity Shader入门精要
  • 作者: 冯乐乐
  • 简介: 本书不仅要教会读者如何使用Unity Shader,更重要的是要帮助读者学习Unity中的一些渲染机制以及如何使用Unity Shader实现各种自定义的渲染效果,希望这本书可以为读者打开一扇新的大门,让读者离制作心目中杰出游戏的心愿更近一步。
  • 出版时间 2016-06-01 00:00:00
  • ISBN: 9787115423054
  • 分类: 计算机-编程设计
  • 出版社: 人民邮电出版社
  • PC地址:https://weread.qq.com/web/reader/20132360715a3e91201bea8

高亮划线

2.1 综述

  • 📌 Shader仅仅是渲染流水线中的一个环节

    • ⏱ 2024-02-22 14:00:34
  • 📌 一书中将一个渲染流程分成3个阶段:应用阶段(Application Stage)、几何阶段(Geometry Stage)、光栅化阶段(Rasterizer Stage)

    • ⏱ 2024-02-22 14:02:17
  • 📌 应用阶段

    • ⏱ 2024-02-22 14:04:13
  • 📌 从名字我们可以看出,这个阶段是由我们的应用主导的,因此通常由CPU负责实现。换句话说,我们这些开发者具有这个阶段的绝对控制权。

    • ⏱ 2024-02-22 14:04:20
  • 📌 准备好场景数据

    • ⏱ 2024-02-22 14:04:59
  • 📌 粗粒度剔除(culling)

    • ⏱ 2024-02-22 14:04:35
  • 📌 设置好每个模型的渲染状态。

    • ⏱ 2024-02-22 14:04:54
  • 📌 这一阶段最重要的输出是渲染所需的几何信息,即渲染图元(rendering primitives)

    • ⏱ 2024-02-22 14:05:21
  • 📌 几何阶段

    • ⏱ 2024-02-22 14:08:06
  • 📌 几何阶段用于处理所有和我们要绘制的几何相关的事情。

    • ⏱ 2024-02-22 14:06:02
  • 📌 几何阶段的一个重要任务就是把顶点坐标变换到屏幕空间中,再交给光栅器进行处理。

    • ⏱ 2024-02-22 14:06:21
  • 📌 屏幕空间的二维顶点坐标

    • ⏱ 2024-02-22 14:07:55
  • 📌 每个顶点对应的深度值、着色等相关信息

    • ⏱ 2024-02-22 14:08:00
  • 📌 光栅化阶段

    • ⏱ 2024-02-22 14:08:04
  • 📌 光栅化的任务主要是决定每个渲染图元中的哪些像素应该被绘制在屏幕上。

    • ⏱ 2024-02-22 14:09:25
  • 📌 对上一个阶段得到的逐顶点数据(例如纹理坐标、顶点颜色等)进行插值,然后再进行逐像素处理。

    • ⏱ 2024-02-22 14:09:34

2.2 CPU和GPU之间的通信

  • 📌 把数据加载到显存中

    • ⏱ 2024-02-22 14:09:49
  • 📌 设置渲染状态

    • ⏱ 2024-02-22 14:09:53
  • 📌 调用Draw Call

    • ⏱ 2024-02-22 14:09:56
  • 📌 显卡对于显存的访问速度更快,而且大多数显卡对于RAM没有直接的访问权利

    • ⏱ 2024-02-22 14:11:25
  • 📌 CPU来设置渲染状态

    • ⏱ 2024-02-22 14:14:05
  • 📌 如果我们没有更改渲染状态,那么所有的网格都将使用同一种渲染状态。

    • ⏱ 2024-02-22 14:15:00
  • 📌 CPU就需要调用一个渲染命令来告诉GPU:“嘿!老兄,我都帮你把数据准备好啦,你可以按照我的设置来开始渲染啦!”而这个渲染命令就是Draw Call。

    • ⏱ 2024-02-22 14:15:27
  • 📌 Draw Call就是一个命令,它的发起方是CPU,接收方是GPU。

    • ⏱ 2024-02-22 14:16:51
  • 📌 仅仅会指向一个需要被渲染的图元(primitives)列表,而不会再包含任何材质信息——这是因为我们已经在上一个阶段中完成了

    • ⏱ 2024-02-22 14:17:12
  • 📌 当给定了一个Draw Call时,GPU就会根据渲染状态(例如材质、纹理、着色器等)和所有输入的顶点数据来进行计算,最终输出成屏幕上显示的那些漂亮的像素

    • ⏱ 2024-02-22 14:17:42

2.3 GPU流水线

  • 📌 几何阶段和光栅化阶段可以分成若干更小的流水线阶段,这些流水线阶段由GPU来实现,每个阶段GPU提供了不同的可配置性或可编程性。

    • ⏱ 2024-02-22 14:20:07
  • 📌 顶点着色器(Vertex Shader)是完全可编程的,它通常用于实现顶点的空间变换、顶点着色等功能。

    • ⏱ 2024-02-22 14:33:35
  • 📌 曲面细分着色器(Tessellation Shader)是一个可选的着色器,它用于细分图元。

    • ⏱ 2024-02-22 14:34:15
  • 📌 几何着色器(Geometry Shader)同样是一个可选的着色器,它可以被用于执行逐图元(Per-Primitive)的着色操作,或者被用于产生更多的图元。

    • ⏱ 2024-02-22 14:34:22
  • 📌 裁剪(Clipping),这一阶段的目的是将那些不在摄像机视野内的顶点裁剪掉,并剔除某些三角图元的面片。这个阶段是可配置的。

    • ⏱ 2024-02-22 14:34:45
  • 📌 使用自定义的裁剪平面来配置裁剪区域,也可以通过指令控制裁剪三角图元的正面还是背面。

    • ⏱ 2024-02-22 14:34:57
  • 📌 屏幕映射(Screen Mapping)。这一阶段是不可配置和编程的,它负责把每个图元的坐标转换到屏幕坐标系中。

    • ⏱ 2024-02-22 14:35:20
  • 📌 光栅化概念阶段

    • ⏱ 2024-02-22 14:36:58
  • 📌 三角形设置(Triangle Setup)和三角形遍历(Triangle Traversal)阶段也都是固定函数(Fixed-Function)的阶段。

    • ⏱ 2024-02-22 14:36:52
  • 📌 片元着色器(Fragment Shader),则是完全可编程的,它用于实现逐片元(Per-Fragment)的着色操作。

    • ⏱ 2024-02-22 14:37:19
  • 📌 逐片元操作(Per-Fragment Operations)阶段负责执行很多重要的操作,例如修改颜色、深度缓冲、进行混合等,它不是可编程的,但具有很高的可配置性。

    • ⏱ 2024-02-22 14:37:26
  • 📌 顶点着色器的处理单位是顶点

    • ⏱ 2024-02-22 14:38:23
  • 📌 顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点之间的关系。

    • ⏱ 2024-02-22 14:38:31
  • 📌 因为这样的相互独立性,GPU可以利用本身的特性并行化处理每一个顶点

    • ⏱ 2024-02-22 14:38:48
  • 📌 顶点着色器需要完成的工作主要有:坐标变换和逐顶点光照。

    • ⏱ 2024-02-22 14:39:41
  • 📌 坐标变换

    • ⏱ 2024-02-22 14:39:54
  • 📌 把顶点坐标从模型空间转换到齐次裁剪空间

    • ⏱ 2024-02-22 14:40:40
  • 📌 最常见的输出路径是经光栅化后交给片元着色器进行处理。

    • ⏱ 2024-02-22 14:45:15
  • 📌 完全在视野内的图元就继续传递给下一个流水线阶段,完全在视野外的图元不会继续向下传递,因为它们不需要被渲染。

    • ⏱ 2024-02-22 14:45:49
  • 📌 不可编程

    • ⏱ 2024-02-22 14:46:47
  • 📌 自定义一个裁剪操作来对这一步进行配置。

    • ⏱ 2024-02-22 14:46:51
  • 📌 屏幕映射(ScreenMapping)的任务是把每个图元的x和y坐标转换到屏幕坐标系(Screen Coordinates)下。

    • ⏱ 2024-02-22 14:48:04
  • 📌 屏幕映射不会对输入的z坐标做任何处理。实际上,屏幕坐标系和z坐标一起构成了一个坐标系,叫做窗口坐标系(Window Coordinates)。这些值会一起被传递到光栅化阶段。

    • ⏱ 2024-02-22 14:48:44
  • 📌 OpenGL把屏幕的左下角当成最小的窗口坐标值,而DirectX则定义了屏幕的左上角为最小的窗口坐标值。

    • ⏱ 2024-02-22 14:49:10
  • 📌 光栅化阶段有两个最重要的目标:计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色。

    • ⏱ 2024-02-22 14:49:59
  • 📌 三角形设置(Triangle Setup)。这个阶段会计算光栅化一个三角网格所需的信息。

    • ⏱ 2024-02-22 14:50:28
  • 📌 三角形边界的表示方式

    • ⏱ 2024-02-22 14:51:11
  • 📌 三角形遍历(Triangle Traversal)阶段将会检查每个像素是否被一个三角网格所覆盖。如果被覆盖的话,就会生成一个片元(fragment)。

    • ⏱ 2024-02-22 14:52:02
  • 📌 扫描变换(Scan Conversion)

    • ⏱ 2024-02-22 14:52:07
  • 📌 一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。

    • ⏱ 2024-02-22 14:57:34
  • 📌 片元着色器的输入是上一个阶段对顶点信息插值得到的结果,更具体来说,是根据那些从顶点着色器中输出的数据插值得到的。而它的输出是一个或者多个颜色值。

    • ⏱ 2024-02-22 14:58:38
  • 📌 这一阶段可以完成很多重要的渲染技术,其中最重要的技术之一就是纹理采样

    • ⏱ 2024-02-22 14:58:54
  • 📌 虽然片元着色器可以完成很多重要效果,但它的局限在于,它仅可以影响单个片元。

    • ⏱ 2024-02-22 15:00:28
  • 📌 逐片元操作(Per-Fragment Operations)是OpenGL中的说法,在DirectX中,这一阶段被称为输出合并阶段(Output-Merger)。

    • ⏱ 2024-02-22 15:00:58
  • 📌 决定每个片元的可见性。

    • ⏱ 2024-02-22 15:01:14
  • 📌 如果一个片元通过了所有的测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并,或者说是混合

    • ⏱ 2024-02-22 15:01:24
  • 📌 深度测试和模板测试的实现过程

    • ⏱ 2024-02-22 15:02:20
  • 📌 模板测试(Stencil Test)。与之相关的是模板缓冲(Stencil Buffer)

    • ⏱ 2024-02-22 15:03:00
  • 📌 GPU会首先读取(使用读取掩码)模板缓冲区中该片元位置的模板值,然后将该值和读取(使用读取掩码)到的参考值(reference value)进行比较,这个比较函数可以是由开发者指定的

    • ⏱ 2024-02-22 15:05:00
  • 📌 修改模板缓冲区,这个修改操作也是由开发者指定的

    • ⏱ 2024-02-22 15:05:36
  • 📌 GPU会把该片元的深度值和已经存在于深度缓冲区中的深度值进行比较

    • ⏱ 2024-02-22 15:07:07
  • 📌 和模板测试有些不同的是,如果一个片元没有通过深度测试,它就没有权利更改深度缓冲区中的值。

    • ⏱ 2024-02-22 15:07:30
  • 📌 如果它通过了测试,开发者还可以指定是否要用这个片元的深度值覆盖掉原有的深度值,这是通过开启/关闭深度写入来做到的

    • ⏱ 2024-02-22 15:07:42
  • 📌 对于不透明物体,开发者可以关闭混合(Blend)操作。

    • ⏱ 2024-02-22 15:08:21
  • 📌 对于半透明物体,我们就需要使用混合操作来让这个物体看起来是透明的。

    • ⏱ 2024-02-22 15:08:30
  • 📌 混合函数

    • ⏱ 2024-02-22 15:09:48
  • 📌 这个混合函数通常和透明通道息息相关,例如根据透明通道的值进行相加、相减、相乘等。混合很像Photoshop中对图层的操作:每一层图层可以选择混合模式,混合模式决定了该图层和下层图层的混合结果,而我们看到的图片就是混合后的图片。

    • ⏱ 2024-02-22 15:10:01
  • 📌 在Unity给出的渲染流水线中,我们也可以发现它给出的深度测试是在片元着色器之前。

    • ⏱ 2024-02-22 15:10:36
  • 📌 Early-Z技术

    • ⏱ 2024-02-22 15:10:39
  • 📌 例如,如果我们在片元着色器进行了透明度测试(我们将在8.3节中具体讲到),而这个片元没有通过透明度测试,我们会在着色器中调用API(例如clip函数)来手动将其舍弃掉。这就导致GPU无法提前执行各种测试。因此,现代的GPU会判断片元着色器中的操作是否和提前测试发生冲突,如果有冲突,就会禁用提前测试。但是,这样也会造成性能上的下降,因为有更多片元需要被处理了。这也是透明度测试会导致性能下降的原因。

    • ⏱ 2024-02-22 15:11:15
  • 📌 双重缓冲(Double Buffering)

    • ⏱ 2024-02-22 15:11:39

2.4 一些容易困惑的地方

  • 📌 OpenGL和DirectX就是这些图像应用编程接口,这些接口用于渲染二维或三维图形。

    • ⏱ 2024-02-22 15:13:08
  • 📌 Draw Call本身的含义很简单,就是CPU调用图像编程接口

    • ⏱ 2024-02-22 15:18:06
  • 📌 Draw Call中造成性能问题的元凶是GPU,认为GPU上的状态切换是耗时的,其实不是的,真正“拖后腿”其实的是CPU。

    • ⏱ 2024-02-22 15:18:28
  • 📌 命令缓冲区包含了一个命令队列,由CPU向其中添加命令,而由GPU从中读取命令,添加和读取的过程是互相独立的。

    • ⏱ 2024-02-22 15:18:52
  • 📌 在每次调用Draw Call之前,CPU需要向GPU发送很多内容,包括数据、状态和命令等。在这一阶段,CPU需要完成很多工作,例如检查渲染状态等。而一旦CPU完成了这些准备工作,GPU就可以开始本次的渲染。

    • ⏱ 2024-02-22 15:19:57
  • 📌 那么,一个很显然的优化想法就是把很多小的DrawCall合并成一个大的Draw Call,这就是批处理的思想。

    • ⏱ 2024-02-22 15:20:47
  • 📌 需要注意的是,由于我们需要在CPU的内存中合并网格,而合并的过程是需要消耗时间的。

    • ⏱ 2024-02-22 15:22:01
  • 📌 批处理技术更加适合于那些静态的物体,例如不会移动的大地、石头等,对于这些静态物体我们只需要合并一次即可。

    • ⏱ 2024-02-22 15:22:07
  • 📌 避免使用大量很小的网格。当不可避免地需要使用很小的网格结构时,考虑是否可以合并它们。

    • ⏱ 2024-02-22 15:22:22
  • 📌 避免使用过多的材质。尽量在不同的网格之间共用同一个材质。

    • ⏱ 2024-02-22 15:22:30
  • 📌 固定函数的流水线(Fixed-Function Pipeline),也简称为固定管线,通常是指在较旧的GPU上实现的渲染流水线。这种流水线只给开发者提供一些配置操作,但开发者没有对流水线阶段的完全控制权。

    • ⏱ 2024-02-22 15:22:42
  • 📌 固定渲染管线是只可配置的管线。

    • ⏱ 2024-02-22 15:23:38

2.5 那么,你明白什么是Shader了吗

  • 📌 GPU流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码是会在GPU上运行的(对于固定管线的渲染来说,着色器有时等同于一些特定的渲染设置);

    • ⏱ 2024-02-22 15:26:47
  • 📌 有一些特定类型的着色器,如顶点着色器、片元着色器等;

    • ⏱ 2024-02-22 15:26:50
  • 📌 依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器来进行顶点变换以及传递数据,用片元着色器来进行逐像素的渲染。

    • ⏱ 2024-02-22 15:26:53
  • 📌 设置适当的渲染状态,使用合适的混合函数,开启还是关闭深度测试/深度写入等。

    • ⏱ 2024-02-22 15:27:10

3.1 Unity Shader概述

  • 📌 Unity一共提供了4种Unity Shader模板供我们选择——Standard Surface Shader, Unlit Shader, Image Effect Shader以及Compute Shader

    • ⏱ 2024-02-22 15:32:45
  • 📌 Standard Surface Shader会产生一个包含了标准光照模型

    • ⏱ 2024-02-22 15:32:50
  • 📌 Unlit Shader则会产生一个不包含光照(但包含雾效)的基本的顶点/片元着色器

    • ⏱ 2024-02-22 15:32:56
  • 📌 Image Effect Shader则为我们实现各种屏幕后处理效果(详见第12章)提供了一个基本模板

    • ⏱ 2024-02-22 15:33:04
  • 📌 Compute Shader

    • ⏱ 2024-02-22 15:33:17

3.2 Unity Shader的基础:ShaderLab

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

    • ⏱ 2024-02-22 15:38:39
  • 📌 Unity Shader是Unity为开发者提供的高层级的渲染抽象层。图3.6显示了这样的抽象。

    • ⏱ 2024-02-22 15:40:05

3.4 Unity Shader的形式

  • 📌 它存在的价值在于,Unity为我们处理了很多光照细节,使得我们不需要再操心这些“烦人的事情”。 ^22691473-21-1617-1664
    • ⏱ 2024-02-22 16:00:46

3.6 答疑解惑

  • 📌 在传统的Shader中,我们仅可以编写特定类型的Shader,例如顶点着色器、片元着色器等。而在Unity Shader中,我们可以在同一个文件里同时包含需要的顶点着色器和片元着色器代码。

    • ⏱ 2024-02-22 16:04:20
  • 📌 在传统的Shader中,我们无法设置一些渲染设置,例如是否开启混合、深度测试等,这些是开发者在另外的代码中自行设置的。而在Unity Shader中,我们通过一行特定的指令就可以完成这些设置。

    • ⏱ 2024-02-22 16:04:26
  • 📌 在传统的Shader中,我们需要编写冗长的代码来设置着色器的输入和输出,要小心地处理这些输入输出的位置对应关系等。而在Unity Shader中,我们只需要在特定语句块中声明一些属性,就可以依靠材质来方便地改变这些属性。而且对于模型自带的数据(如顶点位置、纹理坐标、法线等), Unity Shader也提供了直接访问的方法,不需要开发者自行编码来传给着色器。

    • ⏱ 2024-02-22 16:04:41
  • 📌 对于一些类型的Shader,例如曲面细分着色器(Tessellation Shader)、几何着色器(Geometry Shader)等

    • ⏱ 2024-02-22 16:04:55
  • 📌 CG的代码片段是位于Pass语义块内部的

    • ⏱ 2024-02-22 16:05:34

4.3 点和矢量

  • 📌 a ·b=(ax,ay,az)·(bx,by,bz)=axbx+ayby+azbz

    • ⏱ 2024-02-22 16:58:24
  • 📌 点积的几何意义很重要,因为点积几乎应用到了图形学的各个方面。其中一个几何意义就是投影(projection)。

    • ⏱ 2024-02-22 16:58:37
  • 📌 性质二:点积可结合矢量加法和减法,和性质一类似。

    • ⏱ 2024-02-22 17:00:03
  • 📌 性质三:一个矢量和本身进行点积的结果,是该矢量的模的平方。

    • ⏱ 2024-02-22 17:00:13
  • 📌 a·b=|a||b|cosθ

    • ⏱ 2024-02-22 17:00:28

16.2 影响性能的因素

  • 📌 因此,GPU的性能瓶颈和需要处理的顶点数目、屏幕分辨率、显存等因素有关。 ^22691473-110-1835-1871
    • ⏱ 2024-02-26 10:43:32

16.4 减少draw call数目

  • 📌 那么,什么样的物体可以一起处理呢?答案就是使用同一个材质的物体。

    • ⏱ 2024-02-23 11:08:08
  • 📌 Unity中支持两种批处理方式:一种是动态批处理,另一种是静态批处理。

    • ⏱ 2024-02-23 11:08:51
  • 📌 动态批处理来说,优点是一切处理都是Unity自动完成的,不需要我们自己做任何操作,而且物体是可以移动的,但缺点是,限制很多,可能一不小心就会破坏了这种机制,导致Unity无法动态批处理一些使用了相同材质的物体。

    • ⏱ 2024-02-23 11:09:02
  • 📌 静态批处理来说,它的优点是自由度很高,限制很少;但缺点是可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了(即便在脚本中尝试改变物体的位置也是无效的)。

    • ⏱ 2024-02-23 11:09:18
  • 📌 如果场景中有一些模型共享了同一个材质并满足一些条件,Unity就可以自动把它们进行批处理,从而只需要花费一个draw call就可以渲染所有的模型。

    • ⏱ 2024-02-23 11:10:45
  • 📌 每一帧把可以进行批处理的模型网格进行合并,再把合并后模型数据传递给GPU,然后使用同一个材质对其渲染。

    • ⏱ 2024-02-23 11:10:50
  • 📌 ·使用光照纹理(lightmap)的物体需要小心处理。

    • ⏱ 2024-02-23 11:11:50
  • 📌 多Pass的shader会中断批处理。

    • ⏱ 2024-02-23 11:11:46
  • 📌 它的实现原理是,只在运行开始阶段,把需要进行静态批处理的模型合并到一个新的网格结构中,这意味着这些模型不可以在运行时刻被移动。

    • ⏱ 2024-02-23 11:13:53
  • 📌 需要占用更多的内存来存储合并后的几何结构。

    • ⏱ 2024-02-23 11:14:22
  • 📌 如果在静态批处理前一些物体共享了相同的网格,那么在内存中每一个物体都会对应一个该网格的复制品,即一个网格会变成多个网格再发送给GPU。如果这类使用同一网格的对象很多,那么这就会成为一个性能瓶颈了。

    • ⏱ 2024-02-23 11:14:36
  • 📌 而对于使用了不同材质的物体,静态批处理同样可以提升渲染性能。尽管这些物体仍然需要调用多个draw call,但静态批处理可以减少这些draw call之间的状态切换,而这些切换往往是费时的操作。

    • ⏱ 2024-02-23 11:18:02
  • 📌 无论是动态批处理还是静态批处理,都要求模型之间需要共享同一个材质。

    • ⏱ 2024-02-23 11:20:14
  • 📌 我们可以把这些纹理合并到一张更大的纹理中,这张更大的纹理被称为是一张图集(atlas)。

    • ⏱ 2024-02-23 11:21:06
  • 📌 使用网格的顶点数据(最常见的就是顶点颜色数据)来存储这些参数

    • ⏱ 2024-02-23 11:23:23
  • 📌 一个例子是,森林场景中所有的树使用了同一种材质,我们希望它们可以通过批处理来减少draw call,但不同树的颜色可能不同。这时,我们可以利用网格的顶点的颜色数据来调整。

    • ⏱ 2024-02-23 11:27:26
  • 📌 需要注意的是,如果我们需要在脚本中访问共享材质,应该使用Renderer.sharedMaterial来保证修改的是和其他物体共享的材质,但这意味着修改会应用到所有使用该材质的物体上。另一个类似的API是Renderer.material,如果使用Renderer.material来修改材质,Unity会创建一个该材质的复制品,从而破坏批处理在该物体上的应用,这可能并不是我们希望看到的。

    • ⏱ 2024-02-23 11:27:08

16.5 减少需要处理的顶点数目

  • 📌 尽可能减少模型中三角面片的数目,一些对于模型没有影响、或是肉眼非常难察觉到区别的顶点都要尽可能去掉。

    • ⏱ 2024-02-26 11:02:38
  • 📌 在GPU看来,有时需要把一个顶点拆分成两个或更多的顶点。这种将顶点一分为多的原因主要有两个:一个是为了分离纹理坐标(uv splits),另一个是为了产生平滑的边界(smoothing splits)。

    • ⏱ 2024-02-23 11:33:30
  • 📌 移除不必要的硬边以及纹理衔接,避免边界平滑和纹理分离。

    • ⏱ 2024-02-23 11:34:24
  • 📌 另一个减少顶点数目的方法是使用LOD(Level of Detail)技术。

    • ⏱ 2024-02-23 11:34:36
  • 📌 当一个物体离摄像机很远时,模型上的很多细节是无法被察觉到的。因此,LOD允许当对象逐渐远离摄像机时,减少模型上的面片数量,从而提高性能。

    • ⏱ 2024-02-26 11:03:32
  • 📌 遮挡剔除(Occlusion culling)技术

    • ⏱ 2024-02-23 11:39:13
  • 📌 遮挡剔除和摄像机的视锥体剔除(Frustum Culling)区分开来

    • ⏱ 2024-02-23 11:39:33
  • 📌 视锥体剔除只会剔除掉那些不在摄像机的视野范围内的对象,但不会判断视野中是否有物体被其他物体挡住。

    • ⏱ 2024-02-23 11:39:42
  • 📌 遮挡剔除会使用一个虚拟的摄像机来遍历场景,从而构建一个潜在可见的对象集合的层级结构。

    • ⏱ 2024-02-23 11:39:57

16.6 减少需要处理的片元数目

  • 📌 另一个造成GPU瓶颈的是需要处理过多的片元。这部分优化的重点在于减少overdraw。简单来说,overdraw指的就是同一个像素被绘制了多次。

    • ⏱ 2024-02-23 11:40:48
  • 📌 为了最大限度地避免overdraw,一个重要的优化策略就是控制绘制顺序。

    • ⏱ 2024-02-23 11:44:30
  • 📌 对于半透明对象来说,由于它们没有开启深度写入,因此,如果要得到正确的渲染效果,就必须从后往前渲染。

    • ⏱ 2024-02-23 11:47:38
  • 📌 我们可以尽量减少窗口中GUI所占的面积。如果实在无能为力,我们可以把GUI的绘制和三维场景的绘制交给不同的摄像机,而其中负责三维场景的摄像机的视角范围尽量不要和GUI的相互重叠。

    • ⏱ 2024-02-23 11:48:16
  • 📌 在移动平台上,透明度测试也会影响游戏性能。虽然透明度测试没有关闭深度测试,但由于它的实现使用了discard或clip操作,而这些操作会导致一些硬件的优化策略失效。

    • ⏱ 2024-02-23 11:52:35
  • 📌 如果场景中包含了过多的点光源,并且使用了多个Pass的Shader,那么很有可能会造成性能下降。

    • ⏱ 2024-02-23 15:40:12
  • 📌 这些游戏往往使用了烘焙技术,把光照提前烘焙到一张光照纹理(lightmap)中,然后在运行时刻只需要根据纹理采样得到光照结果即可。

    • ⏱ 2024-02-23 15:40:54
  • 📌 God Ray

    • ⏱ 2024-02-23 15:45:50
  • 📌 把复杂的光照计算存储到一张查找纹理(lookup texture,也被称为查找表,lookup table, LUT)中。然后在运行时刻,我们只需要使用光源方向、视角方向、法线方向等参数,对LUT采样得到光照结果即可。

    • ⏱ 2024-02-23 15:46:14

16.7 节省带宽

  • 📌 使用纹理图集可以帮助我们减少draw call的数目,而这些纹理的大小同样是一个需要考虑的问题。需要注意的是,所有纹理的长宽比最好是正方形,而且长宽值最好是2的整数幂。这是因为有很多优化策略只有在这种时候才可以发挥最大效用。

    • ⏱ 2024-02-23 15:46:54
  • 📌 多级渐远纹理技术(mipmapping)和纹理压缩

    • ⏱ 2024-02-23 15:54:42
  • 📌 当勾选了Generate Mip Maps选项后,Unity就会为同一张纹理创建出很多不同大小的小纹理,构成一个纹理金字塔。而在游戏运行中就可以根据距离物体的远近,来动态选择使用哪一个纹理。这是因为,在距离物体很远的时候,就算我们使用了非常精细的纹理,但肉眼也是分辨不出来的。

    • ⏱ 2024-02-23 15:55:18

16.8 减少计算复杂度

  • 📌 只有Shader的LOD值小于某个设定的值,这个Shader才会被使用,而使用了那些超过设定值的Shader的物体将不会被渲染

    • ⏱ 2024-02-23 15:59:56
  • 📌 把采样坐标的计算放在了顶点着色器中,这样的做法远好于把它们放在片元着色器中。

    • ⏱ 2024-02-23 16:22:25
  • 📌 尽量避免在不同精度之间的转换,这有可能会造成一定的性能下降。

    • ⏱ 2024-02-23 16:22:08
  • 📌 尽可能不要使用全屏的屏幕后处理效果

    • ⏱ 2024-02-26 09:06:31
  • 📌 那些高精度的运算可以使用查找表(LUT)或者转移到顶点着色器中进行处理

    • ⏱ 2024-02-26 09:07:00
  • 📌 尽量把多个特效合并到一个Shader中

    • ⏱ 2024-02-26 09:09:50

读书笔记

本书评论