OpenGL

05-从小实例接触代码理解

2019-05-19  本文已影响0人  SPIREJ

准备工作

#include "GLShaderManager.h"
#include "GLTools.h"
#include <GLUT/GLUT.h>

//定义一个,着色管理器
GLShaderManager shaderManager;
//简单的批次容器,是GLTools的一个简单的容器类。
GLBatch triangleBatch;

/*
 在窗口大小改变时,接收新的宽度&高度。
 */
void changeSize(int w,int h)
{
    /*
      x,y 参数代表窗口中视图的左下角坐标,而宽度、高度是像素为表示,通常x,y 都是为0
     */
    glViewport(0, 0, w, h);  
}

void RenderScene(void)
{
}

void SpeacialKeys(int key,int x,int y)
{
}

void setupRC()
{  
}

int main(int argc,char *argv[])
{
    //设置当前工作目录,针对MAC OS X
    /*
     `GLTools`函数`glSetWorkingDrectory`用来设置当前工作目录。
      在Mac OS X中,这个程序将当前工作文件夹改为应用程序捆绑包中的`/Resource`文件夹。
      `GLUT`的优先设定自动进行了这个中设置,但是这样中方法更加安全。
     */
    gltSetWorkingDirectory(argv[0]);

    //初始化GLUT库,这个函数只是传说命令参数并且初始化glut库
    glutInit(&argc, argv);
    
    glutInitDisplayMode(GLUT_RGBA);
    
    //GLUT窗口大小、窗口标题
    glutInitWindowSize(800, 600);
    glutCreateWindow("Triangle");
    
    
    /*
     GLUT 内部运行一个本地消息循环,拦截适当的消息。然后调用我们不同时间注册的回调函数。我们一共注册2个回调函数:
     1)为窗口改变大小而设置的一个回调函数
     2)包含OpenGL 渲染的回调函数
     */
    
    //注册重塑函数
    glutReshapeFunc(changeSize);
    //注册显示函数
    glutDisplayFunc(RenderScene);

    glutSpecialFunc(SpeacialKeys);
    
    /*
     初始化一个GLEW库,确保OpenGL API对程序完全可用。
     在试图做任何渲染之前,要检查确定驱动程序的初始化过程中没有任何问题
     */
    GLenum status = glewInit();
    if (GLEW_OK != status) {
        printf("GLEW Error:%s\n",glewGetErrorString(status));
        return 1;
    }

    //设置我们的渲染环境
    setupRC();

    glutMainLoop();
    return  0;
}
定义帮助类 重要的函数作用

一、渲染三角形

1. 在 RenderScene() 函数中有如下代码

void RenderScene(void)
{

    //1.清除一个或者一组特定的缓存区
    /*
     缓冲区是一块存在图像信息的储存空间,红色、绿色、蓝色和alpha分量通常一起分量通常一起作为颜色缓存区或像素缓存区引用。
     OpenGL 中不止一种缓冲区(颜色缓存区、深度缓存区和模板缓存区)
      清除缓存区对数值进行预置
     参数:指定将要清除的缓存的
     GL_COLOR_BUFFER_BIT :指示当前激活的用来进行颜色写入缓冲区
     GL_DEPTH_BUFFER_BIT :指示深度缓存区
     GL_STENCIL_BUFFER_BIT:指示模板缓冲区
     */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
    
    
    //2.设置一组浮点数来表示红色
    GLfloat vRed[] = {1.0,0.0,0.0,1.0f};
    
    //传递到存储着色器,即GLT_SHADER_IDENTITY着色器,这个着色器只是使用指定颜色以默认笛卡尔坐标第在屏幕上渲染几何图形
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
    
    //提交着色器
    triangleBatch.Draw();
    
    //在开始的设置openGL 窗口的时候,我们指定要一个双缓冲区的渲染环境。这就意味着将在后台缓冲区进行渲染,渲染结束后交换给前台。这种方式可以防止观察者看到可能伴随着动画帧与动画帧之间的闪烁的渲染过程。缓冲区交换平台将以平台特定的方式进行。
    //将后台缓冲区进行渲染,然后结束后交换给前台
    glutSwapBuffers();
    
}

void setupRC()
{
    //设置清屏颜色(背景颜色)
    glClearColor(0.98f, 0.40f, 0.7f, 1);
    
    
    //没有着色器,在OpenGL 核心框架中是无法进行任何渲染的。初始化一个渲染管理器。
    //在前面的课程,我们会采用固管线渲染,后面会学着用OpenGL着色语言来写着色器
    shaderManager.InitializeStockShaders();
    
    
    //指定顶点
    //在OpenGL中,三角形是一种基本的3D图元绘图原素。
    GLfloat vVerts[] = {
        -0.5f,0.0f,0.0f,
        0.5f,0.0f,0.0f,
        0.0f,0.5f,0.0f
    };
    
    triangleBatch.Begin(GL_TRIANGLES, 3);
    triangleBatch.CopyVertexData3f(vVerts);
    triangleBatch.End();
    
}

2. 运行结果显示

渲染三角形 运行结果

二、画一个正方形并能跟随键盘上下左右键移动

注: 在三角形的代码基础上做修改

1. 定义正方形4个顶点坐标

//blockSize 边长
GLfloat blockSize = 0.1f;

//正方形的4个点坐标
GLfloat vVerts[] = {
        -blockSize,-blockSize,0.0f,
        blockSize,-blockSize,0.0f,
        blockSize,blockSize,0.0f,
        -blockSize,blockSize,0.0f
};

2. setupRC()函数中的批次容器顶点改为GL_TRIANGLE_FAN

//修改为GL_TRIANGLE_FAN ,4个顶点
    triangleBatch.Begin(GL_TRIANGLE_FAN, 4);
    triangleBatch.CopyVertexData3f(vVerts);
    triangleBatch.End();

3. 特殊函数SpecialKeys()中处上下左右键事件
需要注意触碰到边界(4个边界)的处理

void SpecialKeys(int key, int x, int y){
    //每次移动步长
    GLfloat stepSize = 0.025f;
    
    GLfloat blockX = vVerts[0];
    GLfloat blockY = vVerts[10];
    
    printf("v[0] = %f\n",blockX);
    printf("v[10] = %f\n",blockY);
    
    if (key == GLUT_KEY_UP) {
        blockY += stepSize;
    }
    
    if (key == GLUT_KEY_DOWN) {
        blockY -= stepSize;
    }
    
    if (key == GLUT_KEY_LEFT) {
        blockX -= stepSize;
    }
    
    if (key == GLUT_KEY_RIGHT) {
        blockX += stepSize;
    }

    //触碰到边界(4个边界)的处理
    
    //当正方形移动超过最左边的时候
    if (blockX < -1.0f) {
        blockX = -1.0f;
    }
    
    //当正方形移动到最右边时
    //1.0 - blockSize * 2 = 总边长 - 正方形的边长 = 最左边点的位置
    if (blockX > (1.0 - blockSize * 2)) {
        blockX = 1.0f - blockSize * 2;
    }
    
    //当正方形移动到最下面时
    //-1.0 - blockSize * 2 = Y(负轴边界) - 正方形边长 = 最下面点的位置
    if (blockY < -1.0f + blockSize * 2 ) {
        blockY = -1.0f + blockSize * 2;
    }
    
    //当正方形移动到最上面时
    if (blockY > 1.0f) {
        blockY = 1.0f;
    }

    printf("blockX = %f\n",blockX);
    printf("blockY = %f\n",blockY);
    
    // Recalculate vertex positions
    vVerts[0] = blockX;
    vVerts[1] = blockY - blockSize*2;
    
    printf("(%f,%f)\n",vVerts[0],vVerts[1]);
    
    vVerts[3] = blockX + blockSize*2;
    vVerts[4] = blockY - blockSize*2;
    printf("(%f,%f)\n",vVerts[3],vVerts[4]);
    
    vVerts[6] = blockX + blockSize*2;
    vVerts[7] = blockY;
    printf("(%f,%f)\n",vVerts[6],vVerts[7]);
    
    vVerts[9] = blockX;
    vVerts[10] = blockY;
    printf("(%f,%f)\n",vVerts[9],vVerts[10]);
    
    triangleBatch.CopyVertexData3f(vVerts);
    
    //当前窗口需要重新绘制
    glutPostRedisplay();
}

4.main()函数中注册方法SpecialKeys()

//注册特殊函数
glutSpecialFunc(SpecialKeys);

5. 运行结果显示

正方形随键盘上下左右键移动

三、使用固定存储着色器渲染多种图元

1、首先来看一下正式绘制前main.cpp中的架子

运行结果:点击空格来切换不同的图元容器

#include "GLTools.h"
#include "GLMatrixStack.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLBatch.h"
#include "GLGeometryTransform.h"

#include <math.h>
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

//各种需要的工具类
GLShaderManager shaderManager;
GLMatrixStack   modelViewMatrix;
GLMatrixStack   projectionMatrix;
GLFrame         cameraFrame;
GLFrame         objectFrame;

//投影矩阵
GLFrustum       viewFrustum;

//容器类
GLBatch         pointBatch;
GLBatch         lineBatch;
GLBatch         lineStripBatch;
GLBatch         lineLoopBatch;
GLBatch         triangleBatch;
GLBatch         triangleStripBatch;
GLBatch         triangleFanBatch;

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

//颜色
GLfloat vRed[] = {1.0f, 0.0f, 0.0f, 1.0f};
GLfloat vGreen[] = {0.0f, 1.0f, 0.0f, 1.0f};

//跟踪效果步骤
int nStep = 0;

void ChangeSize(int w, int h)
{
    printf("---- ChangeSize() ----\n");
}

void SetupRC()
{
    printf("---- SetupRC() ----\n");
}

//召唤场景
void RenderScene()
{
    printf("---- RenderScene() ----\n");
}

void SpecialKeys(int key, int x, int y)
{
    printf("---- SpecialKeys() ----\n");
}

void KeyPressFunc(unsigned char key, int x, int y)
{
    printf("---- KeyPressFunc() ----\n");
}

int main(int argc, char *argv[])
{
    printf("---- main() ----\n");
    
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    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;
}

代码分析

头文件分析 各种需要的类声明和定义 公共全局变量声明

函数实现

1、ChangeSize()函数

void ChangeSize(int w, int h)
{
    printf("---- ChangeSize() ----\n");
    
    glViewport(0, 0, w, h);
    
    viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 500.0f);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    cameraFrame.MoveForward(-40.0f);
    
    modelViewMatrix.LoadIdentity();
}

2、按击空格键更改窗口标题

void KeyPressFunc(unsigned char key, int x, int y)
{
    printf("---- KeyPressFunc() ----\n");
    
    if (key == 32) {
        nStep++;
        if (nStep > 6) {
            nStep = 0;
        }
    }
    
    switch(nStep)
    {
        case 0:
            glutSetWindowTitle("GL_POINTS");
            break;
            
        case 1:
            glutSetWindowTitle("GL_LINES");
            break;
            
        case 2:
            glutSetWindowTitle("GL_LINE_STRIP");
            break;
            
        case 3:
            glutSetWindowTitle("GL_LINE_LOOP");
            break;
            
        case 4:
            glutSetWindowTitle("GL_TRIANGLES");
            break;
            
        case 5:
            glutSetWindowTitle("GL_TRIANGLE_STRIP");
            break;
            
        case 6:
            glutSetWindowTitle("GL_TRIANGLE_FAN");
            break;
            
        default:
            break;
    }
    glutPostRedisplay();
}

3、上下左右键改变视图坐标

void SpecialKeys(int key, int x, int y)
{
    printf("---- SpecialKeys() ----\n");
    
    if (key == GLUT_KEY_UP) {
        
        //围绕一个指定的X,Y,Z轴旋转。
        //m3dDegToRad 角度->弧度
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
        
    }else if (key == GLUT_KEY_DOWN) {
        
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
        
    }else if (key == GLUT_KEY_LEFT) {
        
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
        
    }else if (key == GLUT_KEY_RIGHT) {
        
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
    }
    
    glutPostRedisplay();
}

2、开始绘制

SetupRC()方法中增加代码

1. 点、线、线段、线环

//定义一些点,三角形形状。
GLfloat vCoast[9] = {
    3,3,0,
    0,3,0,
    3,0,0 
};

//用点的形式
pointBatch.Begin(GL_POINTS, 3);
pointBatch.CopyVertexData3f(vCoast);
pointBatch.End();

//通过线的形式
lineBatch.Begin(GL_LINES, 3);
lineBatch.CopyVertexData3f(vCoast);
lineBatch.End();

//通过线段的形式
lineStripBatch.Begin(GL_LINE_STRIP, 3);
lineStripBatch.CopyVertexData3f(vCoast);
lineStripBatch.End();

//通过线环的形式
lineLoopBatch.Begin(GL_LINE_LOOP, 3);
lineLoopBatch.CopyVertexData3f(vCoast);
lineLoopBatch.End();

2. 三角形创建金字塔

//通过三角形创建金字塔
GLfloat vPyramid[12][3] = {
    -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,
    
    -2.0f, 0.0f, 2.0f,
    -2.0f, 0.0f, -2.0f,
    0.0f, 4.0f, 0.0f};

//GL_TRIANGLES 每3个顶点定义一个新的三角形
triangleBatch.Begin(GL_TRIANGLES, 12);
triangleBatch.CopyVertexData3f(vPyramid);
triangleBatch.End();

3. 三角形扇形

//三角形扇形(六边形)带凸起的偏移
    GLfloat vPoints[100][3];
    int nVerts = 0;
    //半径
    int r = 3.0f;
    
    //原点 (x,y,z) = (0,0,0);
    vPoints[nVerts][0] = 0.0f;
    vPoints[nVerts][1] = 0.0f;
    vPoints[nVerts][2] = 0.0f;
    
    for (GLfloat angle = 0; angle < M3D_2PI; angle += M3D_2PI/10.0f) {
        //数组下标自增(每自增1次就表示一个顶点)
        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] = -1.5f;
    }
    
//    nVerts++;
//    vPoints[nVerts][0] = r;
//    vPoints[nVerts][1] = 0;
//    vPoints[nVerts][2] = 0.0f;
    
    // 加载!
    // 这里在测试的时候发现 分10份,共11个点,不用创建最后一个点 传12个点也能形成闭环
    //GL_TRIANGLE_FAN 以一个圆心为中心呈扇形排列,共用相邻顶点的一组三角形
    triangleFanBatch.Begin(GL_TRIANGLE_FAN, 12);
    triangleFanBatch.CopyVertexData3f(vPoints);
    triangleFanBatch.End();

4. 三角形条带 环状或圆柱段

//三角形条带,一个小环或圆柱段
    //顶点下标
    int iCounter = 0;
    //半径
    GLfloat radius = 3.0f;
    //从0度~360度,以0.3弧度为步长
    for(GLfloat angle = 0.0f; angle <= M3D_2PI; angle += 0.3f)
    {
        //或许圆形的顶点的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.5;
        iCounter++;
        
        vPoints[iCounter][0] = x;
        vPoints[iCounter][1] = y;
        vPoints[iCounter][2] = 0.5;
        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();

5. RenderScene()中对容器的渲染处理(综合)

//召唤场景
void RenderScene()
{
    printf("---- RenderScene() ----\n");
    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(), vRed);
    
    switch (nStep) {
            case 0:
                glPointSize(5.0f);
                pointBatch.Draw();
                glPointSize(1.0f);
            break;
            
            case 1:
                glLineWidth(4.0f);
                lineBatch.Draw();
                glLineWidth(1.0f);
            break;
            
            case 2:
                glLineWidth(4.0f);
                lineStripBatch.Draw();
                glLineWidth(1.0f);
            break;
            
            case 3:
                glLineWidth(4.0f);
                lineLoopBatch.Draw();
                glLineWidth(1.0f);
            break;
            
            case 4:
                DrawWireFramedBatch(&triangleBatch);
            break;
            
            case 5:
                DrawWireFramedBatch(&triangleFanBatch);
            break;
            
            case 6:
                DrawWireFramedBatch(&triangleStripBatch);
            break;
            
        default:
            break;
    }
    
    //还原到以前的模型视图矩阵(单位矩阵)
    modelViewMatrix.PopMatrix();
    
    glutSwapBuffers();
}

6. DrawWireFramedBatch(GLBatch* pBatch)自定义方法添加边框

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); 用于关闭指定的功能 功能由参数决定
     
     */
    
    //画黑色边框
    //偏移深度,在同一位置要绘制填充和边线,会产生z冲突,所以要偏移
    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.0f);
    
    /* GLShaderManager 中的Uniform 值——平面着色器
     参数1:平面着色器
     参数2:运行为几何图形变换指定一个 4 * 4变换矩阵
     --transformPipeline.GetModelViewProjectionMatrix() 获取的
     GetMatrix函数就可以获得矩阵堆栈顶部的值
     参数3:颜色值(黑色)
     */
    
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed);
    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);
}

7. 运行结果示例

运行结果示例
上一篇下一篇

猜你喜欢

热点阅读