实现效果:
能随着观察者的不断移动,都能观察到小球的公转加自转
最终图片效果:
大体实现思路及步骤拆分:
1、先实现绿色方格地板部分
2、实现固定自转的红色大球和一个中型的蓝球,以及位置随机的50个小蓝球
3、实现中型蓝球绕红色大球的公转
4、实现移动观察者(camera)仍能观察红球自转及蓝球围绕红球公转
下面开始代码部分
一、先定义一些变量
GLShaderManager shaderManager; // 定义一个着色器管理器
GLMatrixStack modelViewMatrix; // 定义一个模型视图矩阵
GLMatrixStack projectionMatrix; // 定义一个投影矩阵
GLFrustum viewFrustum; // 视景体
GLGeometryTransform transformPipeline; // 几何图形变换管道
GLTriangleBatch torusBatch; //批次类 -大球
GLTriangleBatch sphereBatch; //批次类 -小球
GLBatch floorBatch; //简单的批次类 -地板
//角色帧 照相机角色帧
GLFrame cameraFrame;
GLFrame objectFrame;
//**4、添加附加随机球
#define NUM_SPHERES50
GLFrame spheres[NUM_SPHERES];
二、在主函数中初始化一些特殊键位操作以及设置窗口默认参数,注册变化函数
int main (int argc, char* argv[])
{
//1、设置当前工作目录,针对MAC OS X /* `GLTools`函数`glSetWorkingDrectory`用来设置当前工作目录。实际上在Windows中是不必要的,因为工作目录默认就是与程序可执行执行程序相同的目录。但是在Mac OS X中,这个程序将当前工作文件夹改为应用程序捆绑包中的`/Resource`文件夹。`GLUT`的优先设定自动进行了这个中设置,但是这样中方法更加安全。 */
gltSetWorkingDirectory(argv[0]);
//2.初始化一个GLUT库
glutInit(&argc, argv);
/* 3、 初始化双缓冲窗口,其中标志GLUT_DOUBLE、GLUT_RGBA、GLUT_DEPTH、GLUT_STENCIL分别指 双缓冲窗口、RGBA颜色模式、深度测试、模板缓冲区
--GLUT_DOUBLE`:双缓存窗口,是指绘图命令实际上是离屏缓存区执行的,然后迅速转换成窗口视图,这种方式,经常用来生成动画效果;
--GLUT_DEPTH`:标志将一个深度缓存区分配为显示的一部分,因此我们能够执行深度测试;
--GLUT_STENCIL`:确保我们也会有一个可用的模板缓存区。 深度、模板测试后面会细致讲到
*/
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
//3、GLUT窗口大小、窗口标题
glutInitWindowSize(800,600);
glutCreateWindow("OpenGL SphereWorld");
/*4、 GLUT 内部运行一个本地消息循环,拦截适当的消息。然后调用我们不同时间注册的回调函数。我们一共注册2个回调函数:
1)为窗口改变大小而设置的一个回调函数
2)包含OpenGL 渲染的回调函数 */
glutReshapeFunc(ChangeSize);//注册函数,在窗口改变大小的时候调用
glutDisplayFunc(RenderScene);//注册渲染函数
glutSpecialFunc(SpeacialKeys);//注册特殊函数
/* 5、 初始化一个GLEW库,确保OpenGL API对程序完全可用。 在试图做任何渲染之前,要检查确定驱动程序的初始化过程中没有任何问题 */
GLenumerr =glewInit();
if(GLEW_OK!= err) {
fprintf(stderr,"GLEW Error: %s\n",glewGetErrorString(err));
return1;
}
//6、设置我们的渲染环境
SetupRC();
//7、这是一个无限执行的循环,它会负责一直处理窗口和操作系统的用户输入等操作。(注意:不会执行在glutMainLoop()之后的所有命令。)
glutMainLoop();
return 0;
}
三、开始设置我们的渲染环境
void SetupRC()
{
//1.设置清屏颜色(背景颜色)
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
//2.初始化着色器
//没有着色器,在OpenGL 核心框架中是无法进行任何渲染的。初始化一 个渲染管理器。目前我们使用固定管线渲染,后面会用OpenGL着色语言来写着色器
shaderManager.InitializeStockShaders();
//3.修改物体的初始坐标,往后移动5.0f的距离也就是z为-5,z轴垂直屏幕指向我们观察者为正,z为-5,就是往屏幕里面移动5
objectFrame.MoveForward(5.0f);
//4.开启深度测试,3D图像渲染需要开启深度测试
glEnable(GL_DEPTH_TEST);
//5.开始设置地板的顶点数据
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();
//6.设置大球模型
gltMakeSphere(torusBatch, 0.4f, 40, 80);
//7.设置小球模型
gltMakeSphere(torusBatch, 0.1f, 26, 13);
//8、随机位置放置小球
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 rotTimer;
float yRot = rotTimer.GetElapsedSeconds() *60.0f;
//3.清除颜色缓存区和深度缓冲区,防止残存的颜色和深度值影响渲染
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//4、加入objectFrame,用于特殊键位操作观察者位置移动
modelViewMatrix.PushMatrix(objectFrame);
//5.获取光源位置
M3DVector4fvLightPos = {0.0f,10.0f,5.0f,1.0f};
//6.绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
//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(inti =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. 让一个小篮球围绕大球公众自转
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();//出栈
//14.执行缓存区交换
glutSwapBuffers();
//15 发送重新渲染
glutPostRedisplay();
}
五、屏幕大小更改或者已经初始化
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 SpeacialKeys(int key,int x,int y){
//设定移动步长
float linear =0.1f;
//设定旋转度数
float angular = float(m3dDegToRad(5.0f));//角度转弧度
if(key ==GLUT_KEY_UP) {//键盘上的方向键 up键
//MoveForward 平移
objectFrame.MoveForward(linear);
}
if(key ==GLUT_KEY_DOWN) {//键盘上的方向键 down键
objectFrame.MoveForward(linear);
}
if(key ==GLUT_KEY_LEFT) {//键盘上的方向键 left键
//RotateWorld 旋转
objectFrame.RotateWorld(angular,0,1,0);
}
if(key ==GLUT_KEY_RIGHT) {//键盘上的方向键 right键
objectFrame.RotateWorld(angular,0,1,0); }
}
七、最终效果视频地址优酷传送门:小球自转公转效果图
[溪浣双鲤的技术摸爬滚打之路](https://www.jianshu.com/p/3fbecd65faae)