OpenGL初探
图形API简介
OpenGL (Open Graphics Library)是一个用于渲染2D、3D矢量图形的跨编程语⾔言、跨平台的编程图形程序接⼝,它将计算机的资源抽象称为一个个OpenGL的对象,对这些资源的操作抽象为一个个的OpenGL指令。
OpenGL ES (OpenGL for Embedded Systems)是 OpenGL 三维图形 API 的子集,针对手机、 PDA和游戏主机等嵌入式设备而设计,去除了许多不必要和性能较低的API接口。
DirectX是由微软公司创建的多媒体编程接口,很多API组成的,DirectX并不是一个单纯的图形API。最重要的是DirectX是属于 Windows上一个多媒体处理理API。并不支持Windows以外的平台,所以不是跨平台框架。按照性质分类,可以分为四大部分,显示部分、声音部分、输入部分和⽹络部分。
Metal是在2014年WWDC上,苹果味游戏开发者推出的新的平台技术。该技术能够为 3D 图像提高 10 倍的渲染性能,是苹果为了解决3D渲染而推出的框架。是一种低层次的渲染应用程序编程接口。
图形API简单来说就算实现图形的底层渲染。比如在游戏开发中,对于游戏场景/游戏人物的渲染;在音视频开发中,对于视频解码后的数据渲染;在地图引擎中,对于地图上的数据渲染;在动画中,实现动画的绘制;在视频处理中,对于视频加上滤镜效果。
OpenGL /OpenGL ES/ Metal 在任何项目中解决问题的本质:就是利用GPU芯片来高效渲染图形图像。
图形API 是iOS开发者唯一接近GPU的方式。
OpenGL专业名词解析
OpenGL上下文(context)
-
在应用程序调用任何OpenGL的指令之前,需要首先创建一个OpenGL的 上下文。这个上下⽂是⼀个⾮常庞大的状态机,保存了OpenGL中的各种状 态,这也是OpenGL指令执行的基础。
-
OpenGL的函数不管在哪个语言中,都是类似C语言一样的面向过程的函数,本质上都是对OpenGL上下文这个庞大的状态机中的某个状态或者对象进行操作,当然你得首先把这个对象设置为当前对象。因此,通过对OpenGL指令的封装,是可以将OpenGL的相关调用封装成为⼀个⾯向对象的图形API的。
-
由于OpenGL上下文是⼀个巨大的状态机,切换上下文往往会产⽣较大的开销,但是不同的绘制模块,可能需要使⽤完全独立的状态来管理。因此,可以在应用程序中分别创建多个不不同的上下⽂,在不同线程中使⽤不同的上下文,上下文之间共享纹理、缓冲区等资源。这样的⽅案,会⽐反复切换上下文,或者⼤量修改渲染状态,更加合理高效。
OpenGL状态机
-
状态机是理论上的一种机器。它描述了一个对象在其⽣命周期内所经历的各种状态,状态间的转变,发⽣转变的动因,条件及转变中所执行的活动。或者说,状态机是一种行为,说明了对象在其⽣命周期中响应事件所经历的状态序列以及对那些状态事件的响应。因此具有以下特点:
1、有记忆功能,能记住其当前的状态
2、可以接收输入,根据输入的内容和⾃己的原先状态,修改⾃己当前状态,并且可以有对应输出
3、当进⼊特殊状态(停机状态)的时候,便不再接收输入,停⽌工作 -
类推到OpenGL 中来,可以这么理解:
1、OpenGL可以记录⾃己的状态(如当前所使用的颜⾊、是否开启了混合功能等)
2、OpenGL可以接收输入(当调⽤OpenGL函数的时候,实际上可以看成 OpenGL在接收我们的输入),如我们调用glColor3f
,则OpenGL接收到这个输入后会修改⾃己的“当前颜⾊色”这个状态
3、OpenGL可以进入停⽌状态,不再接收输入。在程序退出前,OpenGL总会先停⽌工作的
渲染
将图形/图像数据转换成3D空间图像操作
顶点数组(VertexArray)和顶点缓冲区(VertexBuffer)
画图⼀般是先画好图像的骨架,然后再往骨架⾥面填充颜色,这对于 OpenGL也是一样的。顶点数据就是要画的图像的⻣架,和现实中不同的是,OpenGL中的图像都是由图元组成。在OpenGLES中,有3种类型的图元:点、线、三⻆形。那这些顶点数据最终是存储在哪里的呢?开发者可以选择设定函数指针,在调⽤用绘制⽅法的时候,直接由内存传⼊顶点数据,也就是说这部分数据之前是存储在内存当中的,被称为顶点数组。⽽性能更高的做法是,提前分配一块显存,将顶点数据预先传⼊入到显存当中。这部分的显存,就被称为顶点缓冲区
顶点指的是我们在绘制⼀个图形时,它的顶点位置数据。而这个数据可以直接存储在数组中或者将其缓存到GPU内存中
管线
在OpenGL下渲染图形,就会有经历⼀个⼀个节点。而这样的操作可以理解为管 线。⼤家可以想象成流⽔线,每个任务类似流⽔线般执⾏,任务之间有先后顺序。管线是⼀个抽象的概念,之所以称之为管线是因为显卡在处理数据的时候是按照一个固定的顺序来的,而且严格按照这个顺序。就像⽔从⼀根管⼦子的⼀端流到另⼀端,这个顺序是不能被打破的。
固定管线/存储着⾊器
在早期的OpenGL版本,它封装了很多种着⾊器程序块,内置的⼀段包含了光照、坐标变换、裁剪等等诸多功能的固定shader程序来完成。来帮助开发者来完成图形的渲染. 而开发者只需要传入相应的参数,就能快速完成图形的渲染. 类似于iOS开发会封装很多API,⽽我们只需要调用,就可以实现功能.不需要关注底层实现原理
但是由于OpenGL 的使⽤场景⾮常丰富,固定管线或存储着⾊色器器⽆法完成每⼀个业务.这时将相关部分开放成可编程。
着⾊器程序Shader
实现全⾯的将固定渲染管线架构变为了可编程渲染管线。因此,OpenGL在实际调⽤绘制函数之前,还需要指定一个由shader编译成的着色器程序。常见的着⾊色器主要有顶点着⾊器(VertexShader),⽚段着⾊器 (FragmentShader)/像素着⾊器(PixelShader),几何着⾊器 (GeometryShader),曲面细分着⾊器(TessellationShader)。⽚段着⾊器和像素着⾊器只是在OpenGL和DX中的不同叫法⽽已。可惜的是,直到 OpenGLES 3.0,依然只⽀持了顶点着⾊器和片段着⾊器这两个最基础的着⾊器。
OpenGL在处理shader时,和其他编译器⼀样。通过编译、链接等步骤,生成了了着⾊器程序(glProgram),着⾊器程序同时包含了顶点着⾊器和⽚段着⾊器的运算逻辑。在OpenGL进⾏绘制的时候,首先由顶点着⾊器对传入 的顶点数据进行运算。再通过图元装配,将顶点转换为图元。然后进⾏光栅化,将图元这种⽮量图形,转换为栅格化数据。最后,将栅格化数据传入⽚段着⾊器中进⾏运算。片段着⾊器会对栅格化数据中的每⼀个像素进行运算,并决定像素的颜⾊
顶点着色器VertexShader
一般⽤来处理图形每个顶点变换(旋转/平移/投影等)
顶点着⾊器是OpenGL中⽤用于计算顶点属性的程序。顶点着⾊器是逐顶点运算的程序,也就是说每个顶点数据都会执行⼀次顶点着⾊器,当然这是并行的,并且顶点着⾊器运算过程中⽆法访问其他顶点的数据
⼀般来说典型的需要计算的顶点属性主要包括顶点坐标变换、逐顶点光照运算等等。顶点坐标由⾃自身坐标系转换到归⼀化坐标系的运算,就是在这里发⽣生的。
⽚元着⾊器程序FragmentShader
一般⽤来处理图形中每个像素点颜⾊计算和填充
⽚段着⾊器是OpenGL中⽤于计算片段(像素)颜⾊的程序。片段着⾊器是逐像素运算的程序,也就是说每个像素都会执⾏一次片段着⾊器,当然也是并⾏的
GLSL(OpenGL Shading Language)
OpenGL着⾊语⾔(OpenGL Shading Language)是⽤来在OpenGL中着色编程的语言,也即开发人员写的短⼩的自定义程序,他们是在图形卡的GPU (Graphic Processor Unit图形处理单元)上执⾏的,代替了固定的渲染管线的一部分,使渲染管线中不同层次具有可编程性。比如:视图转换、投影转换等。GLSL(GL Shading Language)的着⾊器代码分成2个部分: Vertex Shader(顶点着⾊器)和Fragment(⽚段着⾊器)
光栅化Rasterization
是把顶点数据转换为⽚元的过程。具有将图转化为一个个栅格组成的图像的作⽤,特点片元中的每个元素对应帧缓冲区中的一像素。
光栅化其实是一种将⼏何图元变为⼆维图像的过程。该过程包含了两部分的工作。第一部分工作:决定窗口坐标中的哪些整型栅格区域被基本图元占用;第二部分工作:分配一个颜⾊值和⼀一个深度值到各个区域。光栅化 过程产⽣生的是⽚元
把物体的数学描述以及与物体相关的颜⾊信息转换为屏幕上⽤于对应位置的像素及⽤于填充像素的颜色,这个过程称为光栅化,这是一个将模拟信号转化为离散信号的过程
纹理
纹理可以理解为图片,而且是位图。在渲染图形时需要在其编码填充图片,为了使得场景更加逼真.而这⾥使⽤的图片,就是常说的纹理。但是在OpenGL,我们更加习惯叫纹理,⽽不是图片.
混合(Blending)
在测试阶段之后,如果像素依然没有被剔除,那么像素的颜色将会和帧缓冲区中颜⾊附着上的颜色进⾏混合,混合的算法可以通过OpenGL的函数进行指定。但是OpenGL提供的混合算法是有限的,如果需要更加复杂的混合算法,一般可以通过像素着⾊器进⾏实现,当然性能会⽐原⽣的混合算法差一些.
变换矩阵
图形想发⽣平移,缩放,旋转变换.就需要使⽤变换矩阵.
投影矩阵
⽤于将3D坐标转换为⼆维屏幕坐标,实际线条也将在⼆维坐标下进行绘制
渲染上屏/交换缓冲区(SwapBuffer)
渲染缓冲区一般映射的是系统的资源,⽐如窗口。如果将图像直接渲染到窗口对应的渲染缓冲区,则可以将图像显示到屏幕上。
但是,值得注意的是,如果每个窗⼝只有⼀个缓冲区,那么在绘制过程中屏幕进⾏了刷新,窗口可能显示出不完整的图像
为了解决这个问题,常规的OpenGL程序⾄少都会有两个缓冲区。显示在屏幕上的称为屏幕缓冲区,没有显示的称为离屏缓冲区。在⼀个缓冲区渲染完成之后,通过将屏幕缓冲区和离屏缓冲区交换,实现图像在屏幕上的显示。
由于显示器的刷新一般是逐行进⾏的,因此为了防⽌交换缓冲区的时候屏幕上下区域的图像分属于两个不同的帧,因此交换一般会等待显示器刷新完成的信号,在显示器两次刷新的间隔中进行交换,这个信号就被称为垂直同步信号,这个技术被称为垂直同步
使⽤了双缓冲区和垂直同步技术之后,由于总是要等待缓冲区交换之后再进⾏下⼀帧的渲染,使得帧率无法完全达到硬件允许的最⾼水平。为了解决这个问题,引⼊了三缓冲区技术,在等待垂直同步信号时,来回交替渲染两个离屏的缓冲区,而垂直同步发生时,屏幕缓冲区和最近渲染完成的离屏缓冲区交换,实现充分利用硬件性能的⽬的
投影方式
1、 正射投影(Orthographic Projection)
正射投影,又叫平行投影。这种投影的视景体是一个矩形的平行管道,也就是一个长方体。正射投影的最大一个特点是无论物体距离相机多远,投影后的物体大小尺寸不变。这种投影通常用在建筑蓝图绘制和计算机辅助设计等方面,这些行业要求投影后的物体尺寸及相互间的角度不变,以便施工或制造时物体比例大小正确。
2 透视投影(Perspective Projection)
透视投影符合人们心理习惯,即离视点近的物体大,离视点远的物体小,远到极点即为消失,成为灭点。它的视景体类似于一个顶部和底部都被切除掉的棱椎,也就是棱台。这个投影通常用于动画、视觉仿真以及其它许多具有真实性反映的方面。
详细解释请看openGL两种投影方式
坐标系
OpenGL希望每次顶点着色后,我们的可见顶点都为标准化设备坐标(Normalized Device Coordinate,NDC)。也就是说每个顶点的x,y,z都应该在-1到1之间,超出这个范围的顶点将是不可见的。
通常情况下我们会自己设定一个坐标范围,之后再在顶点着色器中将这些坐标转换为设备坐标。然后这些设备坐标传入光栅器,将它们变换为屏幕上的二维坐标和像素。
将坐标转换为标准化设备坐标,接着再转换为屏幕坐标的过程通常是分步进行的,也就是类似于流水线那样子。在这个流水线中,物体的顶点在最终转换为屏幕坐标之前还会被转换到多个坐标系统中。将物体的坐标转换到几个过渡坐标系(Intermediate Coordinate System)的优点在于:在这些特定的坐标系统中,一些操作或者运算更加方便和容易。对我们来说比较重要的总共有5个不同的坐标系统:
- 局部空间(Local Space,或者称为物体空间(Object Space))
- 世界空间(World Space)
- 观察空间(View Space,或者称为视觉空间(Eye Space))
- 裁剪空间(Clip Space)
- 屏幕空间(Screen Space)
就是一个顶点在最终被转化为片段之前需要经历的所以不同状态。为了将坐标从一个坐标系转换到另一个坐标系中,我们需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。物体顶点的起始坐标在局部空间(Local Space),这里称它为局部坐标(Local Coordinate);它在之后变成世界坐标(World Coordinate)、观察坐标(View Coordinate)、裁剪坐标(Clip Coordinate),并最后以屏幕坐标(Screen Coordinate)的形式结束。
世界坐标系
世界坐标系是系统的绝对坐标系,在没有建立用户坐标系之前,画面上所有的点的坐标都可以在该坐标系的原点来确定各自的位置。世界坐标系始终是固定不变的。
物体坐标系
每个物体都有他们独立的坐标系,当物理移动或者改变方向时。该物体相关联的坐标系将随之移动或改变方向。
物体坐标系是以物体本身而言,比如,我先向你发指令,“向前走一步”是向你的物体坐标系指令。我并不知道你会忘哪个绝对的方向移动。比如说,当你开车时,有人会说向左转,有人说向东。这里向左转是物体坐标系的概念,而向东则是世界坐标系中的概念。
在某些情况下,我们可以理解物体坐标系为模型坐标系。因为模型顶点的坐标都是在模型坐标系中描述的。
摄像机(照相机)坐标系
在坐标系的范畴里,摄像机坐标系和照相机坐标系都是一样的意义。照相机坐标系是和观察者密切相关的坐标系。照相机坐标系和屏幕坐标系相似,差别在于照相机坐标系处于3D空间中,而屏幕坐标系在2D平面里。如下图:

惯性坐标系
指的是世界坐标系到物体坐标系的“半途”。惯性坐标系的原点和物体坐标原点重合,但惯性坐标系的轴平行于世界坐标系的轴。
因为物体坐标系转换到惯性坐标系只需要旋转,从惯性坐标系转换到世界坐标系只需要平移。所以引入惯性坐标系。如下图:

注意:OpenGL坐标系中的物体、世界、照相机坐标系都属于右手坐标系,而规范化设备坐标系使用左手坐标系。笼统地说OpenGL使用右手坐标系是不合适的。

坐标变换的全局图
OpenGL最终的渲染设备是2D的,我们需要将3D表示的场景转换为最终2D形式,前面使用模型变换和视变换将物体坐标系转换到照相机坐标系后,需要进行投影变换,将照相机坐标系转换到裁剪坐标系,经过透视除法后,变换到规范化设备坐标系(NDC),最后进行视口变换后,3D坐标才变换到屏幕上的2D坐标,这个过程如下图所示:

注意:OpenGL只定义了裁剪坐标系、规范化设备坐标系和屏幕坐标系,而局部坐标系(模型坐标系)、世界坐标系和照相机坐标系都是为了方便用户设计而自定义的坐标系,它们的关系如下图所示:

-
图中左边的过程包括:模型变换、视变换、投影变换,这些变换可以由用户根据需要自行指定,这些内容在顶点着色器中完成。
-
图中右边的两个步骤:透视除法、视口变换,这两个步骤是OpenGL自动执行的,在顶点着色器处理后的阶段完成。
上述的每一个步骤都创建了一个变换矩阵:模型矩阵、观察矩阵和投影矩阵。一个顶点坐标将会根据以下过程被变换到裁剪坐标:

这一系列的矩阵变换需要从右往左读。最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。
OpenGL然后对裁剪坐标执行透视除法从而将它们变换到标准化设备坐标。OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点。这个过程称为视口变换。

模型变换
局部坐标系(模型坐标系)是为了方便构造模型而设立的坐标系,建立模型是我们无需关心最终对象显示在屏幕哪个位置。
模型变换的主要目的是通过变换使得用顶点属性定义或者3D建模软件构造的模型,能够按照需要,通过缩小,平移等操作放置到场景中合适的位置。通过模型变换后,物体放置在一个全局的世界坐标系中,世界坐标系是所有物体交互的一个公共坐标系。
视变换
视变换是为了方便观察场景中物体而设立的坐标系,在这个坐标系中相机是假想的概念,是为了便于计算而引入的。相机坐标系中的坐标,就是从相机的角度来解释世界坐标系中位置

OpenGL中相机始终位于原点,指向-Z轴,而以相反的方式来调整场景中物体,从而达到相同的观察效果。例如要观察-Z轴方向的一个立方体的右侧面,可以有两种方式:

投影变换
投影变换(projection transformation)是将一种地图投影点的坐标变换为另一种地图投影点的坐标的过程。研究投影点坐标变换的理论和方法。
知识零散点:
CPU擅长的是类似判断语句的执行,一个图片上有很多像素点,都要执行一次片元着色器,运算量非常大,这时就只能交由GPU来计算。OpenGL基于GPU的,就是由于海量的计算需要GPU来做辅助了。
PNG是压缩过后的图片,需要展示的话,就需要还原成位图。
平面图形:使用 正投影
立体图形:使用 透视投影
摄像机坐标系,其实就是观察者坐标系
GPU是解码视频,CPU是解码图片
实例代码:
#include "GLShaderManager.h"
/*
`#include<GLShaderManager.h>` 移入了GLTool 着色器管理器(shader Mananger)类。没有着色器,我们就不能在OpenGL(核心框架)进行着色。着色器管理器不仅允许我们创建并管理着色器,还提供一组“存储着色器”,他们能够进行一些初步基本的渲染操作。
*/
#include "GLTools.h"
/*
`#include<GLTools.h>` GLTool.h头文件包含了大部分GLTool中类似C语言的独立函数
*/
#include <GLUT/GLUT.h>
/*
在Mac 系统下,`#include<glut/glut.h>`
在Windows 和 Linux上,我们使用freeglut的静态库版本并且需要添加一个宏
*/
//定义一个,着色管理器
GLShaderManager shaderManager;
//简单的批次容器,是GLTools的一个简单的容器类。
GLBatch triangleBatch;
//blockSize 边长
GLfloat blockSize = 0.1f;
//正方形的4个点坐标
GLfloat vVerts[] = {
-blockSize,-blockSize,0.0f,
blockSize,-blockSize,0.0f,
blockSize,blockSize,0.0f,
-blockSize,blockSize,0.0f
};
/*
在窗口大小改变时,接收新的宽度&高度。
⾃自定义函数。通过
glutReshaperFunc(函数名)注册为重塑函数.当屏幕⼤小发⽣变化/或者第一次创建窗⼝时,会调⽤该函数调整窗⼝大⼩/视⼝⼤小.
*/
void changeSize(int w,int h)
{
/*
x,y 参数代表窗口中视图的左下角坐标,而宽度、高度是像素为表示,通常x,y 都是为0
*/
glViewport(0, 0, w, h);
}
/**
⾃自定义函数。通过glutDisplayFunc(函数名)注册为显示渲染函数.当屏幕发⽣变化/或者开发者主动渲染会调⽤此函数,⽤来实现数据->渲染过程
*/
void RenderScene(void)
{
//1.清除一个或者一组特定的缓存区
/*
缓冲区是一块存储图像信息的储存空间,红色、绿色、蓝色和alpha分量通常一起作为颜色缓存区或像素缓存区引用。
OpenGL 中不止一种缓冲区(颜色缓存区、深度缓存区和模板缓存区)
清除缓存区对数值进行预置
参数:指定将要清除的缓存的
GL_COLOR_BUFFER_BIT :指示当前激活的用来进行颜色写入缓冲区
GL_DEPTH_BUFFER_BIT :指示深度缓存区
GL_STENCIL_BUFFER_BIT :指示模板缓冲区
*/
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
//2.设置一组浮点数来表示红色
GLfloat vRed[] = {1.0,0.0,0.0,1.0f};
//传递到存储着色器,即GLT_SHADER_IDENTITY着色器,这个着色器只是使用指定颜色以默认笛卡尔坐标第在屏幕上渲染几何图形
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
//提交着色器
triangleBatch.Draw();
//在开始的设置openGL 窗口的时候,我们指定要一个双缓冲区的渲染环境。这就意味着将在后台缓冲区进行渲染,渲染结束后交换给前台。这种方式可以防止观察者看到可能伴随着动画帧与动画帧之间的闪烁的渲染过程。缓冲区交换平台将以平台特定的方式进行。
//将后台缓冲区进行渲染,然后结束后交换给前台
glutSwapBuffers();
}
/**
⾃自定义函数。设置你需要渲染的图形的相关顶点数据/颜⾊数据等数据的准备工作
*/
void setupRC()
{
//设置清屏颜色(背景颜色)
glClearColor(0.98f, 0.40f, 0.7f, 1);
//没有着色器,在OpenGL 核心框架中是无法进行任何渲染的。初始化一个渲染管理器。
//在前面的课程,我们会采用固管线渲染,后面会学着用OpenGL着色语言来写着色器
shaderManager.InitializeStockShaders();
// //指定顶点
// //在OpenGL中,三角形是一种基本的3D图元绘图原素。
// GLfloat vVerts[] = {
// -0.5f,0.0f,0.0f,
// 0.5f,0.0f,0.0f,
// 0.0f,0.5f,0.0f
// };
//修改为GL_TRIANGLE_FAN ,4个顶点
triangleBatch.Begin(GL_TRIANGLE_FAN, 4);
triangleBatch.CopyVertexData3f(vVerts);
triangleBatch.End();
}
void SpecialKeys(int key, int x, int y){
GLfloat stepSize = 0.025f;
GLfloat blockX = vVerts[0];
GLfloat blockY = vVerts[10];
printf("v[0] = %f\n",blockX);
printf("v[10] = %f\n",blockY);
if (key == GLUT_KEY_UP) {
blockY += stepSize;
}
if (key == GLUT_KEY_DOWN) {
blockY -= stepSize;
}
if (key == GLUT_KEY_LEFT) {
blockX -= stepSize;
}
if (key == GLUT_KEY_RIGHT) {
blockX += stepSize;
}
//触碰到边界(4个边界)的处理
//当正方形移动超过最左边的时候
if (blockX < -1.0f) {
blockX = -1.0f;
}
//当正方形移动到最右边时
//1.0 - blockSize * 2 = 总边长 - 正方形的边长 = 最左边点的位置
if (blockX > (1.0 - blockSize * 2)) {
blockX = 1.0f - blockSize * 2;
}
//当正方形移动到最下面时
//-1.0 - blockSize * 2 = Y(负轴边界) - 正方形边长 = 最下面点的位置
if (blockY < -1.0f + blockSize * 2 ) {
blockY = -1.0f + blockSize * 2;
}
//当正方形移动到最上面时
if (blockY > 1.0f) {
blockY = 1.0f;
}
printf("blockX = %f\n",blockX);
printf("blockY = %f\n",blockY);
// Recalculate vertex positions
vVerts[0] = blockX;
vVerts[1] = blockY - blockSize*2;
printf("(%f,%f)\n",vVerts[0],vVerts[1]);
vVerts[3] = blockX + blockSize*2;
vVerts[4] = blockY - blockSize*2;
printf("(%f,%f)\n",vVerts[3],vVerts[4]);
vVerts[6] = blockX + blockSize*2;
vVerts[7] = blockY;
printf("(%f,%f)\n",vVerts[6],vVerts[7]);
vVerts[9] = blockX;
vVerts[10] = blockY;
printf("(%f,%f)\n",vVerts[9],vVerts[10]);
// 赋值数据,更新数据
triangleBatch.CopyVertexData3f(vVerts);
// 更新显示 调用 RenderScene
glutPostRedisplay();
}
/**
程序⼊口。OpenGL是⾯面向过程编程。所以你会发现利利⽤用OpenGL处理理图形/图像都是链式形式。以及基于OpenGL封装的图像处理理框架也是链式编程
*/
int main(int argc,char *argv[])
{
//设置当前工作目录,针对MAC OS X
/*
`GLTools`函数`glSetWorkingDrectory`用来设置当前工作目录。实际上在Windows中是不必要的,因为工作目录默认就是与程序可执行执行程序相同的目录。但是在Mac OS X中,这个程序将当前工作文件夹改为应用程序捆绑包中的`/Resource`文件夹。`GLUT`的优先设定自动进行了这个中设置,但是这样中方法更加安全。
*/
gltSetWorkingDirectory(argv[0]);
//初始化GLUT库,这个函数只是传说命令参数并且初始化glut库
glutInit(&argc, argv);
/*
初始化双缓冲窗口,其中标志GLUT_DOUBLE、GLUT_RGBA、GLUT_DEPTH、GLUT_STENCIL分别指
双缓冲窗口、RGBA颜色模式、深度测试、模板缓冲区
--GLUT_DOUBLE`:双缓存窗口,是指绘图命令实际上是离屏缓存区执行的,然后迅速转换成窗口视图,这种方式,经常用来生成动画效果;
--GLUT_DEPTH`:标志将一个深度缓存区分配为显示的一部分,因此我们能够执行深度测试;
--GLUT_STENCIL`:确保我们也会有一个可用的模板缓存区。
深度、模板测试后面会细致讲到
*/
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
//GLUT窗口大小、窗口标题
glutInitWindowSize(800, 600);
glutCreateWindow("Triangle");
/*
GLUT 内部运行一个本地消息循环,拦截适当的消息。然后调用我们不同时间注册的回调函数。我们一共注册2个回调函数:
1)为窗口改变大小而设置的一个回调函数
2)包含OpenGL 渲染的回调函数
*/
//注册重塑函数
glutReshapeFunc(changeSize);
//注册显示函数
glutDisplayFunc(RenderScene);
//注册特殊函数
glutSpecialFunc(SpecialKeys);
/*
初始化一个GLEW库,确保OpenGL API对程序完全可用。
在试图做任何渲染之前,要检查确定驱动程序的初始化过程中没有任何问题
*/
GLenum status = glewInit();
if (GLEW_OK != status) {
printf("GLEW Error:%s\n",glewGetErrorString(status));
return 1;
}
//设置我们的渲染环境
setupRC();
//类似于iOS runloop 运⾏行行循环
glutMainLoop();
return 0;
}