OpenGL ES 名词解释(二)

ChatGPT 3.5 国内中文镜像站免费使用啦

零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >> OpenGL ES 基础

零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录  >> OpenGL ES 特效

零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录  >> OpenGL ES 转场

零基础 OpenGL ES 学习路线推荐 :  OpenGL ES 学习目录 >> OpenGL ES 函数

零基础 OpenGL ES 学习路线推荐 :  OpenGL ES 学习目录 >> OpenGL ES GPUImage 使用

零基础 OpenGL ES 学习路线推荐 :  OpenGL ES 学习目录 >> OpenGL ES GLSL 编程


一.前言

《OpenGL ES 名词解释一》中已经讲解了着色器渲染等相关知识,本篇文章着重讲解坐标系和矩阵相关内容;


二.坐标系

1.屏幕坐标系

屏幕坐标系 的 左下点(0, 1),右下角(1,1)  , 左上角(0, 0) , 右上角(1 , 0)

图片[1]-OpenGL ES 名词解释(二)-猿说编程

2.纹理坐标系

纹理坐标系 的 左下点 (0, 0),右下角(1 , 0) , 左上角(0, 1 ), 右上角(1, 1) 

图片[2]-OpenGL ES 名词解释(二)-猿说编程

3.顶点坐标系

顶点坐标系 的 左下点(-1, -1),右下角(1,-1)  , 左上角(-1, 1) , 右上角(1 , 1)

图片[3]-OpenGL ES 名词解释(二)-猿说编程

4.图像坐标系

屏幕坐标系 的 左下点(0, 1),右下角(1,1)  , 左上角(0, 0) , 右上角(1 , 0)

图片[1]-OpenGL ES 名词解释(二)-猿说编程

很多人有一个误解:认为 OpenGL ES 纹理原点在左上角,因为如果绘制时纹理坐标设在左下角,绘制的图像就是上下倒立;而纹理坐标设制在左上角显示正常

原因:图像默认的原点在左上角,而 OpenGL ES 纹理读取数据或者 FBO 读取数据时都是以左下角开始,所以图像才会出现上下倒立的现象;

解决办法:

  • 方案一:绘制时将纹理坐标上下镜像
  • 方案二:绘制时将顶点坐标上下镜像
  • 方案三:绘制时将图像上下镜像后在填充到 OpenGL ES 纹理

关于方案三:将图片上下颠倒可以使用 stb_image 完成

stbi_set_flip_vertically_on_load(true);//开起上下镜像

.混合

假设一种不透明东西的颜色是 A,另一种透明的东西的颜色是 B ,那么透过 B 去看A ,看上去的颜色 C 就是 B 和 A 的混合颜色,可以用这个式子来近似,设 B 物体的透明度为 alpha (取值为 0 – 1 ,0 为完全透明,1 为完全不透明)

R(C)=alpha*R(B)+(1-alpha)*R(A)
G(C)=alpha*G(B)+(1-alpha)*G(A)
B(C)=alpha*B(B)+(1-alpha)*B(A)

R(x)、G(x)、B(x)分别指颜色 x 的 RGB 分量。看起来这个东西这么简单,可是用它实现的效果绝对不简单,应用 alpha 混合技术,可以实现出最眩目的火光、烟雾、阴影、动态光源等等一切你可以想象的出来的半透明效果。

图片[5]-OpenGL ES 名词解释(二)-猿说编程

四.变换矩阵

1.平移

为向量(x,y,z)定义一个平移矩阵

图片[6]-OpenGL ES 名词解释(二)-猿说编程

2.旋转

旋转过程涉及到弧度与角度的转化

弧度转角度:角度 = 弧度 * (180.0f / PI)

角度转弧度:弧度 = 角度 * (PI / 180.0f)

图片[7]-OpenGL ES 名词解释(二)-猿说编程

3.缩放

为向量(x,y,z)定义一个缩放矩阵

图片[8]-OpenGL ES 名词解释(二)-猿说编程

4.矩阵组合顺序

矩阵组合顺序1:先平移,再旋转,最后缩放——— OK

矩阵组合顺序2:先平移,再缩放,最后旋转——— ERROR

矩阵组合顺序3:先缩放,再旋转,最后平移——— ERROR

(除了第一种,其他组合顺序都是错误的)

矩阵组合顺序可以参考glm官方demo案例:

#include <glm/vec3.hpp> // glm::vec3
#include <glm/vec4.hpp> // glm::vec4
#include <glm/mat4x4.hpp> // glm::mat4
#include <glm/ext/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale
#include <glm/ext/matrix_clip_space.hpp> // glm::perspective
#include <glm/ext/scalar_constants.hpp> // glm::pi

glm::mat4 camera(float Translate, glm::vec2 const& Rotate)
{
	glm::mat4 Projection = glm::perspective(glm::pi<float>() * 0.25f, 4.0f / 3.0f, 0.1f, 100.f);
	glm::mat4 View = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -Translate));
	View = glm::rotate(View, Rotate.y, glm::vec3(-1.0f, 0.0f, 0.0f));
	View = glm::rotate(View, Rotate.x, glm::vec3(0.0f, 1.0f, 0.0f));
	glm::mat4 Model = glm::scale(glm::mat4(1.0f), glm::vec3(0.5f));
	return Projection * View * Model;
}

至于矩阵组合顺序为什么是先平移,再旋转,最后缩放,后面将专门留一篇文章做详细讲解!可以关注学习目录《OpenGL ES 基础》


五.投影矩阵

由观察空间到裁剪空间在公式上左乘一个投影矩阵,投影矩阵的产生分为两种:正交投影和透视投影;

不管是正交投影还是透视投影,最终都是将视景体内的物体投影在近平面上,这也是 3D 坐标转换到 2D 坐标的关键一步。

正投影就是没有3D效果的投影方式,用于显示2D效果;

透视投影就是有3D效果的投影方式,用于显示3D效果.

图片[9]-OpenGL ES 名词解释(二)-猿说编程

1.正交投影

正交投影产生的效果无论你离物体多远多近,都不会产生近大远小的效果,大致如下图,视点作为观察点,视椎体由前后左右上下 6 个面包裹而成,物体在视椎体内部,最后投影到 near 近平面,视椎体范围之外将无法显示到屏幕上来

正投影就是没有 3D 效果的投影方式,用于显示 2D 效果;

透视投影就是有 3D 效果的投影方式,用于显示 3D 效果.

图片[10]-OpenGL ES 名词解释(二)-猿说编程

正交投影矩阵,由 Matrix.ortho 这个方法产生

void orthoM(float[] m, int mOffset,
        float left, float right, float bottom, float top,
        float near, float far)

可以把近平面看作屏幕,left、right、top、bottom都是以近平面中心相对的距离,由于手机屏幕的长宽一般不相等,以短边为基准 1 ,长边取值为长/宽,所以如果一个竖屏的手机使用这个正交投影产生的矩阵应该是:

float ratio = (float)height / width;
Matrix.ortho(projectMatrix,0,-1, 1, -ratio, ratio, 1, 6);

2.透视投影

透视投影会产生近大远小的效果,正投影就是没有 3D 效果的投影方式,用于显示 2D 效果;透视投影就是有 3D 效果的投影方式,用于显示 3D 效果.产生的视椎体如下图:

图片[11]-OpenGL ES 名词解释(二)-猿说编程

透视投影也有响应的函数产生投影矩阵:

Matrix.frustumM(float[] m, int offset, float left, 
          float right, float bottom, float top, 
          float near, float far);

3.总结

经过上述的讲解,我们要完成 4 个空间转换,需要用到了 3 个转换矩阵:

从局部空间转换到世界空间,我们需要用到模型矩阵 ModeMatrix 这个矩阵就是我们通常对物体进行 translate 、rorate 换后产生的矩阵

从世界空间到观察空间,我们需要用到观察矩阵 ViewMatrix ,这个矩阵可以 setLookAt 方法帮我们生成

从观察空间到裁剪空间,我们可以用到投影矩阵 ProjectMatrix,使用 ortho 、frustuM 还有 perspectiveM 方法产生投影矩阵

最后以上几个坐标依次左乘我们的定义的坐标 Position 就可以得到归一化坐标了

所以,总结出来的公式

//注意顺序
gl_Position = ProjectMatrix * ViewMatrix * ModeMatrix * g_Position ;
图片[12]-OpenGL ES 名词解释(二)-猿说编程

六.帧缓冲区帧

缓冲区就是显存,也被叫做帧缓存,它的作用是用来存储显卡芯片处理过或者即将提取的渲染数据。如同计算机的内存一样,显存是用来存储要处理的图形信息的部件。

最终”存活”下来的像素需要被显示到屏幕上,但是显示屏幕之前,这些像素是会被先提交在帧缓冲区的。帧缓存区的每一存储单元对应屏幕上的一个像素,整个帧缓存区对应一帧图像。

在下一个刷新频率到来时,视频控制器会把帧缓冲区内的内容映射到屏幕上。一般采用双缓冲机制,存在两个帧缓冲区。


七.VAO

VAO (顶点数组对象:Vertex Array Object)是指顶点数组对象,主要用于管理 VBO 或 EBO ,减少 glBindBuffer 、glEnableVertexAttribArray、 glVertexAttribPointer 这些调用操作,高效地实现在顶点数组配置之间切换。

OpenGL 2.0有 VBO,没有 VAO,VAO 是 OpenGL 3.0 才开始支持的,并且在 OpenGL 3.0 中,强制要求绑定一个 VAO 才能开始绘制。


八.VBO

VBO(顶点缓冲区对象:Vertex Buffer Object)是指把顶点数据保存在显存中,绘制时直接从显存中取数据,减少了数据传输的开销,因为顶点数据多了,就是坐标的数据多了很多的很多组,切换的时候很麻烦,就出现了这个 VAO,绑定对应的顶点数据

OpenGL 2.0有 VBO,没有 VAO,VAO 是 OpenGL 3.0 才开始支持的,并且在 OpenGL 3.0 中,强制要求绑定一个 VAO 才能开始绘制。


九.PBO  

PBO (Pixel Buffer Object)是 OpenGL ES 3.0 的概念(OpenGL 2.0 不支持 PBO ,3.0支持 PBO),称为像素缓冲区对象,主要被用于异步像素传输操作。PBO 仅用于执行像素传输,不连接到纹理,且与 FBO (帧缓冲区对象)无关。PBO设计的目的就是快速地向显卡传输数据,或者从显卡读取数据,我们可以使用它更加高效的读取屏幕数据。

  1. PBO 类似于 VBO(顶点缓冲区对象),PBO 开辟的也是 GPU 缓存,而存储的是图像数据。
  2. PBO 可以在 GPU 的缓存间快速传递像素数据,不影响 CPU 时钟周期,除此之外,PBO 还支持异步传输。
  3. PBO 类似于“以空间换时间”策略,在使用一个 PBO 的情况下,性能无法有效地提升,通常需要多个 PBO 交替配合使用。
图片[13]-OpenGL ES 名词解释(二)-猿说编程

十.FBO

FBO(Frame Buffer Object) 即帧缓冲对象。FBO 有什么作用呢?通常使用 OpenGL ES 经过顶点着色器、片元着色器处理之后就通过使用 OpenGL ES 使用的窗口系统提供的帧缓冲区,这样绘制的结果是显示到窗口(屏幕)上。

图片[14]-OpenGL ES 名词解释(二)-猿说编程

但是对于有些复杂的渲染处理,通过多个滤镜处理,这时中间流程的渲染采样的结果就不应该直接输出显示屏幕,而应该等所有处理完成之后再显示到窗口上。这个时候 FBO 就派上用场了。

图片[15]-OpenGL ES 名词解释(二)-猿说编程

FBO 是一个容器,自身不能用于渲染,需要与一些可渲染的缓冲区绑定在一起,像纹理或者渲染缓冲区。,它仅且提供了 3 个附着(Attachment),分别是颜色附着、深度附着和模板附着。


十一.UBO

UBO,Uniform Buffer Object 顾名思义,就是一个装载 uniform 变量数据的缓冲区对象,本质上跟 OpenGL ES 的其他缓冲区对象没有区别,创建方式也大致一致,都是显存上一块用于储存特定数据的区域。

当数据加载到 UBO ,那么这些数据将存储在 UBO 上,而不再交给着色器程序,所以它们不会占用着色器程序自身的 uniform 存储空间,UBO 是一种新的从内存到显存的数据传递方式,另外 UBO 一般需要与 uniform 块配合使用。


本例将 MVP 变换矩阵设置为一个 uniform 块,即我们后面创建的 UBO 中将保存 3 个矩阵。

#version 310 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
layout (std140) uniform MVPMatrix
{
    mat4 projection;
    mat4 view;
    mat4 model;
};
out vec2 v_texCoord;
void main()
{
    gl_Position = projection * view * model * a_position;
    v_texCoord = a_texCoord;
}

设置 uniform 块的绑定点为 0 ,生成一个 UBO 。


GLuint uniformBlockIndex = glGetUniformBlockIndex(m_ProgramObj, "MVPMatrix");
glUniformBlockBinding(m_ProgramObj, uniformBlockIndex, 0);
glGenBuffers(1, &m_UboId);
glBindBuffer(GL_UNIFORM_BUFFER, m_UboId);
glBufferData(GL_UNIFORM_BUFFER, 3 * sizeof(glm::mat4), nullptr, GL_STATIC_DRAW);

绘制的时候更新 Uniform Buffer 的数据,更新三个矩阵的数据,注意偏移量。

glBindBuffer(GL_UNIFORM_BUFFER, m_UboId);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), &m_ProjectionMatrix[0][0]);
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), &m_ViewMatrix[0][0]);
glBufferSubData(GL_UNIFORM_BUFFER, 2 *sizeof(glm::mat4), sizeof(glm::mat4), &m_ModelMatrix[0][0]);
glBindBuffer(GL_UNIFORM_BUFFER, 0);

十二.TBO

纹理缓冲区对象,即 TBO(Texture Buffer Object),是 OpenGL ES 3.2 引入的概念,因此在使用时首先要检查 OpenGL ES 的版本,Android 方面需要保证 API >= 24 。

TBO 需要配合缓冲区纹理(Buffer Texture)一起使用,Buffer Texture 是一种一维纹理,其存储数据来自纹理缓冲区对象(TBO),用于允许着色器访问由缓冲区对象管理的大型内存表。

在 GLSL 中,只能使用 texelFetch 函数访问缓冲区纹理,缓冲区纹理的采样器类型为 samplerBuffer 。

生成一个 TBO 的方式跟 VBO 类似,只需要绑定到 GL_TEXTURE_BUFFER ,而生成缓冲区纹理的方式与普通的 2D 纹理一样。

//生成一个 Buffer Texture
glGenTextures(1, &m_TboTexId);

float *bigData = new float[BIG_DATA_SIZE];
for (int i = 0; i < BIG_DATA_SIZE; ++i) {
    bigData[i] = i * 1.0f;
}

//生成一个 TBO ,并将一个大的数组上传至 TBO
glGenBuffers(1, &m_TboId);
glBindBuffer(GL_TEXTURE_BUFFER, m_TboId);
glBufferData(GL_TEXTURE_BUFFER, sizeof(float) * BIG_DATA_SIZE,       bigData, GL_STATIC_DRAW);
delete [] bigData;

使用纹理缓冲区的片段着色器,需要引入扩展 texture buffer ,注意版本声明为 #version 320 es


#version 320 es
#extension GL_EXT_texture_buffer : require

in mediump vec2 v_texCoord;
layout(location = 0) out mediump  vec4 outColor;
uniform mediump samplerBuffer u_buffer_tex;
uniform mediump sampler2D u_2d_texture;
uniform mediump int u_BufferSize;

void main()
{
    mediump int index = int((v_texCoord.x +v_texCoord.y) /2.0 * float(u_BufferSize - 1));
    mediump float value = texelFetch(u_buffer_tex, index).x;
    mediump vec4 lightColor = vec4(vec3(vec2(value / float(u_BufferSize - 1)), 0.0), 1.0);
    outColor = texture(u_2d_texture, v_texCoord) * lightColor;
}

绘制时如何使用缓冲区纹理和 TBO

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_BUFFER, m_TboTexId);
glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_TboId);
GLUtils::setInt(m_ProgramObj, "u_buffer_tex", 0);

十三.猜你喜欢

  1. OpenGL ES 简介
  2. OpenGL ES 版本介绍
  3. OpenGL ES 2.0 和 3.0区别
  4. OpenGL ES 名词解释(一)
  5. OpenGL ES 名词解释(二)

ChatGPT 3.5 国内中文镜像站免费使用啦
© 版权声明
THE END
喜欢就支持一下吧
点赞3 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容