学习目标:
- OpenGL 渲染结构
- 如何使用7种OpenGL基础图元
- 如何使用储存着色器
- 如何使用Uniform属性
- 如何使用GLBatch 帮助类传递几何图形
OpenGL 渲染结构
- 基础图形管线
- OpenGL 中的图元是顶点的集合以预定义的⽅式结合⼀起。
- 例如:⼀个单独的点就是⼀个图元。它只需要一个顶点
- OpenGL 中的图元是顶点的集合以预定义的⽅式结合⼀起。
- 渲染管线简化流程
- 客户端和服务端
- 管线分为上下两部分,上部分是客户端,而下部分则是服务端。
- 客户端是存储在CPU储存器中的,并且在应用程序中执行或者在主系统内存中驱动程序中执行。驱动程序回将渲染命令和数组组合起来,发送给服务器执行!(在⼀台典型的个人计算机上,服务器实际上就是图形加速卡上的硬件和内存)
- 服务器和客户机在功能上也是异步的。它们是各⾃独立的软件块或硬件块。我们是希望它们2个端都尽量在不停的工作。客户端不断的把数据块和命令块组合在⼀起输送到缓冲区,然后缓冲区就会发送到服务器执行。
- 如果服务器器停⽌工作等待客户机,或者客户机停⽌工作来等待服务器做好接受更多的命令和准备,我们把这种情况成为
管线停滞
- 着色器
- 上图的Vertex Shader(顶点着⾊器) 和 Fragment Shader(⽚段着⾊器)。
- 着⾊器是使用GLSL编写的程序,看起来与C语⾔非常类似。 着⾊器必须从源代码中编译和链接在一起。最终准备就绪的着⾊器程序
- 顶点着⾊器-->处理从客户机输⼊的数据、应⽤变换、进⾏其他的类型的数学运算来计算关照效果、位移、颜⾊值等等。(**为了渲染共有3个顶点的三⻆形,顶点着⾊器将执行3次,也就是为了每个顶点执⾏⼀次)在⽬前的硬件上有多个执⾏单元同时运⾏,就意味着所有的3个顶点可以同时进⾏处理!
- 上图的Vertex Shader(顶点着⾊器) 和 Fragment Shader(⽚段着⾊器)。
- primitive Assembly(图元装配)
- 说明的是:3个顶点已经组合在一起,而三角形已经逐个片段地进行了光栅化。每个片段通过执行
片元着色器
- 说明的是:3个顶点已经组合在一起,而三角形已经逐个片段地进行了光栅化。每个片段通过执行
-
注意要点
- 必须要为着色器提供数据,否则无法实现!
- 为着色器传递渲染数据的方法有三种
- 属性(Attributes)
- uniform值
- 纹理Texture Data
- 客户端和服务端
- 属性、uniform值、纹理、输出
- 属性
- 对每一个顶点都要作改变的数据元素。实际上,顶点位置本身就是一个属性。属性值可以是浮点数、整数、布尔数据。
- 属性还可以是:纹理坐标、颜色值、关照计算表面法线
- 在顶点程序(shader渲染)可以代表你想要的任何意义。因为都是你设定的。
- 属性会从本地客户机内存中复制存储在图形硬件中的一个缓冲区上。这些属性只提供给顶点着⾊器使用,对于⽚元着⾊器没有太⼤意义。
- 声明: 这些属性对每个顶点都要做改变,但并不意味着它们的值不能重复。通常情况下,它们都是不一样的,但有可能整个数组都是同一值的情况。
- Uniform值
- 属性是⼀种对整个批次属性都取统一值的单一值。它是不变的。通过设置uniform变量就紧接着发送⼀个图元批次命令,而Uniform变量实际上可以⽆数次限制地使⽤,设置一个应用于整个表面的单个颜色值,还可以设置一个时间值。在每次渲染某种类型的顶点动画时修改它。
- 注意: 这⾥的uniform 变量每个批次改变一次,⽽不是每个顶点改变⼀次。
- 与属性相同点:可以是浮点值、整数、布尔值
- 与属性不同:顶点着色器和片元着色器都可以使用uniform变量。uniform变量还可以是标量类型、矢量类型、uniform矩阵。
- 纹理
- 传递给着⾊器的第三种数据类型:纹理数据
- 在顶点着⾊器、⽚段着色器中都可以对纹理数据进行采样和筛选。
- 典型的应用场景: 片段着⾊器对⼀个纹理值进行采样,然后在一个三⻆形表面应用渲染纹理数据。
- 纹理数据,不仅表现在图形,很多图形文件格式都是以无符号字节 (每个颜⾊色通道8位)形式对颜色分量进行存储的。
- 传递给着⾊器的第三种数据类型:纹理数据
- 输出(outs)
- 在图表中第四种数据类型是输出(out);输出数据是作为⼀个阶段着色器的输出定义的,而后续阶段的着⾊器则作为输⼊定义。
- 输出数据可以简单的从⼀个阶段传递到下一个阶段,也可以⽤不同的方式插入。
- 客户端的代码接触不到这些内部变量我们的OpenGL开发暂时接触不到。
- 属性
- 创建坐标系
-
正投影图
这就是一个正投影的例子,在3个轴(X,Y,Z)中,它们的范围都是-100到+100.这个视景体将包括所有的几何图形。
如果你指定了视景体外的几何图形,就会被裁减掉!(它将沿着视景体的边界进行剪切)
在正投影中,所有在这个空间范围内的所有东西都将被呈现在屏幕上。⽽不存在照相机或视点坐标系的概念。
-
透视投影
透视投影会进行透视除法对距离观察者很远的对象进行缩短和收缩。在投影到屏幕之后,视景体背⾯与视景体正面的宽度测量标准不同。
- 上图所示:平截头体(frustum)的几何体,它的观察方向是从金字塔的尖端到宽阔端。观察着的视点与金字塔的尖端拉开一定距离。
GLFrustum类通过setPerspective ⽅方法为我们构建⼀个平截头体。 CLFrustum::SetPerspective(float fFov,float fAspect,float fNear ,float fFar); 参数: fFov:垂直⽅向上的视场⻆度 fAspect:窗口的宽度与高度的纵横比 fNear:近裁剪⾯距离 (视角到近裁剪面距离为fNear) fFar:远裁剪面距离(视角到远裁剪面距离为fFar) 纵横比 = 宽(w)/⾼(h)
- 上图所示:平截头体(frustum)的几何体,它的观察方向是从金字塔的尖端到宽阔端。观察着的视点与金字塔的尖端拉开一定距离。
-
- 使用储存着色器
- 使用背景
- 在OpenGL 核⼼框架中,并没有提供任何内建渲染管线,在提交⼀个⼏何图形进行渲染之前,必须实现⼀个着色器。 在前面的课程可以使用存储着⾊器。这些存储着⾊器由GLTools的C++类 GLShaderManager管理。它们能够满足进行基本渲染的基本要求。要求不高的程序员,这些存储着⾊器已经⾜以满⾜他们的需求。但是,随着时间 和经验的提升,⼤部分开发者可能不满⾜于此。 会开始⾃己着手去写着色器。
- 储存着色器的使用
-
GLShaderManager
的初始化// GLShaderManager 的初始化 GLShaderManager shaderManager; shaderManager.InitializeStockShaders();
-
GLShaderManager
属性
存储着⾊器为每⼀个变量都使⽤一致的内部变量命名规和相同的属性槽。以上就是存储着⾊器的属性列表
-
GLShanderManager
的uniform
值- ⼀般情况,要对⼏何图形进⾏渲染,我们需要给对象递交属性矩阵,⾸先要绑定我们想要使⽤的着⾊程序上,并提供程序的uniform值。但是GLShanderManager 类可以暂时为我们完成工作。
-
useStockShader
函数会选择⼀个存储着⾊器并提供这个着⾊器的uniform值。
GLShaderManager::UserStockShader(GLeunm shader...);
-
单位(Identity 着⾊器)
GLShaderManager::UserStockShader(GLT_ATTRIBUTE_VERTEX,GLfloat vColor[4]);
- 单位着⾊器:只是简单地使用默认笛卡尔坐标系(坐标范围(-1.0, 1.0))。
- 所有的⽚段都应用同⼀种颜⾊,⼏何图形为实⼼和未渲染的。
- 需要设置存储着色器⼀个属性: GLT_ATTRIBUTE_VERTEX(顶点分量)
- 参数2:vColor[4],你需要的颜色
-
平面着色器
GLShaderManager::UserStockShader(GLT_SHADER_FLAT,GLfloat mvp[16],GLfloat vColor[4]);
- GLT_SHADER_FLAT:平面着色器
- mvp[16]:允许变化的4*4矩阵
- 颜色:vColor[4]
- 它将统一着⾊色器器进行了拓展。允许为⼏何图形变换指定一个 4 * 4 变换矩阵。经常被称为
模型视图投影矩阵
-
上色着色器
- 在⼏何图形中应用的变换矩阵。需要设置存储着⾊器的 GLT_ATTRIBUTE_VERTEX(顶点分量) 和GLT_ATTRIBUTE_COLOR(颜⾊分量) 2个属性。颜色值将被平滑地插⼊顶点之间(平滑着⾊)
GLShaderManager::UserStockShader(GLT_SHADER_SHADED,GLfloat mvp [16]);
-
默认光源着色器
GLShaderManager::UserStockShader(GLT_SHADER_DEFAULT_LIGHT,GLfl oat mvMatrix[16],GLfloat pMatrix[16],GLfloat vColor[4]);
- GLT_SHADER_DEFAULT_LIGHT: 默认光源着⾊器
- mvMatrix[16]: 模型视图矩阵
- pMatrix[16]: 投影矩阵
- vColor[4]: 颜⾊值
-
点光源着⾊器
LShaderManager::UserStockShader(GLT_SHADER_DEFAULT_LIGHT_DIEF,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vLightPos[3],GLfloat vColor[4]);
- 参数1:点光源着⾊器
- 参数2:模型视图矩阵
- 参数3:投影矩阵
- 参数4:视点坐标光源位置
- 参数5:颜⾊值
- 区别:点光源着⾊器和默认光源着⾊器很相似,区别在于:光源位置是特定的。 同样需要设置存储着⾊器的 GLT_ATTRIBUTE_VERTEX(顶点分量) 和
GLT_ATTRIBUTE_NORMAL(表⾯法线)
-
纹理理替换矩阵
- 着色器通过给定的模型视图投影矩阵,使⽤绑定到 nTextureUnit (纹理单元) 指定纹理单元的纹理对⼏何图形进行变化。片段颜色:是直接从纹理样本中直接获取的。
需要设置存储着⾊器的 GLT_ATTRIBUTE_VERTEX(顶点分量) 和
GLT_ATTRIBUTE_NORMAL(表⾯法线)GLShaderManager::UserStockShader(GLT_SHADER_TEXTURE_REPLACE,GLfloat mvMatrix[16],GLint nTextureUnit);
- 着色器通过给定的模型视图投影矩阵,使⽤绑定到 nTextureUnit (纹理单元) 指定纹理单元的纹理对⼏何图形进行变化。片段颜色:是直接从纹理样本中直接获取的。
-
纹理调整着⾊器
- 将⼀个基本色乘以一个取自纹理单元 nTextureUnit 的纹理。需要设置存储着⾊器的
GLT_ATTRIBUTE_VERTEX(顶点分量)
和
GLT_ATTRIBUTE_TEXTURE0(纹理坐标)
.GLShaderManager::UserStockShader(GLT_SHADER_TEXTURE_MODULATE,GLfloat mvMatrix[16],GLfloat vColor[4],GLint nTextureUnit);
- 将⼀个基本色乘以一个取自纹理单元 nTextureUnit 的纹理。需要设置存储着⾊器的
-
纹理光源着⾊器
- 将⼀个纹理通过漫反射照明计算机进⾏调整(相乘)。光线在视觉空间中 的位置是给定的。需要设置存储着色器的
GLT_ATTRIBUTE_VERTEX(顶点分量)
和
GLT_ATTRIBUTE_TEXTURE0(纹理坐标)
、GLT_ATTRIBUTE_NORMAL(表面法线)
GLShaderManager::UserStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIEF,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vLightPos[3],GLfloat vBaseColor[4],GLint nTextureUnit);
- 参数1:纹理理光源着⾊器
- 参数2:投影矩阵
- 参数3:视觉空间中的光源位置
- 参数4:⼏何图形的基本色
- 参数5:将要使⽤的纹理单元
- 将⼀个纹理通过漫反射照明计算机进⾏调整(相乘)。光线在视觉空间中 的位置是给定的。需要设置存储着色器的
-
- 点链接
第一次学习任何计算机系统中绘制任何类型的 2 D 图形时,大多数可能从绘制像素开始。像素是计算机屏幕上显示的最小元素。以下是最简单额计算机图形:在计算机屏幕绘制一个点,并将它设置一个特定的颜色。在这个简单的基础。上慢慢学会创建线、多边形、圆和其他性质和图形。但是!使用 OpenGL 在计算机屏幕上进行绘图则完全不同。我们不关物理屏幕坐标和像素,关注的是视景体中的位置坐标。将这些点、线和三角形从创建 3 D 空间投影到计算机屏幕上的 2 D 图形则是着色器程序和光栅化硬件所要完成的工作。
-
点和线
- 今天我们将从更底层更基础的角度来详细学习OpenGL图元渲染。点,是最简单的图像。每个特定的顶点在屏幕上都仅仅是一个单独的点。默认的情况下,点的大小是一个像素的大小。
- 修改点大小的方法:
//1.最简单也是最常用的4.0f ,表示点的大小 glPointSize(4.0f) ; //2.设置点的大小范围和点与点之间的间隔 GLfloat sizes[2] = {2.0f,4.0f}; GLfloat step = 1.0f; //获取点大小范围和最小步长 glGetFloatv(GL_POINT_SIZE_RANGE,sizes); glGetFloatv(GL_ POINT_GRAULARITY ,&step); //3.通过使用程序点大小模式来设置点大小 glEnable (GL_ PROGRAM_ POINT_SIZE) ; //这种模式下允许我们]通过编程在顶点着器或几何着色器中设置点大小。着色器内建变量: gl_ PointSize,并且可以在器源码直接写 gl_PointSize = 5.0;
-
线
- 比点更进一步的就是独立线段了。一个线段就是2个顶点之间绘制的。
默认情况下,线段的宽度是一个像素。改变线段唯一的方式通过:
//1.设置独立线段宽度为2.5f; glLineWidth(2.5f);
- 比点更进一步的就是独立线段了。一个线段就是2个顶点之间绘制的。
-
线环
- 线环是线带的一种简单拓展。在线带的基础上额外增加一条线带闭合的。
-
绘制三角形
-
最简单的实体多边形就是三角形,它只有3个边。光栅化硬件最欢迎三角形。并且现在OpenGL已经是OpenGL中支持的唯一种多边形。每3个顶点定义一个新的三角形。
-
在本次的学习中,我们不仅绘制一个三角形,更绘制4个三角形组成金字塔形的三角形。我们可以使用方向键来旋转金字塔,从不同角度进行观察。但是这个金字塔没有底面,所以我们可以看到它的底部。
-
-
环绕
-
将顺时针方向绘制的三角形用逆时针的方式绘制。在绘制第一个三角形时,线条是按照从VO-V1,再到V2。最后再回到VO的个闭合三角形。这个是沿着顶点顺时针方向。这种顺序与方向结合来指定顶点的方式称为环绕。
上图的2个三角形的缠绕方向完全相反。
在默认的情况下,OpenGL认为具有逆时针方向环绕的多边形是正面的。而右侧的顺时针方向三角形是三角形的背面。
-
为什么会认为这个问题会很重要了?
因为我们常常希望为一个多边形的正面和背面分别设置不同的物理特征。我们可以完全隐藏一个多边形的背面,或者给它设置一种不同的颜色和反射属性。纹理图像在背面三角形中也是相反的。在一个场景中,使所有的多边形保持环绕方向的一致,并使用正面多边形来绘制所有实心物体的表面是非常重要的。//定义前向和背向的多变形: glF rontFace (mode) 参数: GL_CW | GL_CCW GL_CCW: 表示传入的mode会选择逆时针为前向 GL_CW: 表示顺时针为前向。 默认: GL_CCW。逆向时针为前向。
-
三角地带
- 对于很多表面和形状来说,我们可能需要绘制几个相连的三角形。我们可以使用GL_TRIANGLE_STRIP图元绘制一串相连的三角形。从而节省大量的时间。使用三角带而不是分别指定每个三角形,这样做的优点:
- 1.用前3个顶点指定第1个三角形之后,对于接下来的每一个三角形,只需要再指定1个顶点。需要绘制大量的三角形时,采用这种方法可以节省大量的程序代码和数据存储空间。
-
2.提供运算性能和节省带宽。更少的顶点意味着数据从内存传输到图形卡的速度更快,并且顶点着色器需要处理的次数也更少了。
- 对于很多表面和形状来说,我们可能需要绘制几个相连的三角形。我们可以使用GL_TRIANGLE_STRIP图元绘制一串相连的三角形。从而节省大量的时间。使用三角带而不是分别指定每个三角形,这样做的优点:
-
-
简单批次容器
- GLTools库中包含额一个简单的容器类,叫做GBatch。这个类可以作为7种图元的简单批次容器使用。而且它知道在使用GL_ ShaderManager 支持的任意存储着色器时如何对图元进行渲染。
void GLBatch: :Begain(GLeunm primitive,GLuint nVerts,GLuint nTe xttureUnints = 0) ; 参数1:图元 参数2:顶点数 参数3:一组或者2组纹理坐标(可选) // 复制表面法线 void GLBatch:: CopyNormalDataf(GLfloat *vNorms) ; //复制颜色 void GLBatch:: CopyColorData4f(GLfloat *vColors) ; //复制纹理坐标 void GLBatch::CopyTexCoordData2f(GLFloat *vTextCoords , GLuint uiTextureLayer); // 结束绘制 void GLBatch::End(void);
- GLTools库中包含额一个简单的容器类,叫做GBatch。这个类可以作为7种图元的简单批次容器使用。而且它知道在使用GL_ ShaderManager 支持的任意存储着色器时如何对图元进行渲染。
- 使用背景
源码实例
/*
实现功能:
点击屏幕,将固定位置上的顶点数据以6种不同形态展示!
*/
#include "GLTools.h"
#include "GLMatrixStack.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLBatch.h"
#include "GLGeometryTransform.h"
#include
#ifdef __APPLE__
#include
#else
#define FREEGLUT_STATIC
#include
#endif
/*
GLMatrixStack 变化管线使用矩阵堆栈
GLMatrixStack 构造函数允许指定堆栈的最大深度、默认的堆栈深度为64.这个矩阵堆在初始化时已经在堆栈中包含了单位矩阵。
GLMatrixStack::GLMatrixStack(int iStackDepth = 64);
//通过调用顶部载入这个单位矩阵
void GLMatrixStack::LoadIndentiy(void);
//在堆栈顶部载入任何矩阵
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
*/
// 各种需要的类
GLShaderManager shaderManager;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLFrame cameraFrame;//观察者位置
GLFrame objectFrame;//被观察者位置
//投影矩阵
GLFrustum viewFrustum;
//容器类(7种不同的图元对应7种容器对象)
GLBatch pointBatch; // 点
GLBatch lineBatch; // 线
GLBatch lineStripBatch; // 线段
GLBatch lineLoopBatch;//线环
GLBatch triangleBatch;
GLBatch triangleStripBatch;
GLBatch triangleFanBatch;
//几何变换的管道
GLGeometryTransform transformPipeline;
M3DMatrix44f shadowMatrix;
GLfloat vGreen[] = { 0.0f, 1.0f, 0.0f, 1.0f };
GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };
// 跟踪效果步骤
int nStep = 0;
// 此函数在呈现上下文中进行任何必要的初始化。.
// 这是第一次做任何与opengl相关的任务。
void SetupRC()
{
//灰色背景
glClearColor(0.7f, 0.7f, 0.7f, 1.0f);
//初始化
shaderManager.InitializeStockShaders();
// 开启深度测试
glEnable(GL_DEPTH_TEST);
//设置变换管线以使用两个矩阵堆栈
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
//观察者移动位置
cameraFrame.MoveForward(-20.0f);
/*
常见函数:
void GLBatch::Begin(GLenum primitive,GLuint nVerts,GLuint nTextureUnits = 0);
参数1:表示使用的图元
参数2:顶点数
参数3:纹理坐标(可选)
//负责顶点坐标
void GLBatch::CopyVertexData3f(GLFloat *vNorms);
//结束,表示已经完成数据复制工作
void GLBatch::End(void);
*/
//定义一些点,类似佛罗里达州的形状。
GLfloat vCoast[24][3] = {
{2.80, 1.20, 0.0 }, {2.0, 1.20, 0.0 },
{2.0, 1.08, 0.0 }, {2.0, 1.08, 0.0 },
{0.0, 0.80, 0.0 }, {-.32, 0.40, 0.0 },
{-.48, 0.2, 0.0 }, {-.40, 0.0, 0.0 },
{-.60, -.40, 0.0 }, {-.80, -.80, 0.0 },
{-.80, -1.4, 0.0 }, {-.40, -1.60, 0.0 },
{0.0, -1.20, 0.0 }, { .2, -.80, 0.0 },
{.48, -.40, 0.0 }, {.52, -.20, 0.0 },
{.48, .20, 0.0 }, {.80, .40, 0.0 },
{1.20, .80, 0.0 }, {1.60, .60, 0.0 },
{2.0, .60, 0.0 }, {2.2, .80, 0.0 },
{2.40, 1.0, 0.0 }, {2.80, 1.0, 0.0 }
};
// 用点的形式--表示佛罗里达州的形状
pointBatch.Begin(GL_POINTS, 24);
pointBatch.CopyVertexData3f(vCoast);
pointBatch.End();
//通过线的形式--表示佛罗里达州的形状
lineBatch.Begin(GL_LINES, 24);
lineBatch.CopyVertexData3f(vCoast);
lineBatch.End();
//通过线段的形式表示表示佛罗里达州的形状
lineStripBatch.Begin(GL_LINE_STRIP, 24);
lineStripBatch.CopyVertexData3f(vCoast);
lineStripBatch.End();
//通过线环的形式表示佛罗里达州的形状
lineLoopBatch.Begin(GL_LINE_LOOP, 24);
lineLoopBatch.CopyVertexData3f(vCoast);
lineLoopBatch.End();
//通过三角形逆时针创建金字塔
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();
//三角形扇形--六边形
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.0) {
//数组下标自增(每自增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个顶点(包括圆心)
printf("三角形扇形顶点数:%d\n", nVerts);
//添加闭合的终点
nVerts++;
vPoints[nVerts][0] = r;
vPoints[nVerts][1] = 0.0f;
vPoints[nVerts][2] = 0.0f;
//加载!
//GL_TRIANGLE_FAN 以一个圆心为中心呈扇形排列,共用相邻顶点的一组三角形
triangleFanBatch.Begin(GL_TRIANGLE_FAN, 8);
triangleFanBatch.CopyVertexData3f(vPoints);
triangleFanBatch.End();
// 三角形条带,一个小环或圆柱段
// 顶点下标
int iCounter = 0;
//半径
GLfloat radius = 3.0f;
//从0度~360度,以0.3弧度为步长
for (GLfloat angle = 0.0f; angle <= (2.0*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++;
}
//关闭循环
printf("三角形带的顶点数:%d\n", 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();
}
void DrawWireFramedBatch(GLBatch* pBatch)
{
/*------------画绿色部分----------------*/
/* GLShaderManager 中的Uniform 值——平面着色器
参数1:平面着色器
参数2:运行为几何图形变换指定一个 4 * 4变换矩阵
--transformPipeline 变换管线(指定了2个矩阵堆栈)
参数3:颜色值
*/
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen);
pBatch->Draw();
/*-----------边框部分-------------------*/
/*
glEnable(GLenum mode); 用于启用各种功能。功能由参数决定
参数列表:http://blog.csdn.net/augusdi/article/details/23747081
注意:glEnable() 不能写在glBegin() 和 glEnd()中间
GL_POLYGON_OFFSET_LINE 根据函数glPolygonOffset的设置,启用线的深度偏移
GL_LINE_SMOOTH 执行后,过虑线点的锯齿
GL_BLEND 启用颜色混合。例如实现半透明效果
GL_DEPTH_TEST 启用深度测试 根据坐标的远近自动隐藏被遮住的图形(材料
glDisable(GLenum mode); 用于关闭指定的功能 功能由参数决定
*/
//画黑色边框
glPolygonOffset(-1.0f, -1.0f); // 偏移深度,在同一位置要绘制填充和边线,会产生z冲突,所以要偏移
glEnable(GL_POLYGON_OFFSET_LINE);
// 画反锯齿。让黑边好看些
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//绘制线框几何黑色版 三种模式,实心,边框,点,可以作用在正面,背面,或者两面
//通过调用glPolygonMode将多边形正面或者背面设为线框模式,实现线框渲染
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//设置线条宽度
glLineWidth(2.5f);
/* GLShaderManager 中的Uniform 值——平面着色器
参数1:平面着色器
参数2:运行为几何图形变换指定一个 4 * 4变换矩阵
--transformPipeline.GetModelViewProjectionMatrix() 获取的
GetMatrix函数就可以获得矩阵堆栈顶部的值
参数3:颜色值(黑色)
*/
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
pBatch->Draw();
// 复原原本的设置
////通过调用glPolygonMode将多边形正面或者背面设为全部填充模式
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDisable(GL_POLYGON_OFFSET_LINE);
glLineWidth(1.0f);
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);
}
// 召唤场景
void RenderScene(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//压栈
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
//矩阵乘以矩阵堆栈的顶部矩阵,相乘的结果随后简存储在堆栈的顶部
modelViewMatrix.MultMatrix(mCamera);
M3DMatrix44f mObjectFrame;
//只要使用 GetMatrix 函数就可以获取矩阵堆栈顶部的值,这个函数可以进行2次重载。用来使用GLShaderManager 的使用。或者是获取顶部矩阵的顶点副本数据
objectFrame.GetMatrix(mObjectFrame);
//矩阵乘以矩阵堆栈的顶部矩阵,相乘的结果随后简储存在堆栈的顶部
modelViewMatrix.MultMatrix(mObjectFrame);
/* GLShaderManager 中的Uniform 值——平面着色器
参数1:平面着色器
参数2:运行为几何图形变换指定一个 4 * 4变换矩阵
--transformPipeline.GetModelViewProjectionMatrix() 获取的
GetMatrix函数就可以获得矩阵堆栈顶部的值
参数3:颜色值(黑色)
*/
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
//根据nStep值来表示
switch (nStep) {
case 0:
//设置点的大小
glPointSize(4.0f);
pointBatch.Draw();
//画完之后,避免影响到其他的设置,所以要设置恢复默认值
glPointSize(1.0f);
break;
case 1:
//设置线的宽度
glLineWidth(2.0f);
lineBatch.Draw();
glLineWidth(1.0f);
break;
case 2: //线段
glLineWidth(2.0f);
lineStripBatch.Draw();
glLineWidth(1.0f);
break;
case 3: //线环
glLineWidth(2.0f);
lineLoopBatch.Draw();
glLineWidth(1.0f);
break;
case 4:
DrawWireFramedBatch(&triangleBatch);
break;
case 5:
DrawWireFramedBatch(&triangleStripBatch);
break;
case 6:
DrawWireFramedBatch(&triangleFanBatch);
break;
}
//还原到以前的模型视图矩阵(单位矩阵)
modelViewMatrix.PopMatrix();
//进行缓冲区交换
glutSwapBuffers();
}
//特殊键位处理(上、下、左、右移动)
void SpecialKeys(int key, int x, int y)
{
// 围绕一个指定的X,Y,Z轴旋转
/**
RotateWorld(float fAngle, float x, float y, float z)
x,y,z中的值,1表示YES,0表示NO来判断指定哪个轴旋转
*/
if (key == GLUT_KEY_UP) {
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
}
if (key == GLUT_KEY_DOWN) {
objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
}
if (key == GLUT_KEY_LEFT) {
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
}
if (key == GLUT_KEY_RIGHT) {
objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
}
glutPostRedisplay();
}
//根据空格次数。切换不同的“窗口名称”
void KeyPressFunc(unsigned char key, int x, int y)
{
//判断key的次数
if (key == 32) {
nStep++;
if (nStep > 6) {
nStep = 0;
}
}
//switch 判断显示具体操作
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();
}
// 窗口已更改大小,或刚刚创建。无论哪种情况,我们都需要
// 使用窗口维度设置视口和投影矩阵.
void ChangeSize(int w, int h)
{
// 窗口大小
glViewport(0, 0, w, h);
//创建投影矩阵,并将它载入投影矩阵堆栈中
//创建投影矩阵,并将它载入投影矩阵堆栈中
/**
@params fFov 角度
@params fAspect 纵横比(宽高比)
@params fNear
@params fFar
*/
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0, 500.0f);
//投影矩阵
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//调用顶部载入单元矩阵:因为不用做什么模型变化,所以用单元矩阵就行了
modelViewMatrix.LoadIdentity();
}
int main(int argc, char* argv[])
{
////设置工作路径
gltSetWorkingDirectory(argv[0]);
//初始化
glutInit(&argc, argv);
//申请一个颜色缓存区、深度缓存区、双缓存区、模板缓存区
/**
GLUT_DOUBLE 双缓存区
GLUT_RGBA 颜色缓存区
GLUT_DEPTH 深度缓存区
GLUT_STENCIL 模板缓存区
*/
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
//设置window 的尺寸
glutInitWindowSize(800, 600);
//创建window的名称
glutCreateWindow("GL_Point");
//注册回调函数(改变尺寸)
glutReshapeFunc(ChangeSize);
//点击空格时,调用的函数
glutKeyboardFunc(KeyPressFunc);
//特殊键位函数(上下左右)
glutSpecialFunc(SpecialKeys);
//显示函数
glutDisplayFunc(RenderScene);
//判断一下是否能初始化glew库,确保项目能正常使用OpenGL 框架
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
//绘制
SetupRC();
//runloop运行循环
glutMainLoop();
return 0;
}