RedZero9 发表于 2022-12-4 19:49

Unity Shader入门精要读后总结

关于渲染的知识还是比较多的,如果想把其研究透彻,没几年功夫是消化不了的,毕竟是前人的智慧结晶。
很久之前读了“Unity Shader入门精要” 这本书,感觉写的非常不错,当时做了简单的知识汇总,今天跟大家分享下,也是为自己记录下学习的过程。
1、渲染管线
应用阶段 ---几何阶段--- 光栅化阶段
应用阶段包含三部分工作 首先准备数据其次 粗粒度剔除最后设置渲染状态
几何阶段工作在GPU ,负责和每个渲染图元打交道,进行逐顶点,逐多边形的的操作   
    顶点着色器(顶点变换) 、 曲面细分 、几何着色器 、 裁剪(图元不在视野内的就干掉,提升性能) 、 映射(把图元坐标转换到屏幕坐标系下)
光栅化阶段工作在GPU,会将上阶段传递的数据产生屏幕像素,并渲染最终图像
    三角形设置 、 三角形遍历、片元着色器各种测试 混合等

2、实现原理和基本语法
    namesubshader 属性 状态标签(key value)(Queue IgnoreProjector RenderType DisableBatching)pass   fallback
    表面着色器是对顶点/片元着色器的更高抽象,替我们处理了很多的光照细节,语义块被定义在subshader 下 CGPROGRAM 和 ENDCG 之间   使用CG/HLSL 编写
    顶点/片元着色器语义块被定义在Pass下 CGPROGRAM 和 ENDCG 之间   使用CG/HLSL 编写
    固定函数着色器被定义在pass块下使用shaderLab语法来编写而非CG/HLSL

    一个shader调用另个shader的pass 用UsePass命令pass的名称要大写

3、数学知识
   笛卡尔坐标系左手坐标系,左手法则   右手坐标系 ,右手法则
   Unity 模型空间和世界空间使用的是左手坐标系,观察空间用的是右手坐标系
   矢量 标量 点积(计算敌人是否在视野内,光照强度等) 叉积(计算三角面片是正面还是背面,法向量等)
   矩阵方阵 单位矩阵转置逆矩阵正交矩阵(不同行或者列点积为0 ,单位矢量)
   线性变换 f(x) + f(y) = f(x+y)
   仿射变换kf(x) = f(kx)
   平移 旋转 缩放
   坐标空间变换M 转到世界空间 V 转到观察空间 P 投影
   法线变换

   unity shader 采用行优先的方式   unity 脚本采用列优先的方式

4、实现简单的顶点、片元着色器还有辅助技巧
CGIncludes文件夹包含了所有的内置包含文件
DefaultResources包含了一些内置组件或功能所需的Unity shader ,例如一些GUI元素使用的shader
DefaultResourcesExtra则包含了所有的Unity 中的Unity shader ;
Editor 包含了一些脚本文件,用于定义Unity 引入的Standard Shader 所用的材质面板。
shader 语法差异,避免不必要的计算,慎用分支语句和循环语句,不要除0,
shader中临时寄存器数目和运算指令都有限制

5、实现基本的光照模型
如何测量一个光源发射了多少光呢?用辐照度来量化,辐照度是通过计算垂直于l的单位面积单位时间内穿过的能量来得到,因物体表面往往与l是不垂直的,所以辐照度与 d/cosθ 成反比,与cosθ成正比
着色是指根据材质属性(如漫反射属性等)、光源信息(如光源方向、辐照度等)使用一个公式去计算沿某个观察方向的出射度的过程。我们把这个公式叫做光照模型
标准光照模型: 自发光 高光反射 漫反射 环境光   phong光照模型和blinn-phong光照模型 (高光反射的计算不同于phong光照模型) ,逐像素着色(phong着色) 和 逐顶点着色 (gouraud 着色)
这种光照模型有很多局限性,首先很多物理现象无法表现出来,例如菲涅尔反射 其次 ,blinn-phong是各项同性的,也就是说当我们固定视角和光源旋转表面时,反射不会发生改变。
介绍Unity 内置函数

6、使用法线纹理 遮罩纹理等基础纹理
   基本纹理,高度图,法线图
   关于法线贴图:美术一般导出的都是切线空间的图,存储的信息是相对的,而模型空间存储的是绝对法线信息,
   那切线空间的法线贴图有什么优点呢?1、自由度很高,应用到不同的网格上也可以得到合理的结果。2、可进行UV动画 3、可重用法线纹理,比如砖块的6个面可以用一个4、可压缩。由于z方向总是正方向,因此可以存储xy方向推导出z方向
7、实现alpha测试和混合的效果
   关于透明度物体渲染的时候关闭了深度写入,不关闭深度测试
   要先渲染不透明物体,再渲染半透明的物体,半透明物体要排序,但是并不能解决半透明物体交叉的情况
   那Unity是怎么渲染的呢?用SubShader 的 Quene标签来决定我们的模型归于哪个渲染队列。Unity使用一系列整数索引来表示每个渲染队列,且索引号越小越早被渲染
   开启深度写入的半透明效果,使用2个pass来渲染模型 第一个pass 开启深度写入但不输出颜色第二个pass进行正常的透明度混合 ,由于上一个pass已经得到了逐像素的级别的深度信息,该pass就可以按照像素级别的结果进行透明渲染
   透明度混合的双面渲染,直接关闭剔除功能,我们就无法保证同一物体的正面和背面图元的渲染顺序,就有可能得到错误的渲染效果,所以分成两个pass渲染,第一个pass只渲染背面,第二个pass渲染正面

8、复杂的光照实现
unity 支持的渲染路径有三种
(1)前向渲染路径(forward rending path)
      3种处理光照的方式:逐顶点处理、逐像素处理、球鞋函数处理
      Unity 会根据场景中的各个光源的设置以及对物体的影响程度(远近,光源强度)对其做个排序,然后一定数目的光源逐像素处理,然后最多4个光源逐顶点处理,剩下的按SH函数处理
      使用规则如下:
      场景中最亮的平行光总是按像素进行处理的,渲染模式被设置成not important 的光源,会按逐顶点和SH处理,如果设置important 按逐像素处理。如果根据以上规则得到的逐像素光源数量小于Quality Setting 中的逐像素光源数量(Pixel Light Count),会有更多光源以逐像素的方式处理

      前向渲染有两种 Base Pass 和 Additional Pass
      Base Pass
      平行光默认支持阴影
      环境光和自发光是在这个pass计算的 ,因为我们只希望计算一次,而在Additional Pass 会造成多次叠加
      一个Base pass只会执行一次,定义多个base pass除外 ,
      Additional Pass
      渲染光源默认没有阴影即便Light组件中设置了Shadow Type
      会根据影响该物体的其他逐像素光源数目被多次调用

(2)延迟渲染路径 (deferred rendering path)
      有两种延时渲染路径 一种是遗留的延时渲染路径即Unity 5之前的延时渲染路径 (不支持基于物理的Standard Shader) 另一种是Unity 5.x 中使用的延时渲染路径
      对于延时渲染,它最适合在场景中光源数目很多,如果使用前向渲染会造成性能瓶颈的情况下使用,而且延时渲染中每个光源都可以按逐像素的方式处理
      缺点:
      不支持真正的抗锯齿功能、不能处理半透明物体、对显卡有要求,显卡必需支持MRT(multiplt render targets) shader model3.0及以上、深度渲染纹理以及双面的模板缓冲

      当使用时需要2个pass :
      第1个pass 用于渲染G缓冲 。我们会把物体的漫反射颜色、高光反射颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区、对于每个物体来说这个pass只会执行一次
      第2个pass 用于计算真正的光照、这个pass会使用上个pass渲染的数据来计算最终的光照,再存储到帧缓冲区

   (3)顶点照明渲染路径(vetex lit rending path已废弃)
      仅仅是前向渲染路径的一个子集,所有可以在顶点照明渲染路径实现的功能都可以在前向渲染中完成。顶点渲染只是使用了逐顶点的方式计算光照

光源类型 ,点光源 ,平行光,聚光灯,面光源(仅在烘焙时才可发挥作用)
光源的属性 位置,方向 ,强度,颜色以及衰减
1、平行光没有位置 衰减强度也就不会发生变化
2、点光源有位置衰减 强度的变化
3、聚光灯有位置衰减 强度的变化 还有角度 稍微复杂

Unity 阴影是怎么实现的?在实时渲染中最长使用的技术为ShadowMap技术。这种技术理解起来非常简单,他会首先把摄像机的位置放在与光源重合的位置,那么场景中该光源看不到的阴影区域就是摄像机看不到的地方

9、立方体纹理、渲染纹理和程序纹理等高级纹理
立方体纹理:天空盒子 以及环境映射
创建环境映射的立方体纹理的方法有三种 (1)第一种方法是直接由一些特殊布局的纹理创建 (2)第二种是手动创建一个Cubmap资源 再把6张图赋给它 (3)第三种是由脚本生成 Camera.RenderToCubeMap函数
反射、折射
斯涅耳定律 n1sinθ1=n2sinθ2
菲涅尔反射 描述了一种光学现象,即当光线照射到物体表面,一部分发生发射,一部分发生折射

渲染纹理:
GrabPass 使用一个字符串指明了被抓取的屏幕图像将会存储在哪个名称的纹理中,实际上GrabPass 支持两种形式
直接使用Grabpass {},然后在后续的pass中直接使用_GrabTexture来访问屏幕图像。但是当场景中有多个物体都使用了这样的形式来抓去屏幕时,这种方法的性能消耗大
使用GrabPass{"TextureName"} Unity只会在每一帧第一次使用名TextureName的纹理物体执行一次抓去屏幕的操作,而这个纹理同样可以在其他pass中被访问。这种更高效

程序纹理:指的是计算机生成的图像,我们通常使用一些特定的算法来创建个性化图案或非常真实的自然元素。程序纹理材质的自由度很高

10、实现顶点动画和纹理动画
内置时间变量 _Time ,_SinTime ,_CosTime , unity_DeltaTime
序列帧动画、滚动的背景,河流,广告牌

高级篇
11、屏幕后处理 高斯模糊,边缘检测
屏幕后处理通常指渲染完整个场景得到图像后,再对图像进行一系列的操作,实现各种屏幕特效。可以实现景深,运动模糊等
在OnRenderImage函数中我们是利用Graphics.Blit函数来完成对渲染纹理处理,想不对透明物体产生影响在在OnRenderImage函数前添加ImageEffectOpaque属性来实现这个目的
调整屏幕亮度,饱和度,对比度的效果
用卷积进行边缘检测
高斯模糊
Bloom效果

12、深度纹理和法线纹理实现更多特效
深度纹理:实际上就是一张渲染纹理,只不过它里面存储的像素值不是颜色值,而是高精度的深度值。由于被存储在深度纹理中,深度值范围在之间,并且是非均匀分布的。
Unity是怎样得到深度纹理?深度纹理可以直接来自真正的深度缓存,也可以由一个单独的pass渲染而得,这取决于渲染路径和硬件。通常当使用延迟渲染路径时,深度纹理理所当然可以访问到,因为延迟渲染会把这些信息渲染到G-Buffer中。当无法直接获取深度纹理时,深度纹理和法线纹理通过一个单独的pass渲染而得的。具体实现Unity会使用shader replacement 技术选择那些渲染类型(subshader 的renderType)为Opaque的物体,判断他们使用的渲染队列是否小于2500(内置的backGround Geometry AlphaTest 渲染队列均在此范围)如果满足条件就把他渲染到深度和法线纹理中,因此要想物体能够出现在深度和法线纹理,就必须设置renderType标签。

DecodeDepthNormal(float4 enc,out depth,out normal) enc 是对深度和法线纹理的采样结果
再谈运动模糊
全局雾效(均匀的雾效,基于距离的线性/指数雾效,基于高度的雾效)
根据深度纹理来重建每个像素在世界空间中的位置通常会影响性能,
另一种方法是对图像空间下的视椎体射线(从摄像机出发,指向图像上某点的射线)进行差值,这条射线存储了该像素在世界空间下到摄像机的方向信息,然后把该射线和线性化后的视角空间下的深度值相乘,再加上摄像机的位置,就可以得到该像素在世界空间下的位置。当我们得到世界坐标后就可以轻松使用各种公式来模拟全局雾效了。

再谈边缘检测

13、非真实感的渲染算法,卡通(描边)素描(用素描贴图,再用权重混合)

14、噪声在游戏中应用 用噪音纹理
   消融效果,水波效果,再谈全局雾效

15、常用优化技巧
(1)CPU优化使用批处理技术减少drawcall 数目
(2)GPU优化
      减少需要处理的顶点数目1、优化几何体 (移除不必要的硬边以及纹理衔接,避免边界平滑和纹理分离)2、LOD 3、遮挡剔除
      减少需要处理的片元数目1、控制绘制顺序2、警惕透明物体3、减少实时光照 (烘焙,使用God Ray)
      减少计算复杂度    1、LOD2、代码方面的优化
(3)节省内存带宽1、减少纹理大小2、利用分辨率缩放
16、表面着色器渲染
Unity 的渲染工程师Aras 认为把渲染流程分为顶点和像素的抽象层面是错误的,是一种不易我们人类的思考方式。他认为应该分为表面着色器、光照模型和光照着色器这样的层面。其中表面着色器定义了模型表面的反射率、法线和高光等,光照模型选择使用lambert还是blinn-Phonne等模型,而光照着色器负责计算光照衰减、阴影等。这样我们绝大多数时间只要和表面着色器打交道就好了。光照模型是提前定义好的,只需要选择哪种预定义的模型即可,而光照着色器一旦由系统实现后更不会轻易被改变,从而大大减轻shader编写者的工作量。

一个表面着色器最重要的部分是两个结构体以及它的编译指令。其中,两个结构体是表面着色器中不同函数之间信息传递的桥梁,而编译指令是我们和Unity沟通的重要手段。
编译指令最重要的作用是指明该表面着色器使用的表面函数和光照函数。
#pragma surface surfaceFunction lightModel

2个结构体 :输入结构体 Input 以及存储表面的结构体 SurfaceOutput

Unity 是如何为一个表面着色器生成对应的顶点/片元着色器?表面着色器本质上就是包含了很多pass的顶点/片元着色器
建议:
如果需要和各种光源打交道,尤其是想要使用Unity中的全局光照的话,你可能更喜欢使用表面着色器,但要时刻小心它的性能
如果你需要处理的光源数目非常少,例如只有一个平行光,那么使用顶点/片元着色器是一个更好的选择。
最重要的是,如果有很多自定义的的渲染效果,那么请选择顶点/片元着色器

17、基于物理的渲染
PBS的理论和数学基础
关于被微表面折射的光
1、金属具有很高的吸收系数
2、非金属材质会同时表现出吸收和散射两种现象,这些被散射出去的光又被称为次表面散射光。

双向反射分布函数 BRDF,有两种理解方式,第一种理解是,当给定入射角度后,BRDF可以给出所有出射方向上的反射和散射光线的相对分布情况;第二种理解是,当给定观察方向后,BRDF可以给出从所有入射方向到该出射方向的光线分布。

BRDF的着色过程是否是基于物理的需要满足两个特性来判断;交换律和能量守恒
BRDF通常会包含一个单独的部分来描述它们,用于描述表面反射的部分称为高光反射项,用于描述次表面散射的漫反射项

尽管存在很多基于物理的BRDF模型,但在真实的电影或者游戏制作中,我们希望在直观性和物理可信度之间找到一个平衡点,使得实现的BRDF即可以让美工人员直观的调节各个参数,而又有一定的物理可信度,当然为了满足直观性我们不得不牺牲一定的物理特性,得到的BRDF可能不是基于物理原理的。

Unity一共实现了两种PBS模型。一种是基于GGX模型的,另一种是基于归一化的Blinn-Phong模型的
两种基于物理的工作流程
金属工作流和高光反射工作流,不同的工作流可以实现相同的效果,只是他们使用的参数不同而已。金属工作流也不意味着它只能模拟金属类型的材质,金属工作流的名字来源于它定义了材质表面的金属值(是金属类型还是非金属类型), 高光反射工作流来源于它可以直接指定表面的高光反射颜色(有很强的高光反射还是很弱的高光反射)等,而在金属工作流中这个颜色需要由漫反射颜色和金属值衍生而来。在实际的游戏制作过程中,我们可以选择自己更偏好的工作流来制作场景,这更多是个人喜好的问题。当然也可以同时混用两种工作流。

金属材质
1、几乎没有漫反射,因为所有被吸收的光都会被自由电子立刻转化为其他形式的能量
2、有非常强烈的高光反射
3、高光反射通常是有颜色的 例如金子的反光颜色为黄色

非金属材质
1、大多数角度高光反射的强度比较弱,但在掠角时高光反射强度反而会增强,即菲涅尔现象
2、高光反射的颜色比较单一
3、漫反射的颜色多种多样

一个更加复杂的例子
设置光照环境 包括直接光照和间接光照
放置反射探针,调整材质,线性空间,gamma ,HDR

以上就是“Unity Shader入门精要” 这本书的全部内容,希望对大家有所帮助。
页: [1]
查看完整版本: Unity Shader入门精要读后总结