OpenGL初识之OpenGL渲染基础概念传送门https://www.jianshu.com/p/d26879a736af
接着上一节我们学习了OpenGL渲染基础,现在我们来综合使用一下,练习一下绘制OpenGL图元并组合图元:
一、绘制图元点
二、绘制图元线段/连续线段/线环
三、绘制金字塔
四、图元渲染三角形带/三角形扇
一、绘制图元点
先上绘制的总结思路图
开始准备环境和对应的函数(详细介绍请移步我之前的文章,传送门:基本函数解释https://www.jianshu.com/p/b98be0925051)
这里主要说明有区别的地方
1、设置全局变量
GLShaderManager shaderManager;//着色器管理
GLMatrixStack modelViewMatrix;//模型视图矩阵堆栈
GLMatrixStack projectViewMatrix;//投影矩阵堆栈
GLFrame cameraFrame;//观察者
GLFrame objectFrame;
GLFrustum viewFrustum;//投影矩阵
//批次类(7种不同的图元,对应7种容器对象)
GLBatch pointBatch;//点
GLBatch lineBatch;//线
GLBatch lineStripBatch;//连线
GLBatch lineLoopBatch;//线环
GLBatch triangleBatch;//三级形
GLBatch triangleStripBatch;//三角形带
GLBatch triangleFanBatch;//三角形扇
//几何变换管道
GLGeometryTransform transformPipeline;
//颜色RGBA
GLfloat vGreen[] = {0.0f, 1.0f, 0.0f, 1.0f};
GLfloat vYellow[] = {1.0f,1.0f, 0.0f, 1.0f};
//设置按空格键来切换不同图元展示
//记录按了几次空格
int nStep = 0;
2、设置main函数
int main(int argc,char* argv[]){
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
glutInitWindowSize(800,600);
glutCreateWindow("Triangle");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
glutSpecialFunc(SpecialKeys);
//注册点击空格会调用的函数
glutKeyboardFunc(KeyPressFunc);
GLenum err = glewInit();
if(GLEW_OK != err) {
fprintf(stderr,"glew error:%s\n",glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
return 0;
}
3、设置SetupRC函数,注释我都写在里面了
//1、设置背景颜色
glClearColor(0.7f, 0.7f, 0.7f,1.0);
//2、初始化着色器管理器
shaderManager.InitializeStockShaders();
//3、开启深度测试
glEnable(GL_DEPTH_TEST);
//4、设置变换管道
transformPipeline.SetMatrixStacks(modelViewMatrix, projectViewMatrix);
//5、设置观察者位置,便于观察效果
cameraFrame.MoveForward(-15);
//6、设置一些点
//创建一维的六个点的数组
GLfloat vCoast[18] = {
3,3,0,
-3,3,0,
3,0,0,
-3,0,0,
3,-3,0,
-3,-3,0
};
//设置图元渲染方式,点
pointBatch.Begin(GL_POINTS, 6);
pointBatch.CopyVertexData3f(vCoast);
pointBatch.End();
4、设置ChangeSize函数,需要注意的是,这个函数的调用时机,就是窗口已更改大小,或刚刚创建,无论是那种情况,我们都需要使用窗口维度设置视口和设置投影矩阵
//1、设置视口
glViewport(0, 0, w, h);
//2、投影矩阵: 需要设置纵横比
viewFrustum.SetPerspective(35, floorf(w) / floorf(h), 1.0, 500);
//3、设置投影矩阵,加载投影矩阵
projectViewMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//4、模型视图视图矩阵堆栈,加载单元矩阵
modelViewMatrix.LoadIdentity();
5、设置渲染RenderScence函数
//1、清除缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//2、模型视图矩阵---压栈
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.MultMatrix(mCamera);//矩阵相乘,得到变换后的新矩阵
M3DMatrix44f mObjectFrame;
objectFrame.GetMatrix(mObjectFrame);
modelViewMatrix.MultMatrix(mObjectFrame);//矩阵相乘,得到变换后的新矩阵
//3、挑选平面着色器渲染
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen);
//4、容器类提交绘制
switch (nStep) {
case 0:
//设置点的大小
glPointSize(20.0f);
pointBatch.Draw();
glPointSize(1.0f);
break;
default:
break;
}
//5、模型视图矩阵---出栈,还原到以前的模型视图矩阵
modelViewMatrix.PopMatrix();
//6、交换缓冲区
glutSwapBuffers();
6、设置特殊键位函数
void SpecialKeys (int key , int x, int y){
//点击键盘上的上下左右键
if (key == GLUT_KEY_UP) {
//往上移动,围绕X轴,是顺时针方向,由于逆时针为正,所以是-5.0
//m3dDegToRad() 角度转弧度函数
objectFrame.RotateWorld(m3dDegToRad(-5.0), 1.0, 0, 0);//其中的1.0表示YES,0.0表示NO
}else if (key == GLUT_KEY_DOWN) {
//往下移动,围绕X轴,是逆时针方向,由于逆时针为正,所以是5.0
objectFrame.RotateWorld(m3dDegToRad(5.0), 1.0, 0, 0);
}else if (key == GLUT_KEY_LEFT) {
//往左移动,围绕Y轴,是顺时针方向,由于逆时针为正,所以是-5.0
objectFrame.RotateWorld(m3dDegToRad(-5.0), 0.0, 1.0, 0);
}else if (key == GLUT_KEY_RIGHT) {
//往右移动,围绕Y轴,是逆时针方向,由于逆时针为正,所以是5.0
objectFrame.RotateWorld(m3dDegToRad(5.0), 0.0, 1.0, 0);
}
//发生变化需要重新渲染
glutPostRedisplay();
}
7、设置空格切换函数KeyPressFunc(这个主要是用来切换不同图元)
void KeyPressFunc(unsigned char key, int x, int y){
//不断点击空格修改nStep记录步数
if(key == 32){
nStep++;
if(nStep > 6){
nStep = 0;
}
}
//修改窗口名称
switch(nStep)
{
case 0:
glutSetWindowTitle("GL_POINTS");
break;
case 1:
glutSetWindowTitle("GL_LINES");
break;
case 2:
glutSetWindowTitle("GL_LINE_STRIP");
break;
case 3:
glutSetWindowTitle("GL_LINE_LOOP");
break;
case 4:
glutSetWindowTitle("GL_TRIANGLES");
break;
case 5:
glutSetWindowTitle("GL_TRIANGLE_STRIP");
break;
case 6:
glutSetWindowTitle("GL_TRIANGLE_FAN");
break;
}
glutPostRedisplay();
}
二、绘制图元线段/连续线段/线环
绘制图元点的写完后,设置绘制图元线段/连续线段/线环 ,只需要配置SetupRC里面设置对应图元,然后再RenderScence修改图元绘制就行了,对应修改的位置如下
1、修改SetupRC部分代码
//1/设置图元渲染方式,点
pointBatch.Begin(GL_POINTS, 6);
pointBatch.CopyVertexData3f(vCoast);
pointBatch.End();
//2/设置图元渲染方式,线段
lineBatch.Begin(GL_LINES, 6);
lineBatch.CopyVertexData3f(vCoast);
lineBatch.End();
//3/设置图元渲染方式,连续线段
lineStripBatch.Begin(GL_LINE_STRIP, 6);
lineStripBatch.CopyVertexData3f(vCoast);
lineStripBatch.End();
//4/设置图元渲染方式,线环
lineLoopBatch.Begin(GL_LINE_LOOP, 6);
lineLoopBatch.CopyVertexData3f(vCoast);
lineLoopBatch.End();
2、修改RenderScence对应部分代码
switch (nStep) {
case 0:
//设置点的大小
glPointSize(20.0f);
pointBatch.Draw();
glPointSize(1.0f);
break;
case 1:
//设置线段宽度
glLineWidth(5.0f);
lineBatch.Draw();
glLineWidth(1.0f);
break;
case 2:
//
glLineWidth(5.0f);
lineStripBatch.Draw();
glLineWidth(1.0f);
break;
case 3:
glLineWidth(5.0f);
lineLoopBatch.Draw();
glLineWidth(1.0f);
break;
default:
break;
}
三、渲染图元金字塔
接下来我们绘制金字塔,金字塔是由一个底面和四个侧面组成,金字塔没有底面的话,其实侧面是由4个三角形组成的,如果有底面的话,底面是两个三角形组成的,和上面不同图元绘制的方式类似,只需要修改SetupRC和RenderScene对应的代码就行了
1、SetupRC函数中设置金字塔四个顶点坐标数据和图元装配方式
//设置金字塔的顶点数据
//通过三角形创建金字塔
GLfloat vPyramid[12][3] = {
-2.0f, 0.0f, -2.0f,
2.0f, 0.0f, -2.0f,
0.0f, 4.0f, 0.0f,
2.0f, 0.0f, -2.0f,
2.0f, 0.0f, 2.0f,
0.0f, 4.0f, 0.0f,
2.0f, 0.0f, 2.0f,
-2.0f, 0.0f, 2.0f,
0.0f, 4.0f, 0.0f,
-2.0f, 0.0f, 2.0f,
-2.0f, 0.0f, -2.0f,
0.0f, 4.0f, 0.0f};
//GL_TRIANGLES 每3个顶点定义一个新的三角形
triangleBatch.Begin(GL_TRIANGLES, 12);
triangleBatch.CopyVertexData3f(vPyramid);
triangleBatch.End();
2、RenderScence绘制金字塔
case 4:
triangleBatch.Draw();
break;
四、图元渲染三角形扇/图元渲染三角形带
1、绘制六边形三角形扇
修改SetupRC,设置顶点数据,该顶点的坐标数据是通过半径和角度求来的,下面这段代码的循环,就是为了求六边形七个顶点的位置坐标,六个顶点和一个中心点
// 三角形扇形--六边形
GLfloat vPoints[100][3];
int nVerts = 0;
//半径
GLfloat r = 3.0f;
//原点(x,y,z) = (0,0,0);
vPoints[nVerts][0] = 0.0f;
vPoints[nVerts][1] = 0.0f;
vPoints[nVerts][2] = 0.0f;
//M3D_2PI 就是2Pi 的意思,就一个圆的意思。 绘制圆形
for(GLfloat angle = 0; angle < M3D_2PI; angle += M3D_2PI / 6.0f) {
//数组下标自增(每自增1次就表示一个顶点)
nVerts++;
/*
弧长=半径*角度,这里的角度是弧度制,不是平时的角度制
既然知道了cos值,那么角度=arccos,求一个反三角函数就行了
*/
//x点坐标 cos(angle) * 半径
vPoints[nVerts][0] = float(cos(angle)) * r;
//y点坐标 sin(angle) * 半径
vPoints[nVerts][1] = float(sin(angle)) * r;
//z点的坐标
vPoints[nVerts][2] = -0.5f;
}
// 结束扇形 前面一共绘制7个顶点(包括圆心)
//添加闭合的终点
//如果屏蔽173-177行代码,并把绘制节点改为7.则三角形扇形是无法闭合的。
nVerts++;
vPoints[nVerts][0] = r;
vPoints[nVerts][1] = 0;
vPoints[nVerts][2] = 0.0f;
// 加载!
//GL_TRIANGLE_FAN 以一个圆心为中心呈扇形排列,共用相邻顶点的一组三角形
triangleFanBatch.Begin(GL_TRIANGLE_FAN, 8);
triangleFanBatch.CopyVertexData3f(vPoints);
triangleFanBatch.End();
2、绘制六边形三角形扇,修改RenderScence
case 6:
triangleFanBatch.Draw();
break;
3、绘制三角形带,配置顶点坐标以及图元装配方式
//三角形条带,一个小环或圆柱段
//顶点下标
int iCounter = 0;
//半径
GLfloat radius = 3.0f;
//从0度~360度,以0.3弧度为步长
for(GLfloat angle = 0.0f; angle <= (2.0f*M3D_PI); angle += 0.3f)
{
//或许圆形的顶点的X,Y
GLfloat x = radius * sin(angle);
GLfloat y = radius * cos(angle);
//绘制2个三角形(他们的x,y顶点一样,只是z点不一样)
vPoints[iCounter][0] = x;
vPoints[iCounter][1] = y;
vPoints[iCounter][2] = -0.5;
iCounter++;
vPoints[iCounter][0] = x;
vPoints[iCounter][1] = y;
vPoints[iCounter][2] = 0.5;
iCounter++;
}
// 关闭循环
//结束循环,在循环位置生成2个三角形
vPoints[iCounter][0] = vPoints[0][0];
vPoints[iCounter][1] = vPoints[0][1];
vPoints[iCounter][2] = -0.5;
iCounter++;
vPoints[iCounter][0] = vPoints[1][0];
vPoints[iCounter][1] = vPoints[1][1];
vPoints[iCounter][2] = 0.5;
iCounter++;
// GL_TRIANGLE_STRIP 共用一个条带(strip)上的顶点的一组三角形
triangleStripBatch.Begin(GL_TRIANGLE_STRIP, iCounter);
triangleStripBatch.CopyVertexData3f(vPoints);
triangleStripBatch.End();
4、绘制三角形带,修改RenderScence
case 5:
triangleStripBatch.Draw();
break;
运行得到所有的样式,如下图: