OpenGL-基本图元的使用案例

494 阅读5分钟

OpenGL基本图元

OpenGL可以支持很多种不同的图元类型。不过它们最后都可以归结为三种类型中的一种,即点、线或者三角形。线和三角形图元类型可以再组合为条带、循环体(线),或者扇面(三角形)。点、线和三角形也是大部分图形硬件设备所支持的基础图元类型(所谓的硬件支持,也就是图形处理器中直接提供了这些图元类型的光栅化操作。其他图元类型,例如Patch和邻接图元,是无法直接进行光栅化的)。

OpeGL中图元的连接方式大致分为以下几种:

图元 描述
GL_POINTS 每个顶点在屏幕上都是单独点
GL_LINES 每一对顶点定义一个线段
GL_LINE_STRIP 一个从第一个顶点依次经过每一个后续顶点而绘制的线条
GL_LINE_LOOP 和GL_LINE_STRIP相同,但是最后一个顶点和第一个顶点连接起来了
GL_TRIANGLES 每三个顶点定义一个新三角形
GL_TRIANGLE_STRIP 共用一个条带(strip)上的顶点的一组三角形
GL_TRIANGLE_FAN 以一个圆点为中心呈扇形排列,公用相邻顶点的一组三角形
... ...

部分示意图如下图所示:

实例

接下来以代码演示其中部分连接方式并且增加旋转功能。

主要相关代码如下:

// 各种需要的类
GLShaderManager		shaderManager;
GLMatrixStack		modelViewMatrix;
GLMatrixStack		projectionMatrix;
GLFrame				cameraFrame;
GLFrame             objectFrame;
//投影矩阵
GLFrustum			viewFrustum;

//容器类(7种不同的图元对应7种容器对象)
GLBatch				pointBatch;
GLBatch				lineBatch;
GLBatch				lineStripBatch;
GLBatch				lineLoopBatch;
GLBatch				triangleBatch;
GLBatch             triangleStripBatch;
GLBatch             triangleFanBatch;

//几何变换的管道
GLGeometryTransform	transformPipeline;

main函数

int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    //申请一个颜色缓存区、深度缓存区、双缓存区、模板缓存区
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    //设置window 的尺寸
    glutInitWindowSize(800, 600);
    //创建window的名称
    glutCreateWindow("GL_POINTS");
    //注册回调函数(改变尺寸)
    glutReshapeFunc(ChangeSize);
    //点击空格时,调用的函数
    glutKeyboardFunc(KeyPressFunc);
    //特殊键位函数(上下左右)
    glutSpecialFunc(SpecialKeys);
    //显示函数
    glutDisplayFunc(RenderScene);
    
    //判断一下是否能初始化glew库,确保项目能正常使用OpenGL 框架
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    //初始化工作
    SetupRC();
    //runloop运行循环
    glutMainLoop();
    return 0;
}

其中我们通过上下左右的方向键来控制屏幕的旋转,相关函数如下:

//特殊键位处理(上、下、左、右移动),
//GLFrame objectFrame
void SpecialKeys(int key, int x, int y)
{
    if(key == GLUT_KEY_UP) {
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0, 0.0, 0.0);
    }
    if(key == GLUT_KEY_DOWN) {
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0, 0.0, 0.0);
    }
    if(key == GLUT_KEY_LEFT) {
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0, 1.0, 0.0);
    }
    if(key == GLUT_KEY_RIGHT) {
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0, 1.0, 0.0);
    }
    //执行重绘
    glutPostRedisplay();
}

由于我们需要进行旋转,因此需要使用到对应的mvp(modelViewPrejection)矩阵,在ChangeSize回调中进行设置

// 窗口已更改大小,或刚刚创建。无论哪种情况,我们都需要
// 使用窗口维度设置视口和投影矩阵.

void ChangeSize(int w, int h)
{
    glViewport(0, 0, w, h);
    //设置透视模式
    viewFrustum.SetPerspective(35.f, float(w)/float(h), 1.0f, 500.f);
    //把透视矩阵加载到透视矩阵堆栈中
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    //modelViewMatrix 矩阵堆栈 加载单元矩阵,这一步可以省略,因为堆栈底部默认就是一个单元矩阵
    modelViewMatrix.LoadIdentity();
}

我们通过在对SetupRC函数以及RenderScene回调的不同设置来达到显示不同图元的目的。

GL_POINTS

实现效果如下:

SetupRC

// 此函数在呈现上下文中进行任何必要的初始化。.
// 这是第一次做任何与opengl相关的任务。
void SetupRC()
{
    //设置背景颜色
    glClearColor(0.34f, 0.34f, 0.5f, 1.0f);
    //初始化固定着色器
    shaderManager.InitializeStockShaders();
    //设定变换管线来使用模型视图矩阵和投影矩阵
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    //为了让效果明显,将观察者坐标位置Z移动往屏幕里移动15个单位位置
    // 参数:表示离屏幕之间的距离。 负数,是往屏幕后面移动;正数,往屏幕前面移动
    //GLFrame类型,表示camera
    cameraFrame.MoveForward(-15.0f);
    
    GLfloat vCoast[9] = {
        3,3,0,
        0,3,0,
        3,0,0
    };
    //批次类
    pointBatch.Begin(GL_POINTS, 3);
    pointBatch.CopyVertexData3f(vCoast);
    pointBatch.End();
}

RenderScene

// 召唤场景
void RenderScene(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    //模型视图矩阵堆栈
    modelViewMatrix.PushMatrix();
    
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    //矩阵乘以矩阵堆栈的顶部矩阵,相乘的结果随后简存储在堆栈的顶部
    modelViewMatrix.MultMatrix(mCamera);
    
    M3DMatrix44f mObjectFrame;
    objectFrame.GetMatrix(mObjectFrame);
    //矩阵乘以矩阵堆栈的顶部矩阵,相乘的结果随后简存储在堆栈的顶部
    modelViewMatrix.MultMatrix(mObjectFrame);
    //使用平面着色器
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    
    glPointSize(10.0f);
    pointBatch.Draw();
    glPointSize(1.0f);
    
    modelViewMatrix.PopMatrix();
    glutSwapBuffers();
}

通过以上的代码,即可实现在屏幕中渲染出三个顶点的功能。接下来解释一下关于RenderScene中与矩阵相关的操作,与之相关联的有以下几个:

  • GLShaderManager shaderManager; //着色器管理类
  • GLMatrixStack modelViewMatrix; //模型视图矩阵栈
  • GLMatrixStack projectionMatrix; //投影矩阵栈
  • GLFrame cameraFrame; //摄像机视角,负责指定观察的视角位置
  • GLFrame objectFrame; //物体视角,负责控制物体的旋转
  • GLFrustum viewFrustum; //通过viewFrustum来获得投影矩阵
  • GLGeometryTransform transformPipeline; //组合模型视图矩阵和投影矩阵

投影矩阵是在ChangeSize回调中设置好的,在每一次的方向键操作中我们需要改变的是模型视图矩阵来实现视图的旋转。

具体的操作如下:

  1. 模型视图矩阵堆栈压栈
  2. 获取摄像机矩阵,将栈顶矩阵复制一份与摄像机矩阵相乘并替换栈顶矩阵
  3. 获取物体矩阵,将栈顶矩阵复制一份与物体矩阵相乘并替换栈顶矩阵
  4. 模型视图矩阵与投影矩阵组合生成新的矩阵,shaderManger利用平面着色器以及新的矩阵为参数进行重绘。
  5. 模型视图矩阵出栈,恢复原状。

引用大佬的示意图如下所示:

此次操作之后模型视图矩阵堆栈恢复原状,可以进行下一次的变换以及重新绘制。

以上是关于顶点图元的使用。接下来的其他图元只是列出示意图,具体实现可以移步demo代码进行参考。

GL_LINES

GL_LINE_STRIP

GL_LINE_LOOP

GL_TRIANGLES

GL_TRIANGLE_FAN

GL_TRIANGLE_STRIP