计算机图形学目录
理解内容
http://staff.ustc.edu.cn/~lgliu/Resources/CG/What_is_CG.htm
定义:计算机图形学(Computer Graphics 简称CG)是一种使用数学算法将二维或三维图形转化为计算机显示器的栅格形式的科学。
内容:建模、渲染、动画和人机交互(Modeling, Rendering, Animation, Human-computer Interaction HCI) 。
一、建模
1.计算机辅助设计(CAD)中主流方法是采用NURBS(非均匀有理B-样条、Bezier曲线曲面)方法。
2.细分曲面造型方法,作为一种离散迭代的曲面构造方法,其构造过程朴素简单以及实现容易。
3.利用软件直接手工建模。现在主流的商业化的三维建模软件有Autodesk 3D Max和 Maya。
n.三维几何模型的构建过程中,还会涉及到很多需要处理的几何问题,比如数据去噪(denoising or smoothing)、补洞(repairing)、简化(simplification)、层次细节(level of detail)、参数化(parameterization)、变形(deformation or editing)、分割(segmentation)、形状分析及检索(shape analysis and retrieval)等。这些问题构成“数字几何处理”的主要研究内容。
二、渲染
有了三维模型或场景,怎么把这些三维几何模型画出来,产生令人赏心悦目的真实感图像?这就是传统的计算机图形学的核心任务。
因此,如何充分利用GPU的计算特性,结合分布式的集群技术,从而来构造低功耗的渲染服务是发展趋势之一。
三、动画
动画是采用连续播放静止图像的方法产生物体运动的效果。
四、人机交互
人通过一定的交互方式,让计算机完成任务。
五、其他内容
虚拟现实,可视化,计算机艺术等。
学习基础:计算机图形学是一门与很多学科都交叉的学科方向。
一、数学
包括:微积分、线性代数、矩阵计算、微分几何、数值计算和分析、计算方法、偏微分方程、微分方程数值解、最优解、概率、统计、计算几何等。
研究过程中若遇到什么数学知识再去学相关的知识,学习起来会更有兴趣,掌握起来会更快更扎实;学习数学要结合图形,即“数形结合”,需要有图形的想象能力;
二、编程
掌握C++编程语言和面向对象编程思想
三、其他
英语基础要好,因为需要大量阅读英文文献和进行英文论文的写作;英文的听说能力也要好些,因为要跟国际学者交流讨论;
计算机图形学中的很多算法是真实物理世界的模拟,因此,如果你要进行基于物理的建模和仿真,一些物理知识和理论也需要的,比如力学(动力学,运动学,流体力学)和光学等;
四、教材
最好的学习方法就是在使用中学习。因此,计算机图形学的学习和研究提供了你学习其他相关知识的好的过程。
计算机图形学的内容远比教材中或你想象中的内容多得多。
OpenGL基础
Open Graphics Library 开放图形库,是用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(API)。常用于CAD、虚拟现实、科学可视化程序和电子游戏开发。
OpenGL被设计为只有输出的,所以它只提供渲染功能。核心API没有窗口系统、音频、打印、键盘/鼠标或其他输入设备的概念。
1.1 状态机
在图形绘制中,有许多状态量需要管理,如果线条的颜色,线条的粗细,是否打开纹理映射和是否打开光照等。采用状态机机制,对状态量进行管理,即,一个状态量被设置后,将对随后的所有绘制指令生效,直到它被再次设置为止。
- 修改状态量
-
- 对于只需要打开、关闭的状态量,如光照、纹理映射、深度检测等,可以使用函数glEnable() 和glDisable() 。
-
- 对于需要特定修改的状态量,如当前颜色、当前线宽、当前点绘制尺寸、当前顶点法向量、当前定点纹理坐标等,则分别提供了状态量修改函数。glColor()/ glLineWidth()/ glPointSize()/ glNoraml()/ glNormal()/ glTexCoord()。
- 状态量查询
-
- 对于可供打开、关闭的状态量,可以使用函数GLboolean gllsEnabled ( GLenum capability ) 来进行状态量查询。
-
- 对于由参数定义的状态量,则可以使用下面的函数获取其参数。void glGetBooleanv(GLenum pname, GLboolean* params)/ glGetDoublev()/ glGetFloatv()/ glGetIntegerv()。【EG:GLfloat curColor[3] = {0, 0, 0}; glGetFloatv(GL_CURRENT_COLOR, curColor);】
- 状态量的保存、恢复
-
- glPushAttrib( GLbitfield attribute_mask); glPopAttrib(void);
1.2 语法规则
OpenGL语法遵循以下规则:
- 所有的OpenGL函数命名都以gl开头;
- OpenGL规范中的常量都是以GL_为前缀的大写字符串;
- OpenGL有自己的类型定义,其余C语言的标准类型有映射关系;
OpenGL函数通常可以加上以下后缀: - 用数字表示该函数的参数个数,如2,3,4;
- 用字母表示参数的类型,如字母i, f, d, ub, 分别表示整型,浮点型,双精度浮点数,无符号byte类型;
- 字母v表示参数是一个数组;
- e.g.:glColor3f, glColor4fv, glColor3ub;
1.3 二维图形绘制
绘制二维图形,并设置其绘制属性:颜色、线型。填充模式等。
- 图元
-
- 在OpenGL中,所有显示的结果都是由一个或多个图形元素(简称图元)组成。图元包括了点、线段、多边形等。
GL_POINTS
GL_LINES GL_LINE_STRIP GL_LINE_LOOP
GL_TRIANGLES GL_TRIANGLE_STRIP GL_TRIANGLE_FAN
GL_QUADS GL_QUAD_STRIP
GL_POLYGON
- 在OpenGL中,所有显示的结果都是由一个或多个图形元素(简称图元)组成。图元包括了点、线段、多边形等。
-
- 一个创建图元的方法是调用函数glBegin()。这个函数的唯一参数用于指定将要创建图元的类型。
-
- 所有的图元都由顶点组成,这些顶点由函数glVertex()指定。
-
- 当所有的顶点都被指定后,绘制指定图元时,需要调用一次函数glEnd()。
-
- OpenGL窗口缺省为从-1到1对应于从左到右,从底到顶。
-
- OpenGL绘制多边形(包含三角形)时需要满足3个条件:
简单性:边不能自交。
凸性:多边形内部任意两点的连线都要落下多边形内部。
平整性:所有顶点都在一个平面。
- OpenGL绘制多边形(包含三角形)时需要满足3个条件:
-
- OpenGL并不负责检查输入的顶点,这必须由调用方负责检查。而三角形始终满足以上条件,因此三角形被各种图形应用大量使用。
- 图元属性
-
- 点划模式:
glEnable(GL_LINE_STIPPLE);
glLineStipple(3, linestipple);
- 点划模式:
-
- 点的尺寸:
glPointSize(0);
glGetFloatv(GL_POINT_SIZE, &size);
- 点的尺寸:
-
- 线宽:GL_LINE_WIDTH
-
- 边标记:
glEdgeFlag()传递GL_TRUE来指定一个标记,则任意一对顶点将绘制一条线段。如果将GL_FALSE传递给该函数,则不会有线段被绘制。
- 边标记:
-
- 多边形绘制模式:点(顶点)、线段(线框)、填充(多边形)。glGetIntegerv(GL_POLYGON_MODE,v_2)。GL_FRONT/GL_BACK/GL_FRONT_AND_BACK;
GL_POINT/GL_LINE/GL_FILL;
- 多边形绘制模式:点(顶点)、线段(线框)、填充(多边形)。glGetIntegerv(GL_POLYGON_MODE,v_2)。GL_FRONT/GL_BACK/GL_FRONT_AND_BACK;
-
- 多边形着色模式:
glShadeModel(GLenum mode); GL_FLAT/GL_SMOOTH;
- 多边形着色模式:
1.4 三维图形绘制
绘制过程:场景定义,观察定义;
glViewport();
glMatrixMode(GL_PROJECTION);
初始化变换:glLoadIdentity()
glPerspective()
gluLookAt()
glMatrixMode(GL_MODELVIEW);
初始化变换:glLoadIdentity()
glRotatef()
glCallList(object)
- 模型变换
旋转 glRotatef(angle, vx, vy, vz); 绕向量旋转角度
平移 glTranslate(dx, dy, dz); 平移向量
缩放 glScalef(sx, sy, sz); 缩放因子
注意:这里的旋转变换和比例缩放变换都是以坐标系原点为中心进行的。
变换次序:最后指定的变换最先执行,即后进先出。 - 视点设置:观察位置+观察方向+偏转方向
gluLookAt();
eyex,eyey,eyez 观察位置;
centerx,centery,centerz 观察的中心点;
upx,upy,upz 观察的向上方向;
观察方向 = 观察中心点 - 观察位置;
向上方向决定了偏转; - 投影变换
模型变换决定了观察场景,视点设置决定了观察者的参数。现在要把观察者观察到三维场景投影为屏幕上的图像,这个过程称为投影变换, -
- 正投影
gluOrtho();glOrtho2D();
left, right 定义了左右裁剪面
bottom, top 定义了上下裁剪面
near, far 定义了前后裁剪面
定义了三维空间的一个盒子,落在其内部的几何体将被正投影成像。
- 正投影
-
- 透视投影
gluPerspective();
定义了三维空间中的四棱锥台,落在其内部的几何体将被投影成像。
这个函数定义了投影提的顶面和底面的夹角、取景器的长宽比例以及前后裁剪平面。
- 透视投影
- 视区变换
投影变换类似于取景器,最终的显示将放置在平面的某个区域、该显示区域的位置和大小则通过视图变换来指定。
视区变换作为观察定义的最后一步,它对应于观察成像在屏幕上的位置、显示的大小。
glViewport();
x,y 以像素为单位指定视区的左上角,初始值为(0,0);
width, height 以像素为单位指定视区的高度和宽度;
1.5 几何变换
几何变换是OpenGL绘制流水线中模型变换的核心内容,常被用于构造需要绘制的场景。
平移、旋转、缩放和斜切都是几何变换的例子。
- 放射变换:仿射变换保证了模型变换后的直线平行性,但不保证角度和长度在变换后保持不变。
右手坐标系:|/_ zyx /__
左手坐标系: | yxz
齐次坐标:n维的向量用一个n+1维向量来表示。给出点的齐次表达式[X Y H],就可求得其二维笛卡尔坐标,即
[X Y H]→ = [x y 1], 这个过程称为归一化处理。
在几何意义上,相当于把发生在三维空间的变换限制在H=1的平面内。
变换矩阵: - 组合矩阵:变换次序不可交换;变换都是可逆的
- 3D几何变换
齐次坐标系:每个笛卡尔坐标系点(x,y,z)被4个坐标值表示(hx,hy,hz,h)。当w=0时,可以认为该点落在无穷远处,从而简单地认为是一个方向。
推导:定义绕z轴旋转的正方向为,由正x轴方向先转到正y轴方向。
平移:x' = x + a; y' = y + b; z' = z +c; 缩放:x' = ax; y' = by; z' = cz; 斜切:x' = x + ay; y' = y; 旋转:x' = xcos@ - ysin@; y' = xsin@ + ycos@; z' = z;
3D变换矩阵:用4X4矩阵和齐次坐标重新改写。
绕任意点旋转:平移到原点
绕任意轴旋转:平移到原点 - 改变坐标系的变换:
- OpenGL中的几何变换
glPushMatrix();
glPopMatrix();
glMultMatrix(); m’ = m * mx
glLoadMatrixd(); glLoadMatrixf();
glPush();
glPop();
glutSolidCube()
1.6 观察和投影变换
观察变换:相机被放置在坐标系原点,而相机的朝向和-z方向重合,相机的向上方向和y轴重合。在这个坐标框架下,裁剪和投影的过程将大大简化。
投影变换:gluPerspective(45.0f, 1.0f*w/h, 1.0f, 100.0f);glOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 20.0f);
1.7 光源和材料
创建光源,照亮场景中的材质物体。
- 光源的四个分量
漫反射(GL_DIFFUSE),
环境光(GL_AMBIENT),
镜面反射(GL_SPECULAR),
发射光(GL_EMISSION); - 光源的属性
glLight*(light, property, value)
//*代表类型
//light OpenGL常量GL_LIGHT0到GL_LIGHT7
//property 需要设置的属性
//value 值 - 属性:位置
property:GL_POSITION
value:浮点数组,包含了光源位置的x,y,z,w值。光源可以是一个位置光源(w>0)或一个方向光源(w=0)。 - 环境光、漫反射、镜面反射
property: GL_AMBIENT/GL_DIFFUSE/GL_SPECULARE;
value: 浮点数组RGBA值; - 属性:光的衰减
光的强度和传播距离的关系;衰减因子0-1,缺省值为1;
factor = 1/( GL_CONSTANT_ATTENUATION + GL_LINEAR_ATTENUATION * d + GL_QUADRATIC_ATTENUATION * d * d);这里d表示光源和顶点间的距离,缺省值(1,0,0)。
为了产生距离衰减,必须将现行衰减和二次衰减的洗漱设置为不等于0。(如0.2f, 0.08f)。 - 属性:特殊的光源
筒灯光源,它的发射方向局限在一个圆锥内部。
GL_SPOT_DIRECT:定义发射圆锥体的轴向;
GL_SPOT_CUTOFF:相对于圆锥轴向的最大光线角度,值为[0, 90]间的角度。可以根据光线与筒灯轴线的夹角定义光线的衰减。GL_SPOT_ATTENUATION定义了光线的汇聚程度,0为没有衰减,128是最大衰减,该值越大,光线则越会汇聚在圆柱的中心部分。 - 光照模型
//glLightModel*(property, value);
//property 将要设置的属性
//value 设置的属性值
可以使用光照模型,定义一下三个属性:
GL_LIGHT_MODEL_AMBIENT全局环境光颜色;RGBA
GL_LIGHT_MODEL_LOCAL_VIEWER局部或无穷远的视点;定义了镜面光分量如何被计算。镜面高光依赖于顶点和视点的方向,以及顶点和光源的方向。因此高光是依赖于观察点的。@当使用无限远视点时,顶点和视点的方向对所有的顶点保持不变,因此不需要被中心计算。@这样光照的计算较快,这是缺省的视点。而使用局部视点时,视点和顶点的方向需要针对每个顶点计算。这样会产生更加精确的高光计算模型@
GL_LIGHT_MODEL_TWO_SIDE希望两边被照明或者只有一边; - 材料属性
//glMatenal*(face, property, value)
//face:GL_FRONT,GL_BACK,GL_FRONT_AND_BACK
定义了物体如何对光线做出反应:吸收、反射等。
GL_AMBIENT:对环境光反射的颜色。
GL_DIFFUSER:对漫反射的颜色。
GL_SPECULAR&GL_SHININESS:镜面反射的颜色。当光在镜面或金属表面产生高光时定义这种发射。光洁度用来定义高光区域的尺寸和高光的强度。
GL_EMISSION:产生物体似乎发射出光线的效果,用于创建台灯或灯泡。注意:这些物体似乎放射出光线,但并不对场景产生照明。开发者需要在物体的位置创建一个光源来真正地发射出光线。 - 光源与材料的数学关系
全局公式被用来计算一个顶点的颜色:
color=GLOBAL_AMBIENT+AMBIENT+DIFFUSE+SPECULAR+EMISSION;
GLOBAL_AMBIENT:光源的环境光分量,被材料的环境光属性所衰减;GL_LIGHT_MODEL_AMBIENT*GL_AMBIENT(from the material)
AMBIENT,DIFFUSE,SPECULAR:环境光项是从每个光源来的环境光和光照模型的全局环境光之和;AMBIENT+DIFFUSE+SPECULAR=。GL_SPOT_AXPONENT控制光的就算程度。需要了解这些项依赖于顶点位置和朝向(法向量)。
EMISSION:发射项为材料的发射值。 =GL_EMISSION - 照亮场景
创建一个光源,需要三个属性:GL_POSITION,GL_AMBIENT,GL_DIFFUSE;光源的位置,光源的环境光颜色,光源的颜色;
float light_diffuse[] = {1.0f, 1.0f, 0.0f, 1.0f};//散射光<=>物体红色
float light_ambient[]={1.0f, 0.0f, 0.0f, 1.0f};//环境光<=>物体黄色,直接投射到物体表面
float light_pos[]={-2.0f, -2.0f, 7.0f, 1.0f};
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
glEnable(GL_LIGHT0)//打开光源
glEnable(GL_LIGHTING);//打开光照
表面朝向:法向量作用于顶点,多边形的表面朝向通过他的法向量定义。下面演示如何为四边形添加法向量。类似于颜色、纹理坐标,应用于一个顶点的法向量为当前法向量。glNormal3f(x, y, z);立方体有六个面,因此需要对六个面分别指定六个法向量。
glBegin(GL_QUADS);
//第一个四边形
glNormal3f(1.0, 0.0f, 0.0f):
glTexCoord2f(0.0f, 1.0f);
glVertex3d(size/2, size/2, size/2); //V2
glTexCoord2f(0.0f, 0.0f);
glVertex3d(size/2, -size/2, size/2); //V1
glTexCoord2f(1.0f, 0.0f);
glVertex3d(size/2, -size/2, -size/2); //V3
glTexCoord2f(1.0f, 1.0f);
glVertex3d(size/2, size/2, -size/2); //V4
//第二个四边形
…
glEnd(); - 创建不同的光源
点光源、方向光源与筒灯光源,并将同时使用三个不同类型的光源对一个球体进行照明。
方向光源:w=0;没有环境光分量,白色漫反射分量,该光源并不指定在特定的空间位置,所有的管线来自无穷远处,并具有方向(x,y,z),所有的光线平行;
位置光源:w!=0;黄色的环境光分量,黄色的漫反射分量,位置(x,y,z),光线来自指定的位置(x,y,z),并向各个方向发射;
筒灯:两个主要属性(GL_SPOT_DIRECTION和GL_SPOT_CUTOFF),前者定义了筒灯的主轴方向,而后者定义了筒灯的照射范围。可以看做是位置光源的特例。
筒灯衰减:除了3个依赖于光源和照射顶点的距离的衰减分量GL_CONSTANT_ATTENUATION/GL_LINEAR_ATTENUATION/GL_QUADRATIC_ATTENUATION/外,还有一个特别依赖于顶点与筒灯中心轴的距离衰减。该衰减反映了筒灯照明汇聚程度。GL_SPOT_EXPONET指定,取值[0,128],值越大,光越汇聚中轴。
筒灯光源:需要指定筒灯方向GL_SPOT_DIRECTION和筒灯的夹角GL_SPOT_CUTOFF;定义管线汇聚程度;定义衰减;更新光源的位置、方向和夹角。 - 材料设置
环境光和漫反射前面介绍过了,镜面反射和光洁度可以在物体表面产生高光区域。镜面反射分量定义了高光的颜色,而光洁度则决定了高光区域的尺寸和高光的强度。
用指定材料属性绘制球体:
float notmat[]
float mat_ambient[]
float mat_ambient_color[]
float mat_diffuse[]
float mat_specular[]
float no_shiniess
float high_shiness
float mat_emission[]
//绘制一:只有漫反射,没有环境光和镜面
glPushMatrix();
glTranslatef(-3.75f, 3.0f, 0.0f);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_NEMISSION, no_mat);
gluSphere();
glPopMatrix();
1.8 纹理映射
使用OpenGL纹理函数在多边形上贴纹理,自动生成纹理坐标,以及如何在同一张多边形表面同事使用多张纹理。
- 在OpenGL中,使用纹理映射的基本流程如下:
从数据文件中装载纹理数据;
产生一个纹理标识符;
选择当前使用的纹理;
指定纹理的放大/缩小过滤方式;
从纹理数据生成一个纹理;
打开纹理映射;
绘制场景,为使用该纹理的模型顶点指定纹理坐标; - 在多边形上贴纹理
unsigned char* LoadBitmapFile(char* filename, BITMAPINFOHEADER* bitmapinfoHeader);//自定义函数
BITMAPINFOHEADER bitmapinfoHeader; //bitmap信息头
unsigned char* bitmapData;//纹理数据
bitmapData = NULL;
bitmapData = LoadBitmapFile("world.bmp", &bitmapinfoHeader);//读取文件数据
unsigned int texture[2];
glGenTextures(2, texture);//可以一次生成多个纹理标识符
glBindTexture(GL_TEXTURE_2D, texture[0]);//指定当前使用的纹理,余下操作将只作用与该纹理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//放大:当像素比对应纹理上的像素texel小;(GL_NEAREST没有过滤,点采样,取最近的texel值;GL_LINEAR双线性插值)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//缩小:当像素比对应纹理上的像素大;(前两+;GL_NEAREST_MIPMAP_NEAREST取最接近的mipmap的值;GL_LINEAR_MIPMAP_LINEAR三线性插值)
//下面将从bitmap文件中读取的图像文件交给OpenGL纹理,纹理数据在OpenGL内部保存,也可能保存在显卡上。
glTexImage2D(GL_TEXTURE_2D, 0, //mipmap层次,通常为0,表示最上层
GL_RGB, //希望有红绿蓝数据
bitmapInfoHeader.biWidth,//纹理宽度,必须是2^n,若有边框+2
bitmapInfoHeader.biWidth,//纹理高度,必须是2^n,若有边框+2
0, //边框:0=无边框;1=有边框
GL_RGB,//bitmap数据格式
GL_UNSIGNED_BYTE,//每个颜色数据的类型
bitmapData); //bitmap数据指针
//如果使用了mipmap,则需要对每个mipmap都调用一次上面的函数,并相应地改变mipmap的层次数。
glEnable(GL_TEXTURE_2D);//绘制前需要打开纹理映射
//----------------------------------------------------------
//下面的代码创建一个四边形,并将该四边形的顶点与纹理的角点建立关联
glBindTexture(GL_TEXTURE_2D, texture[1];//从已装载纹理中选择当前纹理
glColor3f(1.0f, 1.0f, 1.0f);//将当前颜色设置为白色
//绘制四边形,并为每个顶点设定纹理坐标
glBegin(GL_QUADS);
glTexCoord2i(1, 1); glVertex3i(1,1,0);
glTexCoord2i(1, 0); glVertex3i(1,-1,0);
glTexCoord2i(0, 0); glVertex3i(-1,-1,0);
glTexCoord2i(0, 1); glVertex3i(-1,1,0);
glEnd();
//纹理坐标:X:0~1;Y:0~1, 左下角(0, 0)
//四边形顶点:X:-1~1;Y-1~1;Z:-1~1, 左下角(-1,-1,-1)
- 纹理和光照的混合
使用参数GL_MODULATE,通过纹理环境模式进行光照、颜色和纹理的混合。 - 自动生成纹理坐标
为了实现纹理映射,需要对模型的每个顶点都设置一个纹理坐标。
//调用glTexGen(),并使用函数glEnable()打开了模式GL_TEXTURE_GEN_S和GL_TEXTURE_GEN_T;
planeCoefficients = [1, 0, 0, 0];
glTexGen(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGen(GL_S, GL_OBJECT_PLANE, planCoefficients);
glEnable(GL_TEXTURE_GEN_S);
glBegin(GL_QUADS);
glVertex3f(-3.25, -1, 0);
glVertex3f(-1.25, -1, 0);
glVertex3f(-1.25, 1, 0);
glVertex3f(-3.25, 1, 0);
glEnd();
//用于控制纹理坐标生成的方式:void glTexGeni(GLenum coord, GLenum pname, GLdouble, param);
//coord:指定一类纹理坐标,必须为GL_S,GL_T,GL_R,GL_Q之一
//pname:必须是GL_TEXTURE_GEN_MODE
//param:纹理生成参数,必须为GL_OBJECT_LINEAR,GL_EYE_LINEAR,GL_SPHERE_MAP之一;前两个都是按照顶点到一个平面的距离产生纹理坐标
1.平面定义:AX+BY+CZ+D=0;点[x,y,z]是否落在该平面;向量[A,B,C]是该平面的法向量,即决定了平面的朝向;系数D控制该平面到原点的距离;如果[A,B,C]是一个单位向量,则D等于平面到原点的距离;
2.纹理坐标生成模式:GL_OBJECT_LINEAR; glTexGenfv(GL_S, GL_OBJECT_PLANE, planeCoefficients); 纹理坐标S=AX+BY+CZ+D,这里的[x,y,z]为顶点的坐标值,通过函数glVertex()传递;可以通过给系数乘以不同的比例来控制纹理重复出现的频率(即对纹理进行缩放);
3.纹理坐标生成模式:GL_EYE_LINEAR; glTexGenfv(GL_S, GL_EYE_PLANE, planeCoefficients); GL_OBJECT_LINEAR模式计算的纹理坐标只和函数glVertex()传递的值的相关,不会通过变换改变物体或相机发生变化。而GL_EYE_LINEAR模式使用经过了所有ModelView变换后的顶点位置产生纹理坐标,即它们使用顶点在平面上的坐标产生纹理坐标。
4.二维纹理自动生成:上个实例中,始终只对纹理坐标S进行了生成。事实上,OpenGL支持对纹理坐标S和T的同时自动生成。例如,可以对纹理坐标T使用不同的平面方程调用函数glTexGen():
SplaneCoefficients=[1,0,0,0];
glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR);
glTexGenfv(GL_S,GL_EYE_PLANE,SplaneCoefficients);
glTexGenfv(GL_S,GL_OBJECT_PLANE,SplaneCoefficients);
TplaneCoefficients=[0,1,0,0];
glTexGeni(GL_T,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR);
glTexGenfv(GL_T,GL_EYE_PLANE,TplaneCoefficients);
glTexGenfv(GL_T,GL_OBJECT_PLANE,TplaneCoefficients);
5.球形映射:环境纹理的一种,常被用于模拟反射的表面。在OpenGL中可以通过纹理坐标自动生成GL_SPHERE_MAP来进行控制。在这种模式下,并没有其他的参数:glTexGeni(GL_S, GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP); 在球形映射中,将使用从视点到观察点的向量作为参考,并将它相对于模型表面法向量进行反射,得到的反射向量将用于从纹理中查询颜色。该纹理嘉定提供了周围被反射环境的360度全方位包围。
- 多重纹理
在一次绘制中使用多张纹理,显卡支持ARB扩展GL_ARB_multitexture.
1.扩展检测:首先要做的是检测多重纹理是否被显卡支持。
多重纹理是否被支持
constGLubyte *extensions = glGetString(GL_EXTENSIONS);
bool multiTexturingSupported = strstr((const char*)extensions, "GL_ARB_multitexture") != NULL;
初始化相关函数
glMultiTexCoord1fARB = (PFENGLMULTITEXCOORD1FARBPROC) wglGetProcAddress("glMutiTexCoord1fARB");
glMutiTexCoord2fARB = (PFENGLMULTITEXCOORD2FARBPROC) wglGetProcAddress("glMutiTexCoord2fARB");
glMutiTexCoord3fARB = (PFENGLMULTITEXCOORD3FARBPROC) wglGetProcAddress("glMutiTexCoord3fARB");
glMutiTexCoord4fARB = (PFENGLMULTITEXCOORD4FARBPROC) wglGetProcAddress("glMutiTexCoord4fARB");
glActiveTextureARB = (PFENGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
glClientActiveTextureARB = (PFENGLCLIENTACTIVETEXTUREARBPROC)wglGetProcAddress("glClientActiveTextureARB");
2.OpenGL扩展:
3.多重纹理扩展:允许使用超过一个纹理单元。一个纹理单元可以用来保存纹理坐标、纹理过滤方式、纹理环境状态、纹理坐标自动生成方式等。对每个纹理单元可以对应于一张不同的纹理。多重纹理功能非常强大,允许在一次绘制时使用多张纹理。当物体复杂时,使用多次绘制来实现同样的效果将耗时更多。
4.纹理单元:开发者可以控制当前使用哪个纹理单元。一个纹理单元通过OpenGL常量GL_TEXTUREi_ARB来访问。该常量为一个0到31之间的整数。因此,最多的纹理单元数为32,但并不是所有的显卡都能支持这么多纹理单元(例如,很多显卡只支持8个纹理单元)。
//为了查询可使用的纹理单元数,可以使用函数glGetIntegerv(),并使用参数GL_MAX_TEXTURE_UINTS_ARB;
GLint maxTextureUnits = 0;
glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &maxTextureUnits);
//为了激活一个纹理单元,可以将它的常量作为参数调用函数glActiveTextureARB();
glActiveTextureARB(GL_TEXTUREi_ARB);
//则当前的纹理单元为GL_TEXTUREi_ARB, 直到重新调用这个函数激活另一个纹理单元;
5.使用多重纹理:
//在激活了纹理单元后,对于纹理的操作和使用一个纹理类似绑定纹理,指定纹理过滤方式。唯一的不同在于指定顶点的纹理坐标,现在需要使用函数glMultiTexCoord** ARB()
glMultiTexCoord**ARB(GL_TEXTUREi_ARB, ...); //其中,参数...为纹理坐标
//演示使用两张纹理来绘制一个方块,并且更改不同的模式,使用一张纹理或使用多重纹理。
if (mode == 0){
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture[0]);
}else{
if (mode == 1) {
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture[1]);
}else{
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture[0]);
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture[1]);
}
//将当前颜色设置为白色
if (b['r'])
glColor3f(1.0f, 0.0f, 0.0f);
else
glColor3f(1.0f, 1.0f, 1.0f);
//绘制四边形
//并为每个顶点设定纹理坐标
glBegin(GL_QUADS);
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 1);
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 1);
glVertex3i(1, 1, 0);
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 0);
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 0);
glVertex3i(1, -1, 0);
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 0);
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 0);
glVertex3i(-1, -1, 0);
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 1);
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 1);
glVertex3i(-1, 1, 0);
glEnd();
//恢复纹理状态
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);
glActiveTextureARB(GL_TEXTURE1_ARB);
}
1.9 加速渲染——顶点数组、显示列表和顶点缓冲对象
前面已经介绍了如何使用OPENGL的API进行图形绘制,但他们的效率存在问题。本章重点介绍OPENGL提供的顶点数组、显示列表和顶点顶点缓冲对象等三种渲染加速技术,并讨论他们的优缺点以及如何根据应用场景的不同进行选择。
- 之前
glBegin()//开始绘制OPENGL
glNormal()//指定顶点的法向量
glColor()//指定顶点的颜色
glTexCood()//指定定点的纹理坐标
glVertex()//指定顶点的空间位置
… //依次给出其他顶点的属性
glEnd()
这种向OPENGL输出[绘制指令]的方式被称为立即模式。优点便于理解,缺点效率较低,可能会多次重复项OPENGL输入顶点信息。 - 顶点数组VertexArray
提供非立即模式进行渲染,通过将数据准备和绘制指令分离开,减少数据冗余,提高渲染速度。
使用顶点数组三步:
指定将要使用的顶点数据(包含坐标位置、颜色、法向量、纹理坐标和边可见性标记的数组);
将这些顶点数据传输给OPENGL;
用这些顶点数据进行绘制(将这些顶点组装成什么体素).
- 指定顶点数据:glEnableClientState()和glDisableClientState()来激活和关闭六个不同类型的数组。同时6个函数用于指定这些数组的精确位置(地址),这样OPENGL就可以访问这些数组了。
glVertexPointer(); 指定顶点空间位置
glNormalPointer();指定顶点法向量数组
glColorPointer();指定顶点颜色数组
glIndexPointer();指定颜色索引数组
glTexCoordPointer();指定纹理坐标数组
glEdgeFlagPointer();指定边可见性标记数组
注意这些顶点的属性数组,存放在应用程序中(系统内存),即客户端。而OpenGL在服务端访问他们。这就是为什么对顶点数组需要调用特别的glEnableClientState()和glDisbaleClientState()函数,而不是调用glEnable()和glDisable()函数。
#define X 0.5257311f
#define Z 0.85065080f
GLfloat vertices[][3] = {
{-X, 0.0, Z}, {X, 0.0, Z}, {-X, 0.0, -Z}, {X, 0.0, -Z},
{0.0, Z,X},{0.0,Z,-X},{0.0,-Z,X},{0.0,-Z,-X},
{Z,X,0.0},{-Z,X,0.0},{Z,-X,0.0},{-Z,-X,0.0},
};//多面体的顶点数据
//将使用顶点数组来产生坐标信息
glEnableClientState(GL_VERTEX_ARRAY);
//将顶点数组提交给OPENGL使用
glVertexPointer(3,GL_FLOAT,0,(GLvoid*)vertices);
- 进行绘制
将数据提交给OPENGL后,可以使用函数glArrayElement()进行绘制。glArrayElement(i)将从顶点数组中取出第i个顶点坐标使用,而如果其他数组(法向量、纹理坐标、颜色等)处于激活状态,他们的第i个元素也可以被使用。
glBegin(GL_TRANGLES);
//用第1、4、0个坐标数据中的元素画一个三角形
glArrayElement(1);
glArrayElement(4);
glArrayElement(0);
//用第4、9、0个坐标数据中的元素画一个三角形
glArrayElement(4);
glArrayElement(9);
glArrayElement(0);
glEnd();
通过使用下表访问顶点数据,重复的顶点数据被清除了。但是通过上面的硬编码来指定顶点的组合对于大型的程序显然是不合适的。一个较好的方法是将这些下标保存在一个数组中,如下例
GLfloat vertices[][3] = {
{-X, 0.0, Z}, {X, 0.0, Z}, {-X, 0.0, -Z}, {X, 0.0, -Z},
{0.0, Z,X},{0.0,Z,-X},{0.0,-Z,X},{0.0,-Z,-X},
{Z,X,0.0},{-Z,X,0.0},{Z,-X,0.0},{-Z,-X,0.0},
};//多面体的顶点数据
//将每个三角形的下标值(3个)保存在数组中,该下标值将用于从顶点数组获取数据。
//同时,在其他的地方......
//将使用顶点数组来产生坐标信息
glEnableClient(GL_VERTEX_ARRAY);
//将顶点数组提交给OPENGL使用
glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)vertices);
//将下标从数组取出用用于绘制
glBegin(GL_TRIANGLES);
for (int i = 0; i< 20; i++) {
glArrayElement(triangles[i][0]);
glArrayElement(triangles[i][1]);
glArrayElement(triangles[i][2]);
}
glEnd();
//最后,可采用glDrawElements进一步优化
glDrawElements(GL_TRIANGLES, sizeof(triangles)/3, GL_UNSIGNED_INT, triangles);
//数组triangles里每个元素用来访问顶点数组
//这段代码和上面功效相同,且效率更高
- 显示列表 Display List
显示列表可以看成一串预编译的OPENGL绘制指令。大部分OPENGL指令,如光照设定、纹理操作、矩阵操作、顶点指令等,都可以通过创建显示列表来进行预计算。而在调用该显示列表进行多次绘制时,这些指令不用再次计算,从而实现加速渲染。一次定义,多次绘制。广泛应用。
#define NEW_LIST 1
//为显示列表定义一个唯一的标识符
//显示列表开始
glNewList(NEW_LIST, GL_COMPILE);//编译(但不立即显示)
//参数二还有哦一个参数GL_COMPILE_AND_EXECUTE
glBegin(...);
...
glEnd();
//许多绘制体素,变换,光照函数
...
glEndList();//显示列表被创建。
//执行显示列表
glCallList(NEW_LIST);
- 顶点缓冲对象 Vertex Buffer Object,VBO
顶点数组的数据依然驻留在系统内存中,然而现在显卡有显存,将数据驻留在显卡上进行处理,则可以大大加快渲染效率。
GL_ARB_vertex_buffer_object来支持顶点缓冲对象VBO,VBO使得图形数据可以保存在服务器的高性能内存上,并提供高效的数据传输手段。
顶点数据可以减少图形函数的调用次数,同时消除共享顶点的数据冗余。缺点在于,顶点数据都保存在客户端,每次访问这些数据时,他们都需要重新传输到服务器端。而现实列表是服务端函数,它没有数据传输方面的缺陷,但是当它被编译后,就无法修改其内容。
顶点缓冲对象在服务器端的高性能内存上创建“缓冲对象”,并提供与顶点数据相同的对引用数组的访问函数,如glVertexPointer(),glNormalPointer()等。顶点缓冲对象中的数据可以通过将其映射到系统内存中进行读取和更新。
使用顶点缓冲对象 -
- 1.创建顶点缓冲对象
//用glGenBuffersARB()创建一个新的顶点缓冲对象;
//用glBindBuffersARB()绑定一个顶点缓冲对象;
//用glBufferDataARB将顶点数据复制到顶点缓冲对象;
void glGenBuffersARB(GLsize ini,GLuint* ids);//创建缓冲对象个数,保存返回标志符的数组地址;
void glBindBufferARB(GLenum target, GLuint id);//将缓冲对象与对应的ID建立关联,target用来标明该缓冲对象用于保存顶点数组数据还是索引数组数据GL_ARRAY_BUFFER_ARB或GL_ELEMENT_ARRAY_BUFFER_ARB。一旦函数调用后,VBO将缓冲对象初始化为零尺寸,并设置其初始状态,如使用和访问属性等。
void glBufferDataARB(GLenum target, GLsize size, const void* data, GLenum usage);//当缓冲对象初始化后,可以用函数将数据复制到缓冲对象。target取值GL_ARRAY_BUFFER_ARB或GL_ELEMENT_BUFFER_ARB;size是传送数据的字节数;data指向来源数据的数组;usage指明该缓冲对象将如何使用:静态、动态、流或读、复制、绘制。GL_STATIC_DRAW_ARB、GL_DYNAMIC_COPY_ARB、GL_STREAM_READ_ARB(3x3种)。
//静态static指该缓冲对象中的数据将不会修改(指定一次,修改多次);动态dynamic标明该缓冲对象的数据将经常修改(指定多次,修改多次);流stream表明数据每帧都会修改(指定一次,使用一次);
//绘制draw指数据将会送到图形处理器用于绘制。读Read指数据将被客户端读取。复制copy表明数据将同时用于绘制和读取。对VBO来说,只有绘制时有限的,读和复制为其他的扩展保留。
//下面的代码将创建一个存放顶点坐标数据的VBO。注意,可以在数据复制到VBO后,立即将顶点数组的内存释放
GLuint vbold; //VBO标识符
GLfloat* vertices = new GLfloat[vCount*3];//创建一个顶点数组
...
//创建一个新的VBO,并得到它的相关ID
glGenBuffersARB(1, &vboid);
//绑定VBO以使用它的相关ID
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboid);
//传输数据到该VBO
glBufferDataARB(GL_ARRAY_BUFFER_ARB, dataSize, vertices, GL_STATIC_DRAW_ARB);
//复制数据到VBO后就可以将顶点数据释放了
delete[] vertices;
...
//程序终止时释放该VBO
glDeleteBUffersARB(1, &vboid);
-
- 2.绘制顶点缓冲对象
- 由于顶点缓冲对象的实现建立在顶点数据的实现之上,绘制顶点缓冲对象几乎与绘制顶点数组相同。唯一的区别在于,顶点数据的指针在绘制顶点缓冲对象时作为当前绑定的缓冲对象的偏移量的使用。
//将顶点缓冲对象绑定到顶点数组和索引数组
glBindBuffersARB(GL_ARRAY_BUFFER_ARB, vboid1);//顶点坐标
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, vboid2);//索引值
//和顶点数据绘制相同,除了指针
glEnableClientState(GL_VERTEX_ARRAY);//激活坐标数组
//最后一个参数是偏移量,而不是指针
glVertexPointer(3, GL_FLOAT, 0, 0);
//用索引画留个四边形
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, 0);
glDisableClientState(GL_VERTEX_ARRAY);//关闭顶点数组
//绑定为0,切换到正常的指针操作
glBindBuffersARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
//将缓冲对象绑定为0,则关闭了顶点缓冲对象操作。在使用完顶点缓冲对象后最好将它关闭,这样原来的顶点数组只恨可以被重新激活。
-
- 3.更新顶点缓冲对象
一种方法,使用glBufferDataARB()呵呵glBufferSubDataARB(),重新赋值新的数据到绑定的顶点缓冲对象。应用程序应该始终有一个合法的顶点数组,即开发者始终维护两份顶点数据:一个正在使用,另一个在顶点缓冲对象。
另一种方法,将缓冲对象映射到客户端内存进行修改。客户端使用映射到的缓冲更新数据。
- 3.更新顶点缓冲对象
void* glMapBufferARB(GLenum target, GLenum access);用于将缓冲对象映射到客户端内存。
返回一个指向该缓冲的指针,否则返回NULL.
TARGET:
ACCESS:指明对映射的数据做如何操作:读、写、读和写(GL_READ_ONLY_ARB,GL_WRITE_ONLY_ARB,GL_READ_WRITE_ARB).
GLboolean glUnmapBufferARB(GLenum target):
对VBO的数据修改完毕后,必须撤销在客户端程序建立的映射。
如果映射撤销成功,返回TRUE; 返回FALSE时,标明在建立映射时该VBO的内容已损坏
//绑定,然后映射该VBO
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboid);
float* ptr = (float*)glMapBufferARB(GL_ARRAY_BUFFER_ARB,GL_WRITE_ONLY_ARB);
//如果指针合法,修改VBO内容
if(ptr) {
updateMyVBO(ptr, ...); //修改VBO中的数据
glUmmapBufferARB(GL_ARRAY_BUFFER_ARB);//使用完毕后撤销映射
}
//可以绘制修改后的VBO了
...
1.10 文字输出
OpenGL中,没有内置文字输出函数。
- 位图文字输出
取一位图,256x256像素;每个字符占用16x16个像素,共16x16=256个字符。
基于文字位图的文字绘制由以下三步:
1.将文字位图装载为OpenGL纹理;LoadBitmapFile
2.为位图中每个字符的绘制生成显示列表;
3.在场景绘制时,调用文字的显示列表显示文字
为每个字符生成显示列表
float cx,cy; //存放当前字符的x,y坐标;
base = glGenList(256); //创建256个显示列表