第七节—绘制简单的点和线(投影)
本文为L_Ares个人写作,包括图片皆为个人亲自操作,以任何形式转载请表明原文出处。
全部文章的代码都是在md里面手写的,没有任何的复制粘贴,所以如果出现小的错误,请大家见谅,如果可以帮忙指出的话万分感谢。
本节主要利用上两节学习到的基本集合图元和固定管线进行绘制。绘制一些投影类型的点和线。主要是熟悉整个绘制的流程。废话不多说,下面直接上代码,代码分为三个大块
1. 引用类
2. 全局变量
3. 函数
4.main函数
分别来讲清楚这些引用类是干什么的,全局变量都是干什么的,函数是干什么的,main函数里面需要做什么。
一、引用类
//GLTools的头文件里面包含大多数的GLTool类中类似C语言的独立函数
#include "GLTools.h"
//矩阵工具类
//用于加载单元矩阵/矩阵/矩阵相乘/压栈/出栈/旋转/缩放/平移
#include "GLMatrixStack.h"
//矩阵工具类
//表示位置。通过它可以设置vOrigin、vForward、vUp
#include "GLFrame.h"
//矩阵工具类
//用来快速设置正投影矩阵/透视投影矩阵。里面有功能完成3D到2D的映射
#include "GLFrustum.h"
//三角形批次类
//利用它传递顶点/颜色/光照/纹理等数据到存储着色器中
#include "GLBatch.h"
//变换管道类
//用来快速在程序中传递模型视图矩阵/投影矩阵
#include "GLGeometryTransform.h"
//数学库
#include <math.h>
/**创建版本兼容
根据宏判断系统
MacOS下使用<glut/glut.h>
Windows和Linux下使用<GL/glut.h>也就是FreeGlut的静态版本库
需要添加一个宏定义FREEGLUT_STATIC*/
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif
二、全局变量
//着色器管理器
GLShaderManager shaderManager;
//模型视图矩阵堆栈
GLMatrixStack modelViewMatrix;
//投影矩阵堆栈
GLMatrixStack projectionMatrix;
//观察者视图坐标
GLFrame cameraFrame;
//图形环绕坐标
GLFrame objectFrame;
//图元绘制时的投影方式
GLFrustum viewFrustum;
//图形做变换用的管道
GLGeometryTransform transformPipeline;
//容器。最好使用几个图元绘制方式就设定几个容器。
GLBatch pointBatch;
//颜色RGBA
GLfloat vGreen[] = {0.f,1.f,0.f,1.f};
GLfloat vBlack[] = {0.f,0.f,0.f,1.f};
//点击空格的次数
int nStep = 0;
三、函数
函数这里我会按照执行的逻辑顺序来写,所以从上到下就是程序内部的执行顺序。
1. setUpRC
设置渲染的环境和数据,在这里我们将颜色和顶点数据等环境参数设置好。
void setUpRC()
{
//第一步先设置清屏颜色。随意设置自己要用的
glClearColor(0.7f,0.7f,0.7f,1.f);
//初始化着色器管理器
shaderManager.InitializeStockShaders();
//立体图形,开启深度测试
glEnable(GL_DEPTH_TEST);
//设置变换管道堆栈信息
transformPipeline.SetMatrixStacks(modelViewMatrix,projectionMatrix);
//设置观察者矩阵的位置靠近屏幕是负数,远离屏幕是正
cameraFrame.MoveForward(-15.f);
//设置顶点信息
GLfloat vCoast[9] = {
3.f,3.f,0.f,
0.f,3.f,0.f,
3.f,0.f,0.f
};
//设置顶点的绘制方式
//参数1:选择图元
//参数2:顶点数量
pointBatch.Begin(GL_POINTS,3);
//拷贝顶点数据
pointBatch.CopyVertexData3f(vCoast);
//结束容器设置
pointBatch.End();
}
2.changeSize
重塑函数。它需要在main函数里面注册。注册后,在窗口第一次创建或者窗口发生了变化的时候,都会自动调用。
void changeSize(int w,int h)
{
//设置视口的大小。xy都是0是因为一般都默认用窗口左下角为视口原点
glViewport(0.f,0.f,w,h);
/**在这里设置投影方式和参数。之所以选择这里是因为这里最容易获取宽高。
因为投影矩阵需要设置宽高比
参数解析:
(1).float fFov:指定观察者垂直视角。就像是眼睛可视范围的上下限的夹角。
也就是可视空间顶面与底面的夹角。这个数值必须大于0
(2).float fAspect:纵横比 宽(w)/高(h)
(3).float fNear:视点(观察点)到近剪裁面的距离
(4).float fFar:视点(观察点)到远剪裁面的距离
注意:这里的fFar必须大于fNear*/
viewFrustum.SetPerspective(36.f,float(w)/float(h),1.f,500.f);
//设置投影矩阵
//GetMatrix的方法都可以获取栈顶值,上面设置过投影信息了,
//OpenGL会自己计算投影矩阵,直接从栈顶拿来就行,然后加载到我们的投影矩阵堆栈里面。
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//设置模型视图矩阵堆栈
//模型视图矩阵堆栈直接加载一个单元矩阵,方便做变换相乘,我理解为给了他
//初始化。
modelViewMatrix.LoadIdentity();
}
3.RenderScene
渲染函数。需要在main函数里面注册。在屏幕发生变化或者开发者手动触发的时候会调用,完成从数据到图形显示。
void RenderScene() {
//渲染的第一步一定是清空缓冲区,避免之前的渲染对新的渲染造成影响
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//对于多种图形的绘制,多种图元设置,需要进行压栈,这样在绘制完成一个之
//后可以复制数据然后出栈。避免对后面的绘制造成影响。
modelViewMatrix.PushMatrix();
//开始做变换。定义一个观察者矩阵。
M3DMatrix44f mCamera;
//获取观察者坐标栈顶的矩阵值,并赋值给mCamera
cameraFrame.GetCameraMatrix(mCamera);
//观察者矩阵和模型视图矩阵相乘,结果放在mv栈顶
modelViewMatrix.MultMatrix(mCamera);
//定义一个图形的视图矩阵
M3DMatrix44f mObjectFrame;
//获取图形环绕坐标栈顶的矩阵值,并且赋值给mObjectFrame
objectFrame.GetMatrix(mObjectFrame);
//图形的视图矩阵再和模型视图矩阵相乘,结果放到mv栈顶
modelViewMatrix.MultMatrix(mObjectFrame);
//到上面,本节需要的视图变换就都做完了。开始使用着色器。
//因为我们只用变换和颜色,所以选择了平面着色器就可以。
//直接利用上面定义的变换管道获取mvp矩阵
shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.
GetModelViewProjectionMatrix(),vBlack);
//开始绘制。因为要绘制多个图形,所以根据定义的nStep来分别绘制
switch (nStep) {
case 0:
//设置点的大小
glPointSize(4.f);
//利用对应的容器类绘制
pointBatch.Draw();
//绘制完成后记得把点的大小修改回去,因为OpenGL是个状态机
glPointSize(1.f);
break;
}
//绘制完成一个图形,就要把刚才Push进来的矩阵都Pop出去,避免造成下一次绘制的混乱
modelViewMatrix.PopMatrix();
//交换缓冲区
glutSwapBuffers();
}
下面的4和5就不说顺序了,不是重点的逻辑顺序。
4.SpecialKeys
特殊键位函数。需再main函数里面注册。上下左右让图形发生环绕。
在这里发生环绕有两种思路,一个是让图形的每个顶点都发生仿射变化,完成图形的环绕,另外一个就是让世界坐标系都变化,发生视觉上的相对变化,当图形的顶点很多的时候,明显第一种方法是很难的,所以我们选择第二种。
RotateWorld参数解析:
参数1:发生旋转的弧度
参数2,3,4:分别代表x,y,z轴,可以赋值0和1,表示围绕哪个轴发生环绕
围绕哪个环绕,哪个就是1,不围绕的就是0
void SpecialKeys(int key , int x , int y)
{
switch (key) {
case GLUT_KEY_UP:
objectFrame.RotateWorld(m3dDegToRad(-5.f),1.f,0.f,0.f);
break;
case GLUT_KEY_DOWN:
objectFrame.RotateWorld(m3dDegToRad(5.f),1.f,0.f,0.f);
break;
case GLUT_KEY_LEFT:
objectFrame.RotateWorld(m3dDegToRad(-5.f),0.f,1.f,0.f);
break;
case GLUT_KEY_RIGHT:
objectFrame.RotateWorld(m3dDegToRad(5.f),0.f,1.f,0.f);
break;
}
//手动发送重新渲染绘制的信息
glutPostRedisplay();
}
5.KeyPressFunc
按键函数。需在main函数里面注册。点击空格,切换不同的视图。这里利用的是ASCII码值来做判断,所以F1,F2这种特殊的键就不行了,abc这种带ACSII码的可以。
void KeyPressFunc(unsigned char key , int x , int y)
{
//这里我们使用“空格”来切换视图,空格的ASCII是32
if(key == 32)
{
nStep++;
//只画4个图形,所以到第4个的时候再点“空格”跳回到第一个
if(nStep > 3)
{
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;
}
//手动发送重新渲染的通知
glutPostRedisplay();
}
四、main函数
int main(int argc , char *argv[])
{
//设置工作目录到工程的可执行程序的文件夹下(/Resouce)
//GLUT的优先设定已经设定过了,这里手动是为了保险
gltSetWorkingDirectory(argv[0]);
//初始化GLUT
glutInit(&argc,argv);
//初始化显示模式
glutInitDisplayMode(GLUT_DOUBLE|GLUT_DEPTH|GLUT_RGBA|
GLUT_STENCIL);
//初始化窗口大小
glutInitWindowSize(800,600);
//创建窗口并命名
glutCreateWindow("GL_POINTS");
//注册重塑函数
glutReshapeFunc(changeSize);
//注册渲染函数
glutDisplayFunc(RenderScene);
//注册普通按键函数
glutKeyboardFunc(KeyPressFunc);
//注册特殊键位函数
glutSpecialFunc(SpecialKeys);
//初始化一个Glew库,确保OpenGL的API对程序都可以使用
GLenum status = glewInit();
//在做任何的渲染之前,都必须要保证驱动程序的初始化过程中不能有错误
if (GLEW_OK != status) {
printf("glew Error : %s \n",glewGetErrorString(status));
return 1;
}
//设置渲染环境
setUpRC();
//建立一个类似RunLoop的循环
glutMainLoop();
return 0;
}
结束上述的绘制以后,结果应该是如下图GL_POINTS显示:

这个就是绘制三个点,可以进行上下左右的翻转。
下面写一下每两个点连成一条线(LINES),但是不足两个点则不连接。
每两个点连成一条线,不闭口(STRIP)。
每两个点连成一条线,形成线环(LOOP)。
五、线
线和点的不同点主要在于setUpRC里面要用对应的GLBatch来设置图元类型。在RenderScene中的switch中,case1,2,3的时候要使用对应的GLBatch来进行Draw()。
GLBatch容器最好画几种图元类型就设置几个,这样不会造成互相影响。
下面只写不同的地方,也就是上面说的那两个地方。直接将对应的代码替换掉就好。
//全局变量处添加
GLBatch lineBatch;
GLBatch lineStripBatch;
GLBatch lineLoopBatch;
//setUpRC中添加
lineBatch.Begin(GL_LINES,3);
lineBatch.CopyVertexData3f(vCoast);
lineBatch.End();
lineStripBatch.Begin(GL_LINE_STRIP,3);
lineStripBatch.CopyVertexData3f(vCoast);
lineStripBatch.End();
lineLoopBatch.Begin(GL_LINE_LOOP,3);
lineLoopBatch.CopyVertexData3f(vCoast);
lineLoopBatch.End();
//在RenderScene的switch的case中添加1,2,3
case 1:
//设置线段的宽
glLineWidth(2.f);
lineBatch.Draw();
glLineWidth(1.f);
break;
case 2:
glLineWidth(2.f);
lineStripBatch.Draw();
glLineWidth(1.f);
break;
case 3:
glLineWidth(2.f);
lineLoopBatch.Draw();
glLineWidth(1.f);
break;
结果是如下图三个:
GL_LINES:

GL_LINE_STRIP:

GL_LINE_LOOP:

还可以画三角形带,三角形扇,无底金字塔,这些代码将在下一节的知识学习完成之后再写出。