OpenGL---应用程序编程接口
2021-07-20 18:01:21 0 举报
AI智能生成
OpenGl一般它被认为是一个API(Application Programming Interface, 应用程序编程接口),包含了一系列可以操作图形、图像的函数。然而,OpenGL本身并不是一个API,它仅仅是一个由Khronos组织制定并维护的规范(Specification)。
作者其他创作
大纲/内容
OpenGL
随着时间推移,规范越来越灵活,开发者对绘图细节有了更多的掌控。立即渲染模式确实容易使用和理解,但是效率太低。因此从OpenGL3.2开始,规范文档开始废弃立即渲染模式,推出核心模式(Core-profile),这个模式完全移除了旧的特性。
核心模式
早期的OpenGL使用立即渲染模式(Immediate mode,也就是固定渲染管线),这个模式下绘制图形很方便。OpenGL的大多数功能都被库隐藏起来,开发者很少能控制OpenGL如何进行计算的自由。而开发者迫切希望能有更多的灵活性。
立即渲染模式
核心模式与立即渲染模式
OpenGL的一大特性就是对扩展(Extension)的支持,当一个显卡公司提出一个新特性或者渲染上的大优化,通常会以扩展的方式在驱动中实现。如果一个程序在支持这个扩展的显卡上运行,开发者可以使用这个扩展提供的一些更先进更有效的图形功能。通过这种方式,开发者不必等待一个新的OpenGL规范面世,就可以使用这些新的渲染特性了,只需要简单地检查一下显卡是否支持此扩展。通常,当一个扩展非常流行或者非常有用的时候,它将最终成为未来的OpenGL规范的一部分。
扩展
OpenGL自身是一个巨大的状态机(State Machine):一系列的变量描述OpenGL此刻应当如何运行。OpenGL的状态通常被称为OpenGL上下文(Context)。我们通常使用如下途径去更改OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。
假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。
当使用OpenGL的时候,我们会遇到一些状态设置函数(State-changing Function),这类函数将会改变上下文。以及状态应用函数(State-using Function),这类函数会根据当前OpenGL的状态执行一些操作。只要你记住OpenGL本质上是个大状态机,就能更容易理解它的大部分特性。
状态机
在OpenGL中一个对象是指一些选项的集合,它代表OpenGL状态的一个子集。比如,我们可以用一个对象来代表绘图窗口的设置,之后我们就可以设置它的大小、支持的颜色位数等等。可以把对象看做一个C风格的结构体(Struct):
基元类型(Primitive Type)使用OpenGL时,建议使用OpenGL定义的基元类型。比如使用float时我们加上前缀GL(因此写作GLfloat)。int、uint、char、bool等等也类似。OpenGL定义的这些GL基元类型的内存布局是与平台无关的,而int等基元类型在不同操作系统上可能有不同的内存布局。使用GL基元类型可以保证你的程序在不同的平台上工作一致。
对象
入门
GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户创建OpenGL上下文,定义窗口参数以及处理用户输入。
GLFW
编译生成的库
include文件夹
下载源码
CMake
构建GLFW
编译
链接
因为OpenGL只是一个标准/规范,具体的实现是由驱动开发商针对特定显卡实现的。由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要在运行时查询。任务就落在了开发者身上,开发者需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用。取得地址的方法因平台而异,我们需要对每个可能使用的函数都要重复这个过程。幸运的是,有些库能简化此过程,其中GLEW是目前最新,也是最流行的库
GLEW是用来管理OpenGL的函数指针的
静态(Static)链接是指编译时就将库代码里的内容整合进你的二进制文件。优点就是你不需要管理额外的文件了,只需要发布你单独的一个二进制文件就行了。缺点就是你的可执行文件会变得更大,另外当库有升级版本时,你必须重新进行编译整个程序。
静态链接
动态(Dynamic)链接是指一个库通过.dll或.so的方式存在,它的代码与你的二进制文件的代码是分离的。优点是使你的二进制文件大小变小并且更容易升级,缺点是你最终发布程序时必须带上这些DLL。
动态链接
动静链接
GLEW
创建窗口
告诉OpenGL渲染窗口的尺寸大小
视口(Viewport)
我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口。我们希望程序在我们明确地关闭它之前不断绘制图像并能够接受用户输入。因此,我们需要在程序中添加一个while循环,我们可以把它称之为游戏循环(Game Loop),它能在我们让GLFW退出前一直保持运行。
介绍
glfwWindowShouldClose函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回true然后游戏循环便结束了,之后为我们就可以关闭应用程序了。
glfwWindowShouldClose
glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等),然后调用对应的回调函数(可以通过回调方法手动设置)。我们一般在游戏循环的开始调用事件处理函数。
glfwPollEvents
glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
glfwSwapBuffers
函数
应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。
双缓冲(Double Buffer)
准备引擎
当游戏循环结束后我们需要正确释放/删除之前的分配的所有资源。我们可以在main函数的最后调用glfwTerminate函数来释放GLFW分配的内存。
清除资源(glfwTerminate)
GLFW中实现一些键盘控制,通过使用GLFW的回调函数(Callback Function)来完成
输入
我们要把所有的渲染(Rendering)操作放到游戏循环中,因为我们想让这些渲染指令在每次游戏循环迭代的时候都能被执行
渲染
你好,窗口
实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程
第一部分把3D坐标转换为2D坐标
第二部分是把2D坐标转变为实际的有颜色的像素
两部分
分支主题
顶点数据是一系列顶点的集合
一个顶点(Vertex)是一个3D坐标的数据的集合
顶点数据是用顶点属性(Vertex Attribute)表示的,它可以包含任何我们想用的数据
顶点数据(Vertex Data)
它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理
顶点着色器(Vertex Shader)
将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状,例如三角形
图元封装(Primitive Assembly)
几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。例子中,它生成了另一个三角形。
几何着色器(Geometry Shader)
它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率
光栅化阶段(Rasterization Stage)
计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
片段着色器(fragment shader)
这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。
Alpha测试和混合(Blending)阶段
阶段
图形渲染管线(Graphics Pipeline)
大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。
着色器(Shader)
子主题 1
在GPU内存(通常被称为显存)中储存大量顶点,使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次
使用glGenBuffers函数和一个缓冲ID生成一个VBO对象:
glGenBuffers
OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:
GL_ARRAY_BUFFER
缓冲对象类型
一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上
目标缓冲的类型
用一个简单的sizeof计算出顶点数据大小就行
传输数据的大小(以字节为单位)
发送的实际数据
数据不会或几乎不会改变
GL_STATIC_DRAW
数据会被改变很多
GL_DYNAMIC_DRAW
数据每次绘制时都会改变
GL_STREAM_DRAW
显卡如何管理给定的数据
四大参数
glBufferData
顶点输入
它简明地表达了任意空间中的位置和方向,并且它有非常有用的数学属性。在GLSL中一个向量有最多4个分量,每个分量值都代表空间中的一个坐标,它们可以通过vec.x、vec.y、vec.z和vec.w来获取。注意vec.w分量不是用作表达空间中的位置的(我们处理的是3D不是4D),而是用在所谓透视划分(Perspective Division)上。
向量(Vector)
顶点着色器(Vertex Shader)
为了能够让OpenGL使用顶点着色器源码,我们必须在运行时动态编译它的源码
1. 创建着色器对象
glShaderSource函数把要编译的着色器对象作为第一个参数。第二参数指定了传递的源码字符串数量,这里只有一个。第三个参数是顶点着色器真正的源码,第四个参数我们先设置为NULL。
2. 编译着色器
步骤
检测在调用glCompileShader后编译是否成功了
编译着色器(CompileShader)
片段着色器只需要一个输出变量,这个变量是一个4分量向量,它表示的是最终的输出颜色,我们应该自己将其计算出来。我们可以用out关键字声明输出变量,这里我们命名为color。下面,我们将一个alpha值为1.0(1.0代表完全不透明)的橘黄色的vec4赋值给颜色输出。
作用
两个着色器现在都编译了,剩下的事情是把两个着色器对象链接到一个用来渲染的着色器程序(Shader Program)中。
多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。
创建对象
着色器附加到程序对象上
激活程序对象 glUseProgram(shaderProgram);
删除着色器对象
创建流程
着色器程序对象(Shader Program Object)
渲染前指定OpenGL该如何解释顶点数据
位置数据被储存为32-bit(4字节)浮点值
每个位置包含3个这样的值
在这3个值之间没有空隙(或其他值)。这几个值在数组中紧密排列
数据中第一个值在缓冲开始的位置
告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)
还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。
顶点属性
顶点属性是一个vec3,它由3个值组成,所以大小是3。
顶点属性的大小
这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
数据的类型
如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
是否希望数据被标准化(Normalize)
它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个GLfloat之后,我们把步长设置为3 * sizeof(GLfloat)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔
步长(Stride)
表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。
GLvoid*
glVertexAttribPointer函数
以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的。自此,所有东西都已经设置好了:我们使用一个顶点缓冲对象将顶点数据初始化至缓冲中,建立了一个顶点和一个片段着色器,并告诉了OpenGL如何把顶点数据链接到顶点着色器的顶点属性上。
glEnableVertexAttribArray函数
顶点数据解析
glEnableVertexAttribArray和glDisableVertexAttribArray的调用
通过glVertexAttribPointer设置的顶点属性配置。
通过glVertexAttribPointer调用进行的顶点缓冲对象与顶点属性链接。
可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中
链接顶点属性
索引绘制(Indexed Drawing)
索引缓冲对象(Element Buffer Object,EBO,也叫Index Buffer Object,IBO)
线框模式(Wireframe Mode)
片段着色器(fragment Shader)
绘制三角形
着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。
从基本意义上来说,着色器只是一种把输入转化为输出的程序。
着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。
简介
着色器是使用一种叫GLSL的类C语言写成的。
GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。
结构
版本号
输入和输出变量
uniform
每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。
main函数
声明
当我们特别谈论到顶点着色器的时候,每个输入变量也叫顶点属性(Vertex Attribute)。
我们能声明的顶点属性是有上限的,它一般由硬件来决定。
OpenGL确保至少有16个包含4分量的顶点属性可用,但是有些硬件或许允许更多的顶点属性
查看上限
顶点属性(Vertex Attribute)
GLSL
int
float
double
uint
bool
基础数据类型
可以包含有1、2、3或者4个分量的容器,分量的类型可以是前面默认基础类型的任意一个
一个向量的分量可以通过vec.x这种方式获取,这里x是指这个向量的第一个分量。你可以分别使用.x、.y、.z和.w来获取它们的第1、2、3、4个分量。GLSL也允许你对颜色使用rgba,或是对纹理坐标使用stpq访问相同的分量。
分量获取
重组(Swizzling)
分量选择方式
矩阵(Matrix)
容器类型
数据类型
GLSL定义了in和out关键字专门来实现这个目的。
每个着色器使用这两个关键字设定输入和输出,只要一个输出变量与下一个着色器阶段的输入匹配,它就会传递下去。
顶点着色器的输入特殊在,它从顶点数据中直接接收输入。
为了定义顶点数据该如何管理,我们使用location这一元数据指定输入变量,这样我们才可以在CPU上配置顶点属性。
layout (location = 0)
如何管理顶点数据
它需要一个vec4颜色输出变量,因为片段着色器需要生成一个最终输出的颜色。
如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)
片段着色器
顶点着色器
在顶点着色器中声明了一个vertexColor变量作为vec4输出,并在片段着色器中声明了一个类似的vertexColor。由于它们名字相同且类型相同,片段着色器中的vertexColor就和顶点着色器中的vertexColor链接了。由于我们在顶点着色器中将颜色设置为深红色,最终的片段也是深红色的。
例子
输入与输出
Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。
uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。
无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
一个着色器中添加uniform关键字至类型和变量名前来声明一个GLSL的uniform
片段着色器中声明了一个uniform vec4的ourColor,并把片段着色器的输出颜色设置为uniform值的内容。因为uniform是全局变量,我们可以在任何着色器中定义它们,而无需通过顶点着色器作为中介。
顶点着色器中不需要这个uniform,所以我们不用在那里定义它。
glGetUniformLocation查询uniform ourColor的位置值
查询函数提供着色器程序和uniform的名字
返回-1就代表没有找到这个位置值
找到着色器中uniform属性的索引/位置值
通过glUniform4f函数设置uniform值
更新值
查询uniform地址不要求你之前使用过着色器程序,但是更新一个unform之前你必须先使用程序(调用glUseProgram),因为它是在当前激活的着色器程序中设置unform的。
注意
添加数据
Uniform
颜色数据添加为3个float值至vertices数组
调整顶点着色器,使能接收颜色值作为一个顶点属性输入
用layout标识符来把color属性的位置值设置为1:
重新配置顶点属性指针
重新计算步长值
为获得数据队列中下一个属性值(比如位置向量的下个x分量)我们必须向右移动6个float,其中3个是位置值,另外3个是颜色值,这使我们的步长值为6乘以float的字节数(=24字节)
对于每个顶点来说,位置顶点属性在前,所以它的偏移量是0。颜色属性紧随位置数据之后,所以偏移量就是3 * sizeof(GLfloat),用字节来计算就是12字节。
更新顶点格式
添加颜色属性
添加更多属性
从文件读取
着色器
纹理是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节
除了图像以外,纹理也可以被用来储存大量的数据,这些数据可以发送到着色器上
纹理坐标在x和y轴上,范围为0到1之间(注意我们使用的是2D纹理图像)
使用纹理坐标获取纹理颜色叫做采样(Sampling)
数据格式
纹理坐标(Texture Coordinate)
对纹理的默认行为。重复纹理图像。
GL_REPEAT
和GL_REPEAT一样,但每次重复图片是镜像放置的。
GL_MIRRORED_REPEAT
纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
GL_CLAMP_TO_EDGE
超出的坐标为用户指定的边缘颜色。
GL_CLAMP_TO_BORDER
方式
效果
我们使用的是2D纹理,因此纹理目标是GL_TEXTURE_2D
纹理目标
我们打算配置的是WRAP选项,并且指定S和T轴
指定设置的选项与应用的纹理轴
使用glTexParameter函数的fv后缀形式,用GL_TEXTURE_BORDER_COLOR作为它的选项,并且传递一个float数组作为边缘的颜色值
选择GL_CLAMP_TO_BORDER选项,还需要指定一个边缘的颜色
环绕方式
参数
设置函数
纹理环绕方式(Wrapping)
纹理像素(Texture Pixel,也叫Texel,译注1)映射到纹理坐标
当有一个很大的物体但是纹理的分辨率很低的时候这就变得很重要了
OpenGL会选择中心点最接近纹理坐标的那个像素
邻近过滤(GL_NEAREST)
它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大
线性过滤(GL_LINEAR)
当进行放大(Magnify)和缩小(Minify)操作的时候可以设置纹理过滤的选项
在纹理被缩小的时候使用邻近过滤,被放大时使用线性过滤
设置时机
使用函数
一系列的纹理图像,后一个纹理图像是前一个的二分之一
距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个。
由于距离远,解析度不高也不会被用户注意到。同时,多级渐远纹理另一加分之处是它的性能非常好
背后理念
由于远近图像高分辨率造成的内存浪费问题
解决的问题
使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样
GL_NEAREST_MIPMAP_NEAREST
使用最邻近的多级渐远纹理级别,并使用线性插值进行采样
GL_LINEAR_MIPMAP_NEAREST
在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样
GL_NEAREST_MIPMAP_LINEAR
在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样
GL_LINEAR_MIPMAP_LINEAR
过滤方式
使用
这样没有任何效果,因为多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理,为放大过滤设置多级渐远纹理的选项会产生一个GL_INVALID_ENUM错误代码。
将放大过滤的选项设置为多级渐远纹理过滤选项之一。
常见错误
glGenerateMipmaps
多级渐远纹理(Mipmap)
纹理过滤(Texture Filtering)
加载与创建纹理
SOIL是简易OpenGL图像库(Simple OpenGL Image Library)的缩写,它支持大多数流行的图像格式
必须自己生成.lib
图片路径
SOIL返回图片宽度
SOIL返回图片高度
图片通道数量(channal)
加载图片方式
SOIL_load_image函数
SOIL
glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的GLuint数组中
创建Texture
绑定纹理
设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。
纹理目标(Target)
纹理指定多级渐远纹理的级别
纹理储存为何种格式
纹理的宽度和高度
总是被设置为0
0
使用RGB值加载这个图像
源图的格式
储存为char(byte)数组
源图数据类型
图像数据
调用glTexImage2D时,只有基本级别(Base-level)的纹理图像被加载了,如果要使用多级渐远纹理,我们必须手动设置所有不同的图像(不断递增第二个参数)。或者,直接在生成纹理之后调用glGenerateMipmap。这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。
生成纹理后,释放图像内存并解绑纹理
生成纹理过程
图片生成纹理
生成纹理
纹理坐标更新顶点数据
告诉OpenGL新的顶点格式
调整步长参数
调整顶点着色器
GLSL有一个供纹理对象使用的内建数据类型,叫做采样器(Sampler),它以纹理类型作为后缀,比如sampler1D、sampler3D
调整片段着色器
glDrawElements
流程
应用纹理
在着色器中可以使用多于一个的纹理。通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元
就像glBindTexture一样,我们可以使用glActiveTexture激活纹理单元,传入我们需要使用的纹理单元:
激活纹理单元之后,接下来的glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元,纹理单元GL_TEXTURE0默认总是被激活,所以我们在前面的例子里当我们使用glBindTexture的时候,无需激活任何纹理单元。
纹理单元(Texture Unit)
纹理(Texture)
一个方向
定义
方向(Direction)
大小(Magnitude)
包含
位置向量(Position Vector)
向量与标量运算
向量取反
加
减
向量加减
两个向量的点乘等于它们的数乘结果乘以两个向量之间夹角的余弦值
点乘是通过将对应分量逐个相乘,然后再把所得积相加来计算的
点乘(Dot Product)
需要两个不平行向量作为输入,生成一个正交于两个输入向量的第三个向量
如果输入的两个向量也是正交的,那么叉乘之后将会产生3个互相正交的向量
两个正交向量A和B叉积
叉乘(Cross Product)
向量相乘
运算
勾股定理(Pythagoras Theorem)来获取向量的长度(Length)/大小(Magnitude)
获取
长度是1
用任意向量的每个分量除以向量的长度得到它的单位向量
单位向量(Unit Vector)
长度
矩阵就是一个矩形的数字、符号或表达式数组。
矩阵中每一项叫做矩阵的元素(Element)
矩阵与标量之间的加减
标量值要加到矩阵的每一个元素上
加法和减法只对同维度的矩阵才是有定义的
一个3×2矩阵和一个2×3矩阵(或一个3×3矩阵与4×4矩阵)是不能进行加减的
矩阵与矩阵之间的加减
矩阵的加减
标量就是用它的值缩放(Scale)矩阵的所有元素
矩阵与标量之间的乘法
矩阵的数乘
只有当左侧矩阵的列数与右侧矩阵的行数相等,两个矩阵才能相乘。
矩阵相乘不遵守交换律(Commutative),也就是说A⋅B≠B⋅A
规则
矩阵相乘
向量来表示位置,表示颜色,甚至是纹理坐标
向量其实就是一个N×1矩阵,N表示向量分量的个数(也叫N维(N-dimensional)向量)
很多有趣的2D/3D变换都可以放在一个矩阵中,用这个矩阵乘以我们的向量将变换(Transform)这个向量
矩阵与向量相乘
单位矩阵是一个除了对角线以外都是0的N×N矩阵
这种变换矩阵使一个向量完全不变
变换
单位矩阵(Identity Matrix)
向量的w分量也叫齐次坐标。想要从齐次向量得到3D向量,我们可以把x、y和z坐标分别除以w坐标
我们通常不会注意这个问题,因为w分量通常是1.0。
使用齐次坐标有几点好处:它允许我们在3D向量上进行位移(如果没有w分量我们是不能位移向量的)
如果一个向量的齐次坐标是0,这个坐标就是方向向量(Direction Vector),因为w坐标是0,这个向量就不能位移(译注:这也就是我们说的不能位移一个方向)
齐次坐标(Homogeneous Coordinates)
对一个向量进行缩放(Scaling)就是对向量的长度进行缩放,而保持它的方向不变
定义一个有2或3个缩放变量的向量,每个变量缩放一个轴(x、y或z)
均匀缩放
不均匀缩放
缩放
原始向量的基础上加上另一个向量从而获得一个在不同位置的新向量的过程,从而在位移向量基础上移动了原始向量
在4×4矩阵上有几个特别的位置用来执行特定的操作
操作
位移(Translation)
2D或3D空间中的旋转用角(Angle)来表示
角可以是角度制或弧度制的
周角是360角度或2 PI弧度
弧度和角度互转
在3D空间中旋转需要定义一个角和一个旋转轴(Rotation Axis)
物体会沿着给定的旋转轴旋转特定角度
如何旋转
x轴
y轴
z轴
各轴旋转
它不仅安全,而且计算更加友好
四元数(Quaternion)
解决方法
万向节死锁(Gimbal Lock)
旋转
矩阵的组合
对象所在的坐标空间
局部空间(Local Space)
是指顶点相对于(游戏)世界的坐标
物体变换到的最终空间就是世界坐标系
坐标从局部坐标转换到世界坐标;该转换是由模型矩阵(Model Matrix)
模型矩阵是一种转换矩阵,它能通过对对象进行平移、缩放、旋转来将它置于它本应该在的位置或方向
模型矩阵
世界空间(World Space)
观察空间就是将对象的世界空间的坐标转换为观察者视野前面的坐标
观察空间就是从摄像机的角度观察到的空间
通常是由一系列的平移和旋转的组合来平移和旋转场景从而使得特定的对象被转换到摄像机前面
这些组合在一起的转换通常存储在一个观察矩阵(View Matrix)里,用来将世界坐标转换到观察空间
观察空间(View Space)
OpenGL期望所有的坐标都能落在一个给定的范围内,范围之外的点被裁切(Clipped)。
被裁切的坐标就被忽略
将顶点坐标从观察空间转换到裁切空间
指定坐标范围
由投影矩阵创建的观察区域(Viewing Box)
且每个出现在平截头体范围内的坐标都会最终出现在用户的屏幕上
平截头体(Frustum)
将一定范围内的坐标转化到标准化设备坐标系的过程(而且它很容易被映射到2D观察空间坐标)
投影(Projection)
将位置向量的x,y,z分量分别除以向量的齐次w分量
在每一个顶点着色器运行的最后被自动执行
透视划分是将4维裁剪空间坐标转换为3维标准化设备坐标
正射投影矩阵(Orthographic Projection Matrix)
透视投影矩阵(Perspective Projection Matrix)
转换方式
透视划分(Perspective Division)
投影矩阵(Projection Matrix)
转换矩阵
裁剪空间(Clip Space)
屏幕空间(Screen Space)
种类
设定一个坐标的范围,之后再在顶点着色器中将这些坐标转换为标准化设备坐标
然后将这些标准化设备坐标传入光栅器(Rasterizer)
再将他们转换为屏幕上的二维坐标或像素
顶点数据屏幕上的二维坐标或像素
转换目的
模型(Model)
视图(View)
局部坐标是对象相对于局部原点的坐标;也是对象开始的坐标
将局部坐标转换为世界坐标,世界坐标是作为一个更大空间范围的坐标系统。这些坐标是相对于世界的原点的
接下来将世界坐标转换为观察坐标,观察坐标是指以摄像机或观察者的角度观察的坐标
在将坐标处理到观察空间之后,将其投影到裁剪坐标。裁剪坐标是处理-1.0到1.0范围内并判断哪些顶点将会出现在屏幕上
将裁剪坐标转换为屏幕坐标,我们将这一过程成为视口变换(Viewport Transform),视口变换将位于-1.0到1.0范围的坐标转换到由glViewport函数所定义的坐标范围内
转换的坐标将会送到光栅器,由光栅器将其转化为片段
顶点坐标开始于局部空间(Local Space),称为局部坐标(Local Coordinate),然后经过世界坐标(World Coordinate),观察坐标(View Coordinate),裁剪坐标(Clip Coordinate),并最后以屏幕坐标(Screen Coordinate)结束
转换流程
坐标转换
坐标系统
OpenGL本身没有摄像机的概念
但可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机
这样感觉就像我们在移动,而不是场景在移动
当讨论摄像机/观察空间(Camera/View Space)的时候,是在讨论以摄像机的透视图作为场景原点时场景中所有可见顶点坐标
观察矩阵把所有的世界坐标变换到观察坐标,这些新坐标是相对于摄像机的位置和方向的
定义一个摄像机,我们需要一个摄像机在世界空间中的位置、观察的方向、一个指向它的右测的向量以及一个指向它上方的向量
一个三个单位轴互相垂直的、以摄像机的位置为原点的坐标系
摄像机位置
摄像机方向
右轴
上轴
摄像机/观察空间
摄像机(Camera)
openGL
0 条评论
回复 删除
下一页