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轴平移。
因此,对于大球来说,它是自转,而对于小球来说,它是公转。

最终总结

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

你可能感兴趣的:(OpenGL之 公转自转)