简单得说:计算机图形学是一种使用数学算法将二维或者三维图形转化为计算机显示器所能显示的二维栅格形式的科学。
Modeling(建模):构造场景的三维模型。
Rendering(渲染):将三维场景绘制在屏幕上。3D model -》2D image的
Animation(动画):让动画动起来
简单理解:在计算机将3d模型转化为屏幕上 的图像需要经过一系列的步骤处理,这个处理步骤就是图形流水线。
图形流水线总览:
模型处理阶段(在CPU上进行)-》(之后都在GPU上进行)顶点处理(Vertex operation) -》光栅化(Rasterization)-》片元处理(fragment operation) -》帧缓冲(frame buffer)-》屏幕上的二维图像
其中:
OpenGL:Open Graphics Library(开发式图形编程库)
调用图形硬件的程序接口。
应用程序《 - -》openGL《–》调用图形显卡驱动
由大约150个函数组成,用户可以调用来完成各种绘制任务。
GPU:Graphics Processing Unit,
CPU:Center processing Unit
OpenGL Library(核心库)
OpenGL utility library(实用程序库)
辅助OpenGl编程工具库
问题:windows中,lib和dll的区别?
OpenGL函数的命名规则
OpenGL的数据类型
注意:尽量使用GL内置的类型,为了跨平台。使用这个是有原因的
不由用户来调用,由系统响应消息来调用的函数。需要一个注册函数。
总览:
几何变换-》投影变换-》裁剪-》视口变换
投影
窗口裁剪
视口变换
glTranslate*()
glScale*()
需要注意的是:三角形并没有在原地放缩,而是离得更远了
是以原点(0, 0, 0)为中心的方缩放, 如果想以点p(x1,y1, z1) 为中心放缩的话,需要使用组合的方式实现(三个步骤):
Translate(-x1,-y1, -z1)
Scaling with(Sx, Sy,Sz)
Translate(x1,y1, z1)
glRotate*()
如果想实现沿着任意向量的旋转需要组合:
A是一个正交矩阵,A的转置和A的逆是一样的。
平移,旋转,缩放,都是矩阵。
为什么要把各种变换表示成矩阵运算?
需要注意的是这个T1,和T1是乘在矩阵M的右边,矩阵相乘的顺序是先平移再旋转,但是实际上却是相反的。
在OpenGl里面,其实是用堆栈来管理矩阵。
glPushMatrix()起到了保护环境的作用,glPopMatrix起到了恢复环境的作用;两者一般配合其来使用。
模型变换与视点变换
Model-View Transformation
Model Transformation:视点不变,变物体。
View Transformation:物体不变,变视点。
模型变换和视点变换是可以统一的。目前使用的变换可以认为视点总是不变的,所有的变换都是模型变换。
如果要使用视点变换的话可以使用gluLookAt()函数实现,gluLookAt()函数视图模拟一种物体不动,变换视点的控制效果。但是,本质上还是模型变换。
全局变换和局部变换
如果只有有一次变换(旋转,平移,缩放),那么全局变换和局部变换效果一样。
但是如果是多个变换的组合,那么它们一般效果不同。
局部变量正好相当于组合顺序相反的全局变换。
OpenGl中如果是单独来看是局部变换,但是多个变换是局部变换(也就是全局变换的想法)
OpenGl中采用的是列矩阵,所以其新矩阵总是乘在原矩阵的左边(列列矩阵相当于列矩阵的转置)。
投影变换(projection)
已知给定视点,实现方向,计算出当前顶点的投影点坐标。
透视投影(Perspective projection):有近大远小的特点
平行投影(Parallel projection):没有近大远小的特点
正投影(Orthographic projection):其实是平行投影的一种特殊形式,投影方向与投影面垂直,没有近大远小的特点。
glOrtho和glFrustum定义的投影空间可以是不对称的。但是glPerspective一定是和z轴完全对称的。
投影变换也都是用矩阵来实现的
有一个专门的堆栈来管理投影矩阵
glMatrixMode(GL_PROJECTION)
视口变换
三维几何空间中的坐标系与单位
方案:分而治之
Phong Illumination Model
环境光 Ambient light (最难模拟),与视点位无关
环境光非常复杂,但是Phong模型中只采用一个常数来表示
漫反射光 Diffuse reflection ,与视点位无关
粗糙表面的光会向四周均匀反射
镜面反射光(高光) Specular reflection,与视点位有关
Phong Illumination Model
R = 2N(N.L) - L,计算量比较大,实际使用中,由于R计算不方便,因此常用(N.H)代替(R.V);H为L和V的角平分线上的单位向量。
H = normalize(L+V),normalize:单位化
Blinn-Phong Model
OpenGL中光照的参数设置
step1.设置好物体的法向量。glNormal3f(Nx,Ny, Nz)
I = KaIa + KdIl(N.L) + KsIl(N.H)^n
step2.打开光照:
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0),最多8栈灯从light0-light7.如果超过8栈灯,可以使用shader来实现。
Step3.光照参数
glLightfv(GL_LIGHT0, GL_AMBIENT, vLitAmbient)
glLightfv(GL_LIGHT0, GL_DIFFUSE, vLitDiffuse)
glLightfv(GL_LIGHT0, GL_SOECULAR, vLitSpecular)
glLightfv(GL_LIGHT0, GL_POSITION, vLItPosition),表示光源的位置
Step4.设置材质参数
glMaterialfv(GL_FRONT, GL_AMBIENT, vMatAmb) ,Ka
glMaterialfv(GL_FRONT, GL_DIFFUSE, vMatDif),Kd
glMaterialfv(GL_FRONT, GL_SPECULAR, vMatSpec),Ks
glMaterialfv(GL_FRONT, GL_SHININESS, vShininess),n
glMaterialfv(GL_FRONT, GL_EISSION, vEmission),自发光
Step5,设置聚光灯参数
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, vSpotDir),聚光灯的方向
glLightfv(GL_LIGHT0, GL_SPOT_CUTOFF, vLitCutoff),角度,哪个范围内有光。
glLightfv(GL_LIGHT0, GL_SPOT_EXPONENT, vSpotExponent),聚光灯的衰减,偏离聚光灯方向的时候回衰减
衰减相关参数
glLightfv(GL_LIGHT0, GL_CONSTANT_ATTENUATION, kc),常数衰减
glLightfv(GL_LIGHT0, GL_LINEAR_ATTENUATION, kl),线性衰减
glLightfv(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, kq), 二次方的衰减
衰减因子 = 1/ (kc + kl d + kqd^2) ,d表示光到点的距离,d越大,光效越暗。
OpenGL的状态机的特性,所有的OpenGL的状态参数都有一个默认值。
此处Kc的默认值是1, Kl和Kq的默认参数都是0
方向性光源,位置性光源
glFloat vLitPosition[] = {1.0, 1.0, 1.0, 0.0}
glLightfv(GL_LIGHT0, GL_POSITION, vLitPosition)
若vLitPosition的w值为0.0, 则为方向性光源,否则为位置性光源。
本地视点无限远视点
glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
—本地视点/无限远视点。
双面光照
glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 0.0).
1)如何让光源运动?
1.直接变化vLitPosition中的值
glLightfv(GL_LIGHT0, GL_POSITION, vLitPosition)
2.光源可以认为是一个几何物体,将受到其前面的几何变换的影响
2)光照下物体的颜色有什么来决定?
1.光的颜色:glLightfv()
2.物体的材质:glMaterialfv()
有一个点,如果OpenGL一旦打了光,那么glColor就失效了,但是可以通过以下方式发生作用。
glEnable(GL_COLOR_MATERIAL)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT)
通过这句话,让color的值变成ambient的值。
Vertex operations,只会在顶点计算光照,效率更好!。在光栅化阶段,会根据顶点的颜色来计算中间的颜色。
OpenGL中的Shading:
有几种常见的处理方法:
常数明暗处理(Flag shading)
面片内所有像素都取同样的颜色,是一种顶点级光照计算
Gouraud 明暗处理 (Gouraud shading)
双线性插值计算面片内像素的颜色, 是一种顶点级光照计算
计算出多边形各个顶点的法向量(对周围面片法向进行平均求得次顶点的法向)(重要)(CPU)
计算出多边形各个顶点的光亮度值。(vertex operation)
对多边形顶点的光亮度值进行"双线性插值"计算出多边形任意片元的光亮度值。(Rasterization)
二次插值,所以叫双线性插值。
双线性插值发生在光栅化阶段。
在光栅化阶段效率非常高!
Gouraud 明暗处理的问题:
Phong 明暗处理(Phong shading)
不差之光亮度的颜色,而是插值法向量。是一种像素级光照计算,也称为"法向量插值明暗处理"
Phong 明暗处理缺点:计算量大, 以往图形硬件中不长采用。
顶点处理-光栅化-片元处理
vertex operation处理的对象是顶点,fragment operation处理的是片元。
光栅化做了什么?
以三角形面片作为处理单位,输入的是三个顶点的数据,输出的是三角形所覆盖的所有的像素数据。
光栅化模块没有直接的接口函数,只有间接的函数。
glShaderMode(GL_FLAT/GL_SMOOTH);
glPolygonMode(GLenum face, GLenum mode);
设置画物体是用线框还是用面来处理,影响图元装配。目前还无法对光栅化模块进行shading编程。
缓冲区的每一个单元对应的就是屏幕上的一个个像素
对片元的处理就是利用并修改缓冲区的数据,可以想象,一块缓冲区就是一块内存空间。它不是放在内存上,它是放在显存上。
随机扫描显示器
光栅扫描显示器
像素的颜色都要存储在一块缓冲区中
需要不断重复绘制,即不断刷新屏幕(很快,一秒扫描60屏幕,一屏也就是一帧)
与视口的像素意义对应
单位:FPS:frame per second
color buffer记录每个像素的颜色。
存储视口中每个像素的信息,颜色缓冲区放在显存上,也就是放在显卡那个存储空间上。
假设:Resolution:分辨率为:1024 * 768
像素颜色:采用RGB三个分量来表示颜色,每个分量采用8bits,相当于一个字节来表示。
那么该颜色缓冲区需要的存储空间是:1024 * 768 *3byte =2.25M
glClearColor(0.0, 0.0, 0.0, 0.0)
glClear(GL_COLOR_BUFFER_BIT)
glClearDepth(1.0)
glClear(GL_DEPTH_BUFFER_BIT)
以上都是给图形流水线传达一个参数
gl推荐同时清除帧缓冲和深度缓冲,效率更高
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
采用按位或。
双缓冲可以避免屏幕闪烁,理由是在画的过程中是一行一行画的,单缓冲屏幕可能会出现画的过程。出现屏幕闪烁,效果不好,而双缓冲没有画的过程。只有全图。但是需要注意的是,如今的操作系统对缓冲做了一个优化,及时外部设置单缓存,起内部也是会使用双缓存的。
front buffer
back buffer
depth buffer记录每一个像素的深度。我们经常把depth-buffer叫做z-buffer。其最常用到的一个应用是消隐(visible suface detection)。来判断哪个面在前面,哪个面在后面。
1.在3d到2d的变换中,其实有一个z值,记录深度。
2.在投影时,每个投影点的深度(也就是wz)也会被记录下来,写入深度缓冲。
3.其实是:光栅化时候,每个片元的深度会计算出来,写入深度缓冲。深度缓冲区记录的是片元的深度(也就是wz),而不是顶点的深度(也就是z)
Depth Sorting Method(深度排序算法)
Area-Subdivision Method(区域子分算法)
Ray Casting Mathod(光线投射法)
**Z-buffer算法:**最常见
glEnable(GL_DEPTH_TEST):两个作用
void glDepthFunc(GLenum func)
void glDepthMask(GLboolean flag)
glDisable(GL_DEPTH_TEST);
如果不想更新深度缓冲,只想比较
glEnable(GL_DEPTH_TEST)
glDepthMask(GL_FALSE)
如果不想比较深度值,只想更新深度缓冲,那么可以
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_ALWAYS)
glDepthMask(true)
默认**是
glDisable(GL_DEPTH_TEST)
glDepthMask(GL_FALSE)
glDepthFunc(GL_LESS)
相当于起了一个过滤的作用。效率很高。
glEnable(GL_STENCIL_TEST)
void glStencilFunc(GLenum func, GLint ref, GLuint mask);
void glStencilOp(GLenum fail, GLenum zfail, GLenum zpass)
如何绘制模板缓冲呢?
小方格-》片元,一旦画到缓冲区之后-》像素,区别就在于,像素最终的颜色可能来自多个片元。
片元在写入帧缓冲之前要按书按需经历一系列的测试,在写入帧缓冲之时也会经历一些运算。
操作顺序如下
纹理贴图
主颜色和辅助颜色汇合(区分光照)
雾效计算
其中,前面四个是在测试阶段,也就是写入帧缓冲之前,后面三个是,也就是写入帧缓冲之时。
如果片元在其中某个测试中被排除,那么后面的测试或者操作就不进行了。
void glScissor(GLint x, GLint y,GLsize width,GLsize height)
定义了一个区域,x,y的区域的左下角。
glEnable(GL_SCISSOR_TEST)
只在矩形框里面的场景会被画,之外的场景不会被画,效率非常高,如果要矩形窗口裁剪,glScissor的效率会更高。
将当前片元(正要写入颜色缓冲)的颜色与已经在颜色缓冲中的值进行混合。
常用方法:加权平均
Color = alpha * color-s + (1- alpha)* color-d
这仅仅是一种混合方式。
混合相关的函数
glEnable(GL_BLEND)
void glBlendFunc(GLenum sfactor, GLenum dfactor),混合的方式。
Cs * S + Cd * D
void glBlendEquation(GLenum mode),混合方程。
例子,半透明混合的因子设置。
void glBlendFunc(GLenum sfactor, GLenum dfactor)
Color = alpha * color-s + (1 - alpha) *color-d
sfactor 取GL_SRC_ALPHA
dfactor取GL_ONE_MINUS_SRC_ALPHA
注意:画半透明物体的时候,必须保持从远到近的顺序,也就是说必须排序(针对视点)。
深度检测没通过。
如果场景中物体都是不透明的,那么如下三种绘制方法哪种效率最高?
答:如果忽略掉排序的时间,第二种效率更高,因为z-buffer检测会让你少画很多东西,在测试阶段会少掉很多东西。
OpenGL中可以设置物体的点,线,面绘制模型。
1.void glPolygonMode(GLenum face,GLenum mode)
2.face:GL_FRONT,GL_BACK,GL_FRONT_AND_BACK
3.mode:GL_POINT,GL_LINE,GL_FULL
有的时候我们需要同时绘制两种模式,如在GL_FILL显示表面时同事显示line以突出多边形的边。
多边形偏移功能
雾效是采用混合计算来实现的
Color = f * Color-s + (1-f)*Color-f
f是加权混合因子,有三种计算方式
GL_LINEAR, f = (end - z)/(end - start),start是雾开始的地方,end是雾结束的地方。z是雾的深度值。
GL_EXP
GL_EXP2
衰减的更快
glEnable(GL_FOG),打开雾效
glFog*()用来设置与雾相关的参数
glFogCoord*()指定每个顶点的雾坐标
雾坐标决定雾的浓淡,深浅。
片元的颜色来自于光栅化,片元的在光栅化的基础上进行检查,处理,运算。
如何实现表面细节的效果?
Texture mapping(纹理映射, 纹理贴图)
纹理映射的主要思想?
给一给定的**纹理函数(图就是二维的纹理函数)**映射到物体表面上,在对物体表面进行光亮度计算时可采用相应纹理函数值来影响光照模型中的参数(入漫反射光亮度)以产生纹理效果。
注意
纹理不仅仅可以改变颜色信息,还可以改变所有的信息。
三维纹理相当耗费空间。
基本思想:
Bump map的三种记录方法
Relief Mapping(浮雕贴图)
纹理信息 + 合适的shader -》产生效果。
计算过于复杂,不利于实现
绘制物体时,对物体表面采样,
代价确实是大。
纹理坐标:顶点在空间中对应的坐标;
纹理映射是从顶点阶段开始的。
纹理映射在流水线中的实现过程:
应用纹理的四个步骤:
纹元(texel):纹理当中的单元。
为了画一个三角形调用了多个函数,导致太慢了,慢在函数调用(代价很大,消耗时间),调用函数之前,先为函数开辟一个栈空间,把每一个参数都压栈,入栈,然后才开始执行函数,执行完还得把每一个参数出栈,释放空间。
方法:减少调用函数的次数。
Vertex Array,一次性传入显存,固定流水线。
将顶点颜色,坐标,法向等一次性传入显存, 加快了速度
步骤:
激活(启用)顶点数组,最多可达8个数组
把数据放入数组,并且建立联系:glVertexPointer, glColorPointer, glNormalPointer
访问数组数据,绘制物体
方式1:访问单独是数组元素
glArrayElement(GLint index)
方式2:按照顺序访问整个数组元素
glDrawArray() (VAO)这一步会将所有的顶点数据一次性传入显存,起到了加速的作用。
方式3:创建一个数组元素的索引列表进行访问。
glDrawElements() (EBO)更灵活,避免重复放入数据。
对stride的理解:
步长:数据放在数组里面,顶点和顶点之间默认的空隙。
为什么不给pointer数组中顶点的个数?
不需要,跟顶点没关系,只要取找就行。
glNormalPointer()参数中没有size:固定是3
问题:物体需要不断的刷新,画一次传一次:需要:内存-》显存
Buffer Object:一般在shader中。有可能会加速10倍。
Display list,一次性传入显存,把命令驻留在显存,在过去非常重要。被固化在了流水线上。从一个命令函数的存储和优化入手来提高效率。
Opengl3.1之后被废弃。大概加速不到一般的时间。
Shader 只能做一部分作用:
Vertex shader:
fragment shader
何为主颜色、辅颜色?
各种片元测试和操作
可以交给固定管线去做。
discard来丢弃不想绘制出来的片段:表明片元被丢弃了
if (vColorValue < 0.1f) discard;
glew -》opengl扩展库
装载-编译-链接vs,fs程序代码
如果shader 出错了但是还是会画出来,因为opengl还是会走固定流水线。
VS的输入:针对顶点的属性:一致变量(uniform)
VS的输出:针对顶点的数据
FS的输入:针对片元的属性,一致变量(uniform)
FS的输出:glFragColor;glFragDepth;…
我们的Vertex Shader的处理只需要对单个顶点做处理。GPU会把这一段程序放到GPU里面一块儿处理,有可能是并行。Fragment 同理。
VS:对单个顶点的操作;
FS:对单个片元的操作;
数据流是单向的,VS无法调用FS的数据。
VS的输入变量:
内建的uniform变量:系统本身就有的,如:gl_ModelViewMatrix;
shader编程并没有脱离默认的固定流水线。
自动调用。Access = readOnly
内建的顶点属性变量(“in” 类型) 如:gl_Vertex
自己定义的"uniform"类型变量
Access = readOnly
自己定义的"in"类型数据
纹理数据
VS的输出变量
VS输出的特定变量(Special output Variables),不用与fragment的输出
Access = ReadWrite
VS输出的内建顶点属性变量(加out限定符号)
Access = ReadWrite,往往和fs的输出相对应
VS输出的自定义顶点属性变量(加out限定符)
FS的输入变量
内建的uniform状态变量
内建的"in"类型变量,如gl_Color
Access = ReadOnly
自己定义的"uniform"类型变量
自己定义的"in"类型变量
纹理数据
FS的输出变量
变量前的限定符
学习shader,最重要的是明白它在流水线中的位置,流水线中的数据流的怎么样的
数据类型及其使用
使用:声明、定义、初始化、赋值、访问
与C语言类似,定义一个变量的同时可以进行初始化
glsl是强制类型语言:必须进行显示的强制类型转换,不存在隐式的类型转换。 如:int b = int(2.0);
基本数据类型:
float、 int、bool、uint
向量类型(vec),非常灵活。
Vec4 color;
Vec3 rgb = vec3(color); // 可以直接截取。
访问:
矩阵类型(mat)
数组
结构体
struct dirlight
{
vec3 direction;
vec3 color;
}
dirlight light = dirlight(vec3(1.0), vec3(0.8));
sampler类型
变量前的限定符
内建函数
blin光照模型计算公式:
Grourand Shader
可从外部输入的uniform变量:
Ka、Kd、Ks、n
Ia、Id、Is
灯的位置,视点的位置
以上全是内建变量
需要计算的变量
法向:
normal的变换和顶底的变换不一样,注意,详细红宝书中有说。
计算H,H.L, H.H,向量处理后需要标准化,才能点积。
NdotL 小于 0,说明在背面。
Phong shadering ,计算每个顶点的法向。
实际上我们会发现,光被分层了。
图形对象
基本元素:
点,线,面,体等几何元素经过平移,放缩,旋转,等几何变换。
实体的性质:
物体表面的性质:
样条
曲线描述;
特点:
分形维数
生成过程:为产生物体局部细节指定一个过程
形状语法:
粒子系统:
模拟火,爆炸, 烟, 云,等微小物体的集合。
粒子生成要素:
公告板技术:永远只能看到牌子的正面