OpenGL综合案例(大小球公转自转)

先来看案例的完成效果展示


大小球公转自转

我们把整个绘制的步骤分为
初始化环境——视口调整——绘制地板——绘制大球——绘制小球——绘制公转的小球——移动视角

1.环境

首先我们先全局创建待会需要用到的实例。


GLShaderManager        shaderManager;            // 着色器管理器
GLMatrixStack        modelViewMatrix;        // 模型视图矩阵
GLMatrixStack        projectionMatrix;        // 投影矩阵
GLFrustum            viewFrustum;            // 视景体
GLGeometryTransform    transformPipeline;        // 几何图形变换管道

GLTriangleBatch        torusBatch;             //大球
GLTriangleBatch     sphereBatch;            //小球
GLBatch                floorBatch;          //地板

//角色帧 照相机角色帧
GLFrame             cameraFrame;

//**4、添加附加随机球
#define NUM_SPHERES 50
GLFrame spheres[NUM_SPHERES];

配置绘制环境

int main(int argc,char* argv[])
{
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(800,600);

    glutCreateWindow("OpenGL SphereWorld");
    
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
    
    GLenum err = glewInit();
    if(GLEW_OK != err) {
        fprintf(stderr,"glew error:%s\n",glewGetErrorString(err));
        return 1;
    }
    
    SetupRC();
    glutMainLoop();
    return 0;
}

设置视口和投影方式,初始化变换管道管理两个模型视图矩阵堆栈和投影矩阵堆栈


//屏幕更改大小或已初始化
void ChangeSize(int nWidth, int nHeight)
{
    //1.设置视口
    glViewport(0, 0, nWidth, nHeight);
    //2.创建投影矩阵
    viewFrustum.SetPerspective(35.0f, float(nWidth)/float(nHeight), 1.0f, 100.0f);
    //viewFrustum.GetProjectionMatrix()  获取viewFrustum投影矩阵
    //将获取的投影矩阵加载到投影矩阵堆栈上
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //3.设置变换管道以使用两个矩阵堆栈(变换矩阵modelViewMatrix ,投影矩阵projectionMatrix)
    //初始化GLGeometryTransform的实例transformPipeline.通过将它的内部指针设置为模型视图矩阵堆栈 和 投影矩阵堆栈实例,来完成初始化
    //这个操作也可以在SetupRC 函数中完成,但是在窗口大小改变时或者窗口创建时设置它们并没有坏处。而且这样可以一次性完成矩阵和管线的设置。
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

2. 绘制地板

我们先来画地板。

  1. 配置地板的数据。在SetupRC()中开启深度测试是为了后面球体的展示。设置地板的顶点数据和连接方式
  2. RenderScene(void)中设置地板着色器画笔颜色,清空颜色和深度缓存区,通过变换管道获取到矩阵堆栈的栈顶矩阵,开始绘制地板.
    这时候可以看到地板的效果图
    地板效果图

3. 绘制大球

  1. SetupRC()中设置大球模型gltMakeSphere(torusBatch, 0.4f, 40, 80);
  2. RenderScene(void)中绘制大球,开启自转。使用
//2.基于时间动画
    static CStopWatch rotaTimer;
    //获取当前时间*60度
    float yRot = rotaTimer.GetElapsedSeconds() * 60.0f;

会根据每次调用屏幕重新渲染的时间戳乘以角度,实现动态自转。

重点在于:

  • 在绘制地板之前压栈PushMatrix(),保存当前的OpenGL堆栈的状态。
  • 由于地板的一直静止不动的,为了更好的观察,我们将大球往z轴移动-3,往屏幕里面移动,这两步都是一直持续不变的状态,所以我们要再压一次栈保存这个状态,之后才能保证我们对大球的自转处理不会影响到地板和大球的位置。
  • 大球开启自转,用点光源着色器绘制大球,为什么选择点光源呢,这样才有真实感,球体有明暗变化,更立体。
  • 把栈顶矩阵推出栈,恢复成大球自转前的堆栈状态。

4. 绘制小球

  1. 使用gltMakeSphere(sphereBatch, 0.1f, 13, 26);设置小球模型,给定小球的位置坐标。我这里给了50个小球的坐标,在y方向,将球体设置为0.0的位置,这使得它们看起来是飘浮在眼睛的高度。x方向和z方向我们取随机值,使得小球随机放置,之后通过角色帧函数SetOrigin(x, 0.0f, z);分别给到每个小球的位置。
  2. RenderScene(void)函数中我们用for循环绘制50个小球。每一次绘制都要先压栈,保证小球绘制互不影响,将栈顶的矩阵乘以小球(模型)的矩阵,之后用点光源着色器绘制,然后pop出栈。这是每一个小球的绘制流程。

5. 绘制公转的小球

由于这一步是最后的绘制步骤,后面不会再绘制视图了,绘制完成后就整体出栈,还原成原始堆栈了,所以不需要再压栈了。

  1. modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
    直接在原点设置小球模型围绕y轴转,由于我们要明显的看到小球相对大球自转的公转,所以公转的角度倍数可以设置大点,这里我设置-2.0f,也就是两倍于大球自转的角度,并且是反方向的转。
  2. modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);再让小球距离大球往x轴方向隔开0.8的单位
  3. 开启绘制shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vLightPos, vSphereColor);
    sphereBatch.Draw();
  4. 最后整体出栈。记得PushMatrix()了几次就要用几次PopMatrix();
  5. RenderScene(void)函数最后我们要交换缓冲区glutSwapBuffers();,之后重新提交渲染glutPostRedisplay();,相当于加了一个定时器一直在刷新屏幕。

6. 键位控制移动视角

  1. main()函数里加上glutSpecialFunc(SpeacialKeys);特殊键位函数
  2. void SpeacialKeys(int key, int x, int y)函数里面,我们对键位的移动进行处理,up和down则让观察者向屏幕内外平移,left和right则让观察者围绕观察者坐标系y轴(也就是自身)旋转视角。
  3. RenderScene(void)函数的第一次压栈之后(绘制地板之前),我们需要获得观察者矩阵并压入栈,因为键位控制视角的移动,是要引起所有包括地板、大球、小球的视觉变化(观察者和物体矩阵相乘),才是真实正确的世界视角。所以必须在堆栈顶部先压入观察者的矩阵。而在最后整体pop出栈的时候也要再加上一次PopMatrix();

整个demo的代码如下:


#include <stdio.h>
#include "GLTools.h"
#include "GLMatrixStack.h"
#include "GLBatch.h"
#include "StopWatch.h"
#include "GLFrustum.h"
#include "GLGeometryTransform.h"

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

GLShaderManager        shaderManager;            // 着色器管理器
GLMatrixStack        modelViewMatrix;        // 模型视图矩阵
GLMatrixStack        projectionMatrix;        // 投影矩阵
GLFrustum            viewFrustum;            // 视景体
GLGeometryTransform    transformPipeline;        // 几何图形变换管道

GLTriangleBatch        torusBatch;             //大球
GLTriangleBatch     sphereBatch;            //小球
GLBatch                floorBatch;          //地板

//角色帧 照相机角色帧
GLFrame             cameraFrame;

//**4、添加附加随机球
#define NUM_SPHERES 50
GLFrame spheres[NUM_SPHERES];

//屏幕更改大小或已初始化
void ChangeSize(int nWidth, int nHeight)
{
    //1.设置视口
    glViewport(0, 0, nWidth, nHeight);
    //2.创建投影矩阵
    viewFrustum.SetPerspective(35.0f, float(nWidth)/float(nHeight), 1.0f, 100.0f);
    //viewFrustum.GetProjectionMatrix()  获取viewFrustum投影矩阵
    //将获取的投影矩阵加载到投影矩阵堆栈上
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //3.设置变换管道以使用两个矩阵堆栈(变换矩阵modelViewMatrix ,投影矩阵projectionMatrix)
    //初始化GLGeometryTransform的实例transformPipeline.通过将它的内部指针设置为模型视图矩阵堆栈 和 投影矩阵堆栈实例,来完成初始化
    //这个操作也可以在SetupRC 函数中完成,但是在窗口大小改变时或者窗口创建时设置它们并没有坏处。而且这样可以一次性完成矩阵和管线的设置。
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

void SetupRC()
{
    //1.初始化
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    shaderManager.InitializeStockShaders();
    
    //2.开启深度测试
    glEnable(GL_DEPTH_TEST);
    
     //3. 设置地板顶点数据
    floorBatch.Begin(GL_LINES, 324);
    
    for (GLfloat i = -20.0; i <= 20.0f; i += 0.5) {
        floorBatch.Vertex3f(i, -0.55f, 20.0f);
        floorBatch.Vertex3f(i, -0.55f, -20.0f);
        
        floorBatch.Vertex3f(20.0f, -0.55f, i);
        floorBatch.Vertex3f(-20.0f, -0.55f, i);
    }
    floorBatch.End();
    //4.设置大球模型
    gltMakeSphere(torusBatch, 0.4f, 40, 80);
    //5. 设置小球球模型
    gltMakeSphere(sphereBatch, 0.1f, 13, 26);
    //6. 随机位置放置小球
    for (int i = 0; i < NUM_SPHERES; i++) {
        //y轴不变,X,Z产生随机值
        GLfloat x = (GLfloat)((rand() % 400) - 200) * 0.1f;
        GLfloat z = (GLfloat)((rand() % 400) - 200) * 0.1f;
        //在y方向,将球体设置为0.0的位置,这使得它们看起来是飘浮在眼睛的高度
        //对spheres数组中的每一个顶点,设置顶点数据
        spheres[i].SetOrigin(x, 0.0f, z);
    }
}

//进行调用以绘制场景
void RenderScene(void) {
    //1.颜色值(地板,大球,小球颜色)
    static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f};
    static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f};
    static GLfloat vSphereColor[] = { 0.0f, 0.0f, 1.0f, 1.0f};

    //2.基于时间动画
    static CStopWatch rotaTimer;
    //获取当前时间*60度
    float yRot = rotaTimer.GetElapsedSeconds() * 60.0f;
    
    //3.清除颜色缓存区和深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //4.压栈
    modelViewMatrix.PushMatrix();
    
    //4.加入观察者 平移10步(地板,大球,小球,小小球)
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.PushMatrix(mCamera);
    
    //5.绘制地面
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor);
    
    floorBatch.Draw();
    //6.获取光源位置
    M3DVector4d vLightPos = {0.0f, 10.0f, 5.0f, 1.0f};
    //7.使得大球位置平移(3.0)向屏幕里面
    modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
    //8.压栈
    modelViewMatrix.PushMatrix();
    //9.大球自转
    modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
    //10.指定合适的着色器(点光源着色器)
    shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(), vLightPos, vTorusColor);
    torusBatch.Draw();
    //11.绘制完毕则Pop
    modelViewMatrix.PopMatrix();
    //12.画小球
    for (int i = 0; i < NUM_SPHERES; i++) {
        modelViewMatrix.PushMatrix();
        modelViewMatrix.MultMatrix(spheres[i]);
        shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(),vLightPos ,vSphereColor);
        sphereBatch.Draw();
        modelViewMatrix.PopMatrix();
    }
    
    modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
    modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
    shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vLightPos, vSphereColor);
    sphereBatch.Draw();
    
    modelViewMatrix.PopMatrix();
    modelViewMatrix.PopMatrix();
    glutSwapBuffers();
    glutPostRedisplay();
}

void SpeacialKeys(int key, int x, int y){
    //移动步长
    float linear = 0.1f;
    //旋转度数
    float angular = float(m3dDegToRad(5.0f));
    if (key == GLUT_KEY_UP) {
        //MoveForward 平移
        cameraFrame.MoveForward(linear);
    }
    if (key == GLUT_KEY_DOWN) {
        cameraFrame.MoveForward(-linear);
    }
    if (key == GLUT_KEY_LEFT) {
        //RotateWorld 旋转
        cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
    }
    if (key == GLUT_KEY_RIGHT) {
        cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
    }
}

int main(int argc,char* argv[])
{
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(800,600);

    glutCreateWindow("OpenGL SphereWorld");
    
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
    
    glutSpecialFunc(SpeacialKeys);
    
    GLenum err = glewInit();
    if(GLEW_OK != err) {
        fprintf(stderr,"glew error:%s\n",glewGetErrorString(err));
        return 1;
    }
    
    SetupRC();
    glutMainLoop();
    return 0;
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342