OpenGL綜合案例(大小球公轉(zhuǎn)自轉(zhuǎn))

先來(lái)看案例的完成效果展示


大小球公轉(zhuǎn)自轉(zhuǎn)

我們把整個(gè)繪制的步驟分為
初始化環(huán)境——視口調(diào)整——繪制地板——繪制大球——繪制小球——繪制公轉(zhuǎn)的小球——移動(dòng)視角

1.環(huán)境

首先我們先全局創(chuàng)建待會(huì)需要用到的實(shí)例。


GLShaderManager        shaderManager;            // 著色器管理器
GLMatrixStack        modelViewMatrix;        // 模型視圖矩陣
GLMatrixStack        projectionMatrix;        // 投影矩陣
GLFrustum            viewFrustum;            // 視景體
GLGeometryTransform    transformPipeline;        // 幾何圖形變換管道

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

//角色幀 照相機(jī)角色幀
GLFrame             cameraFrame;

//**4、添加附加隨機(jī)球
#define NUM_SPHERES 50
GLFrame spheres[NUM_SPHERES];

配置繪制環(huán)境

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;
}

設(shè)置視口和投影方式,初始化變換管道管理兩個(gè)模型視圖矩陣堆棧和投影矩陣堆棧


//屏幕更改大小或已初始化
void ChangeSize(int nWidth, int nHeight)
{
    //1.設(shè)置視口
    glViewport(0, 0, nWidth, nHeight);
    //2.創(chuàng)建投影矩陣
    viewFrustum.SetPerspective(35.0f, float(nWidth)/float(nHeight), 1.0f, 100.0f);
    //viewFrustum.GetProjectionMatrix()  獲取viewFrustum投影矩陣
    //將獲取的投影矩陣加載到投影矩陣堆棧上
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //3.設(shè)置變換管道以使用兩個(gè)矩陣堆棧(變換矩陣modelViewMatrix ,投影矩陣projectionMatrix)
    //初始化GLGeometryTransform的實(shí)例transformPipeline.通過將它的內(nèi)部指針設(shè)置為模型視圖矩陣堆棧 和 投影矩陣堆棧實(shí)例,來(lái)完成初始化
    //這個(gè)操作也可以在SetupRC 函數(shù)中完成,但是在窗口大小改變時(shí)或者窗口創(chuàng)建時(shí)設(shè)置它們并沒有壞處。而且這樣可以一次性完成矩陣和管線的設(shè)置。
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

2. 繪制地板

我們先來(lái)畫地板。

  1. 配置地板的數(shù)據(jù)。在SetupRC()中開啟深度測(cè)試是為了后面球體的展示。設(shè)置地板的頂點(diǎn)數(shù)據(jù)和連接方式
  2. RenderScene(void)中設(shè)置地板著色器畫筆顏色,清空顏色和深度緩存區(qū),通過變換管道獲取到矩陣堆棧的棧頂矩陣,開始繪制地板.
    這時(shí)候可以看到地板的效果圖
    地板效果圖

3. 繪制大球

  1. SetupRC()中設(shè)置大球模型gltMakeSphere(torusBatch, 0.4f, 40, 80);
  2. RenderScene(void)中繪制大球,開啟自轉(zhuǎn)。使用
//2.基于時(shí)間動(dòng)畫
    static CStopWatch rotaTimer;
    //獲取當(dāng)前時(shí)間*60度
    float yRot = rotaTimer.GetElapsedSeconds() * 60.0f;

會(huì)根據(jù)每次調(diào)用屏幕重新渲染的時(shí)間戳乘以角度,實(shí)現(xiàn)動(dòng)態(tài)自轉(zhuǎn)。

重點(diǎn)在于:

  • 在繪制地板之前壓棧PushMatrix(),保存當(dāng)前的OpenGL堆棧的狀態(tài)。
  • 由于地板的一直靜止不動(dòng)的,為了更好的觀察,我們將大球往z軸移動(dòng)-3,往屏幕里面移動(dòng),這兩步都是一直持續(xù)不變的狀態(tài),所以我們要再壓一次棧保存這個(gè)狀態(tài),之后才能保證我們對(duì)大球的自轉(zhuǎn)處理不會(huì)影響到地板和大球的位置。
  • 大球開啟自轉(zhuǎn),用點(diǎn)光源著色器繪制大球,為什么選擇點(diǎn)光源呢,這樣才有真實(shí)感,球體有明暗變化,更立體。
  • 把棧頂矩陣推出棧,恢復(fù)成大球自轉(zhuǎn)前的堆棧狀態(tài)。

4. 繪制小球

  1. 使用gltMakeSphere(sphereBatch, 0.1f, 13, 26);設(shè)置小球模型,給定小球的位置坐標(biāo)。我這里給了50個(gè)小球的坐標(biāo),在y方向,將球體設(shè)置為0.0的位置,這使得它們看起來(lái)是飄浮在眼睛的高度。x方向和z方向我們?nèi)‰S機(jī)值,使得小球隨機(jī)放置,之后通過角色幀函數(shù)SetOrigin(x, 0.0f, z);分別給到每個(gè)小球的位置。
  2. RenderScene(void)函數(shù)中我們用for循環(huán)繪制50個(gè)小球。每一次繪制都要先壓棧,保證小球繪制互不影響,將棧頂?shù)木仃嚦艘孕∏颍P停┑木仃?,之后用點(diǎn)光源著色器繪制,然后pop出棧。這是每一個(gè)小球的繪制流程。

5. 繪制公轉(zhuǎn)的小球

由于這一步是最后的繪制步驟,后面不會(huì)再繪制視圖了,繪制完成后就整體出棧,還原成原始堆棧了,所以不需要再壓棧了。

  1. modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
    直接在原點(diǎn)設(shè)置小球模型圍繞y軸轉(zhuǎn),由于我們要明顯的看到小球相對(duì)大球自轉(zhuǎn)的公轉(zhuǎn),所以公轉(zhuǎn)的角度倍數(shù)可以設(shè)置大點(diǎn),這里我設(shè)置-2.0f,也就是兩倍于大球自轉(zhuǎn)的角度,并且是反方向的轉(zhuǎn)。
  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)函數(shù)最后我們要交換緩沖區(qū)glutSwapBuffers();,之后重新提交渲染glutPostRedisplay();,相當(dāng)于加了一個(gè)定時(shí)器一直在刷新屏幕。

6. 鍵位控制移動(dòng)視角

  1. main()函數(shù)里加上glutSpecialFunc(SpeacialKeys);特殊鍵位函數(shù)
  2. void SpeacialKeys(int key, int x, int y)函數(shù)里面,我們對(duì)鍵位的移動(dòng)進(jìn)行處理,up和down則讓觀察者向屏幕內(nèi)外平移,left和right則讓觀察者圍繞觀察者坐標(biāo)系y軸(也就是自身)旋轉(zhuǎn)視角。
  3. RenderScene(void)函數(shù)的第一次壓棧之后(繪制地板之前),我們需要獲得觀察者矩陣并壓入棧,因?yàn)殒I位控制視角的移動(dòng),是要引起所有包括地板、大球、小球的視覺變化(觀察者和物體矩陣相乘),才是真實(shí)正確的世界視角。所以必須在堆棧頂部先壓入觀察者的矩陣。而在最后整體pop出棧的時(shí)候也要再加上一次PopMatrix();

整個(gè)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;          //地板

//角色幀 照相機(jī)角色幀
GLFrame             cameraFrame;

//**4、添加附加隨機(jī)球
#define NUM_SPHERES 50
GLFrame spheres[NUM_SPHERES];

//屏幕更改大小或已初始化
void ChangeSize(int nWidth, int nHeight)
{
    //1.設(shè)置視口
    glViewport(0, 0, nWidth, nHeight);
    //2.創(chuàng)建投影矩陣
    viewFrustum.SetPerspective(35.0f, float(nWidth)/float(nHeight), 1.0f, 100.0f);
    //viewFrustum.GetProjectionMatrix()  獲取viewFrustum投影矩陣
    //將獲取的投影矩陣加載到投影矩陣堆棧上
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //3.設(shè)置變換管道以使用兩個(gè)矩陣堆棧(變換矩陣modelViewMatrix ,投影矩陣projectionMatrix)
    //初始化GLGeometryTransform的實(shí)例transformPipeline.通過將它的內(nèi)部指針設(shè)置為模型視圖矩陣堆棧 和 投影矩陣堆棧實(shí)例,來(lái)完成初始化
    //這個(gè)操作也可以在SetupRC 函數(shù)中完成,但是在窗口大小改變時(shí)或者窗口創(chuàng)建時(shí)設(shè)置它們并沒有壞處。而且這樣可以一次性完成矩陣和管線的設(shè)置。
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

void SetupRC()
{
    //1.初始化
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    shaderManager.InitializeStockShaders();
    
    //2.開啟深度測(cè)試
    glEnable(GL_DEPTH_TEST);
    
     //3. 設(shè)置地板頂點(diǎn)數(shù)據(jù)
    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.設(shè)置大球模型
    gltMakeSphere(torusBatch, 0.4f, 40, 80);
    //5. 設(shè)置小球球模型
    gltMakeSphere(sphereBatch, 0.1f, 13, 26);
    //6. 隨機(jī)位置放置小球
    for (int i = 0; i < NUM_SPHERES; i++) {
        //y軸不變,X,Z產(chǎn)生隨機(jī)值
        GLfloat x = (GLfloat)((rand() % 400) - 200) * 0.1f;
        GLfloat z = (GLfloat)((rand() % 400) - 200) * 0.1f;
        //在y方向,將球體設(shè)置為0.0的位置,這使得它們看起來(lái)是飄浮在眼睛的高度
        //對(duì)spheres數(shù)組中的每一個(gè)頂點(diǎn),設(shè)置頂點(diǎn)數(shù)據(jù)
        spheres[i].SetOrigin(x, 0.0f, z);
    }
}

//進(jìn)行調(diào)用以繪制場(chǎng)景
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.基于時(shí)間動(dòng)畫
    static CStopWatch rotaTimer;
    //獲取當(dāng)前時(shí)間*60度
    float yRot = rotaTimer.GetElapsedSeconds() * 60.0f;
    
    //3.清除顏色緩存區(qū)和深度緩沖區(qū)
    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.大球自轉(zhuǎn)
    modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
    //10.指定合適的著色器(點(diǎn)光源著色器)
    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){
    //移動(dòng)步長(zhǎng)
    float linear = 0.1f;
    //旋轉(zhuǎn)度數(shù)
    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 旋轉(zhuǎn)
        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;
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容