0评论

OpenGL缓冲区对象之Atomic Counter Object详解

文章来自https://blog.csdn.net/csxiaoshui/article/details/33740065 2019-03-11 29浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏开发行业精英群711501594

Atomic Counter是OpenGL 4.2版本通过GL_ARB_shader_atomic_counters扩展引入的新特性,它可以在各种着色语言中使用。Atomic Counter具体来说指的是在缓冲区对象(Buffer Object)中存储着一个或者多个可以用来计数的变量值,对这些变量定义了特定的操作方式,可以让它们在着色语言中进行加一和减一的操作,除此之后其他所有的操作都是非法的。

关于Atomic Counter的使用场景,一个简单的案例是统计场景中哪些像素会先进行渲染。具体的实现思路:当我们调用片元Shader对像素进行着色的时候,我们可以让atomic Counter变量值加1,然后将这个值转换成一种颜色来渲染我们的像素,这样我们就可以很清楚看到那些像素点是先渲染的,哪些像素点是后渲染的了。

使用

首先我们需要创建Atomic Counter的缓冲区,和其他类型的缓冲区的实现过程都是一样的。
GLuint atomicsBuffer;
glGenBuffers(1, &atomicsBuffer);
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicsBuffer);
glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(GLuint) * 3, NULL, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);
上面的代码生成了一个Atomic Counter Object对象,并且在它的存储区中存放了三个Atomic Counter类型的变量(Atomic Counter本质上是一个GLuint类型的变量,占4个字节)与其他缓冲区一样,更新和获取Atomic Counter缓冲区对象的方法如下所示:

更新
GLuint *userCounters;
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicsBuffer);
userCounters = (GLuint*)glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER,
                                         0 ,
                                         sizeof(GLuint) * 3,
                                         GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
memset(userCounters, 0, sizeof(GLuint) *3 );
// unmap the buffer
glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
或者使用glBufferSubData的方式进行更新
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, m_AtomicCountersBuffer);
GLuint a[3] = {0,0,0};
glBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0 , sizeof(GLuint) * 3, a);
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);

获取
GLuint *userCounters;
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicsBuffer);
userCounters = (GLuint*)glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER,
                                         0,
                                         sizeof(GLuint) * 3,
                                         GL_MAP_READ_BIT
                                        );
redPixels = userCounters[0];
greenPixels = userCounters[1];
bluePixels = userCounters[2];
glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
或者使用另一种方式来获取
GLuint userCounters[3];
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, m_AtomicCountersBuffer);
glGetBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint) * 3, userCounters);
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);
redPixels = userCounters[0];
greenPixels = userCounters[1];
bluePixels = userCounters[2];

Shader中的设置

当我们在shader中需要使用Atomic Counter的时候,我们可以定义Atomic Counter类型的变量atomic_uint,在我们定义atomic_uint类型变量的时候我们需要设置这些变量对应到AtomicCounter缓冲区对象存储空间的位置和偏移量,一般来说定义Atomic Counter变量的方式如下:
layout (binding = 1, offset = 0) uniform atomic_uint atRed;
layout (binding = 2, offset = 0) uniform atomic_uint atGreen;
layout (binding = 2, offset = 4) uniform atomic_uint atBlue;

atRed变量对应的binding point是1,偏移量是0 atGreen对应的binding point是2,偏移量是0 atBlue对应的binding point是2,偏移量是4(正好越过atGreen的4个字节)需要注意的是当我们设置两个变量同样的binding和offset,那么他们实际上是同一个atomic_uint
layout (binding = 1, offset = 0) uniform atomic_uint at1;
layout (binding = 1, offset = 0) uniform atomic_uint at2;

at1和at2指向的是同一个变量
尽管atomic_uint类型的变量作为uniform来使用,但是它和samplers一样并不能用在Uniform Block之中

示例

atomic_int支持的操作非常有限,仅包含一下几种:
//获取
uint atomicCounter(atomic_uint c);
// 减一并返回新值
uint atomicCounterDecrement(atomic_uint c); 
//加一并返回旧值
uint atomicCounterIncrement(atomic_uint c);

下面这个例子来自OSG Examples,我将它转换成OpenGL的方式实现:实现的内容:记录一帧绘制中的像素绘制顺序,先绘制的颜色呈现淡黄色,后绘制的颜色黄色逐渐加深直至为(1,1,0)的黄色,实现代码如下:
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "freeglut.lib")
#include <gl/glew.h>
#include <gl/freeglut.h>
#include <iostream>
#include <vector>
#include <algorithm>
#include "glshadertools.h"
GLuint          programID;
GLuint          vboID;
GLuint     eboID;
GLuint atomicCounterArrayRedAndGreen[2];
GLuint atomicCounterArrayBlue[1];
GLuint acboRedAndGreen;
GLuint acboBlue;
GLfloat invNumPixel;
//////////////////////////////////////////////////////////////////////////
GLfloat vertices[] = {
        -1.0f, 1.0f,0.0f,
        -1.0f, -1.0f,0.0f,
        1.0f, -1.0f,0.0f,
        1.0f, 1.0f, 0.0f
};
GLuint indices[] = {
        0,1,2,
        2,3,0
};
//////////////////////////////////////////////////////////////////////////
GLfloat xRot;           
GLfloat yRot;
GLfloat zoom = -10.0f;
bool mouseLeftDown;
float mouseX, mouseY;
void    init()
{
        programID = gltLoadShaderProgram("atomiccounter.vert", "atomiccounter.frag");
        glGenBuffers(1, &vboID);
        glBindBuffer(GL_ARRAY_BUFFER, vboID);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glGenBuffers(1, &eboID);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboID);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        atomicCounterArrayBlue[0] = 0;
        atomicCounterArrayRedAndGreen[0] = 0;
        atomicCounterArrayRedAndGreen[1] = 0;
        glGenBuffers(1, &acboRedAndGreen);
        glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, acboRedAndGreen);
        glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(GLuint)*2, NULL, GL_STREAM_COPY);
        glBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint)*2, atomicCounterArrayRedAndGreen);
        glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, acboRedAndGreen);
        glGenBuffers(1, &acboBlue);
        glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, acboBlue);
        glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(GLuint), NULL, GL_STREAM_COPY);
        glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 2, acboBlue);
        glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);
        GLint invNumPixelLocation; 
        invNumPixelLocation = glGetUniformLocation(programID, "invNumPixel");
        glProgramUniform1f(programID, invNumPixelLocation, 1.0f/(800.0f*600.0f));
        glUseProgram(programID);
}
void reshape(int w, int h)
{
        glViewport(0, 0, (GLsizei)w, (GLsizei)h);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(60.0f, (float)(w)/h, 0.1f, 1000.0f);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
}
void    display()
{
        glClearColor(0, 0, 0, 0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glColor3f(1.0f, 0.0f, 1.0f);
        glLoadIdentity();
        glTranslatef(0, 0, zoom);
        glRotatef(xRot, 1, 0, 0);   // pitch
        glRotatef(yRot, 0, 1, 0);   // heading
        //更新蓝色成分缓冲区中的数据为0,让绘制结果呈现黄色
        GLuint *userCounters;
        glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, acboBlue);
        userCounters = (GLuint*)glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER,
                0,
                sizeof(GLuint) * 1,
                GL_MAP_WRITE_BIT
                );
        atomicCounterArrayBlue[0] = 0;
        glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
        //将蓝色成分缓冲区中的数据设置为0
        glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, acboBlue);
        glBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint), &atomicCounterArrayBlue);
        glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_INDEX_ARRAY);
        glBindBuffer(GL_ARRAY_BUFFER, vboID);
        glVertexPointer(3, GL_FLOAT, 0, 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboID);
        glIndexPointer(GL_UNSIGNED_INT, 0, 0);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_INDEX_ARRAY);
        glutSwapBuffers();
//////////////////////////////////////////////////////////////////////////
        //一帧结束之后的操作
        //获取前一帧绘制的像素总数(记录在蓝色成分的AtomicBuffer之中)
        //并将这个值设置给invNumPixel
        glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, acboBlue);
        GLubyte *src = (GLubyte*)glMapBuffer(GL_ATOMIC_COUNTER_BUFFER, GL_READ_ONLY);
        if (src)
        {
                memcpy((void*)atomicCounterArrayBlue, src, 4);
        }
        glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
        unsigned int numPixel = std::max(1u, atomicCounterArrayBlue[0]);
        GLint invNumPixelLocation; 
        invNumPixelLocation = glGetUniformLocation(programID, "invNumPixel");
        GLfloat invNumPixelValue;
        glGetUniformfv(programID, invNumPixelLocation, &invNumPixelValue);
        glProgramUniform1f(programID, invNumPixelLocation, 1.0f / static_cast<float>(numPixel));
        //将表示红绿成分的AtomicBuffer设置为0
        glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, acboRedAndGreen);
        userCounters = (GLuint*)glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER,
                0,
                sizeof(GLuint) * 2,
                GL_MAP_READ_BIT
                );
        atomicCounterArrayRedAndGreen[0] = 0;
        atomicCounterArrayRedAndGreen[1] = 0;
        glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
}
void mouse(int button, int state, int x, int y)
{
        mouseX = (float)x;
        mouseY = (float)y;
        switch (button)
        {
        case GLUT_LEFT_BUTTON:
                {
                        if (state == GLUT_DOWN) {
                                mouseLeftDown = true;
                        } else if (state == GLUT_UP) {
                                mouseLeftDown = false;
                        }
                }
                break;
        case GLUT_RIGHT_BUTTON:
                {
                        if (state == GLUT_DOWN) {
                        }
                }
                break;
        default:
                break;
        }
}
void mouseMove(int x, int y)
{
        if(mouseLeftDown)
        {
                yRot += (x - mouseX);
                xRot += (y - mouseY);
                mouseX = (float)x;
                mouseY = (float)y;
        }
        glutPostRedisplay();
}
void mouseWheel(int wheel, int direction, int x, int y)
{
        switch (direction)
        {
        case 1: //means wheel up
                {
                        zoom -= 1.0f;
                }
                break;
        case -1: //means wheel down
                {
                        zoom += 1.0f;
                }
                break;
        default:
                break;
        }
        glutPostRedisplay();
}
void keyboard(unsigned char key, int x, int y)
{
        switch(key)
        {
        case 27: // ESCAPE
                exit(0);
                break;
        default:
                break;
        }
}
void idle()
{
        glutPostRedisplay();
}
int main(int argc, char** argv)
{
        glutInit(&argc, argv);
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
        glutInitWindowSize(640, 480);
        glutCreateWindow(argv[0]);
        if (glewInit()) {
                std::cerr << "Unable to initialize GLEW ... exiting" << std::endl;
                exit(EXIT_FAILURE);
        }
        init();
        glutDisplayFunc(display);
        glutReshapeFunc(reshape);
        glutMouseFunc(mouse);
        glutMotionFunc(mouseMove);
        glutMouseWheelFunc(mouseWheel);
        glutKeyboardFunc(keyboard);
        glutIdleFunc(idle);
        glutMainLoop();
}