【图形学与游戏编程】开发笔记-基础篇6:缓冲区与混合

(本系列文章由pancy12138编写,转载请注明出处:http://blog.csdn.net/pancy12138)

上一次的教程大家了解了最简单的着色方法,那么这一节我们来讲解一些与着色无关的渲染管线流程。虽然在上一次的教程中我们成功的展示了如何给一个球体的表面进行光照着色。但是并不是说我们以往所有的知识就足以完美的展示出一个3D场景了。之所以我们能够看到那个球体而没有产生任何不适的感觉,主要是因为我们不知不觉的已经用到了一些缓冲区的知识。当然,这在基础篇一开始的时候就有涉及了,只是当初我们并没有过多的去关注这些知识。那么今天我们就要来仔细的分析缓冲区这一在图形管线中极为重要的算法。

首先,神马是缓冲区,这个名词大家应该不会陌生,所谓缓冲区就是一个和屏幕大小相当的数组。这在我们入门篇讲解双缓冲抗闪屏的时候就已经讲到了。当初我们在讲解交换链的时候第一次提及到了后台(颜色)缓冲区。那么事实上,为了保证绘制过程的完美,整个绘制过程中我们需要很多缓冲区来实现各种各样的算法。那么今天我们要讲的缓冲区包括:深度缓冲区,模板缓冲区,颜色缓冲区。然后,还有一个配合颜色缓冲区的alpha混合。注意,这里所有的缓冲区都只是一个和屏幕一样大的数组,所以他们都是工作于光栅化之后的,并且他们只会对每个屏幕像素保留一个值。使用的时候也是如此。大家不要习惯性的认为这些缓冲区能够存贮整个3D场景的所有信息。

首先是深度缓冲区,这个缓冲区的作用非常的明显,就是为了判断物体之间的“遮挡”关系。比如说,当你把一个苹果放在一个桌子前面的时候,苹果就会把桌子的一部分挡住。但是我们之前讲的3D->2D的投影操作以及矢量->光栅的光栅化操作都不能得到这个效果,这就会在之后得到一些错误的图像,比如如果先画苹果后画桌子,就会发现桌子把苹果给覆盖了,这很明显和我们当初的想法是不符的。于是,无论是directx还是opegl都默认提供了一个深度缓冲区,这个缓冲区将会在光栅化完毕之后对每一个点进行一次“深度测试”,看看这个点究竟是不是离摄像机最近的点,如果是,那么久盖住之前的点,如果不是,那么就不允许它绘制。用最简单的程序语言来描述就是:

float depth_buffer[wind_width][wind_height];
void clear_buffer
{
     for(int i = 0; i < wind_width; ++i)
     {
          for(int j = 0; j < wind_height; ++j)
         {
             depth_buffer[i][j] = 1.0f;
         }
     }
}
void depth_check(float depth,int width,int height)
{
     if(depth < depth_buffer[width][height])
    {
        draw_point(width,height);
        depth_buffer[width][height] = depth;
    }
}

也就是跟大家平常写的找一个数组里的最大值差不多。每一帧开始的时候把缓冲区的每个像素都清空成最远(注意这里使用投影之后的点的z坐标作为距离摄像机的距离,所以最远是1.0,最近是0.0)。然后,每次绘制物体的时候就调用检验函数来进行深度测试。当然深度测试函数肯定不能像上面我写的那样,在图形库里面基本上都是调用的GPU加速的算法,并且属于内置算法之一,运行速度是非常快的。这里也许有些人会有一些疑问,为什么物体距离摄像机的深度可以用它的z坐标来表示?如果换了观察视角怎么办。事实上这个问题我们在之前的教程中已经讲解过了。无论是directx还是opengl都是不允许你更改观察视角的,也就是说那个视角其实是固定的。如果我们需要观察视角变更的话,只要把物体挪动就好了。也就是说你想看一个物体的背面,并不需要走到他后面去,只需要把他转一下把背面露给你就好了。深度缓冲区的用法事实上是很简单的。directx里面对于深度缓冲区是使用ID3D11depthstencilview来进行管理和使用的,而由于深度缓冲区的本质是一个二维图片数组。而二维图片数组本身是属于“纹理”范畴的。所以,他的基本资源是一个texture2D资源,而管理器资源是 ID3D11depthstencilview。 大笑讲到这里大家估计会比较晕。博主你到底在说啥呢?这里我稍微提及一下directx的资源管理方案:

首先,我们要知道,所有的大型资源(几何体,纹理图片,缓冲区等等)都必须存储在显存当中。但是我们控制绘制调用是用的CPU来调用的。因此,为了避免CPU和GPU的数据交换,directx为每一种不同的资源提供了不同的访问方法,比如说shaderresourceview(SRV)用于访问纹理,unorderdaccessview(UAV)用于访问GPU可读写资源,depthstencilview(DSV)用于访问深度模板缓冲区资源,这些资源访问类我们可以理解成定义在cpu的指针,指向GPU的指针。当然这是一种更高级的访问方式。而这些指针只拥有访问资源的权限,并不能创建资源。如果要创建资源的时候,我们对于不同的资源会有不同的类来进行,比如说Id3d11buffer,或者texture2D这些。这些类都是继承了id3d11resource类,其功能就是创建以及修改显存中存储的各种大型资源。由于显存和内存之间进行数据交换非常的缓慢,所以我们一般只有在程序初始化的时候用这些类来进行显存的创建工作,一旦显存创建完毕,我们就可以只使用前面的那些GPU访问类来对显存资源进行调用。这样就可以让程序变的非常的流畅。而openGL也是以类似的思想来进行资源管理的(opengl1.0除外),只不过opengl里面并没有所谓的访问指针,创建指针等分得很细的东西,我们在cpu上用一个int变量就可以代表显存上相应的资源,由于所有的操作都封装在OpenGL的状态机里面,所有我们既可以用这个int进行显存的创建,也可以用它进行显存的访问。这种方法的话有好处也有坏处,好处是使用起来简单,坏处是资源交换以及绑定就不简单以及不太好封装起来。不过这都不是重点,大家程序写多了自然就都习惯了,所以对于这种资源管理的知识,大家要多练习,这样才能掌握的比较好。

D3D11_TEXTURE2D_DESC dsDesc;
	dsDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	dsDesc.Width = wind_width;
	dsDesc.Height = wind_height;
	dsDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	dsDesc.MipLevels = 1;
 	dsDesc.CPUAccessFlags = 0;
	dsDesc.MiscFlags = 0;
	dsDesc.Usage = D3D11_USAGE_DEFAULT;
	dsDesc.SampleDesc.Count = 4;
	dsDesc.SampleDesc.Quality = 0;

	ID3D11Texture2D* depthStencilBuffer;
	device_pancy->CreateTexture2D(&dsDesc, 0, &depthStencilBuffer);
	device_pancy->CreateDepthStencilView(depthStencilBuffer, 0, &depthStencilView);
	depthStencilBuffer->Release();
上述就是一个深度缓冲区的创建过程,这里要注意的是深度模板缓冲区的msaa抗锯齿倍数一定要和渲染目标,也就是后台缓冲区的渲染倍数相同。不能出现比如后台缓冲区4倍抗锯齿,深度缓冲区8倍或者不开的这种情况。

这里我们发现,深度缓冲区并不是一个单独的缓冲区,他还附带了一个模板缓冲区,一般一个32位的缓冲区会被分为两部分,其中24位作为深度缓冲区,8位作为模板缓冲区。那么神马又是模板缓冲区呢,这个缓冲区又有什么作用呢?接下来我们就要来讲解这一部分的知识。
由于深度缓冲区在directx与opengl中属于功能不可更改的缓冲区(只能工作于深度比较)。但是其比对的思想又非常的好用。所以我们有时候会希望达到类似的像素测试效果,但是不希望用深度作为标准,比如说我们希望x坐标小于1的像素点不要显示在屏幕上。那么这个时候我们就需要一个自定义的缓冲区,这也就是模板缓冲区的作用,我们在使用模板缓冲区的时候,先要给缓冲区设定一个比较函数。然后再给模板缓冲区写入一个用于比较的区域,然后再进行正式的渲染。这个时候就可以进行模板测试,凡是不能符合测试的像素点都不允许通过。这样干说理论可能大家比较晕。我举一个最简单的例子,比如说镜面效果,就是通过镜子能够看到物体的镜像这一效果:


上图就展示了模板缓冲区的使用方法,首先我们先把镜子渲染一遍,借助这一过程把镜子所在的像素点全部标记成可访问的模式(白色区域),得到一张下面所示的模板缓冲区的数据。然后第二遍正式渲染的时候我们就可以开启模板测试,只要最终渲染的像素坐标对应的模板颜色不是白色的(也就是不可访问),那就不能显示,最终就可以使得我们渲染的模型的影子只出现在镜面所在的位置。

当然,这只是最简单的一个模板缓冲区的使用方式,模板缓冲区的应用还是比较多的,最经典的比如说shadow volume算法,这个会在之后讲解全局阴影的时候来进行讲解。现在可以大概的了解模板缓冲区的作用就好了,因为这一缓冲区在大部分传统算法中还是很少用到的,等到真正用到的时候再仔细学习也不算迟。


最后我们来讲一个比较常用的东西,后台(颜色)缓冲区以及alpha混合。颜色缓冲区以及其对应的颜色测试算法属于最后一个测试算法。这个测试算法主要是用来屏蔽一些颜色输出以及进行最终的颜色处理工作。我们在一开始讲解后台缓冲区的时候只是提到他是为了防止闪屏而存在的。不过经过多年的优化,他也在进行图像累计的过程中同时做了很多的颜色处理工作。首先是颜色屏蔽。如果我们的程序需要最终渲染的颜色的某个通道(RGBA)被屏蔽掉的话就可以开启这个颜色屏蔽,这样就可以达到一些比较特殊的效果,比如说我的博客译文里面的毛发渲染,就是在pass1屏蔽了RGB通道来记录最终的alpha效果。除了直接屏蔽通道以外,我们还可以根据自己的想法来进行颜色剔除工作,这个也很简单,就是在pixel shader里面,使用clip()函数来裁剪掉我们不想要的像素点。

然后是alpha混合,我们知道很多时候我们用于渲染的图片并不是全部都是我们想要的,比如说一个角色的原画,周围的一些黑乎乎的东西我们就不希望他出现在渲染结果里面:


比如说上面这张图片,我们希望把角色显示出来,但是不要显示周围的黑乎乎的背景。在ps中我们一般会选择抠图来解决。换算到shader里面就是我们说的clip(color.r < 0.001f && color.g <=0.001f && color.b <=0.001f)这种类似的方式。但是这种硬生生的抠图算法会使得图像的边缘非常的不平整,也就是不够平滑。这是其一。其二就是对于一些“半透明”的物体,比如说水晶啊,水域啊这些的,我们希望透过它看到一些东西的时候就不能简单地靠抠图来进行解决。这个时候就需要alpha混合算法来进行。所谓的alpha混合就是指在进行颜色合成的时候,我们新渲染的物体的颜色会和之前渲染的物体的颜色进行合成。合成的公式有很多,最简单的就是a.rgb*a.a + b.rgb*(1-a.a)这样得到一个新的颜色效果,这种做法即可以平滑的处理“抠图”的效果,也可以很好的处理“透过a看到b”这一现象。alpha混合在很多算法里面都有涉及,其应用范围可以说是非常的广。不过目前来说我们基础篇的程序尚且没有用到这一功能。所以大家先了解这一概念就可以了。之后再在讲解相关的渲染算法的时候我们会经常提到这一概念。

ok,那么这一次的教程到这里就算是结束了。这一次的教程我们并没有讲解任何程序相关的东西,只是给大家普及了缓冲区以及alpha混合的概念。这些东西暂时还是用处比较小的,但是他们是确实存在于渲染管线当中,并且在以后的算法设计过程中会体现出非常巨大的效果。所以大家可以先了解了解这些知识。这样之后在了解更为复杂的算法的时候就会更加的得心应手。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值