interview review

M:

1. 渲染管线

根据RTR4, 将渲染管线分成四部分

在这里插入图片描述

a) 应用阶段

CPU为GPU准备数据, 类似VAO, VBO, shader里面uniform变量设置等等, 提供图元类别, 此外还可以运用culling进行一些优化
在这里插入图片描述

这是一个由CPU主要负责的阶段,且完全由开发人员掌控。在这个阶段,CPU将决定递给GPU什么样的数据(譬如渲染目标场景中的灯光、场景的模型、摄像机的位置),有时候还会对这些数据进行处理(譬如只递给GPU可以被摄像机看见的元素,其他不可见的元素被剔除(culling)出去),并且告诉GPU这些数据的渲染状态(譬如纹理、材质、着色器等)。

顶点数据:顶点数据用来为后面的顶点着色器等阶段提供处理的数据。是渲染管线的数据
主要来源。送入到渲染管线的数据包括顶点坐标、纹理坐标、顶点法线和顶点颜色等顶点
属性。为了让OpenGL明白顶点数据构成的是什么图元,我们需要在绘制指令中传递相对
应的图元信息。常见的图元包括:点(GL_POINTS)、线(GL_LINES)、线条
(GL_LINE_STRIP)、三角面(GL_TRIANGLES)。"

在这里插入图片描述

CPU渲染逻辑 该阶段通常是由CPU负责实现,作为开发人员,可以对这个阶段进行控制。在这个阶段主要包含以下几个步骤:

进行剔除(Culling)工作:剔除主要分为三类,分别是

视锥体剔除(Frustum
Culling):如果场景中的物体和在视锥体外部,那么说明物体不可见,不需要对其进行渲染.。在Unity中可以通过设置Camera的Field
of view, Clipping Planes等属性修改视锥体属性。

层级剔除(Layer Culling Mask):通过给物体设置不同的层级,让摄像机不渲染某一层,在Unity中可以通过Culling
Mask属性设置层级可见性

遮挡剔除(Occlusion Culling):当一个物体被其他物体遮挡而不在摄像机的可视范围内时不对其进行渲染

设置渲染顺序:渲染顺序主要由渲染队列(Render Queue)的值决定的,不透明队列(RenderQueue <
2500),根据摄像机距离从前往后排序,这样先渲染离摄像机近的物体,远处的物体被遮挡剔除;半透明队列(RenderQueue >
2500),根据摄像机距离从后往前排序,这是为了保证渲染正确性,例如半透明黄色和蓝色物体,不同的渲染顺序会出现不一样的颜色 。

打包数据: 将数据提交打包准备发送给GPU,这些数据主要包含三部分,分别是

模型信息:顶点坐标、法线、UV、切线、顶点颜色、索引列表…

变换矩阵:世界变换矩阵、VP矩阵(根据摄像机位置和fov等参数构建)

灯光、材质参数:shader、材质参数、灯光信息

调用SetPass Call, Draw Call:

SetPass Call:
Shader脚本中一个Pass语义块就是一个完整的渲染流程,一个着色器可以包含多个Pass语义块,每当GPU运行一个Pass之前,就会产生一个SetPassCall,所以可以理解为调用一个完整渲染流程。

DrawCall:CPU每次调用图像编程接口命令GPU渲染的操作称为一次Draw Call。Draw
Call就是一次渲染命令的调用,它指向一个需要被渲染的图元(primitive)列表,不包含任何材质信息。GPU收到指令就会根据渲染状态(例如材质、纹理、着色器等)和所有输入的顶点数据来进行计算,最终输出成屏幕上显示的那些漂亮的像素。

Unity中可以通过开启Stats查看SetPass Call
和DrawCall调用的次数,它们可能会占用大量CPU资源,是性能优化中非常值得关注的一个点

CPU渲染阶段最重要的输出是渲染所需的几何信息,即渲染图元(rendering
primitives),通俗来讲,渲染图元可以是点、线、三角面等,这些信息会传递给GPU渲染管线处理。

b) 几何处理

Vertex Shader(可编程, model, view变换) ->
Tessellation Shader( 曲面细分着色器:可编程,此阶段可选, 包括Hull-Shader Stage -> Tessellation Stage-> Domain-Shader Stage ) ->

Ⅰ. Hull-Shader Stage(细分控制):此阶段是可编程的,可以指挥GPU如何对顶点进行曲面细分操作,但是还未具体执行细分。

Ⅱ. Tessellation Stage(细分操作):此阶段是不可编程,GPU会根据 Hull-Shader Stage 阶段对曲面细分的指令,执行曲面细分,这个阶段才是真正的曲面细分执行阶段。

Ⅲ. Domain-Shader Stage(细分计算):此阶段是可编程的,可以控制计算经过曲面细分后的顶点。

Geometry Shader( 几何着色器可编程,此阶段可选) ->

与顶点着色器不能销毁或创建顶点不同,几何着色器的主要亮 点就是可以创建或销毁几何图元,此功能让GPU可以实现一些有趣的效果。例如,根据输入图元类型扩展为一个或更多其他类型的图元,或者不输出任何图元。需要注意的是,几何着色器的输出图元不一定和输入图元相同。几何着色器的一个拿手好戏就是将一个点扩展为一个四边形(即两个三角形)。几何着色器通常用来实现一种叫做公告栏(BillBoards)的 视觉效果

projection->
clipping->
back face culling->
screen mapping

c) rasterization

包括

图元组装(Primitive Assembly or Triangle Setup):

具体就是算三角形三条线的参数方程, 用来判断一个像素的中心在三角形内还是外

https://ocw.mit.edu/courses/6-837-computer-graphics-fall-2012/53d96abf747a3c82fd3497d2fea540f5_MIT6_837F12_Lec21.pdf

三角形遍历(Triangle Traversal)Finding which samples or pixels are inside a triangle is often called triangle traversal. Each triangle fragment’s properties are generated using data interpolated among the three triangle vertices. 属性插值在这里进行的.

这里各种资料总结得不一致, 有分歧尽量找权威的资料细读去确认.

d) pixel processing
Fragment Shader
Per-Fragment Operations逐片元操作:不可编程,但可通过参数控制,这一阶段也被称为合并阶段(Output-Merger),主要就是对片元进行测试(Test)以及合并(Merge)。
在这里插入图片描述

测试是可以通过参数配置来控制的,主要有裁剪测试(Scissor Test)、透明度测试(Alpha Test)、模板测试(Stencil Test)以及深度测试(Depth Test)这几种方式。一般情况下,大量的被遮挡的片元会在测试过程中被剔除,最终成为不了像素,但这些片元却几乎参与了整个渲染管线流程,消耗GPU大量资源,所以有深度测试提前(Early-Z)技术来优化这种情况,不过这个技术会导致透明度测试的冲突, 我们后面会具体讲Early-Z.

引用资料:
http://geekfaner.com/shineengine/Translation2_RealTime_Rendering_4th_Edition1.html
https://ocw.mit.edu/courses/6-837-computer-graphics-fall-2012/53d96abf747a3c82fd3497d2fea540f5_MIT6_837F12_Lec21.pdf
https://zhuanlan.zhihu.com/p/433303673
https://www.bilibili.com/read/cv13035667/
https://zhuanlan.zhihu.com/p/137780634
https://positiveczp.github.io/%E7%BB%86%E8%AF%B4%E5%9B%BE%E5%BD%A2%E5%AD%A6%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF.pdf
https://www.cnblogs.com/forever-Ys/p/15520028.html

2. early-z and z prepass

在这里插入图片描述

early z 就是把深度测试提前, 这样只需要渲染最前面的片元. early z 属于硬件优化,如果硬件支持会自动开启

但是以下情况会失效

开启Alpha Test 或 clip/discard 等手动丢弃片元操作, (但是深度还是early z的深度)
手动修改GPU插值得到的深度
开启Alpha Blend(Zwrite off, 深度写不了)
关闭深度测试Depth Test

高效利用Early-z

通过CPU将物体按照摄像机的距离,由近到远进行排序,在交付给GPU进行渲染。但场景复杂的情况下,这样的排序操作将消耗CPU的性能,并且如果严格按照由近到远进行渲染的话,将不能同时搭配合批优化手段

解决方法:使用两个Pass

在第一个Pass即Z-Prepass中仅仅只写入深度,不计算输出任何颜色
所有物体的Z-Prepass结果自动形成了一个最小深度的深度缓冲区Z-Buffer,无需进行CPU手动排序
这个Pass片元计算前也有Early-Z阶段,可能再提升一点效率

在第二个Pass中关闭深度写入,并且将深度比较函数设置为相等
在第二个Pass片元着色器前可以凭借Z-Prepass的结果进行深度测试比较是否相等

但是又带来了一个问题:一个拥有多个Pass的shader的物体是无法进行动态批处理的(例如不能通过Scriptable Render Pipeline(SRP)来优化,这带来了Draw Call的问题. 一个物体2个drawcall, n个物体就是2n个drawcall.

为了使用类似SRP的优化,可以采用提前分离z-prepass

就是将两个pass分别写个shader, 然后SRP可以进行优化,也就是多个物体一样的材质共享GPU的一块内存

关于SRP参见: https://blog.csdn.net/lsjsoft/article/details/90734932

但使用Z-Prepass的透明效果无法看到背面,按理来说透明物体是可以透过自身看到背面的

如果想看到背面的话,首先要渲染物体的背面,然后剔除正面,随后在第二个pass中渲染物体的正面剔除背面

引用资料:
https://zhuanlan.zhihu.com/p/480914627

3. BRDF

BRDF中RGB三个通道函数不一样, 是因为其中的F项跟光波长有光, 算出radiance后值可能大于1, 需要进行tone mapping, gamma correction最后显示

在这里插入图片描述
在这里插入图片描述

具体的推导见:https://zhuanlan.zhihu.com/p/21376124

在这里插入图片描述
a quotient of two differentials: radiance(辐射率, 每单位面积每单位立体角的辐射通量密度。用符号L表示) and irradiance(辐照率, 指单位时间内到达单位面积的辐射能量, 用符号E表示), radiance不仅要考虑被照的面积, 而且要考虑被照射的方向, irrdiance只考虑被照的面积

另外irradiance d Φ d A \frac{d \Phi}{d A} dAdΦ与距离平方成反比(理解为光束强度不变, 但是越远面积越大, 单位面积接受到的辐射就越小), radiant density ( d Φ d ω \frac{d \Phi}{d \omega} dωdΦ)不随距离变化(理解为光束强度不受距离影响) , radiance d Φ d ω d A ⊥ \frac{d \Phi}{d \omega d A^\perp} dωdAdΦ 不随距离变化(距离变大, 面积不变, d Φ d A ⊥ \frac{d \Phi}{d A^\perp} dAdΦ与距离平方成反比, 立体角等于面积除以距离平方, 距离平方约了, 所以不变 ),

在这里插入图片描述
上面这个式子可以这么理解: L i ( l ) L_i(l) Li(l)是垂直于光线的单位面积的辐射率, 乘以 c o s θ i cos \theta_i cosθi得到实际表面的辐射率, 再在不同方向积分最终得到辐照率

在这里插入图片描述
https://www.bilibili.com/video/BV1YK4y1T7yY?p=10&vd_source=7e7882c4af943e977e1137955f6f5419
注意闫老师指点批评了, 不要把diffuse和microfacet 搞混了, diffuse是半球, miceorfacet 分部更像高斯分布

在这里插入图片描述
在这里插入图片描述
Cook-Torrance BRDF的镜面反射部分包含三个函数,此外分母部分还有一个标准化因子 。字母D,F与G分别代表着一种类型的函数,各个函数分别用来近似的计算出表面反射特性的一个特定部分。三个函数分别为

  • 法线分布函数(Normal Distribution Function)
    在这里插入图片描述

  • 菲涅尔方程(Fresnel Rquation)
    在这里插入图片描述

  • 几何函数(Geometry Function):
    在这里插入图片描述

引用资料:
https://learnopengl-cn.github.io/07%20PBR/01%20Theory/
https://blog.csdn.net/haozi2008/article/details/111061303
https://chengkehan.github.io/PhysicallyBasedShading2.html

image space 和 screen space的区别, image space 类似shadow map, 可以从其他物体的视角出发, 而screen space是从camera的视角出发. 3d出发, LPV(light propagation volumne) 和 VXGI(voxel global illumination)

4. 光照模型

在这里插入图片描述
Lambert 模型, 也就是漫反射模型, 只与入射角有关
I = k d ( L ⋅ N ) i d I = k_d(L \cdot N)i_d I=kd(LN)id

Gouraud模型
Gouraud着色处理首先是计算三角形的每个顶点的光照,然后用顶点颜色通过插值处理计算三角形内部的光照颜色。通常在shader代码的顶点着色器中处理光照, 而不是片元着色器.

phone 模型:
在这里插入图片描述
注意最后的颜色是 I p I_p Ip乘以材料本身的颜色
在这里插入图片描述

blinn-phone 模型:
在这里插入图片描述
好处:

  1. 半角向量计算比反射向量快
  2. Phong模型当光源和观察方向在同一方向时cos(R*V) = 0 => 出错

5. path tracing

whitted-stype ray tracing : 增加了阴影, 镜面反射, 电解质(反射+折射)

global illumination: 判断条件 漫反射表面会不会继续反光

在这里插入图片描述
θ k \theta_k θk 是入射光与法向量夹角

在这里插入图片描述

完结? No, diffuse expensive
在这里插入图片描述

解决办法:
solution 1: N = 1, 结果很多噪声(不确定性)
solution 2: N = 1, SPP = 100(samples pex pixel), 采样100次, 结束条件, 光打到光源或者虚空才结束.
问题就是两个diffuse的材质之间弹来弹去形成死循环
solution3: 附加结束条件 a) 弹N次结束 b) Russian Roulette

在这里插入图片描述

6. SSAO请添加图片描述

SSAO通过法向量纹理和深度纹理得到每个像素点的法向量和深度, 法向量确定了半球面的方向, 深度和屏幕坐标可以反推到空间三维坐标(怎么推?请见:https://zhuanlan.zhihu.com/p/92315967 ), 然后在这个半球内随机发射64条线段(注意有长有短), 得到线段另一端的三维坐标, 然后投影到屏幕坐标得到uv坐标和点的深度, 然后通过uv坐标查询深度图得到场景的深度,如果点的深度大于场景的深度就说明场景在这个点前面,把它遮住了, ao + 1, 如果小于说明没遮挡, ao不变. 另外注意范围检测机制,
在这里插入图片描述
在这里插入图片描述
采样到天空, 然后线段被柱子挡住了, 设置范围判定消除这个artifact.

参考资料:
https://1024114.xyz/posts/3eb3a01c/
https://www.bilibili.com/video/BV1AT4y1d762?p=2&vd_source=7e7882c4af943e977e1137955f6f5419
https://blog.csdn.net/qq_36005498/article/details/120236468
https://bbs.huaweicloud.com/blogs/358812
https://vis.uni-jena.de/Lecture/ComputerGraphics2/Lec12_b_SSAO.pdf
https://blog.csdn.net/qjh5606/article/details/120001743
https://zhuanlan.zhihu.com/p/150431414
https://zhuanlan.zhihu.com/p/92315967

7. anti aliasing

SSAA

一个up-scaling和down-scaling, 超采样后对每个sub-sample进行frag shader计算, 然后用down-sclaing 用box filter进行平均, 缺点计算量大

MSAA

具体参见 https://zhuanlan.zhihu.com/p/415087003

简单描述一下就是一个像素, 会采样几个子像素, 子像素只用来进行深度, cover测试, 颜色的计算还是以像素中心来算, 子像素采用像素中心的值(如果覆盖, 不然就用centroid samping, 通常GPU就会挑选最近的通过覆盖测试的次像素点), 这样一个像素只进行一次frag shader计算(对于一个图元来说)

在这里插入图片描述

The reference rasterizer chooses a sample location for centroid
sampling similar to this:

The sample mask allows all samples. Use a pixel center if the pixel is
covered or if none of the samples are covered. Otherwise, the first
covered sample is chosen, starting from the pixel center and moving
outward.

参照资料
https://learn.microsoft.com/en-us/windows/uwp/graphics-concepts/rasterization-rules

TAA

具体参照: https://zhuanlan.zhihu.com/p/425233743

就是每帧加上一个抖动, 具体实现就是将透视矩阵第三列前两个数字改成offset(第三行前两个数字如果是左乘), 当前帧抖动后会与前一帧进行混合, 混合的关键找到前面两帧像素匹配对. 而找匹配的关键就是算每个像素的motion vector.

在渲染物体时,我们需要用到上一帧的投影矩阵和上一帧该物体的位置信息,这样可以得到当前帧和上一帧的位置差,并写入到 Motion Vector。

// 减去抖动坐标值,得到当前实际的像素中心UV值
uv -= _Jitter;
// 减去Motion值,算出上帧的投影坐标
float2 uvLast = uv - motionVectorBuffer.Sample(point, uv);
//使用双线性模式采样
float3 historyColor = historyBuffer.Sample(linear, uvLast);

在这里插入图片描述

另外就是一个小问题需要注意下: 一是前一帧没出现的物体在当前帧出现

当镜头的移动时,可能会导致物体的遮挡关系发生变化,比如一个远处的物体原来被前面的物体遮挡住,现在因为镜头移动而忽然出现,这时采样Motion 偏移得到的位置,上帧中其实是没有渲染的数据的。因此为了得到更加平滑的数据,可以在当前像素点周围判断深度,取距离镜头最近的点位置,来采样Motion Vector的值,这样可以减弱遮挡错误的影响

注意这里只能减弱, 也就是新出现的点会跟它邻居中离镜头的点进行混合

另外一个小问题是ghosting, 具体来说就是上一帧出现了我们不想混合的元素, 这个时候我们就可以通过截断clamping来限制上一帧混合进来的颜色范围, 只有在这个范围的颜色才会计算混合运算

由于像素抖动,模型变化,渲染光照变化导致渲染结果发生变化时,会导致历史帧得到的像素值失效,就会产生 鬼影/ghosting 和 闪烁
/flicking 问题。

在这里插入图片描述

最后注意一下, tone mapping与AA之间的关系,

一般是先tone mapping把颜色范围降下来, 然后进行AA算平均, 然后invert tone mapping 回去把颜色范围还原, 然后做一些HDR的后处理, 最后tone mapping回来把颜色范围降下来显示.

接下来就是对历史的帧进行混合了,首先我们要确定 TAA 在图形管线中的位置,从结果上来看,如果使用HDR颜色作为输入,得到的抗锯齿效果不佳。所以需要把 TAA 放到 Tonemapping 之后。但是这样又会影响后续需要 HDR的Bloom等特效的计算。因此我们需要先进行一次 Tonemapping,进行 TAA 后再将 Tonemapping 还原,然后处理需要HDR颜色输入的后处理,最终再进行一次 Tonemapping 计算。第一次的 Tonemapping 使用比较简单的 Reinhard Tonemapping 算法即可。这里的处理方式和前面我们讲过的 HDR 下 MSAA 的处理方式是一样的。

FXAA

https://zhuanlan.zhihu.com/p/431384101

FXAA分FXAA Quality 和 FXAA Console

FXAA Quality

属于后处理AA, 需要先判断需要AA的像素, 判断方式

float MaxLuma = max(N, E, W, S, M);
float MinLuma = min(N, E, W, S, M);
// 周围5个像素点,最大亮度和最小亮度的差,作为对比度
float Contrast =  MaxLuma - MinLuma;
if(Contrast >= max(_MinThreshold, MaxLuma * _Threshold)) ...

然后确定混合系数

float Filter = 2 * (N + E + S + W) + NE + NW + SE + SW;
Filter = Filter / 12;
// 计算出基于亮度的混合系数
Filter = abs(Filter -  M);
Filter = saturate(Filter / Contrast);
// 使输出结果更加平滑
float PixelBlend = smoothstep(0, 1, Filter);
PixelBlend = PixelBlend * PixelBlend;

然后确定混合方向, 也就是那个方向变化最大, 具体看参照资料

最后就是混合,

最后需要加上边界混合系数, 边界混合系数根据, 具体看参照资料

EdgeBlend = 0.5f - PDistance / (PDistance + NDistance);
// 取两种方式算出的混合系数中的最大值
float FinalBlend = max(PixelBlend, EdgeBlend);
// 进行混合
float4 Result = tex2D(_MainTex, UV + PixelStep * FinalBlend);
FXAA console

一样, 先确定需要AA的像素

float MaxLuma = max(NW, NE, SW, SE);
float MinLuma = min(NW, NE, SW, SE);
float Contrast =  max(MaxLuma, M) -  min(MinLuma, M);
if(Contrast >= max(_MinThreshold, MaxLuma * _Threshold)) ...

然后算切线方向, 也就是梯形垂直的方向

// 防止除 0
NE += 1.0f / 384.0f;
float2 Dir;
Dir.x = -((NW + NE) - (SW + SE));
Dir.y = ((NE + SE) - (NW + SW));
Dir = normalize(Dir);
float2 Dir1 = Dir * _MainTex_TexelSize.xy * _Scale;

然后混合

float4 N1 = tex2D(_MainTex, UV + Dir1);
float4 P1 = tex2D(_MainTex, UV - Dir1);
float4 Result = (N1 + P1) * 0.5f;

这种方式对于斜向的锯齿比较友好,但是对于水平和垂直方向的锯齿

然后处理水平和垂直方向的锯齿, 因此在这里我们进行一次额外的计算,将偏移距离延伸至更远处,具体的做法就是 Dir 向量分量的最小值的倒数,将 Dir1 进行缩放。这样如果 Dir 的最小分量的值越小,就能采样到越远的地方:

float DirAbsMinTimesC = min(abs(Dir.x), abs(Dir.y)) * _Sharpness;
// 将偏移距离进行放大,锯齿越接近水平或垂直方向,放大的系数也就越大
float2 Dir2 = clamp(Dir1 / DirAbsMinTimesC, -2, 2) * 2;

_Sharpness 是控制锐利程度的参数值,一般取8。为了使 Dir2 的值不至于太大,最后进行一次 clamp。

为了防止 Dir2 采样到亮度变化较大的区域,产生噪点,这里我们再对 Dir2 采样到的亮度值进行一次判断,如果得到的结果超过了周围最小最大的亮度范围,则丢弃新的采样的结果,使用上一步中得到的结果:

float4 N2 = tex2D(_MainTex, UV + Dir2 * _MainTex_TexelSize.xy);
float4 P2 = tex2D(_MainTex, UV - Dir2 * _MainTex_TexelSize.xy);
float4 Result2 = Result * 0.5f + (N2 + P2) * 0.25f;
// 判断下结果是否在合适的范围内
if(Luminance(Result2.xyz) > MinLuma && Luminance(Result2.xyz) < MaxLuma) {
    Result = Result2;
}
MLAA(Morphological Antialiasing)

https://zhuanlan.zhihu.com/p/342211163

先判断左上是否是边界, 判断方式就是差值超过阈值

然后就要判断锯齿模式, 水平锯齿有16种, 具体怎么判断见参照资料

在这里插入图片描述

在这里插入图片描述

然后以到左右的距离百分比为uv来查询texture得到混合系数, 然后上, 下, 左, 右各一个混合系数, 然后加权平均混合

SMAA(Enhanced Subpixel Morphological Antialiasing)

对MLAA几个部分进了改进:

a) 对边界进行二次判断

在这里插入图片描述
b) 转角保留
在这里插入图片描述
e 3 e_3 e3这里是边界可以乘以系数 r r r, 如果系数等于0就等于不混合,保持直角也 就是洋红线, e 4 e_4 e4不是边界, 保持蓝线混合

c) 对角线混合

以45度线为标准, 也就是中图中的绿线, 然后一样以左右距离来采样混合系数, 混合像素 e 1 e_1 e1 e 2 e_2 e2

在这里插入图片描述
在这里插入图片描述

d) 更准备的边界搜索
在这里插入图片描述

这里采样一个值就可以四个像素的值, 也就变值可以知道这四个像素的上边界和左边界的情况, 每个边界用一个通道0,1表示

8. deferred rendering

https://learnopengl-cn.github.io/05%20Advanced%20Lighting/08%20Deferred%20Shading/

延迟着色法基于我们延迟(Defer)或推迟(Postpone)大部分计算量非常大的渲染(像是光照)到后期进行处理的想法。它包含两个处理阶段(Pass):

在第一个几何处理阶段(Geometry Pass)中,我们先渲染场景一次,之后获取对象的各种几何信息,并储存在一系列叫做G缓冲(G-buffer)的纹理中;想想位置向量(Position Vector)、颜色向量(Color Vector)、法向量(Normal Vector)和/或镜面值(Specular Value)。场景中这些储存在G缓冲中的几何信息将会在之后用来做(更复杂的)光照计算。下面是一帧中G缓冲的内容:

我们会在第二个光照处理阶段(Lighting Pass)中使用G缓冲内的纹理数据。在光照处理阶段中,我们渲染一个屏幕大小的方形,并使用G缓冲中的几何数据对每一个片段计算场景的光照;在每个像素中我们都会对G缓冲进行迭代。我们对于渲染过程进行解耦,将它高级的片段处理挪到后期进行,而不是直接将每个对象从顶点着色器带到片段着色器。光照计算过程还是和我们以前一样,但是现在我们需要从对应的G缓冲而不是顶点着色器(和一些uniform变量)那里获取输入变量了。

在这里插入图片描述
优: 将光源和物体分开, 复杂度从O(m*n) 减小到O(m+n)
缺: MSAA,ALPHA失效

另外延迟渲染不能使用MSAA, https://zhuanlan.zhihu.com/p/135444145

光照阶段使用的输入是GBuffer,如果还像前向渲染一样,在光照计算以后执行MSAA,会得到错误的结果。具体来说,使用单倍GBuffer来进行计算,会因为得不到三角形的覆盖信息而无法判定应该将该点的颜色值复制到哪几个子Sample上,也不会出现同一个像素的子Sample会被不同面片覆盖的情况(tj: 即一个像素一部分被一个图元覆盖, 一部分被另一个图元覆盖)(因为GBuffer就是一张图,已经不知道该点被几个三角形覆盖了)。而使用多倍大小的GBuffer的话,又无法通过顶点插值获取中心处原始像素的位置、深度、法线、纹理坐标等数据,因为原始三个顶点的信息已经没有了(tj: 子像素需要参照中心像素的值, 所以需要算中心像素)。更重要的是,在多倍大小的GBuffer上我们是没办法判断哪几个子Sample是与中心像素在同一三角形上的(tj: 需要判断子像素与中心像素一同在哪个图元来着色),如果试图使用四个子Sample的数据插值获得中心像素,对深度和法线进行插值会导致意料之外的后果。上面两个原因综合起来,就是“丢失其他像素信息导致无法使用MSAA”这种说法的来源了。(MSAA说白了就是子像素的颜色是需要依赖中心像素的, 不像SSAA子像素各算各)

9. mipmap

关于怎么算层数
https://blog.csdn.net/u013746357/article/details/107975128
https://zhuanlan.zhihu.com/p/70269212

图中红框表示一个屏幕像素(pixel),红色箭头分别表示屏幕的水平(x)/垂直(y)方向。黑白色块表示一个贴图纹素(texel),蓝色箭头分别表示贴图的水平(u)/垂直(v)方向。
∂u/∂x是u对x的偏微分,用人类的语言来说就是:当屏幕像素沿x轴变化一个单位,贴图沿u方向变化了几个单位。同理,∂u/∂y就是u沿y轴的变化率,∂v/∂x就是v沿x轴的变化率,∂v/∂y就是v沿y轴的变化率。

另外,w是3d贴图的第三个坐标轴,在处理2d贴图时,∂w/∂x和∂w/∂y将永远为0。

举个例子,在下方的贴图映射中,∂u/∂x等于4,∂v/∂y等于2,∂u/∂y和∂v/∂x都等于0。
在这里插入图片描述

float4 _MipMapColors[11];
float4 _MainTex_TexelSize;

fixed4 frag (v2f_img i) : SV_Target
{
    float2 uv = i.uv * _MainTex_TexelSize.zw;
    float2 dx = ddx(uv);
    float2 dy = ddy(uv);
    float rho = max(sqrt(dot(dx, dx)), sqrt(dot(dy, dy));
    float lambda = log2(rho);
    int d = max(int(lambda + 0.5), 0);
    return _MipMapColors[d];
}

(假设原本0-1的UV可以铺满1000x1000的屏幕,相邻像素点UV差值为0.001=1/1000,当只能铺满1/4屏幕,也就是500x500的屏幕时,相邻像素点UV差值为0.002=1/500)。

_MainTex_TexelSize.zw是纹素大小,假设第0层跟屏幕大小一样1000x1000, 那么纹素大小就是1/1000 x 1/1000. ddx(f)是算一个横像素内函数值的变动, 也就是X方向的梯度, 如果纹理缩小一半, 那么一个像素占两个纹素

d d x ( u v ) = ( u + 2 ) ∗ ( 1 / 1000 ) − u ∗ ( 1 / 1000 ) 1 / 1000 = 2 ddx(uv) = \frac{(u+2)*(1/1000) - u * (1/1000) }{1/1000}=2 ddx(uv)=1/1000(u+2)(1/1000)u(1/1000)=2

然后mipmap选第一层

10. shadow

https://blog.csdn.net/yx314636922/article/details/123484135
https://www.bilibili.com/video/BV1YK4y1T7yY?p=3&vd_source=7e7882c4af943e977e1137955f6f5419

shadow map 原理:

先从光源视角得到各方向最浅深度shadow map。
再从相机视角得到可视点(shading point), 转到光源坐标系中求得深度与shadow map中的值比较

可能问题:
自遮挡, 处理方法加个bias, 也就是如果shading point 的深度 < shadowmap 中的值 + bias 才会算成被遮挡, bias可以是自适应性, 可根据光线角度变化
在这里插入图片描述
锯齿化走样
shadow map分辨率不够会出现锯齿,cascaded 或者动态分辨率。 或者PCF

PCF(Percentage Closer Filtering)

原理是在阴影判定时做一个filtering----不仅计算当前着色点对应shadow map上的深度,还计算该点在shadow map上周围一圈(比如7x7)的深度判定结果(非0即1)并取平均,将平均值作为Visibility项。「如果滤波核比较大,可以在范围内随机采样固定个数」

PCSS(Percentage closer soft shadows)

根据PCF的原理,可以得到PCSS算法步骤:

a) Blocker search,计算物体平均遮挡深度(在固定或动态范围内去遮挡区域计算)。即计算 d B l o c k e r d_{Blocker} dBlocker

要算小于d的像素的平均深度
在这里插入图片描述

算一个矩阵范围的深度平均值 (也就等价于深度求和)

MIPMAP
mipmap做的是快速近似方形查询,其中用到了差值,有误差,个别有长方形用各向异性过滤,具体内容101讲过了,不懂回去看下。

SAT-Summed Area Tables
mipmap局限于正方形或特殊情况长方形,想随意取矩形计算就不划算。Summed Area Table每个像素的值为原纹理从(0,0)到该像素组成的矩形中所有像素值的和,是100%准确的,如下图:

在这里插入图片描述
原理与1D 的prefix sum类似
在这里插入图片描述

b) Penumbra estimation,用平均遮挡深度确认该点滤波核大小。得到 w p e n u m b r a w_{penumbra} wpenumbra, 核越小, 阴影越硬, 反之亦然.

在这里插入图片描述
在这里插入图片描述

c) 应用PCF算法。
要求小于d的像素的百分比, 也可以大于d的像素百分比, 运用切比雪夫不等式

在这里插入图片描述
均值方差求法:
在这里插入图片描述
​切比雪夫在高斯分布时近似效果比较好, 不然会出现过黑或者漏光的情况

在这里插入图片描述

MSM(Moment矩 shadow mapping)

深度分布复杂时假设为正态分布问题不大,反而场景简单时就可能不成立了,实际可能是多峰分布,会造成light leaking漏光现象
Probability Density Functions (PDFs) 概率密度函数
Cumulative Distribution Functions (CDFs) 累加分布函数

MSM思路:用前m阶矩来拟合CDF(会有m/2个阶梯)
在这里插入图片描述

SDF (Signed Distance Field) soft shadows

有向距离场SDF记录了空间中任何一点到定义该场的物体之间的最小距离,并用正负号表示在物体内外(0表示在物体上)

SDF应用

a) Ray Marching

解决光线和SDF的求交问题,将SDF距离作为安全距离,判断光线是否与物体相交(sphere tarcing)(第一次取的安全距离作为步长,在光线方向取第二次,以此类推,直到安全距离小于一定值或者在该光线上走了很远,然后取最小值)。「运动刚体可以用此方法,但形变物体不行」

在这里插入图片描述

b) 生成距离场软阴影

估计大概的percentage of occlusion(实际上不准但符合人们观察)。下图左模拟人眼看向面光源的阴影距离场计算,对光线方向计算不会被物体遮挡的最小安全角度(这里是 θ 3 \theta_3 θ3)(光线上每个step都要计算取最小,如下右图),安全角度越小,阴影越硬。

在这里插入图片描述
算arcsin贵, 用后面的代替, k的大小是控制阴影的软硬程度,k越大阴影越硬
在这里插入图片描述

cascaded shadow mapping

挖个坑

11 bounding

课程请见:https://www.bilibili.com/video/BV1X7411F744?p=14&vd_source=7e7882c4af943e977e1137955f6f5419

线与AABB相交请见: https://blog.csdn.net/seamanj/article/details/134390058?spm=1001.2014.3001.5501
在这里插入图片描述

在这里插入图片描述
先median object是一个topk 问题, 快速划分 来解决, 类似快排

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
其他剔除算法

https://blog.csdn.net/chenweiyu11962/article/details/118032517

12. 纹理压缩

在这里插入图片描述
将图片 分成4x4block, 取block里面最亮和最暗的像素, 其中的像素通过插值来表示.

13. tone mapping

14. gamma correction

人眼和相机对强度的变化敏感程度不一样, 人对暗部更敏感.

上面一条人眼觉得是均匀, 下面一条是相机真实的均匀.
在这里插入图片描述
在这里插入图片描述
人眼觉得的中间值 在相机中是21.8%, 为了让灰度的分布更符合人眼, 我们需要对图片进行gamma校正, 使得各用128个数字来表示相机真实世界中21.8%为界线的两个部分, 这时的gamma对亮度进行调节使得暗位细节更多了, 亮位细节变少了, 更符合人眼特征, 但是储存在图片中就是灰度值 变大了, 所以当我们显示在屏幕上时, 需要对这个灰度值还原到真实的灰度值.

所以gamma 编码是让更多数字来表达暗部(结果就是数字表达的值是符合人眼的均匀值,但是大于真实灰度值, 比如128表示21.8的强度), 而gamma解码是把这个数字还原到真实的灰度值(把128还原成21.8)

  1. intrinsic matrix
    [ f x s c x 0 f y c y 0 0 1 ] \begin{bmatrix}f_x & s & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1\end{bmatrix} fx00sfy0cxcy1

( c x , c y ) (c_x, c_y) (cx,cy): camera center in pixels
( f x , f y ) (f_x, f_y) (fx,fy): focal length in pixels
在这里插入图片描述

X Z = u f x \frac{X}{Z} = \frac{u}{f_x} ZX=fxu
其中 f x = f p x f_x = \frac{f}{p_x} fx=pxf, f f f 是focal length in world unites (millimeters), p x p_x px像素宽度, 通过胶片宽度除以像素X方向的个数得到.

下面来看skew

s = f x t a n α s = f_x tan \alpha s=fxtanα

假设每 p y p_y py p x p_x px上面的偏移是 b b b
那么 t a n α = b p x p y tan \alpha = \frac{bp_x}{p_y} tanα=pybpx
在这里插入图片描述

那么 [ f x s c x 0 f y c y 0 0 1 ] ∗ [ X Z Y Z 1 ] \begin{bmatrix}f_x & s & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1\end{bmatrix} *\begin{bmatrix} \frac{X}{Z} \\ \frac{Y}{Z} \\ 1 \end{bmatrix} fx00sfy0cxcy1 ZXZY1

第一行可得 f x ∗ X Z + s ∗ Y Z + c x = f x ∗ X Z + f x t a n α ∗ Y Z + c x f_x * \frac{X}{Z} +s * \frac{Y}{Z} + c_x = f_x * \frac{X}{Z} + f_x tan \alpha * \frac{Y}{Z} + c_x fxZX+sZY+cx=fxZX+fxtanαZY+cx

这里 f x t a n α ∗ Y Z f_x tan \alpha * \frac{Y}{Z} fxtanαZY有两种理解方式, 第一种是通过 t a n α ∗ Y tan \alpha *Y tanαY将在世界坐标系中将 Y Y Y转成 X X X方向的偏移 b X bX bX, 然后将这个偏移通过乘以 f x z \frac{f_x}{z} zfx转成x方向的像素偏移

第二种是 f x t a n α ∗ Y Z = f p x ∗ b p x p y ∗ Y Z = b f y ∗ Y Z f_x tan \alpha * \frac{Y}{Z} = \frac{f}{p_x}*\frac{bp_x}{p_y} * \frac{Y}{Z} = bf_y*\frac{Y}{Z} fxtanαZY=pxfpybpxZY=bfyZY 先算在y方向有多少像素, 再乘以 b b b得到偏移

第二行注意如果有skew, 那么 f y f_y fy得用 f y c o s α \frac{f_y}{cos\alpha} cosαfy代替, 因为y轴变斜了, 相应也变长了

https://towardsdatascience.com/camera-calibration-fda5beb373c3

https://towardsdatascience.com/what-are-intrinsic-and-extrinsic-camera-parameters-in-computer-vision-7071b72fb8ec

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值