首先我们来看效果
公转自转效果.gif
由上图,我们可以看到,地面由蓝色的网格线组成,最中心有一个大球,地面上分布着小球,而有一个小球围着大球在再转。
渲染流程
渲染流程与之前的OpenGL使用小案例--绘制正方形并进行移动流程是一样的。
从main开始进入程序,我们在main函数中做了这么几件事:
- setupRC(),设置我们的渲染环境,这是做为绘画进行一些准备工作,设置背景色,初始化shaderManager等等。
2.注册函数,GLUT 内部运行一个本地消息循环,拦截适当的消息。然后调用我们不同时间注册的回调函数。这就相当于iOS的block以及代理,在特定的时间会調起这些函数。
- 注册重塑函数:glutReshapeFunc(changeSize);,这个函数会在两个时间点进行触发:
1.程序运行时;
2.窗口大小改变时。 - 注册显示函数:glutDisplayFunc(RenderScene);这是整个项目的核心内容,在这个函数里,我们会进行图形的绘制。
- 注册特殊函数:glutSpecialFunc(SpecialKeys);这个函数中,我们点击特殊按钮,来改变图形位置,让图形移动。
- glutMainLoop(),这个方法就是为程序提供一个runloop,保证程序处在一个循环中。
准备工作
首先我们需要定义一些变量。
- 我们定义了3个批次类,为
torusBatch
,sphereBatch
floorBatch
,分别对应大球,小球和地板。 - 通过宏定义
#define NUM_SPHERES 50
定义了50个小球。
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];
Main()
- 定义窗口大小
glutInitWindowSize(800,600);
- 注册几个函数,
glutReshapeFunc(ChangeSize);
设置视口
glutDisplayFunc(RenderScene);
具体绘制
glutSpecialFunc(SpeacialKeys);
特殊键位 -
SetupRC();
初始化项目 -
glutMainLoop();
运行一个循环,以免程序退出
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;
}
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);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//3. 变换管道设置2个矩阵堆栈(管理)
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
绘制地板
void SetupRC()
首页在SetupRC中做些初始化的工作
- 清楚缓存中的颜色,并设置背景色为黑色
- 初始化固定着色器
shaderManager
- 注意,由于项目中绘制了立体图形,所以要进行深度测试
glEnable(GL_DEPTH_TEST);
大家可以去看正背面剔除、深度测试这篇文章,具体讲到了深度测试。
//1. 初始化
glClearColor(0, 0, 0, 1);
shaderManager.InitializeStockShaders();
glEnable(GL_DEPTH_TEST);
接下来,将地板数据放入floorBatch
这个批次类中。
//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();
void SetupRC()
接下来绘制地板,我们使用平面着色器进行渲染
但是,首先,清除缓存
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
否则,页面会显示不出样式。
//4.地面绘制;
//定义地板线条颜色
static GLfloat vFloorColor[] = {0.0f,1.0f,0.0f,1.0f};
shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vFloorColor);
floorBatch.Draw();
还有,渲染完成后别忘了glutSwapBuffers();
进行缓存区交换,从而渲染到屏幕上。
绘制大球
void setupRC()
由于OpenGL为我们提供了绘制球的方法,所以在setupRC
中我们只需要做一件事
//设置好球的参数后将参数存入torusBatch中
gltMakeSphere(torusBatch, 0.4f, 40, 80);
void RenderScene(void)
现在我们要让这个球进行自转,所以如下操作
- 首先,我们要在最前面压一个空栈
//压一个空栈
modelViewMatrix.PushMatrix();
为什么要PushMatrix呢?因为我们这次绘制到屏幕之后,不能对下次操作产生影响,所以压一个栈让每次绘制到处于原始状态。
- 然后,我们要让球往里面移动,否则看不到
//移动
modelViewMatrix.Translate(0, 0, -3);
- 接下来,我们让球旋转
//定时器
static CStopWatch roTimer;
float yRot = roTimer.GetElapsedSeconds()*60.0f;
//
modelViewMatrix.Rotate(yRot, 0, 1, 0);
- 最后绘制大球
//设置点光源的点
M3DVector4f vLightPos = {0,10,5,1};
//使用点光源着色器
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vLightPos,vTorusColor);
//绘制
torusBatch.Draw();
modelViewMatrix.PopMatrix();
但这个时候,球并不会转,因为我们并没有调用RenderScene
,调用
glutPostRedisplay();
让球进行自转。效果如下:
绘制大球.png
绘制小球
void setupRC()
我们随机定义50个小球,如下
gltMakeSphere(sphereBatch, 0.1f, 30, 60);
for (int i = 0 ; i<NUM_SPHERES; i++) {
//Y值固定,x,z值随机
GLfloat x = ((GLfloat)((random() % 400) - 200) *0.1f);
GLfloat z = ((GLfloat)((random() % 400) - 200) *0.1f);
spheres[i].SetOrigin(x,0.0f,z);
}
我们随机定义球的x和z轴的坐标,而固定y轴的坐标保证球在同一个平面上。
void RenderScene(void)
我们for
循环的将小球绘制到屏幕上
注意,push跟pop要成对出现。
for (int i = 0 ; i<NUM_SPHERES; i++) {
modelViewMatrix.PushMatrix(spheres[i]);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vLightPos,vSphereColor);
sphereBatch.Draw();
modelViewMatrix.PopMatrix();
}
绘制旋转小球
此时我们仍然可以使用之前定义的sphereBatch
具体代码如下
//旋转
modelViewMatrix.Rotate(yRot * -2, 0, 1, 0);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vLightPos,vSphereColor);
sphereBatch.Draw();
此时不用push和pop,因为这是最后一个绘制,并不会影响前面的绘制
接入观察者
void SpecialKeys(int key, int x, int y)
我们用上下左右键位对cameraFrame进行控制
void SpecialKeys(int key, int x, int y){
float linear = 0.1f;
float angular = float(m3dDegToRad(5.0));
//往外移动观察者
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.0, 1.0, 0.0);
}
//观察者视角往右移动
if (key == GLUT_KEY_RIGHT) {
cameraFrame.RotateWorld(-angular, 0.0, 1.0, 0.0);
}
}
最后把观察者压栈放入矩阵中
void RenderScene(void)
//将观察者放入矩阵中
M3DMatrix44f mCamare;
cameraFrame.GetCameraMatrix(mCamare);
modelViewMatrix.PushMatrix(mCamare);
注意,push跟pop要成对出现
modelViewMatrix.PopMatrix();
运行后就是我们最终的效果了。
网友评论