OGLplus教程学习笔记 -- Outline

最近一直在thinking inmodern C++,大部分都是使用基于对象(object based)的技术。使得对象相较于C++的面向对象风格的程序而言,对象要散列一些,结构更清晰。对象之间适配使用了大量的泛型编程(Generic programming)技术。Boost C++库的发展和C++11语言新特性对模板的支持,优秀的泛型库大量涌现。它们都继承了STL极高的可复用性与很低的学习曲线。Jeremy Ong写的Selene库是笔者目前阅读过的非常精彩的modern C++风格的程序库,但也有一些show off的感觉。还有两个数学库glm与mtl。

对于图形API的C++ wapper库确实不多,OGLplus算是一个。代码并算不上是非常精彩,诸如没有使用traits/policy编程技法来降低复杂性,使用了decltype关键字这种较为初学的泛型技术,没有考虑空基类优化等细节问题。但也有很多出彩的地方,比如对OpenGL具名对象的封装(参见模板类Named)以及对OpenGL的Operation的封装达到完全消除对OpenGL API函数的裸调用(Raw Call)等。

OGLplus是一个仅包含头文件(header only)的modern C++风格的库。它主要是对OpenGL的C++包装(Wrapper)。官网上面,它自称是面向对象(Object oriented)的OpenGL外观,但是笔者认为它是基于对象的(Object based)。下面用一段代码来展示OGLplus的Object based风格的使用。

#include <cassert>
#include <iostream>
#include <GL/glew.h>
#include <GL/glut.h>
#include <oglplus/all.hpp>

class Example
{
private:
 oglplus::Context gl;
 oglplus::VertexShader vs;
 oglplus::FragmentShader fs;
 oglplus::Program prog;
 oglplus::Buffer verts;

public:
 Example(void)
 {
  using namespace oglplus;

  vs.Source(" \
   #version 330\n \
   in vec3 Position; \
   void main(void) \
   { \
    gl_Position = vec4(Position, 1.0); \
   } \
  ");

  vs.Compile();

  fs.Source(" \
   #version 330\n \
   out vec4 fragcolor; \
   void main(void) \
   { \
    fragcolor = vec4(1.0, 0.0, 0.0, 1.0); \
   } \
  ");
  fs.Compile();

  prog.AttachShader(vs);
  prog.AttachShaders(fs);
  prog.Link();
  prog.Use();

  GLfloat triangle_verts[9] = {
   0.0f, 0.0f, 0.0f,
   1.0f, 0.0f, 0.0f,
   0.0f, 1.0f, 0.0f
  };
  verts.Bind(Buffer::Target::Array);
  verts.Data(Buffer::Target::Array, 9, triangle_verts);
  VertexAttribArray vert_attr(prog, "Position");
  vert_attr.Setup<GLfloat>(3);
  vert_attr.Enable();

  gl.ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
  gl.ClearDepth(1.0f);
 }

 void Display(void)
 {
  using namespace oglplus;
  gl.Clear().ColorBuffer().DepthBuffer();
  gl.Viewport( 0,0, 800, 600 );
  gl.DrawArrays(PrimitiveType::TriangleStrip, 0, 3);
 }

 static Example& GetInstance()
 {
  static Example example;
  return example;
 }
};

static void Display(void)
{
 Example::GetInstance().Display();
 glutSwapBuffers();
}

int main(int argc, char* argv[])
{
 glutInit(&argc, argv);
 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
 glutInitWindowSize(800, 600);
 glutInitWindowPosition(100,100);
 glutCreateWindow("OGLplus+GLUT+GLEW");

 if(glewInit() == GLEW_OK) try
 {
  glGetError();
  glutDisplayFunc(Display);
  glutMainLoop();
  return 0;
 }
 catch(oglplus::Error& err)
 {
  std::cerr <<
   "Error (in " << err.GLSymbol() << ", " <<
   err.ClassName() << ": '" <<
   err.ObjectDescription() << "'): " <<
   err.what() <<
   " [" << err.File() << ":" << err.Line() << "] ";
  std::cerr << std::endl;
  err.Cleanup();
 }
 return 1;
}

Example类包裹的是OpenGL标准,这也是OGLplus所做的所有关于OpenGL的事情。main函数与一个glut的渲染回调::Display()都是与平台gl实现相关的代码,这些OGLplus并没有考虑,也算是合理的。虽然OGLplus在OpenGL API外层封装了C++风格的Wrapper,但OGLplus保留了OpenGL的状态机编程风格,使得拥有一定OpenGL编程经验的程序员能够非常快的上手。OGLplus的封装到底做了哪些事情呢?

OGLplus是类型安全的OpenGL包装。OpenGL管理创建在OpenGL环境(context, 在不同实现中由不同的系统内核对象管理)下的名字对象(如Buffer,Texture,Shader,FrameBuffer等),通常用一个GLuint类型的标识。这种无型别的标识使得程序在运行期出错的几率大大增加。资源在创建时便已知其型别,使用C++型别来管理这些OpenGL资源,应属上策。OpenGL的名字对象由oglplus::Named类统一管理,这也是官网提及的资源自动管理的特性支持的基础组件。OGLplus为我们封装了所有的OpenGL函数调用,并同时封装了调用这些函数的异常处理,并抛出C++异常。OGLplus的封装,是不是让代码变得clean了许多呢?

最后再来谈谈我觉得这段代码一个非常出彩的地方。就是Example::Display方法中的gl.Clear().ColorBuffer().DepthBuffer()一句。这一句fluent interface风格的代码中的三次函数调用创建了三个oglplus::context::ClrBits的对象,Clear()创建一个空对象为了接下来的调用提供interface,后面依然依次创建ClrBIts型别的对象,并为后面的调用提供接口。创建的对象都是临时对象,离开代码段即进行对象的析构,而在ClrBits型别的析构函数中调用的则是glClear方法。这是使用RAII对象的良好实践。

好了,Outline先写到这里,modern C++设计的旅程刚刚开始。

OGLplus 的详细介绍:请点这里
OGLplus 的下载地址:请点这里

相关推荐