大球自传小球公转案例是OpenGL比较综合的一个经典案例,案例效果如下所示,接下来我们将一步一步得来完成这个经典案例。
大球自转小球公转
这个案例主要分为4个部分:
一、绘制地板
二、绘制大球(自转)
三、绘制小球(包含50个静态小球+1个围绕大球公转的动态小球)
四、移动(特殊键位上下左右触发)
一. 绘制地板
既然刚开始绘制,首先按照OpenGL绘制图形的顺序一个一个的来
1. SetupRC函数
- 设置背景颜色
- 初始化着色器管理器
- 开启深度测试
- 绘制地板顶点数据
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 x = -20.0; x <= 20.0f; x+= 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
}
2. 窗口大小改变回调函数ChangeSize
- 设置视口
- 创建投影矩阵,并加入堆栈
- 设置变换管道
//屏幕更改大小或已初始化
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);
}
3. RenderScene函数:利用平面着色器绘制地板
- 设置地板颜色
- 清理缓冲区
- 交换缓冲区
//进行调用以绘制场景
void RenderScene(void)
{
//1.颜色值(地板,大球,小球颜色)
static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f};
//2.清除颜色缓存区和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//3.绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
//4.执行缓存区交换
glutSwapBuffers();
}
二、绘制大球(自转)
地板已经绘制好了,我们只需要在绘制地板的几个函数中加入我们的大球模型并对大球模型进行矩阵变换即可
1. SetupRC函数
- 设置大球模型
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 x = -20.0; x <= 20.0f; x+= 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
//4.设置大球模型
gltMakeSphere(torusBatch, 0.4f, 40, 80);
}
2. RenderScene函数
- 设置大球颜色
- 设置定时器:基于时间的变化,记录当前时间的角度
- 设置大球变换(仅平移一次,并随定时器旋转)及绘制大球
- 开启定时器:通过提交重新渲染请求,实现定时器触发的效果
//进行调用以绘制场景
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 };
//2.基于时间动画
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
//2.清除颜色缓存区和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//**// 压栈
modelViewMatrix.PushMatrix();
//3.绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
//4.获取光源位置
M3DVector4f vLightPos = {0.0f,10.0f,5.0f,1.0f};
//5.使得大球位置平移(3.0)向屏幕里面
modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
//6.压栈(复制栈顶)
modelViewMatrix.PushMatrix();
//7.大球自转
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
//8.指定合适的着色器(点光源着色器)
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightPos, vTorusColor);
torusBatch.Draw();
//9.绘制完毕则Pop
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
//10.执行缓存区交换
glutSwapBuffers();
glutPostRedisplay();
}
三、绘制小球(包含50个静态小球+1个围绕大球公转的动态小球)
地板和大球已经绘制好了,我们只需要在之前绘制的几个函数中加入我们的小球模型并对自传小球模型进行矩阵变换即可
1. SetupRC函数
- 设置小球模型
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 x = -20.0; x <= 20.0f; x+= 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
//4.设置大球模型
gltMakeSphere(torusBatch, 0.4f, 40, 80);
//5. 设置小球球模型
gltMakeSphere(sphereBatch, 0.1f, 26, 13);
//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);
}
}
2. RenderScene函数
- 设置小球颜色
- 绘制50个静态小球:每绘制一个小球都需要push和pop
- 绘制公转动态小球
//进行调用以绘制场景
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 rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
//3.清除颜色缓存区和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
modelViewMatrix.PushMatrix();
//4.绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
//5.获取光源位置
M3DVector4f vLightPos = {0.0f,10.0f,5.0f,1.0f};
//6.使得大球位置平移(3.0)向屏幕里面
modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
//7.压栈(复制栈顶)
modelViewMatrix.PushMatrix();
//8.大球自转
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
//9.指定合适的着色器(点光源着色器)
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightPos, vTorusColor);
torusBatch.Draw();
//10.绘制完毕则Pop
modelViewMatrix.PopMatrix();
//11.画小球
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();
}
//12. 让一个小篮球围绕大球公众自转
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vSphereColor);
sphereBatch.Draw();
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
//13.执行缓存区交换
glutSwapBuffers();
glutPostRedisplay();
}
小球公转
其实小球公转可以理解为小球自传,画面就是小球和大球一起围绕y轴自传,这样我们看不到大球,因为被我们的大球挡住了。既然是围绕大球公转,所以我们需要围绕y轴平移一段距离进行自传。这里的rotate+tranalate的顺序是不能互换的,因为矩阵相乘是叉乘,不满足交换律。具体效果可自行修改查看效果。这便是如下这段代码的含义:
//12. 让一个小篮球围绕大球公众自转
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
四、移动(特殊键位上下左右触发)
到这步,主要的功能都基本实现了,只需要在增加特殊键位的移动即可,此时的移动是作用于所有图形的,所以需要在绘制图形前增加一个观察者,用于记录所有图形的变换
首先,我们需要先在RenderScene函数矩阵堆栈顶部先加入观察者矩阵
RenderScene函数
- 加入观察者
//进行调用以绘制场景
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 rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
//3.清除颜色缓存区和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
modelViewMatrix.PushMatrix();
//4.加入观察者 平移10步(地板,大球,小球,小小球)
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
//5.获取光源位置
M3DVector4f vLightPos = {0.0f,10.0f,5.0f,1.0f};
//6.绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
//大球默认位置(0,0,0)Z深度(3.0) 正负指的是方向, 数字指的移动距离
//7.使得大球位置平移(3.0)向屏幕里面 只移动1次
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();
}
//13. 让一个小篮球围绕大球公众自转
//学员提问: 为什么这里老师没有加PushMatrix/PopMatrix ,是可以加的.但是因为是最后的绘图因为没有不会影响就么有添加. 这边给大家添加一组.
modelViewMatrix.PushMatrix();
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();
modelViewMatrix.PushMatrix();
//14.执行缓存区交换
glutSwapBuffers();
//15
glutPostRedisplay();
}
SpeacialKeys函数
- 键位变换:上下 -> 平移 左右 -> 旋转
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);
}
}
到此为止,我们的大球自传小球公转的综合练习就完成了。
Demo:
OpenGLComplexDemo
网友评论