OpenGL之 公转自转

本篇将会介绍一个大球的自转以及一个小球围绕大球公转的demo,效果如下图:

公转自转.gif

实现过程

image.png

如上图所示,整个项目的基本流程较之前几个例子没有太多的变化。都是:

  • 初始化窗口;
  • 注册各函数的监听,如 重塑函数、重绘函数等;
  • 调用setupRC,初始化窗口背景、着色器管理器、顶点数据等;
  • 开启glut的mainloop,类似iOS的runloop。

1、SetupRC方法

#pragma mark - 顶点数据私有方法
void vertextDataFloor() {
    floorBatch.Begin(GL_LINES, 324);
    
    /*
     GL_LINES  每两个点画一条线
     画法是
     第一次循环
     (-20,-0.5,20) 到(-20,-0.5,-20) 画一条直线
     (20,-0.5,-20)到(-20,-0.5,-20)画一条直线
     第二次循环
     (-19.5,-0.5,20) 到(-19.5,-0.5,-20) 画一条直线
     (20,-0.5,-19.5)到(-20,-0.5,-19.5)画一条直线
     
     直到最后一次循环,闭合整个网格
     */
    for (GLfloat x = -20.f; x <= 20.f; x+=0.5f) {
        floorBatch.Vertex3f(-x, commonY, 20.f);
        floorBatch.Vertex3f(-x, commonY, -20.f);
        
        floorBatch.Vertex3f(20.f, commonY, x);
        floorBatch.Vertex3f(-20.f, commonY, x);
    }
    
    floorBatch.End();
}

void vertextDataBigBall() {
    gltMakeSphere(torusBatch, 0.5f, 40, 80);
}

void vertextDataSmallBallRotate() {
    gltMakeSphere(sphereBatch, 0.2, 20, 40);
}

void vertextDataRandomSmallBall(){
    for (int i = 0; i < SMALL_BALL_NUMBER; i ++) {
        GLfloat randowX = ((random()%400) - 200) * 0.1;
        GLfloat randowZ = ((random()%400) - 200) * 0.1;
        sphereFrames[i].SetOrigin(randowX,0,randowZ);
    }
}

void SetupRC() {
    glClearColor(1, 1, 1, 1);
    
    glEnable(GL_LINE_SMOOTH);
    shaderManager.InitializeStockShaders();
    
    vertextDataFloor();
    
    vertextDataBigBall();
    
    vertextDataSmallBallRotate();
    
    vertextDataRandomSmallBall();
}

在这个方法中,主要做一些初始化的工作,如初始化窗口背景色、着色器管理器、顶点数据等。

2、ChangeSize方法

void ChangeSize(int nWidth, int nHeight) {
    glViewport(0, 0, nWidth, nHeight);
    viewFrustum.SetPerspective(35.f, float(nWidth)/float(nHeight), 1.f, 500.f);
    
    //将投影矩阵载入投影矩阵堆栈
    projectionMatrixStack.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //可以不调用,默认会有一个单元矩阵
    modelViewMatrixStack.LoadIdentity();
    transformPipeline.SetMatrixStacks(modelViewMatrixStack, projectionMatrixStack);
    
}

如代码所示,在这个方法中依然是常规的设置视口的位置和大小、设置投影方式、载入投影矩阵到投影矩阵堆栈、载入单元矩阵进模型视图矩阵堆栈,然后将这两个堆栈设置到变化管道对象里,方便使用和管理。

3、RenderScence方法

void RenderScence() {
    //GL_STENCIL_BUFFER_BIT 在这个demo中,可以不清
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    //开启深度测试
    glEnable(GL_DEPTH_TEST);
    
    modelViewMatrixStack.PushMatrix();
    
    //画地面
    static GLfloat vBlue[] = {0.f,0.5f,1.f,1.f};
    static GLfloat vBigBall[] = {0,0,1,1};
    static GLfloat vSmallBall[] = {1,0.5,0,1};
    static GLfloat vSmallBallRandom[] = {0,0.5,1,1};
    
    static CStopWatch watchObj;
    GLfloat angle = watchObj.GetElapsedSeconds() * 60.f;
    
    M3DMatrix44f viewMatrix;
    viewFrame.GetCameraMatrix(viewMatrix);
    modelViewMatrixStack.MultMatrix(viewMatrix); 
shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vBlue);
    floorBatch.Draw();
    
    //栈顶矩阵z轴负方向方向平移
    modelViewMatrixStack.Translate(0, 0, -3);
    
    //复制一份,画完大球之后,pops栈顶数据,然后栈顶数据是设置过平移的矩阵
    modelViewMatrixStack.PushMatrix();
    modelViewMatrixStack.Rotate(angle, 0, 1, 0);
    
    M3DVector4f pointPosition = {0,10,5,1};
    shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),
                                 transformPipeline.GetProjectionMatrix(),pointPosition,vBigBall);
    
    torusBatch.Draw();
    
    modelViewMatrixStack.PopMatrix();
    
    
    for (int i = 0; i < SMALL_BALL_NUMBER; i ++) {
        GLFrame frame = sphereFrames[i];
        modelViewMatrixStack.PushMatrix();
        
        modelViewMatrixStack.MultMatrix(frame);
        
        shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),
                                     transformPipeline.GetProjectionMatrix(),pointPosition,vSmallBallRandom);
        sphereBatch.Draw();
        
        modelViewMatrixStack.PopMatrix();
    }
    
    
    //无需压栈,因为这是当前绘制的最后一个图形
    modelViewMatrixStack.Rotate(angle * -2.f, 0, 1, 0);
    //z值的绝对值越大,离大球越远。
    modelViewMatrixStack.Translate(0, 0, 1.1);
    
    shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),
                                 transformPipeline.GetProjectionMatrix(),pointPosition,vSmallBall);
    
    sphereBatch.Draw();
    
    modelViewMatrixStack.PopMatrix();
    
    glutSwapBuffers();
    glutPostRedisplay();
    //关闭深度测试
    glDisable(GL_DEPTH_TEST);
}

这个方法是本次案例中,与之前的案例相比最大的方法。

3.1 画地板
  • 清理颜色缓存区和深度缓存区,这里的模板缓存区在本demo中可以不清。
  • 开启深度测试
  • 栈顶矩阵copy一份压栈
  • 初始化各颜色值以及一个定时器,定时器用于计算当前旋转角度
  • 从观察者角色帧获取观察者矩阵,并用当前模型视图矩阵堆栈栈顶矩阵乘以观察者矩阵,得到结果覆盖栈顶矩阵
  • 使用平面着色器处理数据
  • 通过批次类画出地板
  • 在全部图形绘制完之后,会交换缓存区
3.2 画大球
  • 栈顶矩阵沿z轴负方向移动3;
  • 栈顶矩阵copy一份压栈(后续计算完成出栈之后,栈顶矩阵依然是之前沿着z轴负方向移动3之后的矩阵)
  • 沿着y轴旋转
  • 设置光源位置
  • 使用点光源着色器处理数据
  • 通过三角形批次类画出大球
  • 针对最近一次的入栈操作进行出栈,保证栈顶矩阵为之前沿着z轴负方向移动3之后的矩阵。
  • 在全部图形绘制完之后,会交换缓存区
3.3 画多个分散的小球

在上述的SetupRC方法中调用的vertextDataRandomSmallBall方法里,做了很多顶点数据的初始化,这些就是随机小球的位置数据。
在RenderScence方法中也需要将他们绘制出来。

  • 开启for循环
  • 每次循环都复制一份栈顶矩阵压栈
  • 将栈顶矩阵乘以当前小球的角色帧数据,赋值给栈顶数据
  • 使用点光源着色器处理数据
  • 使用三角形批次类进行绘制
  • 将每次循环的栈顶矩阵出栈
  • 在全部图形绘制完之后,会交换缓存区
3.4 画公转的小球
  • 栈顶矩阵绕y轴旋转一定的角度,角度根据当前的计时器的时间来计算
  • 栈顶矩阵沿z轴平移操作,无论正负,绝对值越大,离大球越远
  • 使用点光源着色器处理数据
  • 使用三角形批次类进行绘制
  • 将当前栈顶矩阵出
3.5 收尾
  • 交换缓存区
  • 提交重新渲染,保证重复调用RenderScence方法,形成旋转动画
  • 关闭深度测试

总结:
RenderScence方法中,会有各种矩阵变换的计算操作,将原始的顶点数据,经过各种变换,全部到达一个新的位置,最终实现我们要的效果。在计算的过程中,需要注意的几个点:

  • 整体的入栈和出栈要成对出现,有入栈就必须有出栈,否则下次绘制的时候数据会错乱
  • 对于整个变换过程,拿公转小球举例,虽然它是经过了
modelViewMatrixStack.Translate(0, 0, -3);
modelViewMatrixStack.Rotate(angle * -2.f, 0, 1, 0);
modelViewMatrixStack.Translate(0, 0, 1.1);

这三部,但是在OpenGL实现的时候,由于OpenGL采用的是右乘的方式,所以,其实小球是经过了先z轴平移,再旋转,再z轴平移的操作。
而对于大球来说,它经过了

modelViewMatrixStack.Translate(0, 0, -3);
modelViewMatrixStack.PushMatrix();
modelViewMatrixStack.Rotate(angle, 0, 1, 0);

这三部操作,而OpenGL实现的时候,其实大球是先旋转,再z轴平移。
因此,对于大球来说,它是自转,而对于小球来说,它是公转。

最终总结

上述为个人实现与总结,如有错漏之处,欢迎并感谢批评指正。

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