先來(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)畫地板。
- 配置地板的數(shù)據(jù)。在
SetupRC()中開啟深度測(cè)試是為了后面球體的展示。設(shè)置地板的頂點(diǎn)數(shù)據(jù)和連接方式 -
RenderScene(void)中設(shè)置地板著色器畫筆顏色,清空顏色和深度緩存區(qū),通過變換管道獲取到矩陣堆棧的棧頂矩陣,開始繪制地板.
這時(shí)候可以看到地板的效果圖
地板效果圖
3. 繪制大球
- 在
SetupRC()中設(shè)置大球模型gltMakeSphere(torusBatch, 0.4f, 40, 80); - 在
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. 繪制小球
- 使用
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è)小球的位置。 - 在
RenderScene(void)函數(shù)中我們用for循環(huán)繪制50個(gè)小球。每一次繪制都要先壓棧,保證小球繪制互不影響,將棧頂?shù)木仃嚦艘孕∏颍P停┑木仃?,之后用點(diǎn)光源著色器繪制,然后pop出棧。這是每一個(gè)小球的繪制流程。
5. 繪制公轉(zhuǎn)的小球
由于這一步是最后的繪制步驟,后面不會(huì)再繪制視圖了,繪制完成后就整體出棧,還原成原始堆棧了,所以不需要再壓棧了。
-
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)。 -
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);再讓小球距離大球往x軸方向隔開0.8的單位 - 開啟繪制
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vLightPos, vSphereColor);
sphereBatch.Draw(); - 最后整體出棧。記得
PushMatrix()了幾次就要用幾次PopMatrix(); -
RenderScene(void)函數(shù)最后我們要交換緩沖區(qū)glutSwapBuffers();,之后重新提交渲染glutPostRedisplay();,相當(dāng)于加了一個(gè)定時(shí)器一直在刷新屏幕。
6. 鍵位控制移動(dòng)視角
- 在
main()函數(shù)里加上glutSpecialFunc(SpeacialKeys);特殊鍵位函數(shù) - 在
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)視角。 - 在
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;
}
