本篇文章主要讲解一个综合案例,类似于太阳系一样,中间一个红色大球自转, 其余的蓝色小球围绕大球公转。效果图如下:
关于OpenGL的绘制流程,前面已经介绍过了,这里不再赘述。主要还是用到了以下结果方法:
SetupRC()
RenderScene()
ChangeSize()
SpeacialKeys()
下面将主要按照绘制流程,逐步讲解绘制的代码流程。
1.画网格地板
现在SetupRC初始化数据。地板使用一条一条的线来绘制的,所以就需要的大量的线,总共需要324条线,用一个for循环来初始化数据。
void SetupRC(){
//1 初始化
glClearColor(0, 0, 0, 1);
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();
...
}
在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();
}
完成后运行结果如下:
2.画大球
同样在SetupRC中设置大球的数据模型。
//设置大球模型
gltMakeSphere(torusBatch, 0.4f, 40, 80);
绘制大球
CStopWatch类似于iOS里的定时器 GetElapsedSeconds函数返回从程序开始要现在的时间,单位为秒。乘以60意思就是一分钟转60度。6分钟转自转一圈。
为了时大球能够为观察者观察到,大球需要向后移动3个单位。此时观察者在原点位置。大球向后移动的越多,在观察者眼里,球体越小。
使用点光源着色器进行绘制大球。
记得最要两次出栈,因为我们前面进行了两次压栈操作。
//大球颜色为红色
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };
//CStopWatch 类似于iOS里的定时器 GetElapsedSeconds函数返回程序到
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
//获取光源位置
M3DVector4f vLightPos = {0.0f,10.0f,5.0f,1.0f};
//使得大球位置平移(3.0)向屏幕里面
modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
//压栈(复制栈顶)
modelViewMatrix.PushMatrix();
//大球自转
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
//指定合适的着色器(点光源着色器)
shaderManager.UseStockShader(
GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
vLightPos,
vTorusColor);
torusBatch.Draw();
//绘制完毕则Pop
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
运行效果如下:
3.画小球
设置小球模型,并设置50个随机小球位置。小球的y值都为0,这样可以保证小球全部在同一水平面上。
//设置小球球模型
gltMakeSphere(sphereBatch, 0.1f, 26, 13);
//随机位置放置小球
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);
}
画小球。 这里我们只使用了一个批次类来绘制小球, 通过循环将50个小球全部绘制完成,记得每次的压栈和出栈。
//设置小球颜色
static GLfloat vSphereColor[] = { 0.0f, 0.0f, 1.0f, 1.0f};
//循环画小球
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();
}
小球公转。 因为是小球绕着大球旋转,所以小球的旋转半径要比大球的半径要大。设置小球位移0.8f距离。并且小球的旋转速度要比大球的转速快一些。
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();
效果如下:
4. 视角移动
首先在RenderScene将视角帧压入模型视图矩阵。前边文章中我们说过,在应用任何其他模型变换之前, 必须先应⽤视图变换。对于视觉坐标系⽽言, 视图变换移动了当前的工作的坐标系; 后续的变化都会基于新调整的坐标系进行。
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
在SpeacialKeys中控制视角帧的变换。上下控制前进或后退,左右控制视角的左右旋转。
void SpeacialKeys(int key,int x,int y){
float linear = 0.1f;
float angular = float(m3dDegToRad(5.0f));
if (key == GLUT_KEY_UP) {
cameraFrame.MoveForward(linear);
}
if (key == GLUT_KEY_DOWN) {
cameraFrame.MoveForward(-linear);
}
if (key == GLUT_KEY_LEFT) {
cameraFrame.RotateWorld(angular, 0, 1, 0);
}
if (key == GLUT_KEY_RIGHT) {
cameraFrame.RotateWorld(-angular, 0, 1, 0);
}
}
最终效果就是我们文章开头所展示的那样。
完整demo链接github