从0开始的OpenGL学习(三十五)-延迟着色

标签:延迟着色

延迟着色的效果

前向着色和延迟着色

在开始本章的新内容前,我们先来回忆一下之前的渲染流程是什么:在渲染一个物体前,我们需要把shader准备好。由于需要在片元着色器中进行光照计算,我们需要把场景中所有的光源信息都传递到shader中保存,这样在渲染物体的时候,就可以计算出这个物体在光照下的显示效果。如果场景中有多个物体(事实上场景中只有一个物体的情况非常少),那么我们就必须对每一个物体都走一遍上述的流程,直到所有物体都渲染完成为止。

上面的流程有没有问题?没有。这个流程非常容易理解,而且实现的效果也不错。按照这个流程,我们可以在大部分的场景中获得一个不错的渲染效果。但是(没错,又是但是),如果我们的场景中物体和光源都非常多,那么上述的流程就显得有些笨拙了。

分析一下,假设场景中有m个物体和n个光源,我们执行渲染流程的次数就是m * n次,算法的时间复杂度就是O(m * n)。这个复杂度很高,如果m=n,那么这个复杂度就是一个平方级的复杂度,在算法的领域里,平方级的复杂度属于不可接受的范围,这就逼着我们去想办法降低这个复杂度,使其达到一个可以让人接受的范围。

在算法中,复杂度有这么几种等级:lg(n), n的平方根,n,n * lg(n),n的平方,n的三次方,2的n次方,n的阶乘。速度最快的等级是lg(n),这种速度在现实生活中就像是光速一样,快地让人感觉不到。而n的阶乘的速度,就像是蜗牛爬行的速度一样慢的令人发指,可能运行到世界末日都没有结果。一般而言,可以接受的复杂度是:lg(n),n的平方根,n,以及n * lg(n)。

这下,我们本章要讲的东西就有了发挥的余地。我们把上述的渲染流程称作前向渲染或者前向着色,接下来要介绍的方法被称作延迟渲染或者延迟着色。为了方便起见,我们统一用前向着色和延迟着色来称呼两种方法。延迟着色方法,是先把场景中所有物体渲染一次,将其相关数据(包括位置、法向量、纹理等)保存到一个帧缓存中,然后,用这些数据配合上光源信息再进行一次计算,得到最终的效果。这样,我们的算法复杂度就降低成O(m + n),也就是线性复杂度,这就是一个可以接受的复杂度等级。

延迟着色的流程

一个完整的延迟着色包括以下4个步骤

  • 1、几何阶段:这个阶段要将场景中的物体渲染到帧缓存中,将物体的数据保存起来。
  • 2、光照阶段:在这个阶段,运用上述帧缓存中的数据,结合光源信息,计算出场景经过光照后的结果
  • 3、后期处理阶段:经过光照后,还需要对场景进行一些抗锯齿等后期处理,保证场景的效果
  • 4、最终阶段:将图像传递到主缓存中去,然后在屏幕上显示

下面这张图很好的展示了延迟着色的流程,请仔细看:

 

延迟着色的流程

在本章的例子中,我们不去实现抗锯齿等后期特效,专注于前面两个阶段,整个场景在光照阶段完成后直接输出到屏幕上,不需要再做其他处理。下面,我们分别来看一下两个阶段到底要做些什么事情。

几何阶段

几何阶段中,我们要将场景的信息保存到帧缓存,以便后面的光照阶段使用。这就产生了一个问题,我们需要保存哪些数据呢?先来列举我们在前向着色中用到的数据:

  • 顶点的坐标
  • 顶点的纹理
  • 顶点的法向量
  • 镜面高光强度
  • 光源位置
  • 光源颜色
  • 观察者位置

在几何阶段,我们需要准备的东西是可以让光照阶段使用的数据。研究一下之前的片元着色器代码,我们发现,光源位置、光源颜色、观察者位置都是可以在光照阶段直接传递给片元着色器的。而需要从顶点着色器传递过来的数据是:法向量、片元位置、纹理坐标。也就是说,物体的信息都需要从几何阶段传递过去,这样,我们就能得出结论:顶点坐标、纹理、法向量以及镜面高光强度都是需要保存的数据。我们要将这些数据保存到一个名叫G-Buffer的缓存中。

G-buffer是一个我们创造出来的概念,它本质上是一个帧缓存,是那些我们在几何阶段用来保存数据缓存的统称。这些缓存可能是纹理图,也可能是其他东西我们不知道,我们把存有这些数据的帧缓存统称为G-buffer。

创建帧缓存的流程我们已经非常熟悉了,由于我们要保存4种数据,我们至少需要将3个颜色缓存(顶点的纹理和高光强度共用一个缓存)附加到帧缓存上,构成MRT。这样,我们就能使用一次渲染得到所有的物体信息。(有关MRT的内容,请参考HDR和Bloom一文。)如果要把G-Buffer中的数据显示出来,结果就是这个样子:

G-Buffer中的数据

 

这里只给出了顶点、法线和纹理数据,没有高光信息。因为高光信息和纹理数据是保存在一起的,它只占了1个字节的空间,显示出来的话就是一片红色,没有太大意义,有兴趣的同学不妨尝试显示出来。

光照阶段

我们已经拥有了光照计算要用到的所有数据,这些数据保存在G-Buffer中,以纹理图的格式保存。到了光照阶段,我们就要用起来了。这一阶段的主要工作集中在片元着色器中,我们通过绑定纹理图的方式将G-Buffer中的3个颜色缓存纹理图传递到片元着色器中。同时,也将场景中的光源信息传递到片元着色器中,这些信息包括:光源位置、光源颜色等。然后,在片元着色器中只要像平常一样进行光照计算,就可以使整个场景都得到光照的效果了。

这一个阶段完成后,我们的场景就是这个样子:

 

 

运行效果

这个场景中,我们用了128个聚光灯光源去照射前面的盒子,每个光源还能移动其位置,完成计算后,我们的场景就是这么华丽。

延迟着色的实现

终于到了编码实现的时候了。在编码之前,请先下载本章要用到的工程源码,我们会在这些代码的基础上添加场景,完成延迟着色的功能。在动手之前,我们先来理一下实现的思路,上面的源码是一个空壳子里面没有任何的物体,也没有光源,更加没有帧缓存的东西,这些都是要我们一步步去实现的,因此,我们的实现思路是:

  • 一、在场景中的固定位置放置一些立方体盒子,放置的位置是xy平面,在原点的周围放置11x11个盒子。
  • 二、创建G-Buffer,包括3个颜色附件和一个深度值附件
  • 三、创建着色器,将场景渲染到G-Buffer中
  • 四、显示G-Buffer中的信息
  • 五、创建光源,为光源添加移动的功能
  • 六、光照计算,显示最终场景
  • 七、显示光源,用纯色立方体代替光源显示

顺着这个思路,我们就能写出上面的场景,想想有点小激动,赶紧开始吧。

第一步、创建场景

创建场景的方法很简单,使用renderTextureCube()函数就可以了。在循环体中,我们只要设定好盒子的位置、大小、以及旋转量就可以非常容易的绘制出这个场景。代码如下:



作者:闪电的蓝熊猫
链接:https://www.jianshu.com/p/a36ecd4856a9
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值