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中需要使用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(); }