Posts LearnOpenGL
Post
Cancel

LearnOpenGL

LearnOpenGL

OpenGL

图形渲染接口 (Linux,Windows,MacOS,iOS,Android跨平台图形接口,底层库实现由显卡厂商(AMD NVIDIA)或系统厂商(APPLE)实现)

渲染模式: Immediate mode OR Core-profile

Immediate mode(已废弃):

流水线固定渲染模式,这个模式下,所有步骤固定,只能控制整个过程中的一些阶段中一些功能的开启或者关闭

Core-profile:

核心模式,这个模式下,所有流程中的几个阶段不仅可以开启或者关闭,还可以自定义设置数据,比Immediate mode的编程自由性更强

OpenGL 整个渲染过程是一个流水的加工处理改变状态的一个过程,整体为一个状态机。

一瓶纯净水流水生产流程,从入口经过过滤、消毒、蒸馏、罐装、封瓶、封箱等一系步骤下来的一个过程。

图形渲染也类似,经过定义数据、生成图形、渲染颜色、输出至屏幕这么个大概过程,当然整个过程很复杂,前面几个简述步骤下还要细分,并且涉及到很多知识点尤其数学知识(线性代数,三角学)

OpenGL仅是一个图形渲染接口,很多其他功能并不提供,我们需要一些其他库来帮我们来完成环境开发的搭建

1.GLFW 窗口,输入处理

是一款开源、跨平台的图形窗口管理库,不仅可以用来管理窗口,还支持读取输入,处理事件等。

2.GLAD 提供OpenGL各平台底层库实现的函数调用位置查找功能

前面提到OpenGL是一个接口,每个平台有不同的实现库,那么在运行编译并不知道这些库的函数地址,那么GLAD库就是用来查找各平台库对应函数接口的位置,来让上层调用。

3.GLM 数学库

编写图形渲染代码中需要大量的使用到矩阵变换,向量变换以及各种针对矩阵和向量的计算。这一整套功能都由 GLM 库为我们提供

4.stb_image.h 文件图像加载库

能够加载大部分流行的文件格式,并且能够很简单得整合到你的工程之中,仅仅需要把这个头文件添加到你的项目中即可. 当我们需要给物体添加纹理、贴图(即现实中各种区分物品的表面可视的表面特征,金属、木头、墙、水、毛发、皮肤等)时,可用这个库来加载文件

GLSL: OpenGL Shading Language

用于编写OpenGL Shadingd代码的OpenGL专属编程语言

OpeGL渲染流程:

Vertex Data -> Vertex Shader -> Shape Assembly -> Geometry Shader -> Rasterization -> Fragment Shader -> Tests And Blending

顶点数据 -> 顶点着色器 -> 形状装配 -> 几何着色器 -> 光栅化 -> 片段着色器 -> 测试与混合

顶点数据

通常以数组方式传递一组数据(组成物体的每个顶点的3D向量数据

{x,y,z, x1,y1,z2, …})

想象在纸上画几个点,然后用数字表示这些点距这张纸左下角的(水平方向)(垂直方向)多远

顶点着色器

对输入的3D坐标进行处理,如不同的坐标系统的坐标变换

想象你在一张A4纸上画了几个点后,想临摹到一张A2纸上,你可以把A4纸铺在A2纸上,两张纸可以左上角对左上角,右下角对右下角,右上角对右上角,左下角对左下角,或者中心对中心这几种方式。

把A4纸上的点画到A2纸上,此时这些点在A2纸上的点距离A2纸左下角的距离与A4纸这些点距离左下角的距离可能是不一样,因为两张纸大小不一样,A4纸放置在A2纸上的位置也有几种方式,因此最后描绘在A2纸上的点也有几种不同位置的结果。

另一个例子,一个人在新疆的乌鲁木齐,在不同的空间中确定他的位置:

基于中国: 在中国土地的左上方

基于地球: 在地球的上半部分中的下方

基于太阳系: 靠近太阳中心的第三颗星球

基于不同大小的空间,物体的位置会不断的发生变化,这就需要进行坐标变换.

这基于小物体在大空间中放置位置的不同,会在大空间中对小物体位置描述产生不同的结果,最后要得到一个确定的位置,顶点着色器可以进行这种不同空间位置的变换,达到你想要的效果。

形状装配

将上一步传入的顶点进行连线装配成具有形状的图元

我们想象在纸上画了几个点,那么我们现在再想象用笔在这几个点之间画线连起来,就是形状装配

几何着色器

在生成的图元形状范围内生成新顶点,并将新生成的顶点与图元进行组合生成新形状

上一个步骤中,我们画了线在点之间连成形状,如果我们突然想加多个三角形,我们可以在原原的某三条线段每条上画一个点,然后再将三个点连起来,新的点和线所组成的新形状是这个阶段来生成的.

光栅化

在刚刚生成的形状图元区域内生成对应于屏幕显示的像素填充,然后将屏幕不显示的区域中的物体部分裁剪掉

片段着色器

在刚刚形状内部填充的像素中,计算每个像素最终显示的颜色,这个每个像素的颜色最后是基于物体本来的颜色+不同光源照射影响混合计算最后生成的颜色。

测试和混合

最后这个阶段会进行物体之间的深度测试(即物体之间谁在前面和后面,遮挡与被遮挡),以及Alpha透明度测试并进行混合,最后输出的不一样的颜色透明度等其他测试。

Vertex Array Object - VAO 顶点数组对象

该顶点数组可用来保存VB0对象,即创建一次添加到数组中,即可保持对刚刚创建的VBO对象进行引用,不用每次需要时重复很多步骤来重新创建。

需要设置顶点属性配置,即指针指向起始位置,索引间隔

有几种保存方式,分为一对一,一对多

VAO[VBO1,VBO2,…]

VAO[VBO]

Vertex Buffer Object - VBO 顶点缓冲对象

在GPU内存(显存)中管理保存顶点数据的对象,把顶点数据保存到显存中通过VBO来管理读取,比经过CPU->Memory->GPU的读取效率速度要快很多,几乎能立即访问到数据.

Element Buffer Object - EBO OR Index Buffer Object - IBO 索引缓冲对象

在绘制形状时,很多情况下不同形状的顶点位置同一个是重复的,如果使用EBO来进行指定索引,那么我们只需要在数组中保存一份顶点数据就行。如果不使用EBO,程序会按我们定义好的的指针起始位置和数据间隔来读取数据,那么每个形状要单独保存一份顶点数据,会重复很多相同的顶点数据,这样会占用更多的显存.

Normalized Device Corrdinates - NDC

标准化设备坐标是指,在一个 X[-1,1] Y[-1,1] Z[-1,1]之间的一个坐标系空间范围中,我们输入的顶点数据会被转化成这个坐标系中相应的位置,如果在这个范围之外的数据,就会被抛弃掉,不被用在后面的屏幕空间中。

这里有对NDC的深入讨论,这块知识深入理解会在后面补充

Shader 着色器

着色器内部就是进行编写图像处理代码的地方

in、out关键字表示数据的输入、输出

数据结构:int、float、double、uint和bool

Uniform 着色器全局变量,可以被着色器程序(Shdaer Program)任意访问和改变变量值

Texture 纹理

纹理即我们真实世界上区别不同物体的一个外在表现,人的皮肤、钢铁的铁锈、木头的木纹、毛衣、牛仔裤的不同,这些物体的表面理解为每个物体的纹理.

纹理坐标: X[0,1] Y[0,1] 即[0,0] - [1,1]这个矩形范围内的坐标用来放置一个纹理,超出这个范围有几种方式来表现超出这个范围外的纹理.

两个关于材质制作在动画与游戏渲染中的使用和创作的资料,一位在顽皮狗和迪士尼工作过的大神级人物的:

访谈

公开演讲

Transformations 变换 线性代数知识

“direction(方向)” + “magnitude(大小)” = Vectors(向量)

向量是具有 “方向” 和 “大小” 的变量 形象的想象 从一个点 指向 另一个点 的一个箭头

数学中 v = {x, y, z} 用来表示一个3D空间中的向量

默认,向量是从原点{0, 0, 0}指向空间中的任意一个点, {x, y, z}则是所指向的点

Scalar is a single number, 标量是一个数字 可以当作是向量中的一个分量

向量与标量的运算:{x, y, z} + Scalar =

{ x + Scalar = new X,

y + Scalar = new Y,

z + Scalar = new Z }

向量有 加、减、乘运算和取反

取反 -> 向量取当前方向的相反

-{x, y, z} = {-x, -y, -z}

加、减 -> 两个向量的x,y, z分别加减:

{x1, y1, z1} + {x2, y2, z2} =

{ x1 + x2 = new X,

y1 + y2 = new Y,

z1 + z2 = new Z }

长度 -> 如何得到向量的长度:

高中学过的勾股定理(Pythagoras Theorem)来获取长度(length)

取向量{x, y, z}中的x, y

 v 表示向量长度, 就等于 开根号下 的 x平方 + y平方

垂直三角形的两直角边相加开平方根等于第三边斜边的长度 就取的了向量的长度

(博客渲染引擎无法渲染出数学公式= =!,只好先文字描述,虽然看文字很难理解,无法直观的理解,解决博客框架的渲染引擎后加上直观的数学公式)

乘 -> 点乘(Dot Product)、叉乘(Cross Product)为什么不是普通的相乘,因为普通的乘法在向量上是没有定义的,因为它在视觉上是没有意义的

点乘:

两向量的数乘结果再乘与两向量间夹角的余弦值

v⋅k = v  k ⋅cosθ

那么点乘的意义在哪?让v和k向量取单位向量即为1那么

v⋅k = 1⋅1⋅cosθ = cosθ

v⋅k = cosθ

两向量相乘为cosθ, 因为cos0 = 1cos90 = 0, 那么就可以判断两向量是平行还是垂直

因为一些情况下,我们并不在意两向量的长度,而关注的是他们的方向,从而可以计算得出余弦值,再获得向量夹角,在计算光照时,有重要作用.

叉乘:

两个不平行向量作为输入,生成一个正交于两个输入向量的第三个向量

叉乘的作用即为获得一条正交于另外两个向量的新向量

矩阵

最重要的一个知识点到了,前面关于向量的知识点都是为这里做铺垫的.

那么矩阵的作用是用来干嘛的呢,就是用来变换向量的。可以将一个向量进行缩放、位移、旋转,以及如果同时进行前面这三个变换可以将前面三个组合。

那么就有变换矩阵、位移矩阵、旋转矩阵,以及组合三个变换的组合矩阵,最终得到一个你所需要的矩阵,再用向量与矩阵相乘,即可得到新向量。

一个知识点,矩阵相乘的顺序很重要,因为矩阵乘法不遵守交换律,先旋转再位移和先位移再旋转是不一样的结果的。

矩阵相乘顺序是从右往左,如果要先缩放,再位移,那么缩放矩阵要在最右手边再往左才是位移矩阵。

矩阵的加减乘的数学实现可以在这里阅读,这里就不再重新复述一次,写好多字很累呀!

而上面的这些数学公式及运算在GLM这个数学库中已经被实现,我们只需要使用这个库来进行运算即可.

Coordinate Systems 坐标系统

Local Space -> World Space -> ViewSpace -> Clip Space -> Screen Space

几个重要的坐标变换矩阵

Model MatrixView MatrixProjection Matrix

那么整个流程:

Local Space -> [Model Matrix] -> World Space -> [View Matrix] -> View Space -> [Projection Matrix] -> Clip Space -> [ViewPort TransForm] -> Screen Space

在View Space -> [Projection Matrix] -> Clip Space 这个阶段中

Projection Martix有两种矩阵Orthographic Projection Matrix和Perspective Projection Matrix

即正射和透视两种矩阵,区别在于透视具有近大远小的处理,而正射则没有

一个顶点坐标经过 模型矩阵、观察矩阵和投影矩阵,最后就变换乘裁剪空间中的坐标

Vclip = Mprojection ⋅ Mview ⋅ Mmodel ⋅ Vlocal

光照和颜色

现实世界中的物体所呈现出来的外观颜色是 物体本身所固有的颜色 + 光照 = 混合后反射至我们眼睛所看到的颜色

因此,在OpenGL中,我们需要对物体颜色及所有能对物体进行照射的光源 进行光照数值计算 从而得到最后真实的颜色

Ambient Lighting 环境光照

在极端情况如凌晨,此时周围的亮度时最暗的时候,最暗时我们依然能隐约看到物体外轮廓,月光等一些微弱环境光源能提供最基础的光,让我们能看清楚周围的环境,这些光称为环境光照。

Diffuse Lighting 漫反射光照

物体表面粗糙程度不同都会反射各个角度照射在其上面的光,我们从各个角度会看到差不多的颜色,我们称这个为漫反射光,漫反射光能表现出这个物体表面的材质特征

Specular Lighting 镜面光照

物体的高光部分,我们视线投射在物体上,总有一小块区域的亮度时明显高于其他区域的,因此这块区域的颜色及亮度我们基于镜面光照来计算

法向量

法向量是一个垂直于顶点向量表面的单位向量,我们可以通过计算照射在物体表面的光线与法向量的夹角,从而计算出漫反射的最终颜色的反射系数大小

法向量变换到模型空间时,物体的缩放、位移、旋转都会影响到法向量,因为法向量仅代表一个方向,并没位置信息,不等比例缩放也会让法向量不垂直于片段表面,从而让光照计算无法得到正确结果。

因此,在转换过程中我们要使用专门的 Normal Matrix 法线矩阵 [模型矩阵左上角的逆矩阵的转置矩阵] 来进行变换,从而保证得到正确的法向量。

Materials 材质

材质用来表现世界中各种各样的物体,钢、陶瓷、木头对光的反射和他们纹理都是不同,所以需要材质来表现出每个物体的光线和纹理特征

上面提到的光照的内容中:环境光、漫反射、镜面光都是构成一个材质的一部分,因此一个材质有这么些内容构成

环境光、漫反射、镜面光、反光度系数、纹理

最后输出 result = ambient + diffuse + specular

光源

光源有以下几种:

平行光 - 太阳光

光线近似平行,并来自同一个方向

点光源 - 路灯、灯泡、火

光线向光源360度方向发射,随距离增加而亮度逐渐衰减至0

衰减公式

Fatt = 1.0 / Kc + Kl d + Kq d^2

Kc 常数项保持1,保证分母永远不比1小,随距离增加亮度不会变亮

Kl d 一次向乘于距离,让亮度呈线性衰减

Kq d^2 二次想乘于距离的平方,让亮度随距离呈指数衰减

聚光 - 手电筒

向一个方向一定半径内发射,也是随距离亮度逐渐衰减至0

通过 聚光灯的 Position 位置向量 与 片段位置向量 FragPosition 计算得到 光线距离(灯到物体可见的任意位置) LightDir

然后用聚光灯的方向向量LightDirection与光线距离LightDir点乘得到两者间的夹角,从而用得到的夹角判断是否在聚光灯的照射角度范围内,从而做出物体表面片段的光照计算

This post is licensed under CC BY 4.0 by the author.