Showing posts with label OpenGL. Show all posts
Showing posts with label OpenGL. Show all posts

Saturday, October 23, 2010

OpenGL 的工作流程

上图为 OpenGL Programming Guide 中的图片,特此说明。

我们需要理解 OpenGL 的工作机制。OpenGL本身仅仅支持最原始点、线段和平面的绘制,因此多数情况下我们都是准备好 vertex 数据,这些或者组织成为 display list 提交给显卡的 evaluator,这个 evaluator 将点信息转换成为需要描述的面的信息(如法向量等,这个功能在创建复杂的几何形体如曲面等时会经常用到);之后通过设定的 per-vertex 操作,如进行几何变换,计算纹理位置,光源、材料等等;然后是 assemble,获得显示到窗口的投影坐标。这之后就需要将这些计算出来的数据进行 rasterization(像素化,栅格化),同时将纹理信息加入到这些面上,进一步就可以做 fragment 操作(如根据深度去掉被遮挡的、又或者进行 blending 等,一般每种操作对应某种 buffer),最后将结果放到某个 buffer 中(可能是用于显示的,也可能是用于 swap 的)。而一般外部的位图数据,一般会首先经过处理(如放缩、平移、映射等)形成纹理。

编写 OpenGL 的程序要理解清楚,我们所绘制的对象是一个所谓的 OpenGL context,这个 context 里面包括了所有打开的 OpenGL 使用的 buffer 等对象,我们做图时往往需要指定一个 context,做图的过程就是将这个 context 的状态不停的改变,比如我们希望改变渲染的对象,我们会在 GL_RENDER 下切换到矩阵的 GL_MODELVIEW 里面,而如果只是改变投影我们是在 GL_PROJECTION 里面;如果我们需要选择所画的某些对象,我们会切换到 GL_SELECT 这种模式,并通过 GL_PROJECTION 设定我们观看的区域,然后切换回 GL_MODELVIEW 重新绘制,退回 GL_RENDER 的时候就会获得一个被选择的对象列表。所以我们需要搞清楚有些什么样的状态,对应状态下可以进行一些什么操作。

推荐的文档有两个,一个是 OpenGL 红皮书(红宝书?)即 OpenGL Programming Guide,现在已经到第 7 版了,非常经典的书,现在也开始涉及到 OpenGL 3.0 的东西,最新的 4.1 尚未涉及;4.1 相关的主要是 NVIDIA 在推动;这本书网上有电子版,中文版只能说翻译质量一般;另一个自然就是官方的 spec,网上可以免费下载(见这里)。

另外,我们需要理解清楚下面几个概念。
  • OpenGL 与 WGLAGLGLX 的关系:OpenGL 本质上和窗口系统无关,因此如果需要在某些窗体环境中使用 OpenGL 需要这些 GUI 提供某种 OpenGL 扩展,WGL 是 Windows 的扩展,AGL(另外还有 CGL、Cocoa 的 NSOpenGL 类)是苹果的扩展,而 GLX 是 X11 扩展(并且允许 network-transparent 的 OpenGL 渲染)。
  • OpenGL 与 GLU 和 GLUT 之间的关系:前面说了 OpenGL 本身仅仅提供了非常原始的操作;GLU 另外提供了层次纹理(mipmapping)、简单的矩阵操作(更加方便)、polygon tessellation(将曲面分解成为多边形)、二次曲面以及 NURBS 的支持;GLUT 提供的是接口统一跨平台的 GUI 接口。
后面我们会学习红皮书和 GLU 方面的东西。

Saturday, October 16, 2010

depth buffer 与 blending buffer

OpenGL 通过 depth buffer 维护最后显示在屏幕上的像素,深度上较大的像素会被较小的覆盖;blending buffer 主要是为了渲染半透明的效果,主要是为了将不同层次的能够融合在一起。两个 buffer 作用的顺序是先 depth 后 blend。

这导致了一个问题,如果我们打开了两个 buffer,那么深度测试后被遮挡的物体将被剔除,进入 blending buffer 的时候不存在 blending 的问题了。因此 blending 失效。

解决的方案是,打开 depth buffer,先画不需要 blending 的物体,然后自己计算这些半透明物体的深度,并按照深度从深到浅画出。

相关的讨论见 NeHe 的 tutorial 以及 OpenGL 官方的 FAQ

billboard 的 OpenGL 实现

原文见这里

基本想法是两个:

要不我把当前的 MODELVIEW 矩阵拿出来,把旋转去掉,这只需要把那个矩阵的 3x3 部分变成单位阵就行了,因为把矩阵理解成为对当前点的变换可以发现我们仅仅需要平移到当前点,然后画需要的 billboard 就好了。

要不我把当前的 MODELVIEW 矩阵拿出来,3x3 那部分里面每行相当于是在 x、y、z 上的投影,因此只需要取前两行作为方向计算该处物体的坐标就行了。

一般 billboard 都是矩形,因此计算量不算很大,后者需要用 CPU 计算所有的 billboard 坐标,如果 billboard 较少比较适合;前者实际上需要将矩阵从显存放到内存,然后写回去,如果 billboard 计算量太小则不太适合。

Monday, October 11, 2010

Qt 的 OpenGL 支持

首先需要知道 OpenGL 是一个渲染 3D 图像的接口,其定义与语言几乎无关。我们这里直接在 C/C++ 里面使用对应的 OpenGL 语言。说到头,OpenGL 只是渲染一个图像,因此很多做图的原语被反复的使用。我们知道的 Mesa 本质上是实现了一个 CPU 版本(以及少量显卡硬件支持)的 OpenGL。为什么需要 OpenGL 呢?如果每个显卡都有自己的一套渲染方案,但提供给用户的 API 不同,则每个图形应用程序都需要针对特定的显卡进行编写。而如果显卡提供了一致的标准,编写程序的时候则会轻松很多。但是这要求显卡提供商实现两部分内容。一部分是驱动程序,被 OS 隐藏起来,用于直接与硬件打交道;另一部分是用来向 OS 提交请求的用户 API 接口。这样显卡提供商一方面需要尽可能的通过自己显卡的硬件环境优化 OpenGL 的渲染实现,也需要通过 marshalling 减少从用户态转换到内核态的次数(避免每次 OpenGL 调用都 involve 一次转换)。这样将一些 rendering 的工作(特别是大量 3D 渲染)从 CPU 转移到 GPU 之后就能加速应用程序。

第二,Qt 对 OpenGL 的支持在于它提供了一个用于显示 OpenGL 渲染结果的 widget,即 QGLWidget,如果我们需要显示 OpenGL 的渲染结果,我们就应该继承 QGLWidget,然后实现它三个方法:initializeGL() 用于初始化 OpenGL,通常设置一些前期的背景色、光照等等;paintGL() 用于渲染整个场景;resizeGL() 用于在 widget 大小变化的时候产生合理的 view。

第三,如果需要设置动画效果,本质上是在 paintGL() 过程中加入某些变化的参数,这样每次调用 paintGL() 的时候产生不同的渲染结果。实现动画需要调用 QTimer 之类的定时器,而这些对象并不能访问前面的这些方法(因为是 protected 的),因此一般调用含有 updateGL() 方法的 slot,将 QTimer 的某些 signal 与之 connect 即可。

第四,如果需要加入鼠标和键盘的操作,应该设置 mousePressEvent()、mouseMoveEvent() 等事件,将需要的方法绑定到对应的事件中。

总而言之,OpenGL 的使用并不神秘,只是我们需要进一步理解其中的一些基本概念。下面是一个简单的 OpenGL 程序,可以看到前面第二点里面所说的三个要素,我们在后面的分析中会逐步加深。下面是继承 QGLWidget 类的声明
#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QGLWidget>
#include <QVector>
#include <gl.h>

class GLWidget : public QGLWidget {
 public:
  GLWidget( QWidget* = NULL, const QGLWidget* = NULL,
            Qt::WindowFlags = Qt::Widget ) ;

 protected:
  void initializeGL();
  void paintGL();
  void resizeGL(int width, int height);

 private:
  QVector<GLuint> stars ;
} ;

#endif
下面是相关实现,
#include "glwidget.h"

GLWidget::GLWidget( QWidget* parent, const QGLWidget* shared,
                    Qt::WindowFlags flags)
  : QGLWidget( parent, shared, flags ), stars(1) {
}

void
GLWidget::initializeGL() {
  QGLWidget::initializeGL() ;
  glClearColor(0.0, 0.0, 0.0, 0.0);
  glClearDepth( 1.0 ) ;
}

void
GLWidget::paintGL() {
  glClearColor( 0.0, 0.0, 0.0, 0.0 ) ;
  glClear (GL_COLOR_BUFFER_BIT);

  glLoadIdentity() ;
  glColor3f (1.0, 1.0, 1.0);
  glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
  glBegin(GL_POLYGON);
    glVertex3f( 0.25, 0.25, .0 ) ; 
    glVertex3f( 0.75, 0.25, .0 ) ; 
    glVertex3f( 0.75, 0.75, .0 ) ; 
    glVertex3f( 0.25, 0.75, .0 ) ;
  glEnd();
  glFlush();
}

void
GLWidget::resizeGL(int w, int h ) {
  glViewport( 0, 0, w, h ) ;
  glMatrixMode( GL_PROJECTION ) ;
  glLoadIdentity() ;
}
我想后面我们应该会更关注 OpenGL 本身,而不仅仅在 Qt 上。