1.7 多重渲染目标

上一节已经介绍了帧缓冲与渲染缓冲的相关知识与案例,相信读者对于自定义帧缓冲与渲染缓冲的使用已有所了解。本节将介绍多重渲染目标(Multiple Render Targets),主要分为基本原理和简单案例两部分进行介绍。

1.7.1 基本知识

多重渲染目标允许程序同时渲染到多个颜色缓冲,向不同的颜色缓冲中送入渲染结果的不同方面(如不同RGBA色彩通道的值、深度值等)。不少高级特效渲染时需要使用多重渲染目标技术,例如延迟着色、屏幕空间环境光遮蔽等。下面介绍使用多重渲染目标技术的基本步骤。

(1)首先需要创建一个自定义的帧缓冲,并绑定到此帧缓冲。

(2)接着可以创建并初始化一批纹理,总数量等于要输出的不同渲染结果方面的数量,并且不能超过系统的最大限制数。

(3)然后将这一批纹理一一连接到自定义帧缓冲中的不同颜色附件中。

(4)接着在绘制时正常绘制前调用glDrawBuffers方法设置要输出的颜色附件。

(5)最后在片元着色器中定义多个输出变量一一对应到要输出的颜色附件。

提示 看完上述步骤之后,读者可能不是很明白。没有关系,后面会给出专门的案例供读者学习,那时就很清楚了。

了解了上述步骤后还有两个相关的方法需要介绍一下,具体内容如下。

(1)首先给出的是设置绘制时要输出颜色附件的glDrawBuffers方法,其方法签名如下。

      1    public static void glDrawBuffers (int n, int[] bufs, int offset)

说明 参数n为要输出的颜色附件的数量,参数bufs为存放了n个要输出的颜色附件标志值的数组,参数offset为bufs数组的偏移量。

(2)接着介绍用于查询系统所支持最大输出颜色附件数的glGetIntegerv方法,其使用时的代码片段如下。

      1    int[] params=new int[1];                    //声明数组用于记录颜色附件的最大值
      2    GLES30.glGetIntegerv(GLES30.GL_MAX_COLOR_ATTACHMENTS, params, 0); //查询颜色附件最大值

说明 从上述代码片段中可以看出,首先需要声明一个数组用于存放查询的结果值,然后以参数GL_MAX_COLOR_ATTACHMENTS调用glGetIntegerv方法进行查询即可。另外,所有的OpenGL 3.0实现都支持的颜色附件最小值是4,若读者需要使用的量小于等于4时可以放心直接使用。

1.7.2 一个简单的案例

上一小节介绍了关于多重渲染目标的基本知识,本小节将给出一个使用多重渲染目标的简单案例Sample1_6。首先给出的是该案例的运行效果图,如图1-7和图1-8所示。

▲图1-7 Sample1_6的运行效果图1

▲图1-8 Sample1_6的运行效果图2

说明 图1-7和图1-8为本小节案例Sample1_6的运行效果图,其中图1-7为案例开始运行时的效果图,图1-8为手指向下滑动屏幕时的效果图。由于本书采用灰度印刷,对于此案例而言读者很难看到效果,这里建议读者使用真机运行本案例进行观察。

案例Sample1_6同时也使用了二次渲染技术,其在第一轮渲染时使用了多重渲染目标。将RGBA四个色彩通道的值送到自定义帧缓冲的0号颜色附件,将R、G、B三个色彩通道的值各自单独送到了1、2、3号颜色附件。每个颜色附件本身是一幅纹理,然后在第二轮绘制时将4个颜色附件对应的纹理分别渲染到了一个纹理矩形上,此时就看到了如图1-7和图1-8所示的情况。

本小节案例是基于前面的案例Sample1_5改变而来的,各个类的基本框架大致相同,因此相同的部分不再重复赘述,在此只介绍几处有代表性的部分,具体内容如下。

(1)首先介绍SceneRenderer类中用于初始化帧缓冲的initFBO方法,该方法的主要功能为声明指定长度的颜色附件常量数组、初始化帧缓冲、初始化渲染缓冲、绑定颜色附件以及设置纹理的采样方式和拉伸方式等,其具体代码如下。

代码位置:源代码/第1章/Sample1_6/src/com/bn/Sample1_6目录下的MySurfaceView.java。1

public boolean initFBO(){

      2         int[] attachments=new int[]{GLES30.GL_COLOR_ATTACHMENT0, //声明颜色附件常量数组
      3                GLES30.GL_COLOR_ATTACHMENT1, GLES30.GL_COLOR_ATTACHMENT2,
      4               GLES30.GL_COLOR_ATTACHMENT3};   //包含颜色附件0~3的标志值
      5         int tia[]=new int[1];                 //用于存放创建帧缓冲编号的数组
      6         GLES30.glGenFramebuffers(1, tia, 0);  //创建一个帧缓冲
      7         frameBufferId=tia[0];                 //将帧缓冲编号记录到成员变量中
      8         GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBufferId); //绑定帧缓冲
      9         GLES30.glGenRenderbuffers(1, tia, 0); //创建一个渲染缓冲用作深度缓冲
      10        renderDepthBufferId=tia[0];           //将渲染缓冲编号记录到成员变量中
      11        //绑定用作深度缓冲的渲染缓冲
      12        GLES30.glBindRenderbuffer(GLES30.GL_RENDERBUFFER, renderDepthBufferId);
      13        GLES30.glRenderbufferStorage(GLES30.GL_RENDERBUFFER,  //为渲染缓冲初始化存储
      14            GLES30.GL_DEPTH_COMPONENT16, GEN_TEX_WIDTH, GEN_TEX_HEIGHT);
      15        GLES30.glFramebufferRenderbuffer(      //设置自定义帧缓冲的深度缓冲附件
      16            GLES30.GL_FRAMEBUFFER,             //帧缓冲类型
      17            GLES30.GL_DEPTH_ATTACHMENT,        //附件类型——深度附件
      18            GLES30.GL_RENDERBUFFER,            //附件为渲染缓冲
      19            renderDepthBufferId                //深度渲染缓冲编号
      20         );
      21        GLES30.glGenTextures(textureIds.length, textureIds,0);    //创建4个纹理
      22        for(int i=0; i<attachments.length; i++){//遍历颜色附件常量数组
      23             GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds[i]);  //绑定纹理
      24             GLES30.glTexImage2D(             //设置颜色附件纹理的格式
      25                 GLES30.GL_TEXTURE_2D,        //纹理类型
      26                 0,                           //层次
      27                 GLES30.GL_RGBA,              //内部格式
      28                 GEN_TEX_WIDTH,               //宽度
      29                 GEN_TEX_HEIGHT,              //高度
      30                 0,                           //边界宽度
      31                 GLES30.GL_RGBA,              //格式
      32                 GLES30.GL_UNSIGNED_BYTE,                        //每个像素数据的格式
      33                  null);
      34             GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,        //设置MIN采样方式
      35                  GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
      36             GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,        //设置MAG采样方式
      37                  GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
      38             GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,        //设置S轴拉伸方式
      39                  GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
      40             GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,        //设置T轴拉伸方式
      41                  GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
      42             GLES30.glFramebufferTexture2D(            //设置自定义帧缓冲的颜色附件
      43                  GLES30.GL_DRAW_FRAMEBUFFER,          //帧缓冲类型
      44                  attachments[i],                      //附件类型——颜色附件i
      45                  GLES30.GL_TEXTURE_2D,                //附件为2D纹理
      46                   textureIds[i],                      //纹理id
      47                   0);                                 //纹理层次
      48        }
      49        GLES30.glDrawBuffers(attachments.length, attachments,0); //设置要输出的颜色附件
      50        if(GLES30.GL_FRAMEBUFFER_COMPLETE ! =                 //检查帧缓冲的完整性
      51            GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER)){return false; }
      52        return true;
      53    }

❑ 第2~第4行声明了长度为4的颜色附件标志值常量数组,在将指定纹理连接到帧缓冲中的特定颜色附件时使用。

❑ 第5~第8行创建并绑定到了一个自定义的帧缓冲。

❑ 第9~第20行为初始化渲染缓冲的相关代码,其中首先创建了一个渲染缓冲,然后绑定到了此渲染缓冲,并为其初始化了存储,接着将其作为前面自定义帧缓冲的深度附件。

❑ 第21~第48行为对多重渲染目标所需的4个颜色附件进行初始化的相关代码,其中首先创建了4个纹理,然后对每个纹理进行初始化,并把这4个纹理分别连接到了0~3号颜色附件。

❑ 第49~第52行首先调用glDrawBuffers方法设置要输出的颜色附件,并检查帧缓冲的完整性。若检查成功则返回true,否则返回false。

(2)介绍完了初始化帧缓冲的initFBO方法后,接下来介绍用于绘制纹理矩形的drawShadowTexture方法,具体代码如下。

代码位置:源代码/第1章/Sample1_6/src/com/bn/Sample1_6目录下的MySurfaceView.java。

      1    public void drawShadowTexture(){                        //绘制纹理矩形的方法
      2         GLES30.glClearColor(0.5f,0.5f,0.5f,1.0f);          //设置屏幕背景色RGBA
      3         GLES30.glViewport(0,0, SCREEN_WIDTH, SCREEN_HEIGHT); //设置视口大小及位置
      4         GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);    //绑定到系统默认帧缓冲
      5         //清除深度缓冲与颜色缓冲
      6         GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT |GLES30.GL_COLOR_BUFFER_BIT);
      7         MatrixState.setProjectOrtho(-ratio, ratio, -1, 1, 2, 100);  //设置正交投影
      8         MatrixState.setCamera(0,0,3,0f,0f,0f,0f,1.0f,0.0f); //调用此方法产生摄像机9参数矩阵
      9         MatrixState.pushMatrix();                       //保护现场
      10        MatrixState.translate(-ratio/2, 0.5f, 0);       //执行平移
      11        tr.drawSelf(textureIds[0]);                     //绘制左上角纹理矩形
      12        MatrixState.popMatrix();                        //恢复现场
      13        MatrixState.pushMatrix();                       //保护现场
      14        MatrixState.translate(ratio/2, 0.5f, 1);        //执行平移
      15        tr.drawSelf(textureIds[1]);                     //绘制右上角纹理矩形
      16        MatrixState.popMatrix();                        //恢复现场
      17        MatrixState.pushMatrix();                       //保护现场
      18        MatrixState.translate(-ratio/2, -0.5f, 0);      //执行平移
      19        tr.drawSelf(textureIds[2]);                     //绘制左下角纹理矩形
      20        MatrixState.popMatrix();                        //恢复现场
      21        MatrixState.pushMatrix();                       //保护现场
      22        MatrixState.translate(ratio/2, -0.5f, 1);       //执行平移
      23        tr.drawSelf(textureIds[3]);                     //绘制右下角纹理矩形
      24        MatrixState.popMatrix();                        //恢复现场
      25    }

❑ 第2~第6行的功能为设置屏幕背景颜色,设置视口大小与位置,绑定系统默认帧缓冲以及清除深度缓冲与颜色缓冲。

❑ 第7~第8行设置了正交投影,并调用setCamera方法产生摄像机9参数矩阵。

❑ 第9~第13行的功能为保护现场,平移坐标系并绘制左上角的小纹理矩形。

❑ 第14~第24行的功能也是通过平移分别绘制右上角小纹理矩形、左下角小纹理矩形以及右下角小纹理矩形。

(3)上面介绍了用于绘制4个小纹理矩形的drawShadowTexture方法,接下来介绍带有4个输出变量(对应于前面的4个颜色附件)的多重渲染目标片元着色器,其具体代码如下。

代码位置:源代码/第1章/Sample1_6/assets目录下的frag.sh。

      1    #version 300 es
      2    precision mediump float;                 //给出默认的浮点精度
      3    uniform sampler2D sTexture;              //纹理内容数据
      4    in vec4 ambient;                         //接收从顶点着色器过来的环境光最终强度
      5    in vec4 diffuse;                         //接收从顶点着色器过来的散射光最终强度
      6    in vec4 specular;                        //接收从顶点着色器过来的镜面最终强度
      7    in vec2 vTextureCoord;                   //接收从顶点着色器过来的纹理坐标
      8    layout (location=0)out vec4 fragColor0;  //对应0号颜色附件的输出变量
      9    layout (location=1)out vec4 fragColor1;  //对应1号颜色附件的输出变量
      10    layout (location=2)out vec4 fragColor2; //对应2号颜色附件的输出变量
      11    layout (location=3)out vec4 fragColor3; //对应3号颜色附件的输出变量
      12    void main(){
      13         vec4 finalColor=texture(sTexture, vTextureCoord);    //进行纹理采样
      14         vec4 fragColor = finalColor*ambient+finalColor*specular+finalColor*diffuse;
                  //记录最终颜色值
      15         fragColor0=fragColor;    //将RGBA四个色彩通道综合值输出到0号颜色附件
      16         fragColor1=vec4(fragColor.r,0.0,0.0,1.0);    //将R色彩通道值输出到1号颜色附件
      17         fragColor2=vec4(0.0, fragColor.g,0.0,1.0);    //将G色彩通道值输出到2号颜色附件
      18         fragColor3=vec4(0.0,0.0, fragColor.b,1.0);    //将B色彩通道值输出到3号颜色附件
      19    }

说明 上述片元着色器的代码与前面案例Sample1_5中片元着色器的代码大致相同,不同的是该片元着色器中含有对应前面4个颜色附件的输出变量,从而实现了多重渲染目标的功能。