http://blog.csdn.net/u010462297/article/details/50589991

引言

在某些情况下会需要用到多层绘制。FBO下有多个颜色挂接点(Color Attachment),可以用不同的挂接点挂接不同的纹理对象,实现绘制多张纹理(MRT),这在之前的文章里已经有所描述。但是有时候这种方法是不够好用的:
- 当纹理非常多时,挂接点往往不够;
- 用多层绘制还能实现一些更为方便的功能,比如只调用一次shader就完成多个不同视点图像的绘制。
特别是后面一点,实在是利器,配合视口矩阵(Viewport Array),简直是方便。

网上的资料非常有限,不过幸好有 OpenGL Wiki 这种官方的文档,只是文档往往也写得不够详细,英文的看着也费劲得很。折腾了好久,终于试出来了。

要实现Layered Rendering,需要用到多层纹理Array Texture、帧缓存对象FBO、几何着色器Geometry Shader。

多层纹理(Array Texture)

多层纹理就是一个纹理对象的一层MipMap下储存着多张纹理。最常用的二维纹理是GL_TEXTURE_2D,对应的多层纹理就是GL_TEXTURE_2D_ARRAY。其实这种纹理跟三维纹理差不多,用的函数往往也是三维纹理相关的函数,区别只在于第三维:深度,多层纹理的深度值就是层号,而三维纹理的深度值是像素坐标。

生成

二维多层纹理的生成方法跟普通的二维纹理几乎没有区别,只不过将GL_TEXTURE_2D换成GL_TEXTURE_2D_ARRAY。当然,在传入纹理数据时,应当换成glTexImage3D函数,而这个函数相比glTexImage2D也就多了一个深度变量,这个变量的值是纹理层数。对于Layered Rendering而言,是要将这个纹理绑到FBO上,然后画到这个纹理里,因此只需要调用:

  1. glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, width, height, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
  • 1
  • 1

这里是写了2层width*height分辨率的纹理,用的是RGBA像素排列格式,最后一个参数是nullptr因为不必传内容。

读取

当然,纹理的读出还是有一些讲究的。在内存里纹理是按行优先的顺序存储,因此实际上拿到的w×h×d分辨率的二维多层纹理的数据相当于w×hd分辨率的普通二维纹理,每一层将在高度方向叠起来。

帧缓存对象(FBO)

FBO可讲的地方不多,很多文章都有介绍过。不过依然要注意两个地方。

完备性

FBO要能用,就必须实现其完备性(Completeness)。一般二维纹理挂接到FBO的颜色挂接点上,然后再生成一个深度缓存,挂到深度挂接点上,不会有什么问题。但是,对于多层纹理而言,则有一个强制要求:FBO的所有挂接点上挂接的必须都是多层的对象。所以,如果还需要深度信息,则深度缓存必须也做成多层的。所以,应该再开一个多层纹理,挂接到深度挂接点上来作为深度缓存。

纹理挂接

其实无论什么性质的纹理,挂接到FBO上时都可以统一用以下这句代码:

  1. glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texID, 0);
  • 1
  • 1

假设将纹理texID挂接到0号颜色挂接点。这就不必关心纹理到底是什么格式。

几何着色器(Geometry Shader)

要实现Layered Rendering,必须要用到Geometry Shader。以前一直都是跟Vertex Shader和Fragment Shader打交道,后来用了一下Compute Shader,现在终于用到这个了。

Geometry是介于Vertex和Fragment之间的一道步骤,其作用应该是对Vertex给出的顶点的进一步细分描述吧,具体并不太懂。但是它能够使顶点“无中生有”,这是画多层图像的关键。
试想一下,对于多层纹理而言,每一层的纹理坐标实际上是相同的。层号如何指定呢?固然可以在建立VBO时给纹理坐标第三个维度值,但是这并不会让OpenGL画到你想画的层上:Fragment的过程就是将顶点像素化的过程,而这个过程是二维的……最后拿到的二维图像必然没有深度,层数无从谈起。

但是Geometry却有个关键的内建输出变量:out int gl_Layer,正是它能够指定绘制的层数。我们不妨先将我写出来的geometry shader的内容贴出来,注意显卡至少要支持到OpenGL 4.0,#version这句至少是400:

  1. #version 450
  2. layout (triangles, invocations = 2) in; //输入三角形,2次调用
  3. layout (triangle_strip, max_vertices = 3) out; //输出三角形
  4. in vec2 gTexCoord[]; //从Vertex传过来的纹理坐标
  5. out vec2 fTexCoord; //传到Fragment去的纹理坐标
  6. out int gl_Layer; //层数的标记
  7. void main()
  8. {
  9. for(int k=0; k<gl_in.length(); k++) //针对三角形每个顶点
  10. {
  11. gl_Layer = gl_InvocationID; //用调用编号标记层号
  12. fTexCoord = gTexCoord[k]; //纹理坐标传递
  13. gl_Position = gl_in[k].gl_Position; //顶点坐标传递
  14. EmitVertex(); //开始传递顶点信息
  15. }
  16. EndPrimitive(); //结束
  17. }

需要注意的是,triangles意味着你在OpenGL的绘制指令必须是GL_TRIANGLEGL_TRIANGLE_STRIP或者GL_TRIANGLE_FAN。其他的对应关系可以在Wiki查到。三角形有3个顶点,这一组3个顶点将同时进入Geometry中,因此在Geometry中能拿到一个gl_in[]的内建数组,这个数组的大小应该跟绘制时一组顶点的数量一致,三角形就是3。而在这里我不需要增加顶点,因此输出也还是3个顶点。同时纹理坐标也理所当然地变成了数组。因此需要一个循环来对三角形的每个顶点进行操作。

这里顶点坐标和纹理坐标都不必改,因此直接传递过去了。重点在于gl_Layer这一句。gl_Layer这个内建变量用于指示当前绘制的层号,这个值将影响像素化后像素绘制到哪一层上。OpenGL要求一组顶点(这里是一个三角形)内部的gl_Layer必须一致。这里赋值之后顺便把它传到Fragment里作为标志。
关键的一步在于第二行的invocations = 2gl_InvocationID这个内建变量。invocations=n指示Geometry对每组顶点做n次运算,用gl_InvocationID来标记每次运算的序号。我们可以将这个序号送到gl_Layer来作为层号的值!这样geometry就会将这一组顶点重复发送两次,而这两次是发送到不同层上的,从而实现不同层的绘制!

接下来只要在Fragment里拿到这个gl_Layer,根据需要分层作处理就可以了。

其他用途:多视口绘制

除了gl_Layer外,还有一个内建变量叫做gl_ViewportIndex,用于标记视口矩阵(Viewport
Array)的序号。视口矩阵可以往显卡送入多个视口,通过这个序号指定要使用哪个。因此,可以将这个特性用于同一场景的多视口绘制,比如类似3Ds
MAX的三视图绘制。可以把Vertex
Shader的大部分工作交给Geometry来完成,在Geometry内部对每个不同视口进行不同的顶点变换,从而主程序不需要再用循环,只要一次性将数据传入显卡,通过一次Geometry的流程实现多视口绘制。具体操作可以下回试试。

参考文献

OpenGL Wiki: https://www.opengl.org/wiki/
Stack Overflow:http://stackoverflow.com/

OpenGL实现多层绘制(Layered Rendering) [转]的更多相关文章

  1. Android OpenGL 入门示例----绘制三角形和正方形

    Android上对OpenGl的支持是无缝的,所以才有众多3D效果如此逼真的游戏,在Camera的一些流程中也有用到GLSurfaceView的情况.本文记录OpenGL在Android上的入门级示例 ...

  2. Linux OpenGL 实践篇-3 绘制三角形

    本次实践是绘制两个三角形,重点理解顶点数组对象和OpenGL缓存的使用. 顶点数组对象 顶点数组对象负责管理一组顶点属性,顶点属性包括位置.法线.纹理坐标等. OpenGL缓存 OpenGL缓存实质上 ...

  3. <opengl>使用glu绘制二次曲面

    绘制二次曲面通常要以下四步:   1.首先我们创建一个二次方程状态对象 GLUquadricObj *m_pObj;    //保存绘图模式.法线模式.法线朝向.纹理等信息 //创建二次方程状态对象 ...

  4. 利用OpenGL固定流水线绘制球体

    在OS X上的一个OpenGL简单demo.所附赠的代码是绘制半个球体.开启了深度缓存和多重采样,采样数是4. 详细下载地址请见:http://www.cocoachina.com/bbs/read. ...

  5. iOS OpenGL ES简单绘制纹理

    OpenGL 中任何复杂的图形都是由点,线 和三角形组成的. 那么一个矩形 就需要有两个三角形组成. 纹理, 可以理解为一张图片, 我么可以将整张or部分图片绘制到圆形, 矩形等目标图形中. 下图表示 ...

  6. iOS OpenGL ES简单绘制三角形

    OpenGL 是用于2D/3D图形编程的一套基于C语言的统一接口. windows,Linux,Unix上均可兼容. OpenGL ES 是在OpenGL嵌入式设备上的版本, android/iOS ...

  7. OpenGL——点的绘制(使用OpenGL来绘制可旋转坐标系的螺旋线)

    package com.example.opengl1; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio. ...

  8. 【OpenGL】如何绘制Shadow

    背景 Shadow即阴影,它是光线被不透明物体遮挡而产生的黑暗区域,与光源的方向相反. 在Blender中编辑过程中没有Shadow,只有在经过渲染后才能显示.目前有一个基于Blender的项目,要求 ...

  9. opengl使用FreeType绘制字体

    原文地址:http://www.cnblogs.com/zhanglitong/p/3206497.html

随机推荐

  1. C# 获取存在DataTable1不存在DataTable2的数据的快速方法

    通过合并和获得改变两个方法获得差异的部分: dataTable1.AcceptChanges();dataTable1.Merge(dataTable2);DataTable changesTable ...

  2. niceScroll在ie11和edge浏览器上面滚动时抖动问题

    niceScroll 是一个很好的jquery插件,相信很多人都用过,我们项目中使用的是3.7.6版本的,一般我们在开发的时候都是使用的火狐或者是谷歌浏览器,使用 niceScroll 没有什么问题, ...

  3. 【C++】隐式类型转换和 explicit关键字

    来自:黄邦勇帅 1. 当类中带有一个参数的构造函数时,将执形对象的隐式转换,比如有类 A,有一个带有一个参数的构造函数A(int i){}则当出现语句A m=1;就会调用带有一个参数的构造函数来创建对 ...

  4. k8s 组件架构

    一.整体架构 kubernetes分为 master节点和工作节点,前者是管理节点,后者是容器运行的节点.其中master节点主要有3个重要组件,分别是APIServer,sheduler和contr ...

  5. python的上下文管理(context)(1)

    本文转载自:http://blog.csdn.net/G_66_hero/article/details/53048540 什么是Python中的上下文管理器 怎么使用上下文管理器 如何创建自己的上下 ...

  6. IOC(控制反转)的理解

    1.IOC的理论背景 我们知道在面向对象设计的软件系统中,它的底层都是由N个对象构成的,各个对象之间通过相互合作,最终实现系统地业务逻辑[1]. 图1 软件系统中耦合的对象 如果我们打开机械式手表的后 ...

  7. 解决nextjs部署到now上之后出现的“Unable to import module 'now__launcher'”错误

    解决nextjs部署到now上之后出现的“Unable to import module 'now__launcher'”错误 这个错误是由于在next.config.js中直接引用了withLess ...

  8. mysql having的用法

    having的用法 having字句可以让我们筛选成组后的各种数据,where字句在聚合前先筛选记录,也就是说作用在group by和having字句前.而 having子句在聚合后对组记录进行筛选. ...

  9. 非负整数可重集去重&排序+获得可重集的全排列的几种方法

    非负整数可重集O(n)去重并排序 可重集是指元素可重复的集合,对于在一定区间内的正整数集,比如[1,n],我们可以在不不使用任何额外空间(包括不使用O(1)的空间)的情况下,用O(n)的时间复杂度完成 ...

  10. Javascript 行为委托

    JavaScript 与C++,Java的面相对象编程的语言不同.就像不懂Linux人,会创造一个蹩脚的linux一样.一些JavaScript的编程总在尝试利用JavaScript 去模拟传统的面向 ...