写在前面:
十一假期几乎天天刷短视频,这两年抖音快手等短视频铺天盖地而来,天猫京东也快速进入短视频。随着5G时代的到来,短视频已渐渐成为人们社交,娱乐,购物等新的生活方式。对于移动端开发者,这既是乘风波浪的机遇,也是优胜劣汰的法则。作为奔梦人,学习OpenGL刻不容缓。于是买了OpenGl超级宝典,但通读一遍之后,仍有困惑,没有发挥本书的最大价值。故而开始研读第二遍,同时也画重点做笔记整理记录于此。
第一章 3D图形
本章内容
- 简单介绍计算机图形历史
- 如何在2D屏幕上创建3D图形
- 基本的3D效果和术语
- 3D坐标系和视口工作原理
- 什么是顶点及如何使用
- 不同类型的3D投影
一、计算机图形的简单历史回顾
最早的计算机是又一行行开关和灯组成的,可以认为计算机图形的最初的形式就是在一块板面上闪烁的灯。
进入电子时代。由纸作为媒介到阴极射线管,再到实时计算机图形。
走向3D。2D + 透视 = 3D
二、3D图像技术和术语
将数学和图形数据转化成3D空间图像的操作叫做渲染。
1、变换和投影
顶点能通过一种称为变换矩阵的数学结构进行旋转。
另外还有一种矩阵叫做投影矩阵。用于将3D坐标转化为二维屏幕坐标,实际的线条也将在二维屏幕坐标上进行绘制。
2、光栅化
实际绘制或填充每个定点之间的像素形成线段叫做光栅化。
虽然用线段绘图(线框渲染)也有它的用处,但大多数情况下我们用实心三角形进行渲染。像线段一样,三角形和多边形也会被光栅化或填充。早期图像硬件用纯色对三角形填充,3D感不强。
3、着色
沿着表面(在顶点之间)改变颜色值,能够轻松创建光照射在立方体面上的效果。
光照和着色在3D图形专业领域占据非常大的比重,着色器则是在图形硬件上执行的单独程序,用来处理顶点和执行光栅化任务。
4、纹理贴图
纹理贴图是硬件技术的进步,一个纹理是一幅用来贴到三角形或是多边形上的图片,纹理将渲染提高到了一个崭新的层次。
在如今的硬件上,纹理是快捷有效的,而一个纹理所能再现的表面如果用三角形来实现的话,可能需要几千甚至上百万个。
5、混合
混合时能够将不同的颜色混在一起。
首先绘制颠倒的立方体,然后再在它的上面绘制地板并与它进行混合,在绘制上面正常的立方体,就能获得倒影这种反射效果。
6、将点连接起来
上述就是所谓的计算机图形了。
实心3D几何体无非是将顶点间的点连接起来,然后对三角形进行光栅化而使对象变得有实体。变换、着色、纹理和混合,任何计算机渲染场景都是灵活运用这4种技术产生的。
三、3D图像的常见用途
1、实时3D
实时3D图形是指活动的并与用户交互的图形。
2、非实时3D
在一台非常快速的计算机上,为一部电影渲染一个单独的帧可能需要耗费几个小时。渲染并保存成千上万个帧的过程生成了一个可以回放的动画序列。尽管这个动画序列在回放时看上去像是实时的,但它的内容却不是交互性的。因此,它不是实时的,而是预渲染的。
3、着色器
在实时计算机图形中,最前沿的技术是可编程着色器。
今天图形卡不在是低能的渲染芯片了,而是功能强大的高度可编程的渲染计算机。
GPU图像处理单元,特指当今图形卡上的可编程芯片,它是高度并发的,具有非常快的速度。
更重要的是,程序员可以进行重新配置图形卡的工作方式,几乎可以实现任何可以想象到的特殊效果。
四、3D编程的基本原则
1、并非工具包
OpenGL基本上是一种底层渲染API。
需要手动,通过载入三角形,应用必要的变换和正确的纹理、着色器并在必要时应用混合模式来组合模型。使得我们能够大量的底层控制。
2、坐标系统
2D笛卡尔坐标
坐标裁剪,指定占据窗口的笛卡尔空间区域。裁剪出客户区域。
视口,裁剪区域的宽度和高度很少正好与窗口的宽度高度相匹配。因此,坐标系统必须从逻辑笛卡尔坐标映射到物理屏幕像素坐标。这个映射是通过视口的设置来指定的。视口就是窗口内部用于绘制裁剪区域的客户区域。视口简单地把裁剪区域映射到窗口的一个区域。
顶点,绘制一个物体时,实际上是用一些更小的称为图元的形状来组成物体。图元是一维或二维的实体或表面,如点、线和多边形。多边形(或其它任意图元)的每个角称为顶点。顶点也就是2D或3D坐标中的一个点。
3D笛卡尔坐标
3、投影:从3D到2D
投影:用于创建几何图形的3D坐标将投影到一个2D表面。
通过指定投影,可以指定在窗口中显示的视景体,并指定如何对它进行变换。
主要有两种投影:正投影(平行投影)和透视投影
第二章 OpenGL入门
本章内容
- OpenGL的发展历程和未来趋势
- 核心框架和“不鼓励使用”的功能
- 如何检测OpenGL编程错误
- 如何向OpenGL传递性能提示(Hint)
- 如何获得一个基本项目并进入Xcode
- 如何使用在一个基本编程框架中使用GLUT
一、什么是OpenGL?
OpenGL的前身是SGL公司的IRIS GL,是对其移植性改进和提高的结果。
SGL最初控制了OpenGL的许可,ARB(SGL,EDC,IBM,Intel,Microsoft组成OpenGL体系结构审核委员会)于1992年出台OpenGL规范1.0版。2006年实际已经破产的SGL把对OpenGL标准的控制权从ARB移交新的工作组Khronos,大多是ARB成员已经成为Khronos成员,因此这个变动没有太大波折。今天Khronos小组继续发展和升级OpenGL和它的姊妹API OpenGL ES。
OpenGL以两种形式存在,第一种是“OpenGL规范”,定义行业标准,用完整和明确的术语描述OpenGL。第二个事OpenGL的实现,软件开发人员可以使用它生成实时图形。
OpenGL允许提供商通过它的扩展机制进行创新。提供商能够向OpenGL API中增加开发人员可用的新函数;其次,可以添加能够被已存在的OpenGL函数识别的标记或枚举。
二、使用OpenGL
1、支持阵容
GLUT:代表OpenGL实用工具箱
GLEW:OpenGL主要是通过扩展机制来发展。GLEW是一个开源的自动初始化所有新函指针并包含所需类型定义、常量和枚举值的扩展加载库。GLTools库就是基于GLEW的。
GLTools:其包含一个用于操作矩阵和向量的3D数学库,并依靠GLEW获得OpenGL3.3中用来生产和渲染一些简单3D对象的函数,以及对视觉平截头体、相机类和变换矩阵进行管理的函数的充分支持。
2、OpenGl API特性
在函数命名和变量声明上采用了一些标准规则。API简单清晰,便于提供商进行扩展,便于程序员记忆。并有着灵活,强大和快速的特点。
数据类型:
为了跨平台移植,OpenGL定义了数据类型。可以映射到所有平台上的特定最小格式。
3、OpenGL错误
OpenGL提供了一种有用的机制,可以在代码中执行一种偶然健全性检查。
GLenum glGetError(void);
glGetError会返回下表中的一个值。
4、确认版本
const GLubyte *glGetString(GLenum name);
GL函数库可以通过调用glGetString来返回与它们版本号和生产商有关的特定信息。
使用gjHint获取线索:
一种OpenGL的实现常包含两种方法来执行特定任务,一种是快速的方法,在性能上稍作妥协;一种是慢速的方法,着重于改进视觉质量。glHint函数允许指定偏重于视觉质量还是速度,以适应不同的需求。
void glHint(GLenum target, GLenum mode);
5、OpenGL状态机
OpenGL中把一些变量的集合称为管线的状态。状态机是一个抽象的模型,表示一组状态的集合。每个状态有不同的值,或只能打开关闭等。当一个状态被设置后,会一直保持这个状态,直到其他函数对其修改为止。
打开某些状态变量
void glEnable(GLenum capability);
关闭某些状态变量
void glDisable(GLenum capability);
判断状态变量是否被打开
Glboolean glIsEnable(GLenum capability);
许多OpenGL函数专门用于设置变量的值,设置后其值一直不变,直到再次被修改。也提供了一些查询布尔型,整形,单精度浮点型和双精度浮点型变量值的函数,
void glGetBooleanv(GLenum pname, GLboolean *params);
void glGetDoublev(GLenum pname, GLboolean *params);
void glGetFloatv(GLenum pname, GLboolean *params);
void glGetIntegerv(GLenum pname, GLboolean *params);
三、实例:行走的四边形
//`#include<GLShaderManager.h>` 移入了GLTool 着色器管理器(shader Mananger)类。
#include "GLShaderManager.h"
//`#include<GLTools.h>` GLTool.h头文件包含了大部分GLTool中类似C语言的独立函数
#include "GLTools.h"
//在Mac 系统下,`#include<glut/glut.h>`。在Windows 和 Linux上,我们使用freeglut的静态库版本
#include <GLUT/GLUT.h>
//着色管理器
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
};
//在窗口大小改变时,接收新的宽度&高度。
void changeSize(int w,int h)
{
//x,y 参数代表窗口中视图的左下角坐标,而宽度、高度是像素为表示,通常x,y 都是为0
glViewport(0, 0, w, h);
}
void RenderScene(void)
{
/*1.清除一个或者一组特定的缓存区
参数:指定将要清除的缓存
*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.设置一组浮点数来表示绿色
*传递到存储着色器,即GLT_SHADER_IDENTITY着色器,这个着色器只是使用指定颜色在屏幕上渲染几何图形
*/
GLfloat vRed[] = {0.0,1.0,0.0,0.5f};
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
//3.提交着色器
triangleBatch.Draw();
/*4.交换缓冲区
*在设置openGL窗口的时候,我们指定要一个双缓冲区的渲染环境。这就意味着将在后台缓冲区进行渲染,渲染结束后交换给前台。这种方式可以防止观察者看到可能伴随着动画帧与动画帧之间的闪烁的渲染过程。
*缓冲区交换平台将以平台特定的方式进行。
*/
glutSwapBuffers();
}
void setupRC()
{
//设置清屏颜色(背景颜色)
glClearColor(0.6f, 0.6f, 0.1f, 1);
//初始化着色管理器。没有着色器,在OpenGL核心框架中是无法进行任何渲染的。
shaderManager.InitializeStockShaders();
//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);
glutPostRedisplay();
}
int main(int argc,char *argv[])
{
//初始化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();
glutMainLoop();
return 0;
}
网友评论