赞
踩
最近在准备复习面试腾讯游戏开发,接下来会出一系列复习文章,总结一些他人的面试题与经验,以及之前自己面试时经验,并给出一些自己的见解,供大家一起学习。
渲染流水的起点是CPU,即应用阶段,应用阶段大致分为3个阶段:
question 1:CPU GPU如何并行工作的?
是通过命令缓冲区实现的,命令缓冲区是一个队列,CPU向其中添加,GPU读取,添加跟读取是相互独立的,命令缓冲区有很多种类型,drawcall是一种,还有像改变渲染状态(例如改变使用的shader,使用不同的纹理等)。
question 2:为什么drawcall多了会影响效率:
每次drawcall之前,CPU需要向GPU发送很多内容,包括数据,状态,指令,在这一阶段,CPU还要完成一些其他工作,比方说检查渲染状态等。一旦这些准备工作完成,GPU就可以开始本次的渲染。GPU的渲染能力是很强的,渲染10个跟100个mesh通常差别不大,因此drawcall高,渲染速度往往快于CPU提交命令的速度,造成CPU过载。
question 3:如何减少drawcall:
一般是采用合批的方式减少,常见的有:
任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为适应你屏幕的2D像素。3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。
从图中可以看出,GPU流水线接收定点数据作为输入。这些定点数据是由应用阶段加载到显存的,再由drawcall指定,随后被传入定点着色器。
顶点着色器:输入为cpu加载到显存的顶点数据,处理单位为顶点,即每个顶点都会调用一次顶点着色器。主要任务:
图元装配:本阶段将顶点着色器输出的所有顶点作为输入,并所有的点装配成指定图元的形状;
几何着色器:几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。
光栅化:这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
片段着色器:主要目的是计算一个像素的最终颜色,这也是所有高级效果产生的地方。通常,片段着色器会要使用3D场景的许多数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
测试:
混合:对于不透明的物体,可以直接关闭混合操作,因为不透明的物体的颜色值可以直接覆盖掉颜色缓冲区的值。如果是透明的物体,让该物体的颜色值与颜色缓冲区的值进行混合。
question 1:法线要使用切线空间的优点:
question 2:法线贴图为什么是蓝色:
这是因为切线空间保存每个法线贴图,每个法线方向所在的坐标空间不一样,即表面每个点有各自的切线空间。法线纹理其实就是存储了在每个顶点各自的Tangent Space中,法线的扰动方向。也就是说,如果一个顶点的法线方向不变,那么在它的Tangent Space中,新的normal值就是z轴方向,也就是说值为(0, 0, 1)。但这并不是法线纹理中存储的最终值,因为一个向量每个维度的取值范围在(-1, 1),而纹理每个通道的值范围在(0, 1),因此我们需要做一个映射,即pixel = (normal + 1) / 2。这样,之前的法线值(0, 0, 1)实际上对应了法线纹理中RGB的值为(0.5, 0.5, 1),而这个颜色也就是法线纹理中那大片的蓝色。这些蓝色实际上说明顶点的大部分法线是和模型本身法线一样的,不需要改变。
1. SSAA(Super Sampling Anti-Aliasing):
从名字可以看出,超采样技术就是以一个更大的分辨率来渲染场景,然后再把相邻像素值做一个过滤(比如平均等)得到最终的图像(Resolve)。因为这个技术提高了采样率,所以它对于解决上面几何走样和着色走样都是有效果的。如下图所示,首先经对每个像素取n个子采样点,然后针对每个子像素点进行着色计算。最后根据每个子像素的值来合成最终的图像。
虽然SSAA可以有效的解决几何走样和着色走样问题,但是它需要更多的显存空间以及更多的着色计算(每个子采样点都需要进行光照计算),所以一般不会使用这种技术。顺着上面的思路,如果我们对最终的每个像素着色,而不是每个子采样点着色的话,那这样虽然显存还是那么多,但是着色的数量少了,那它的效率也会有比较大的提高。这就是我们今天想要主要说的MSAA技术。
2. MSAA(Multisample anti aliasing):
SSAA等于暴力渲染了4倍分辨率的图像,在目前的硬件条件下这种性能开销是不可接受的,因此在SSAA的基础上发展出了MSAA。
MSAA与SSAA的区别在于像素着色器(Pixel Shader)的运行次数。MSAA同样对于每个像素进行了4次子采样(Sample),但是只在像素中心位置运行一次像素着色,然后根据Sample是否被三角形覆盖而将像素着色的颜色复制到Sample上。注意这里有一个很重要的点,就是每个子像素都有自己的颜色、深度模板信息。
以上图为例,在前向渲染中,三角形的绘制是依次进行的。绘制蓝色三角形时,MSAA的具体执行步骤如下:
可以看到在MSAA流程中所使用的所有缓冲区都变成了原来的四倍大小,这也是为什么MSAA增加了非常多的显存和带宽消耗。上述流程中第4步如果改成对每个Sample运行像素着色,MSAA就变成了SSAA。
3. FXAA( Fast Approximately -Aliasing):
FXAA拥有一种很朴素的算法思想。 它的思路很简单,既然我们要进行图像抗锯齿,那总得区分出来哪些像素是锯齿,哪些像素不是。根据图像锯齿的成因和日常的经验我们知道,它通常出现在与背景呈现高对比度的物体边缘(视觉比较明显)。FXAA是种后处理技术,后处理技术一般在画面完成后,通过像素颜色检测边缘(色彩差异太大时,不是边缘也被认为成边缘,精度有问题)。后处理技术一般没倍数概念,因为不存在放大。效果上FXAA接近MSAA的4倍效果,但在一些细节上会比4倍MSAA差一点。
4. FXAA( temporal anti-aliasing):
从时间维度上进行抗锯齿处理,使用同个像素在不同帧上的不同采样点,根据时间先后进行一个加权平均计算;但是TAA也有缺点,就是容易出现鬼影和抖动的现象;
5. DLSS( Deep Learning Super Sampling):
DLSS 2.0首先也是一个基于多帧的图像重建技术。DLSS2.0抛弃了人肉手调的启发式算法,用一个在超级计算机上数万张超高质量图片训练的神经网络来代替这些Heuristics。就像深度学习在几年前在计算机视觉领域超越了各种手调的特征提取算法一样,深度学习第一次在实时渲染中也非常合理的跑赢了图形领域的手调算法。
用DLSS 2.0重建的渲染图像序列达到了非常高的多帧样本利用率,这也是为什么只用四分之一的样本就可以重建出媲美原生分辨率渲染的图像质量的原因。
DLSS 2.0加速渲染的原理很简单。开启DLSS后,引擎的渲染会在1/2到1/4像素的低分辨率下运行。这意味着,一大半的像素级别的计算直接被粗暴的砍掉了。像素级别的计算通常包括GBuffer的渲染,动态光源、阴影的计算,屏幕空间的特效例如屏幕空间环境遮挡(SSAO)、屏幕空间反射(SSR),甚至实时光线追踪。
所以DLSS 2.0的加速多少,也直接取决于像素计算在多大程度上是性能瓶颈。通常来说,画面越好的3A大作,越会用更加耗费性能的渲染技术,像素也就会更大程度的成为瓶颈,而DLSS则会提供更大的加速!
边缘检测的原理是利用一些边缘检测算子对图像进行卷积操作。
什么是卷积?
在图像处理中,卷积操作指的就是使用一个卷积核( kernel)对一张图像中的每个像素进行系列操作。卷积核通常是一个四方形网格结构(例如2×2、3×3的方形区域),该区域内每个方格都有一个权重值。当对图像中的某个像素进行卷积时,我们会把卷积核的中心放置于该像素上,如图所示,翻转核之后再依次计算核中每个元素和其覆盖的图像像素值的乘积并求和,得到的结果就是该位置的新像素值。
常见的卷积核:
常见的边缘检测算子如图所示,它们都包含了两个方向的卷积核,分别用于检测水平方向和竖直方向上的边缘信息。在进行边缘检测时,我们需要对每个像素分别进行一次卷积计算,得到两个方向上的梯度值Gx和Gy,而整体的梯度可按下面的公式计算而得:
G =
G
x
2
+
G
y
2
\sqrt{G_x^2 + G_y^2}
Gx2+Gy2
由于上述计算包含了开根号操作,出于性能的考虑,我们有时会使用绝对值操作来代替开根号操作:
G=
∣
G
x
∣
|G_x|
∣Gx∣+
∣
G
y
∣
|G_y|
∣Gy∣
当得到梯度G后,我们就可以据此来判断哪些像素对应了边缘(梯度值越大,越有可能是边缘点)。
阴影映射由两个步骤组成:
一:以光源为视点,正交投影渲染整个场景,得到深度图(shadow map)并保存变换矩阵。深度图中记录了以光源为视点时,所有的可视点的深度。
二:以相机为视点渲染场景,对于场景中的每个顶点,将其变换到以光源为视点的空间,若其深度大于shadow map中对应点的深度值,则说明光源射来的光线被物体遮蔽了。则该点处于阴影中
float ShadowCalculation(vec4 fragPosLightSpace) { // 执行透视除法 vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; // 变换到[0,1]的范围 projCoords = projCoords * 0.5 + 0.5; // 取得最近点的深度(使用[0,1]范围下的fragPosLight当坐标) float closestDepth = texture(shadowMap, projCoords.xy).r; // 取得当前片段在光源视角下的深度 float currentDepth = projCoords.z; // 检查当前片段是否在阴影中 float shadow = currentDepth > closestDepth ? 1.0 : 0.0; return shadow; } // 计算阴影 float shadow = ShadowCalculation(fs_in.FragPosLightSpace); vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color; FragColor = vec4(lighting, 1.0f);
向前渲染:
前向渲染路径是传统的渲染方式,也是最常用的一种渲染路径。前向渲染路径的原理每进行一次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:个是颜色缓冲区,一个是深度缓冲区。我们利用深度缓冲来决定一个片元是否可见,如果可见就更新颜色缓冲区中的颜色值。
对于每个逐像素光源,我们都需要进行上面一次完整的渲染流程。如果一个物体在多个逐像素光源的影响区域内,那么该物体就需要执行多个Pass,每个Pass计算一个逐像素光源的光照结果,然后在帧缓冲中把这些光照结果混合起来得到最终的颜色值。假设,场景中有N个物体,每个物体受M个光源的影响,那么要渲染整个场景一共需要N*M个Pass可以看出,如果有大量逐像素光照,那么需要执行的Pass数目也会很大。因此,渲染引擎通常会限制每个物体的逐像素光照的数目。
延迟渲染:
前向渲染的问题是:当场景中包含大量实时光源时,前向渲染的性能会急速下降。例如,如果我们在场景的某一块区域放置了多个光源,这些光源影响的区域互相重叠,那么为了得到最终的光照效果,我们就需要为该区域内的每个物体执行多个Pass计算不同光源对该物体的光照结果,然后在颜色缓存中把这些结果混合起来得到最终的光照。然而,每执行一个Pass我们都需要重新渲染一遍物体。除了前向渲染中使用的颜色缓冲和深度缓冲外,延迟渲染还会利用额外的缓冲区,这些缓冲区也被统称为G缓冲( G-buffer),其中G是英文 Geometry的缩写。G缓冲区存储了我们所关心的表面(通常指的是离摄像机最近的表面)的其他信息,例如该表面的法线、位置、用于光照计算的材质属性等
延迟渲染的原理:延迟渲染主要包含了两个Pass。在第一个Pass中,我们不进行任何光照计算,而是仅仅计算哪些片元是可见的,这主要是通过深度缓冲技术来实现,当发现一个片元是可见的,我们就把它的相关信息存储到G缓冲区中。然后,在第二个Pass中,我们利用G缓冲区的各个片元信息,例如表面法线、视角方向、漫反射系数等,进行真正的光照计算。
区别:
(1)区别:正向渲染,先执行着色计算,再执行深度测试;渲染n个物体在m个光源下的着色,复杂度为O(n*m),光源数量对计算复杂度影响大;对于正向渲染,我们通常会对一个像素运行多次片段着色器;延迟渲染,先进行深度测试,再执行着色计算;对于延迟渲染,每一个像素只会执行一次片段着色器。
(2)延迟渲染的优点:将光源的数目和场景中物体的数目在复杂度层次上完全分开。渲染n个物体在m个光源下的着色,复杂度为O(n+m),只渲染可见的像素,节省计算量;
(3)延迟渲染的缺点:
[1].learnopengl-cn
[2].Unity Shader入门精要 ,冯乐乐 pdf链接:https://pan.baidu.com/s/1USBgxsxyB0B5ZWDEVZWSQw 提取码:7iin
[3].深入剖析MSAA
[4].learnopengl-cn
[5].DLSS 2.0 - 基于深度学习的实时渲染图像重建
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。