OpenGL 03案例 ----点、线、面、金字塔、伞、手环(二)

OpenGL 03案例 ----点、线、面、金字塔、伞、手环(一)

前面我们了解如何绘制点和线,甚至用GL_LINE_LOOP来绘制一些封闭的多边形。但这些终归只是线条而已,而不能对它进行填充颜色。这时候我们需要一个封闭的图形,用来填充颜色或纹理,构建实体对象。

三角形

这是OpenGL支持的最简单的实体多边形了。其他多边形都是由三角形构成。下面通过三角形构建一个金字塔。

金字塔

// 通过三角形创建金字塔
    GLfloat vPyramid[12][3] = {
        -2.0f, 0.0f, -2.0f,
        2.0f, 0.0f, -2.0f,
        0.0, 4.0f, 0.0f,
        
        2.0f, 0.0f, -2.0f,
        2.0f, 0.0f, 2.0f,
        0.0f, 4.0f, 0.0f,
        
        2.0f, 0.0f, 2.0f,
        -2.0f, 0.0f, 2.0f,
        0.0f, 4.0f, 0.0f,
        
        -2.0f, 0.0f, 2.0f,
        -2.0f, 0.0f, -2.0f,
        0.0f, 4.0f, 0.0f
    };
    // GL_TRIANGLES 每三个顶点定义一个新的三角形
    triangleBatch.Begin(GL_TRIANGLES, 12);
    triangleBatch.CopyVertexData3f(vPyramid);
    triangleBatch.End();

SetupRC函数中 定义4个三角形顶点,并将之保存到GCD缓存区中。下面我们需要使用着色器对三角形填充颜色。为了看得更加清晰,这里我们还需要将它的边框设置成黑色线条。这里,抽出一个公用函数,用来填充颜色,设置边框颜色。

void DrawWireFramedBatch(GLBatch *pBatch) {
    /*--------- 画绿色部分 -----------*/
    /* GLShaderManager 中Uniform值————平面着色器
     参数1:平面着色器
     参数2:运行为几何图形变换指定一个4 * 4变换矩阵
        ————transformPipeline 变换管线(指定了2个矩阵堆栈)
     参数3:颜色值
     */
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen);
    pBatch->Draw();
    
    /* ---------- 边框部分 ------------*/
    /*
     glEnable(GLenum mode); 用于启动各种功能。功能由参数决定
     参数类表:http://blog.csdn.net/augusdi/article/details/23747081
     注意:glEnable() 不能写在glBegin()和glEnd()中间
     GL_POLYGON_OFFSET_LINE 根据函数glPolygonOffset的设置,启用线的深度偏移
     GL_LINE_SMOOTH         执行后,过滤线点的锯齿
     GL_BLEND               启用颜色混合。例如实现半透明效果
     GL_DEPTH_TEST          启用深度测试 根据坐标的远近自动隐藏被遮住的图形
     
     glDisable(GLenum mode);用于关闭指定的功能 功能由参数决定
     */
    
    // 画黑色边框
    glPolygonOffset(-1.0f, -1.0f);
    glEnable(GL_POLYGON_OFFSET_LINE);
    
    // 画反锯齿,让黑边好看些
    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    // 绘制线框几何黑色版 三种模式,实心,边框,点,可以作用的正面,背面,或两者
    // 通过调用glPolygonMode将多边形正面或者背面设为线框模式,实现线框渲染
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    // 设置线条宽度
    glLineWidth(2.5f);
    
    /* GLShaderManager 中的Uniform值————平面着色器
     参数1:平面着色器
     参数2:运行为几何图形变换指定一个4 * 4变换矩阵
     --transformPipeline.GetModelViewProjectionMatrix()  获取的GetMatrix函数就可以获得矩阵堆栈顶部的值
     参数3:颜色值(黑色)
     */
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    pBatch->Draw();
    
    // 复原原本的设置
    // 通过调用glPolygonMode将多边形正面或背面设置为全部填充模式
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glDisable(GL_POLYGON_OFFSET_LINE);
    glLineWidth(1.0f);
    glDisable(GL_BLEND);
    glDisable(GL_LINE_SMOOTH);
}

主要的绘制代码已经写好了,接下来的案例(如手环)也会用到这部分代码,我就不会再重复写了。

这里采用了深度测试解决正背面隐藏的问题,后面会详细的讲到

金字塔

旋转图形

为了更好的展示金字塔(以及下面的其他图形),我们需要对金字塔进行旋转展示。
接下来需要用到变换管道、模型矩阵、投影矩阵的知识。首先我们定义几个全局变量。

GLMatrixStack modelViewMatrix;  // 模型矩阵堆栈
GLMatrixStack projectionMatrix; // 投影矩阵堆栈
GLFrame cameraFrame;
GLFrame objectFrame;
GLFrustum viewFrustum;
// 几何变换管道
GLGeometryTransform transformPipeline;

接下来需要在SetupRC函数中,利用变换管道将模型矩阵和投影矩阵关联起来。设置视角,使得图形看起来更加有立体感。

transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
cameraFrame.MoveForward(-30.0f);

ChangeSize函数也需要变化

// 创建投影矩阵,并将它载入投影矩阵堆栈中
    viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());

    // 调用顶部载入单元矩阵
    modelViewMatrix.LoadIdentity();

接下来就是重点,在绘制函数RenderScene中将变化了的矩阵传入到着色器中

// 压栈
    modelViewMatrix.PushMatrix();
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);

    //矩阵乘以矩阵堆栈的顶部矩阵,相乘的结果随后存储在堆栈的顶部
    modelViewMatrix.MultMatrix(mCamera);

    M3DMatrix44f mObjectFrame;
    // 只要使用 GetMatrix                                                                                               函数就可以获取矩阵堆栈顶部的值,这个函数可以进行2次重载。用来使用GLShaderManager的使用。或者是获取顶部矩阵的顶点副本数据
    objectFrame.GetMatrix(mObjectFrame);

    // 矩阵乘以矩阵堆栈的顶部矩阵,相乘的结果随后存储在堆栈的顶部
    modelViewMatrix.MultMatrix(mObjectFrame);

    /*
     GLShaderManager 中的Uniform值--平面着色器
     参数1:平面着色器
     参数2:运行为集合图形变换指定一个4*4变换矩阵
     --transformPipeline.GetModelViewProjectionMatrix()获取的
     GetMatrix函数就可以获得矩阵堆栈顶部的值
     参数3:颜色值(黑色)
     */
    //2.设置一组浮点数来表示红色
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);

别忘了最后需要调用Draw函数后,推出栈顶数据。

modelViewMatrix.PopMatrix();

最后在特殊函数SpecialKeys中,添加如下代码:

// 特殊键位处理(上、下、左、右)
void SpecialKeys(int key, int x, int y) {
    if (key == GLUT_KEY_UP) {
        // 围绕一个指定的x、y、z轴选旋转
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
    }
    if (key == GLUT_KEY_DOWN) {
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
    }
    if (key == GLUT_KEY_LEFT) {
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
    }
    if (key == GLUT_KEY_RIGHT) {
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
    }
    glutPostRedisplay();
}

使用RotateWorld 传入弧度。这边弧度需要自己去计算,所以使用glTools中封装好的宏m3dDegToRad进行转化。后面三个参数代表的x,y,z,作用就是在哪个轴上进行旋转。如上代码,我们会根据键盘的上下左右来围绕x,y轴进行旋转。

旋转三角形.gif

SetupRC函数中定义伞的各个顶点。

// 三角形扇形————六边形
    GLfloat vPoints[100][3];
    int  nVerts = 0;
    // 半径
    GLfloat r = 3.0f;
    // 原点(x,y,z) = (0,0,0);
    vPoints[nVerts][0] = 0.0f;
    vPoints[nVerts][1] = 0.0f;
    vPoints[nVerts][2] = 0.0f;
    
    // M3D_2PI 就是2pi的意思,就是一个圆的意思。绘制圆形
    for (GLfloat angle = 0; angle < M3D_2PI; angle += M3D_2PI / 6.0f) {
        // 数组下标自增 (每自增一次就表示一个顶点)
        nVerts++;
        /*
         弧长=半径*角度,这里的角度是弧度值,不是平时的角度值
         既然知道cos值,那么角度=arccos,求一个反三角形函数就行了
         */
        // x点坐标 cos(angle) * 半径
        vPoints[nVerts][0] = float(cos(angle)) * r;
        // y点坐标 sin(angle) * 半径
        vPoints[nVerts][1] = float(sin(angle)) * r;
        // z点坐标
        vPoints[nVerts][2] = -0.5f;
    }
    
    // 结束扇形 前面一个绘制7个顶点 (包括圆心)
    // 添加闭合的重点
    // 课程添加演示:屏蔽148-150行代码,并把绘制节点改为7, 则三角形扇形无法闭合的。
    nVerts++;
    vPoints[nVerts][0] = r;
    vPoints[nVerts][1] = 0;
    vPoints[nVerts][2] = 0.0f;
    
    // 加载!
    // GL_TRIANGLE_FAN 以一个圆心为中心呈扇形排列,共用相邻顶点的一组三角形
    triangleFanBatch.Begin(GL_TRIANGLE_FAN, 8);
    triangleFanBatch.CopyVertexData3f(vPoints);
    triangleFanBatch.End();
  • 将360度分为六等分,也就是说要构建6个三角形。以原点(0,0,0)为圆点,使用for循环创建6个三角形对应的顶点。
  • 最后使用GL_TRIANGLE_FAN这个扇形排列的规则来进行图元装配。
  • 着色这块还是用上面的使用到的DrawWireFramedBatch函数就行绘制。
 DrawWireFramedBatch(&triangleFanBatch);
旋转伞.gif

手环

SetupRC函数中定义手环的顶点

// 三角形条带,一个小环或圆柱段
    // 顶点喜爱宝
    int iCounter = 0;
    // 半径
    GLfloat radius = 3.0f;
    // 从0度~360度,以0.e弧度为步长
    for(GLfloat angle = 0.0f;angle <= (2.0f * M3D_PI); angle += 0.3) {
        // 或许圆形的顶点x,y
        GLfloat x = radius * sin(angle);
        GLfloat y = radius * cos(angle);
        
        // 绘制2个三角形 (他们的x,y顶点一样,只有z点不一样)
        vPoints[iCounter][0] = x;
        vPoints[iCounter][1] = y;
        vPoints[iCounter][2] = -0.5f;
        iCounter++;
        
        vPoints[iCounter][0] = x;
        vPoints[iCounter][1] = y;
        vPoints[iCounter][2] = 0.5f;
        iCounter++;
    }
    
    // 关闭循环
    printf("三角形带的顶点数:%d\n",iCounter);
    // 结束循环,在循环位置生成2个三角形
    vPoints[iCounter][0] = vPoints[0][0];
    vPoints[iCounter][1] = vPoints[0][1];
    vPoints[iCounter][2] = -0.5;
    iCounter++;
    
    vPoints[iCounter][0] = vPoints[1][0];
    vPoints[iCounter][1] = vPoints[1][1];
    vPoints[iCounter][2] = 0.5;
    iCounter++;
    
    // GL_TRIANGLE_STRIP 共用一个条带(strip)上的顶点的一组三角形
    triangleStripBatch.Begin(GL_TRIANGLE_STRIP, iCounter);
    triangleStripBatch.CopyVertexData3f(vPoints);
    triangleStripBatch.End();
  • 使用for循环,根据弧度值的变化,求出对应顶点的 x,y,z值
  • 为了让手环闭合 设置顶点数组倒数第一、第二的值和第一、第二一样。
  • 使用GL_TRIANGLE_STRIP来进行图元装配
  • 最后在RenderScene进行绘制。

这边还有几个技术点,比如深度测试,后面会着重讲解到。
这样金字塔、伞、手环在这就制作完成了。如果想查看完整代码点击这里下载。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。