目录

  • 一.前言
  • 二.坐标系
    • 1.屏幕坐标系
    • 2.纹理坐标系
    • 3.顶点坐标系
    • 4.图像坐标系
  • 三.混合
  • 四.变换矩阵
    • 1.平移
    • 2.旋转
    • 3.缩放
    • 4.矩阵组合顺序
  • 五.投影矩阵
    • 1.正交投影
    • 2.透视投影
    • 3.总结
  • 六.帧缓冲区帧
  • 七.VAO
  • 八.VBO
  • 九.PBO  
  • 十.FBO
  • 十一.UBO
  • 十二.TBO
  • 十三.猜你喜欢

零基础 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)

2.纹理坐标系

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

3.顶点坐标系

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

4.图像坐标系

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

很多人有一个误解:认为 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 混合技术,可以实现出最眩目的火光、烟雾、阴影、动态光源等等一切你可以想象的出来的半透明效果。

四.变换矩阵1.平移

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

2.旋转

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

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

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

3.缩放

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

4.矩阵组合顺序

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

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

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

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

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

#include  // glm::vec3#include  // glm::vec4#include  // glm::mat4#include  // glm::translate, glm::rotate, glm::scale#include  // glm::perspective#include  // glm::piglm::mat4 camera(float Translate, glm::vec2 const& Rotate){glm::mat4 Projection = glm::perspective(glm::pi() * 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 效果.

1.正交投影

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

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

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

正交投影矩阵,由 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 效果.产生的视椎体如下图:

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

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 ;

六.帧缓冲区帧

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

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

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

七.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 交替配合使用。

十.FBO

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

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

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

十一.UBO

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

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

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

#version 310 eslayout(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 TextureglGenTextures(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 ,并将一个大的数组上传至 TBOglGenBuffers(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 : requirein 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 名词解释(二)

本文由博客 – 猿说编程 猿说编程 发布!