先来看案例的完成效果展示
我们把整个绘制的步骤分为
初始化环境——视口调整——绘制地板——绘制大球——绘制小球——绘制公转的小球——移动视角
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. 绘制地板
我们先来画地板。
- 配置地板的数据。在
SetupRC()
中开启深度测试是为了后面球体的展示。设置地板的顶点数据和连接方式 -
RenderScene(void)
中设置地板着色器画笔颜色,清空颜色和深度缓存区,通过变换管道获取到矩阵堆栈的栈顶矩阵,开始绘制地板.
这时候可以看到地板的效果图
3. 绘制大球
- 在
SetupRC()
中设置大球模型gltMakeSphere(torusBatch, 0.4f, 40, 80);
- 在
RenderScene(void)
中绘制大球,开启自转。使用
//2.基于时间动画
static CStopWatch rotaTimer;
//获取当前时间*60度
float yRot = rotaTimer.GetElapsedSeconds() * 60.0f;
会根据每次调用屏幕重新渲染的时间戳乘以角度,实现动态自转。
重点在于:
- 在绘制地板之前压栈
PushMatrix()
,保存当前的OpenGL堆栈的状态。 - 由于地板的一直静止不动的,为了更好的观察,我们将大球往z轴移动-3,往屏幕里面移动,这两步都是一直持续不变的状态,所以我们要再压一次栈保存这个状态,之后才能保证我们对大球的自转处理不会影响到地板和大球的位置。
- 大球开启自转,用点光源着色器绘制大球,为什么选择点光源呢,这样才有真实感,球体有明暗变化,更立体。
- 把栈顶矩阵推出栈,恢复成大球自转前的堆栈状态。
4. 绘制小球
- 使用
gltMakeSphere(sphereBatch, 0.1f, 13, 26);
设置小球模型,给定小球的位置坐标。我这里给了50个小球的坐标,在y方向,将球体设置为0.0的位置,这使得它们看起来是飘浮在眼睛的高度。x方向和z方向我们取随机值,使得小球随机放置,之后通过角色帧函数SetOrigin(x, 0.0f, z);
分别给到每个小球的位置。 - 在
RenderScene(void)
函数中我们用for
循环绘制50个小球。每一次绘制都要先压栈,保证小球绘制互不影响,将栈顶的矩阵乘以小球(模型)的矩阵,之后用点光源着色器绘制,然后pop出栈。这是每一个小球的绘制流程。
5. 绘制公转的小球
由于这一步是最后的绘制步骤,后面不会再绘制视图了,绘制完成后就整体出栈,还原成原始堆栈了,所以不需要再压栈了。
-
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
直接在原点设置小球模型围绕y轴转,由于我们要明显的看到小球相对大球自转的公转,所以公转的角度倍数可以设置大点,这里我设置-2.0f,也就是两倍于大球自转的角度,并且是反方向的转。 -
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)
函数最后我们要交换缓冲区glutSwapBuffers();
,之后重新提交渲染glutPostRedisplay();
,相当于加了一个定时器一直在刷新屏幕。
6. 键位控制移动视角
- 在
main()
函数里加上glutSpecialFunc(SpeacialKeys);
特殊键位函数 - 在
void SpeacialKeys(int key, int x, int y)
函数里面,我们对键位的移动进行处理,up和down则让观察者向屏幕内外平移,left和right则让观察者围绕观察者坐标系y轴(也就是自身)旋转视角。 - 在
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;
}