目录
零基础 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 名词解释一》中已经讲解了着色器渲染等相关知识,本篇文章着重讲解坐标系和矩阵相关内容;
屏幕坐标系 的 左下点(0, 1),右下角(1,1) , 左上角(0, 0) , 右上角(1 , 0)

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

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

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

很多人有一个误解:认为 OpenGL ES 纹理原点在左上角,因为如果绘制时纹理坐标设在左下角,绘制的图像就是上下倒立;而纹理坐标设制在左上角显示正常;
原因:图像默认的原点在左上角,而 OpenGL ES 纹理读取数据或者 FBO 读取数据时都是以左下角开始,所以图像才会出现上下倒立的现象;
解决办法:
关于方案三:将图片上下颠倒可以使用 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 混合技术,可以实现出最眩目的火光、烟雾、阴影、动态光源等等一切你可以想象的出来的半透明效果。

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

旋转过程涉及到弧度与角度的转化:
弧度转角度:角度 = 弧度 * (180.0f / PI)
角度转弧度:弧度 = 角度 * (PI / 180.0f)

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

矩阵组合顺序 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 效果.

正交投影产生的效果无论你离物体多远多近,都不会产生近大远小的效果,大致如下图,视点作为观察点,视椎体由前后左右上下 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);
透视投影会产生近大远小的效果,正投影就是没有 3D 效果的投影方式,用于显示 2D 效果;透视投影就是有 3D 效果的投影方式,用于显示 3D 效果.产生的视椎体如下图:

透视投影也有响应的函数产生投影矩阵:
Matrix.frustumM(float[] m, int offset, float left,
float right, float bottom, float top,
float near, float far);
经过上述的讲解,我们要完成 4 个空间转换,需要用到了 3 个转换矩阵:
从局部空间转换到世界空间,我们需要用到模型矩阵 ModeMatrix ,这个矩阵就是我们通常对物体进行 translate 、rorate 换后产生的矩阵
从世界空间到观察空间,我们需要用到观察矩阵 ViewMatrix ,这个矩阵可以 setLookAt 方法帮我们生成
从观察空间到裁剪空间,我们可以用到投影矩阵 ProjectMatrix,使用 ortho 、frustuM 还有 perspectiveM 方法产生投影矩阵
最后以上几个坐标依次左乘我们的定义的坐标 Position 就可以得到归一化坐标了
所以,总结出来的公式
//注意顺序
gl_Position = ProjectMatrix * ViewMatrix * ModeMatrix * g_Position ;

缓冲区就是显存,也被叫做帧缓存,它的作用是用来存储显卡芯片处理过或者即将提取的渲染数据。如同计算机的内存一样,显存是用来存储要处理的图形信息的部件。
最终”存活”下来的像素需要被显示到屏幕上,但是显示屏幕之前,这些像素是会被先提交在帧缓冲区的。帧缓存区的每一存储单元对应屏幕上的一个像素,整个帧缓存区对应一帧图像。
在下一个刷新频率到来时,视频控制器会把帧缓冲区内的内容映射到屏幕上。一般采用双缓冲机制,存在两个帧缓冲区。
VAO (顶点数组对象:Vertex Array Object)是指顶点数组对象,主要用于管理 VBO 或 EBO ,减少 glBindBuffer 、glEnableVertexAttribArray、 glVertexAttribPointer 这些调用操作,高效地实现在顶点数组配置之间切换。
OpenGL 2.0 有 VBO,没有 VAO,VAO 是 OpenGL 3.0 才开始支持的,并且在 OpenGL 3.0 中,强制要求绑定一个 VAO 才能开始绘制。
VBO(顶点缓冲区对象:Vertex Buffer Object)是指把顶点数据保存在显存中,绘制时直接从显存中取数据,减少了数据传输的开销,因为顶点数据多了,就是坐标的数据多了很多的很多组,切换的时候很麻烦,就出现了这个 VAO,绑定对应的顶点数据
OpenGL 2.0 有 VBO,没有 VAO,VAO 是 OpenGL 3.0 才开始支持的,并且在 OpenGL 3.0 中,强制要求绑定一个 VAO 才能开始绘制。
**PBO (Pixel Buffer Object)是 OpenGL ES 3.0 的概念(OpenGL 2.0 不支持 PBO ,3.0 支持 PBO),称为像素缓冲区对象,**主要被用于异步像素传输操作。PBO 仅用于执行像素传输,不连接到纹理,且与 FBO (帧缓冲区对象)无关。PBO 设计的目的就是快速地向显卡传输数据,或者从显卡读取数据,我们可以使用它更加高效的读取屏幕数据。

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

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

FBO 是一个容器,自身不能用于渲染,需要与一些可渲染的缓冲区绑定在一起,像纹理或者渲染缓冲区。,它仅且提供了 3 个附着(Attachment),分别是颜色附着、深度附着和模板附着。
**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(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);
本文由博客 - 猿说编程 猿说编程 发布!
我正在阅读SandiMetz的POODR,并且遇到了一个我不太了解的编码原则。这是代码:classBicycleattr_reader:size,:chain,:tire_sizedefinitialize(args={})@size=args[:size]||1@chain=args[:chain]||2@tire_size=args[:tire_size]||3post_initialize(args)endendclassMountainBike此代码将为其各自的属性输出1,2,3,4,5。我不明白的是查找方法。当一辆山地自行车被实例化时,因为它没有自己的initialize方法
如thisquestion,当在其自己的赋值中使用未定义的局部变量时,它的计算结果为nil。x=x#=>nil但是当局部变量的名称与现有的方法名称冲突时,就比较棘手了。为什么下面的最后一个示例返回nil?{}.instance_eval{a=keys}#=>[]{}.instance_eval{keys=self.keys}#=>[]{}.instance_eval{keys=keys}#=>nil 最佳答案 在Ruby中,因为可以在没有显式接收器和括号的情况下调用方法,所以在局部变量引用和无接收器无参数方法调用之间存在语法歧义:f
我使用Jekyll运行博客,并认为我会解决RedcarpetMarkdown解释器,因为它是developedandusedbyGitHub.好吧,我只是碰巧遇到了一个错误,去检查问题,然后foundthis.Maintainersays,"Asyouprobablyhavenoticed(harharharhar)Idon'thavetimetomaintainRedcarpetanymore.It'snotapriorityforme(IfindMarkdownthoroughlyboring)andit'snotapriorityforGitHub,becausewenolong
我正在学习Ruby,遇到了inject。我正处于理解它的风口浪尖,但当我是那种需要真实世界的例子来学习一些东西的人时。我遇到的最常见的例子是人们使用inject来添加一个(1..10)范围的总和,我不太关心这个。这是一个任意的例子。在实际程序中我会用它做什么?我正在学习,所以我可以继续使用Rails,但我不必有一个以Web为中心的示例。我只需要一些我可以全神贯注的目标。谢谢大家。 最佳答案 inject有时可以通过它的“其他”名称reduce更好地理解。它是一个对Enumerable进行操作(迭代一次)并返回单个值的函数。它有许多有
我使用Ruby编程已经有一段时间了,现在只使用Ruby的标准MRI实现,但我一直对我经常听到的其他实现感到好奇。前几天我在读有关Rubinius的文章,这是一个用Ruby编写的Ruby解释器。我试着在不同的地方查找它,但我很难弄清楚这样的东西到底是如何工作的。我在编译器或语言编写方面从来没有太多经验,但我真的很想弄明白。一门语言究竟如何才能被自己解释?编译中是否有一个我不明白这有意义的基本步骤?有人可以像我是个白痴一样向我解释这个吗(因为无论如何这都不会太离谱) 最佳答案 它比你想象的要简单。Rubinius并非100%用Ruby编
谁能解释一下这段Ruby代码:defadd_spec_path_to(args)#:nodoc:args我看到了运算符用于连接字符串或在其他语言中用作按位运算符,但有人可以在这种情况下对其进行解释。它是以某种方式将一个空白的lamda附加到args上还是我完全错了?我还可以看到它是这样使用的:before_parts(*args)是Hash关键字?我也不确定||=是什么接线员在说。我同样对什么一无所知caller(0)[2]是。 最佳答案 我假设args是一个Array。Hash是类的名称-第一行将空哈希{}推送到argsunles
在编译型语言中,源代码由编译器转化为目标代码,不同的目标文件(如果有多个文件)由链接器链接并由加载器加载到内存中执行。如果我有一个使用解释性语言(例如ruby或python)编写的应用程序,并且如果源代码跨多个文件拆分,那么这些文件究竟何时组合在一起。换句话说,链接何时完成?解释型语言一开始就有链接器和加载器,还是解释器包揽一切?我真的很困惑,无法理解它!!谁能对此有所启发?! 最佳答案 解释型语言或多或少是可执行文件的大型配置,称为解释器。该可执行文件(例如/usr/bin/python)是实际运行的程序。然后它读取它要执行的
我打算学习Ruby。我知道这是一种解释语言。我知道编译语言最终会被翻译成机器码,但是ruby解释器是做什么的呢?我读到解释器是用C编写的,但是每一行ruby都转换为c,然后再次编译为机器代码吗?我也听说过JIT,但是如果这会增加答案的复杂性,那么您就不需要回答它了。我正在寻找的是我的Ruby代码发生了什么。 最佳答案 它将Ruby代码转换为某种更简单的“中间”表示形式(在最近的版本中,它编译为字节码)。它还会在您计算机的内存中构建一个虚拟机,模拟执行该表示的物理机。这台机器是一台物理机器的镜像,至少在合理和有用的范围内。它通
我刚刚在我的程序中的一些数字操作中发现了一个错误,我得到了一个FloatDomainError(NaN)所以我开始记录传入的数字:if(metric.is_a?(Numeric))self.metric=metricelseLOGGER.warn("metric#{metric}isnotanumber")self.metric=0end但传入的数字是NaN显然is_a?(Numeric)因为我没有收到日志警告,它会将指标传递给我的指标=方法,这是我获取FloatDomainError的地方现在,如果我错了,请纠正我,但是NaN(不是数字)的类型似乎在语义上是错误的吗??谁能给我解释一
我现在正在努力学习Ruby和RubyonRails。我正在学习LearningRails,第1版,但我很难理解其中的一些代码。我通常使用C、C++或Java工作,因此Ruby对我来说是一个很大的改变。我目前对数据库迁移器的以下代码块感到困惑:defself.upcreate_table:entriesdo|t|t.string:namet.timestampsendendt变量来自哪里?它实际上代表什么?它有点像for(i=0;i另外,:entries是在什么地方定义的?(entries是我的Controller的名称,但是这个函数怎么知道的?) 最佳答案