本文为L_Ares个人写作,包括图片皆为个人亲自操作,以任何形式转载请表明原文出处。
OpenGL的一些库中是有一些自带图形的,用于一些简单的图形绘制是很好用的,另外本节代码也会为下一节做一下铺垫。
直接上代码,代码中都有非常详细的注释,而且都是经过测试可以跑起来的。
//
// main.cpp
// 07矩阵堆栈
//
// Created by EasonLi on 2020/8/29.
// Copyright © 2020 EasonLi. All rights reserved.
//
#include <stdio.h>
#pragma mark - 引用类
//着色器管理器。负责管理和创建着色器管理器。里面有一些固定的着色器,可用于初级操作
#include "GLShaderManager.h"
//GLTool工具类,里面包含了GLTool大多数类似C语言的函数
#include "GLTools.h"
//矩阵堆栈类。包含了矩阵堆栈的操作
#include "GLMatrixStack.h"
//管道类,用于更加方便的管理矩阵
#include "GLGeometryTransform.h"
//用于设置观察者或者物体的位置
#include "GLFrame.h"
//投影矩阵获取
#include "GLFrustum.h"
//三角形批次类
#include "GLBatch.h"
//3d数学类
#include "math3d.h"
//宏定义判断系统,引入GLUT
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif
#pragma mark - 公共变量
//着色器管理器
GLShaderManager shaderManager;
//几何变换管道
GLGeometryTransform transformPipeline;
//模型视图矩阵
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
//观察者(相机)位置
GLFrame cameraFrame;
//物体世界坐标位置
GLFrame objectFrame;
//视景体,用来构造投影矩阵
GLFrustum viewFrustum;
//三角形批次类
//球
GLTriangleBatch sphereBatch;
//环
GLTriangleBatch torusBatch;
//圆柱
GLTriangleBatch cylinderBatch;
//锥
GLTriangleBatch coneBatch;
//磁盘
GLTriangleBatch diskBatch;
//颜色
GLfloat vRed[] = {1.f,0.f,0.f,1.f};
GLfloat vBlack[] = {0.f,0.f,0.f,1.f};
//记录点击空格的次数
int step = 0;
#pragma mark - 函数
/**
设置窗口
*/
void ChangeSize(int w,int h)
{
if (h == 0) {
h = 1;
}
//设置视口
glViewport(0, 0, w, h);
//设置透视投影
//1.观察者垂直视角度数。2.宽高比。3.视点与近剪裁面距离。4.视点与远剪裁面距离
viewFrustum.SetPerspective(36.f, float(w)/float(h), 1.f, 500.f);
//projectionMatrix矩阵堆栈加载透视投影矩阵
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//modelViewMatrix堆栈矩阵加载一个单元矩阵。其实不加载也没事,源码中可以看到,已经帮忙加载了一个了。
modelViewMatrix.LoadIdentity();
//通过GLGeometryTransform管理矩阵堆栈(模型视图矩阵堆栈、投影矩阵堆栈)
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
/**
设置渲染环境
*/
void SetUpRC ()
{
//设置清屏颜色
glClearColor(0.3f, 0.3f, 0.3f, 1.f);
//着色器管理器初始化
shaderManager.InitializeStockShaders();
//开启需要用到的环境
//开启深度测试
glEnable(GL_DEPTH_TEST);
//设置观察者和物体的距离,保证物体都能看得到
//这里有两种方式(1)(2)
//(1)将观察者(摄像机)位置沿其Z轴向内部移动15个单位。负数往里面移动,正数向外面移动。
//cameraFrame.MoveForward(-15.f);
//(2)将物体移动15个单位,观察者(摄像机)不动
objectFrame.MoveForward(15.f);
//这里直接使用一下GLTools里面给过的图形
/**
球:
gltMakeSphere(GLTriangleBatch& sphereBatch, GLfloat fRadius, GLint iSlices, GLint iStacks);
参数:
(1)sphereBatch:三角形批次类对象
(2)fRadius:球体半径
(3)iSlices:从球体底部堆叠到球体顶部的三角形带的数量。可以看出球体是一圈圈的三角形带组成的。
(4)iStacks:围绕球体一圈排列的三角形片段对数
建议:一个对称性较好的球体一般满足片段数量是三角形带数量的2倍。iSlices * 2 = iStacks
绘制球体都是围绕Z轴的,因为这样Z轴的正方向(+)就是球体顶部,Z轴负方向(-)就是球体底部。
*/
gltMakeSphere(sphereBatch, 3.f, 10, 20);
/**
环:
gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
参数:
(1)torusBatch:三角形批次类对象
(2)majorRadius:圆环中心到外边缘的半径(大圆半径)
(3)minorRadius:圆环中心到内边缘的半径(小圆半径)
(4)numMajor:大圆的三角形数量
(5)numMinor:小圆的三角形数量
*/
gltMakeTorus(torusBatch, 3.f, 1, 16, 16);
/**
圆柱:
gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
参数:
(1)cylinderBatch:三角形批次类对象
(2)baseRadius:底部圆半径
(3)topRadius:顶部圆半径
(4)fLength:圆柱体的长度(高度)
(5)numSlices:围绕Z轴的三角形带的对数
(6)numStacks:从圆柱底到圆柱顶的三角形带的数量
*/
gltMakeCylinder(cylinderBatch, 3.f, 3.f, 3.f, 16, 3);
/**
锥:特殊的圆柱
gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
参数:
(1)cylinderBatch:三角形批次类对象
(2)baseRadius:底部圆的半径
(3)topRadius:顶部圆半径,锥体直接给0就行了
(4)fLength:锥体长度(高度)
(5)numSlices:围绕Z轴的三角形带的对数
(6)numStacks:从圆柱底到圆柱顶的三角形带的数量
*/
gltMakeCylinder(coneBatch, 3.f, 0.f, 3.f, 16, 3);
/**
磁盘:
gltMakeDisk(GLTriangleBatch& diskBatch, GLfloat innerRadius, GLfloat outerRadius, GLint nSlices, GLint nStacks);
参数:
(1)diskBatch:三角形批次类对象
(2)innerRadius:内部圆半径
(3)outerRadius:外部圆半径
(4)nSlices:围绕Z轴的三角形对的数量
(5)nStacks:从内边缘到外边缘三角形带的对数
*/
gltMakeDisk(diskBatch, 1.f, 3.f, 16, 4);
}
/**
利用三角形批次类进行绘制
*/
void DrawBatch(GLTriangleBatch *dBatch)
{
//绘制图形
shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vRed);
//这里用的是指针,看清楚上面,不能用点方法
dBatch->Draw();
//绘制黑色线条
//这里就需要开启颜色混合、多边形偏移、抗锯齿等功能
//开启多边形偏移
glEnable(GL_POLYGON_OFFSET_LINE);
//设置偏移的面和模式
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//指定偏移量
glPolygonOffset(-1.f, -1.f);
//开启混合
glEnable(GL_BLEND);
//设置混合因子
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//开启线段模式抗锯齿(简单的抗锯齿)
glEnable(GL_LINE_SMOOTH);
//可以设置一下线段的宽度
glLineWidth(3.f);
//利用平面着色器画黑线
shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vBlack);
dBatch->Draw();
//绘制完成记得关闭之前开启的功能,回复图形填充方式,并恢复线段宽度
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDisable(GL_POLYGON_OFFSET_LINE);
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);
glLineWidth(1.f);
}
//观察者移动(摄像机),物体不动
void CameraMoveObjNo()
{
//复制栈顶的值,并且压入栈顶
modelViewMatrix.PushMatrix();
//获取观察者(摄像头)矩阵
//先定义一个矩阵,方便一会存储获取到的观察者(摄像头)矩阵
M3DMatrix44f mCamera;
//从cameraFrame中获取它栈顶的矩阵,然后赋值给mCamera,好用来做矩阵变换,这样做能保证多图形的情况下,不改变原
//cameraFrame的栈顶矩阵,而用一份复制出来的操作,不会影响到其他的图形绘制
cameraFrame.GetCameraMatrix(mCamera);
//让modelViewMatrix模型视图矩阵和观察者(摄像头)矩阵相乘,发生第一步变化,相乘的结果会存储到
//modelViewMatrix栈顶里面
modelViewMatrix.MultMatrix(mCamera);
//获取物体的矩阵
//先定义一个矩阵,也是方便存储获取到的物体仿射变换后的矩阵
M3DMatrix44f mObject;
//从objectFrame中获取其栈顶的矩阵,并且赋值给mObject,原理和上面的一样
objectFrame.GetMatrix(mObject);
//让modelViewMatrix模型视图矩阵和mObject相乘,结果矩阵依然存储在modelViewMatrix栈顶
modelViewMatrix.MultMatrix(mObject);
}
//物体移动,观察者(摄像机)不动
void ObjMoveCameraNo()
{
modelViewMatrix.PushMatrix(objectFrame);
}
/**
渲染
*/
void RenderScene()
{
//清空缓冲区
//虽然模版缓冲区都没用到,但是养成全部清空的好习惯肯定不会错
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//当观察者移动(摄像机),而物体不移动的时候,对应了SetUpRC中的(1)
//CameraMoveObjNo();
//当观察者不动,物体移动的时候,对应了SetUpRC中的(2)
ObjMoveCameraNo();
//到上面这一步,矩阵的变换已经都做完了,下面要绘制了
//判断空格点击次数,更换不同的图形
switch (step) {
case 0:
DrawBatch(&sphereBatch);
break;
case 1:
DrawBatch(&torusBatch);
break;
case 2:
DrawBatch(&cylinderBatch);
break;
case 3:
DrawBatch(&coneBatch);
break;
case 4:
DrawBatch(&diskBatch);
break;
}
//绘制完成一个图形,就把栈顶的矩阵可以Pop出去了,不要影响下一个图形的绘制
modelViewMatrix.PopMatrix();
//交换缓冲区
glutSwapBuffers();
}
/**
设置特殊键位(上下左右)
*/
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();
}
/**
设置键盘键位(空格)
*/
void SpaceKey(unsigned char key,int x,int y)
{
//拿空格键的ASCII码来判定点击的是键盘上的空格键
if (key == 32) {
step++;
if(step > 4) step = 0;
}
switch (step) {
case 0:
glutSetWindowTitle("Sphere球");
break;
case 1:
glutSetWindowTitle("Torus圆环");
break;
case 2:
glutSetWindowTitle("Cylinder圆柱");
break;
case 3:
glutSetWindowTitle("Cone锥");
break;
case 4:
glutSetWindowTitle("Disk磁盘");
break;
}
//发送重新渲染的信号
glutPostRedisplay();
}
#pragma mark - 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(600, 600);
//创建窗口并命名
glutCreateWindow("Sphere球");
//注册函数
//注册窗口
glutReshapeFunc(ChangeSize);
//注册渲染
glutDisplayFunc(RenderScene);
//注册特殊键位
glutSpecialFunc(SpecialKeys);
//注册空格键位
glutKeyboardFunc(SpaceKey);
//初始化Glew库
GLenum status = glewInit();
if (status != GLEW_OK) {
printf("glew init error : %s \n",glewGetErrorString(status));
}
//设置渲染环境
SetUpRC();
//构建本地循环
glutMainLoop();
return 0;
}
显示效果我就不贴图了,有问题的话可以留言。
这上面void SetUpRC()
和void RenderScene()
中的(1)(2)的变换,就是视变换的结果,说的通俗点,就是山不向你走来,你就向山走去。
网友评论