EDITORIAL
現在、我々は常に、均一なシェーダ変数の複数の通過均一の複数を使用し、メインプログラムにこれらの変数の値を設定する必要があり、変数が複数シェーダ間で共有される場合、例えば、投影ビュー投影行列及び変換行列ビューは、次に、依然としてそれぞれ異なるシェーダ均一なため、これらの変数を設定する必要があります。このセクションでは、インターフェイスブロックを導入し、この均一なバッファ・オブジェクト(UBO)に基づいて、これらの技術は、転送や共有シェーダ変数を単純化するであろう。サンプルプログラムからのこのセクションでは、することができ、私のGitHubをダウンロードしました。
本节内容参考自:
1. www.learningopengl.com高度なGLSL
2. GLSLチュートリアル-制服のブロック
3 」のOpenGL 4.0シェーディング言語クックブック」制服ブロックと制服バッファオブジェクトを-Using
インタフェースブロック
INTERFACブロックはとして単純かつ明確な構造体ではなく、GLSLシェーダの入力、出力、ユニフォーム変数の集合、C言語の構造体と同様、いくつかの、内部に設置され、その中に含まれる他のいくつかの選択肢があります。すっきり書くようにインターフェイスブロックを使用することにより、我々は、グループとして管理変数をシェーダすることができます。
宣言フォームインタフェースブロックです。
storage_qualifier block_name
{
<define members here>
} instance_name;
前記storage_qualifier指定されたブロックは、修飾子に格納される、修飾子が出、に使用することができ、均一な、または(GLSL4.3のサポート)バッファなど、与えられた名前をblock_nameが、名前指定されたインスタンスのINSTANCE_NAME。
例えば、我々は、点光源を達成するために前処理を必要ブロックにそれらをパッケージ化するために、頂点シェーダ及びフラグメントシェーダの法線、テクスチャ座標および他の変数の間を通過するように、コードはよりコンパクトです。頂点シェーダ出力変数は、以下の形式で定義されています。
// 定义输出interface block
out VS_OUT
{
vec3 FragPos;
vec2 TextCoord;
vec3 FragNormal; }vs_out;
フラグメントシェーダでは、同じblock_nameを受け入れるために、インスタンス名が異なっていてもよく、形態は以下のように定義することができます。
// 定义输入interface block
in VS_OUT
{
vec3 FragPos;
vec2 TextCoord;
vec3 FragNormal; }fs_in;
あなたは接頭辞指定INSTANCE_NAMEのINSTANCE_NAMEを追加する必要がある場合は、これらの変数は、例えば、フラグメントシェーダで参照されています。
// 环境光成分
vec3 ambient = light.ambient * vec3(texture(material.diffuseMap, fs_in.TextCoord));
何instance_nameはありませんが指定されている一方、これは直接使用することができ、同様に変数は、グローバルかつ均一でブロックします。INSTANCE_NAMEを与えられていない場合は、制服を与えられ、繰り返さない与えられた変数名でインターフェースブロック、に注意を払う、あるいは、次の定義として再定義エラーが再定義エラーになりますが発生する必要があります。
uniform MatrixBlock
{
mat4 projection;
mat4 modelview;
};
uniform vec3 modelview; // 重定义错误 和MatrixBlock中冲突
組織の変数は、これらの変数は前に比べて分散した形で書かれている、インターフェイスブロックは、より合理的、論理明確に設定することができます。
メインプログラムは、同じ効果を達成するために、点光源を変更していない、ここで効果が与えられる以下の通りであります:
上記からわかるように、インターフェースブロックは、我々は、合理的なシェーダ変数を整理しようとしている問題を解決しません。これは、我々が言及した最初の問題です。
UBOコンセプト
シェーダ複数の変数間で共有する方法を簡単なこのセクションの冒頭で述べた第二の問題、。GLSLは、均一なバッファによって達成することができます。均一なアイデアを実現するためのバッファである:同じシェーダブロック均一(すなわち、定義された均一な修飾子を使用して、インターフェイスブロック上にある)複数の定義され、そして、対応する一様な均一ブロックオブジェクトバッファにそれらを結合するように、バッファ均一なオブジェクト実際にこれらの変数を共有する必要が格納されます。(から以下に示すようにメインプログラムと均一なバッファ・オブジェクト・シェーダの均一なブロックは、OpenGLの結合点(結合点)を介して接続され、それらの関係はGLSLがwww.learningopengl.com高度):
使用される場合、均一なブロックの各シェーダは、インデックスポイントXによる結合のOpenGLに接続された定義されたインデックスを有し、メインプログラムは、データ転送後、UBOは、対応するXに結合することが、均一なバッファ・オブジェクトを作成するために、プログラムの中で、当社の業務のデータUBOをリンクUBOおよびOpenGL上のシェーダ均一なブロックの後、我々は異なるシェーダー間で共有することができるようになります。例えば、上記行列インデックス数字は、AとBのシェーダは結合点0点を定義する、彼らは、OpenGL uboMatricesにUBOのデータを共有します。シェーダのBとA間、データライトシェーダ、異なるUBOに向けそれぞれ。
UBOの使用
在上面我们介绍了UBO的概念,下面通过实例了解UBO的实际使用。UBO的实现依赖于着色器中uniform block的定义,uniform block的内存布局四种形式:shared, packed, std140, and std430(GLSL4.3以上支持),默认是shared内存布局。本节我们重点学习shared和std140这两种内存布局形式,其他的形式可以在需要时自行参考OpenGL规范。
-
shared 默认的内存布局 采用依赖于具体实现的优化方案,但是保证在不同程序中具有相同定义的block拥有相同的布局,因此可以在不同程序之间共享。要使block能够共享必须注意block具有相同定义,同时所有成员显式指定数组的大小。同时shared保证所有成员都是激活状态,没有变量被优化掉。
-
std140 这种方式明确的指定alignment的大小,会在block中添加额外的字节来保证字节对齐,因而可以提前就计算出布局中每个变量的位移偏量,并且能够在shader之间共享;不足在于添加了额外的padding字节。稍后会介绍字节对齐和padding相关内容。
下面通过两个简单例子,来熟悉std140和默认的shared内存布局。这个例子将会在屏幕上通过4个着色器绘制4个不同颜色的立方体,在着色器之间共享的是投影矩阵和视变换矩阵,以及为了演示shared layout而添加的混合颜色的示例。
layout std140
字节对齐的概念
字节对齐的一个经典案例就是C语言中的结构体变量,例如下面的结构体:
struct StructExample {
char c;
int i;
short s;
};
你估计它占用内存大小多少字节? 假设在int 占用4字节,short占用2个字节,那么整体大小等于 1+ 4+ 2 = 7字节吗?
答案是否定的。在Windows平台测试,当int占用4个字节,short占用2个字节是,实际占用大小为12个字节,这12个字节是怎么算出来的呢? 就是用到了字节补齐的概念。实际上上述结构体的内存布局为:
struct StructExample {
char c; // 0 bytes offset, 3 bytes padding
int i; // 4 bytes offset short s; // 8 bytes offset, 2 bytes padding }; // End of 12 bytes
内存布局如下图所示:
字节对齐的一个重要原因是为了使机器访问更迅速。例如在32字长的地址的机器中,每次读取4个字节数据,所以将字节对齐到上述地址 0x0000,0x0004和0x0008, 0x000C将使读取更加迅速。否则例如上面结构体中的int i将跨越两个字长(0x0000和0x0004),需要两次读取操作,影响效率。当然关于为什么使用字节对齐的更详细分析,感兴趣地可以参考SO Purpose of memory alignment。
关于字节对齐,我们需要知道的几个要点就是(参考自wiki Data structure alignment):
-
一个内存地址,当它是n字节的倍数时,称之为n字节对齐,这里n字节是2的整数幂。
-
每种数据类型都有它自己的字节对齐要求(alignment),例如char是1字节,int一般为4字节,float为4字节对齐,8字节的long则是8字节对齐。
-
当变量的字节没有对齐时,将额外填充字节(padding)来使之对齐。
上面的结构体中,int变量i需要4字节对齐,因此在char后面填充了3个字节,同时结构体变量整体大小需要满足最长alignment成员的字节对齐,因此在short后面补充了2个字节,总计达到12字节。
关于字节对齐这个概念,介绍到这里,希望了解更多地可以参考The Lost Art of C Structure Packing。
std140的字节对齐
std140内存布局同样存在字节对齐的概念,你可以参考官方文档获取完整描述。常用标量int,float,bool要求4字节对齐,4字节也被作为一个基础值N,这里列举几个常用的结构的字节对齐要求:
类型 | 对齐基数(base alignment) |
---|---|
标量,例如 int bool | 每个标量对齐基数为N |
vector | 2N 或者 4N, vec3的基数为4N. |
标量或者vector的数组 | 每个元素的基数等于vec4的基数. |
矩阵 | 以列向量存储, 列向量基数等于vec4的基数. |
结构体 | 元素按之前规则,同时整体大小填充为vec4的对齐基数 |
例如一个复杂的uniform block定义为:
layout (std140) uniform ExampleBlock
{
// // base alignment // aligned offset
float value; // 4 // 0 vec3 vector; // 16 // 16 (must be multiple of 16 so 4->16) mat4 matrix; // 16 // 32 (column 0) // 16 // 48 (column 1) // 16 // 64 (column 2) // 16 // 80 (column 3) float values[3]; // 16 // 96 (values[0]) // 16 // 112 (values[1]) // 16 // 128 (values[2]) bool boolean; // 4 // 144 int integer; // 4 // 148 };
上面的注释给出了它的字节对齐,其中填充了不少字节,可以根据上面表中给定的对齐基数提前计算出来,在主程序中可以设置这个UBO的变量:
GLuint exampleUBOId;
glGenBuffers(1, &exampleUBOId);
glBindBuffer(GL_UNIFORM_BUFFER, exampleUBOId);
glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_DYNAMIC_DRAW); // 预分配空间 大小可以提前根据alignment计算
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferBase(GL_UNIFORM_BUFFER, 1, exampleUBOId); // 绑定点为1 // step4 只更新一部分值 glBindBuffer(GL_UNIFORM_BUFFER, exampleUBOId); GLint b = true; // 布尔变量在GLSL中用4字节表示 因此这里用int存储 glBufferSubData(GL_UNIFORM_BUFFER, 144, 4, &b); // offset可以根据UBO中alignment提前计算 glBindBuffer(GL_UNIFORM_BUFFER, 0);
说明: 上面最终计算出的大小为152,UBO整体不必满足vec4的字节对齐要求。152 /4 = 38,满足N的对齐要求即可。
从上面可以看到,当成员变量较多时,这种手动计算offset的方法比较笨拙,可以事先编写一个自动计算的函数库,以减轻工作负担。
std140的简单例子
下面通过一个简单例子来熟悉UBO的使用。
Step1: 首先我们在顶点着色器中定义uniform block如下:
#version 330 core
layout(location = 0) in vec3 position; layout(location = 1) in vec3 normal; uniform mat4 model; // 因为模型变换矩阵一般不能共享 所以单独列出来 // 定义UBO layout (std140) uniform Matrices { mat4 projection; mat4 view; }; // 这里没有定义instance name,则在使用时不需要指定instance name void main() { gl_Position = projection * view * model * vec4(position, 1.0); }
Step2 在主程序中设置着色器的uniform block索引指向到绑定点0:
// step1 获取shader中 uniform buffer 的索引
GLuint redShaderIndex = glGetUniformBlockIndex(redShader.programId, "Matrices");
GLuint greeShaderIndex = glGetUniformBlockIndex(greenShader.programId, "Matrices");
...
// step2 设置shader中 uniform buffer 的索引到指定绑定点
glUniformBlockBinding(redShader.programId, redShaderIndex, 0); // 绑定点为0 glUniformBlockBinding(greenShader.programId, greeShaderIndex, 0); ...
这里为了演示代码中重复写出了4个着色器,实际中可以通过vector装入这4个着色器简化代码。
Step3: 创建UBO,并绑定到绑定点0
我们需要传入2个mat4矩阵,由于mat4中每列的vec4对齐,因此两个mat4中没有额外的padding,大小即为2*sizeof(mat4)。
GLuint UBOId;
glGenBuffers(1, &UBOId);
glBindBuffer(GL_UNIFORM_BUFFER, UBOId);
glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_DYNAMIC_DRAW); // 预分配空间
glBindBuffer(GL_UNIFORM_BUFFER, 0); glBindBufferRange(GL_UNIFORM_BUFFER, 0, UBOId, 0, 2 * sizeof(glm::mat4)); // 绑定点为0
Step4: 更新UBO中的数据
这里使用前面介绍的glBufferSubData更新UBO中数据,例如更新视变换矩阵如下:
glm::mat4 view = camera.getViewMatrix(); // 视变换矩阵
glBindBuffer(GL_UNIFORM_BUFFER, UBOId);
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view));
glBindBuffer(GL_UNIFORM_BUFFER, 0);
通过上面的步骤,我们完成了着色器中unifrom block和UBO的连接,实现了投影矩阵和视变换矩阵在4个着色器之间的共享,绘制4个立方体如下图所示:
验证ExampleBlock
这里在着色器中添加一段代码测试下上面那个复杂的ExampleBlock的内容,我们在主程序中设置boolean变量为true,在着色器中添加一个判断,如果boolean为true,则输出白色立方体:
if(boolean)
{
color = vec4(1.0, 1.0, 1.0, 1.0); }
最终显示获得了4个全是白色的立方体,效果如下:
这就验证了上述计算出那个复杂ExampleBlock的大小为152,boolean变量位移偏量为144是正确的。
layout shared
同std140内存布局方式不一样,shared方式的内存布局依赖于具体实现,因此我们无法提前根据某种字节对齐规范计算出UBO中变量的位移偏量和整体大小,因此在使用shared方式时,我们需要多次利用OpenGL的函数来查询UBO的信息。
这里在着色器中定义一个用于混合颜色的uniform block:
#version 330 core
// 使用默认shared方式的UBO
uniform mixColorSettings {
vec4 anotherColor;
float mixValue; }; out vec4 color; void main() { color = mix(vec4(0.0, 0.0, 1.0, 1.0), anotherColor, mixValue); }
在出程序中首先查询UBO整体大小,预分配空间:
GLuint colorUBOId;
glGenBuffers(1, &colorUBOId);
glBindBuffer(GL_UNIFORM_BUFFER, colorUBOId);
// 获取UBO大小 因为定义相同 只需要在一个shader中获取大小即可
GLint blockSize;
glGetActiveUniformBlockiv(redShader.programId, redShaderIndex,
GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
glBufferData(GL_UNIFORM_BUFFER, blockSize, NULL, GL_DYNAMIC_DRAW); // 预分配空间
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferBase(GL_UNIFORM_BUFFER, 1, colorUBOId); // 绑定点为1
然后,通过查询UBO中成员变量的索引和位移偏量来设置变量值:
// 通过查询获取uniform buffer中各个变量的索引和位移偏量
const GLchar* names[] = {
"anotherColor", "mixValue"
};
GLuint indices[2]; glGetUniformIndices(redShader.programId, 2, names, indices); GLint offset[2]; glGetActiveUniformsiv(redShader.programId, 2, indices, GL_UNIFORM_OFFSET, offset); // 使用获取的位移偏量更新数据 glm::vec4 anotherColor = glm::vec4(0.0f, 1.0f, 1.0f, 1.0f); GLfloat mixValue = 0.5f; glBindBuffer(GL_UNIFORM_BUFFER, colorUBOId); glBufferSubData(GL_UNIFORM_BUFFER, offset[0], sizeof(glm::vec4), glm::value_ptr(anotherColor)); glBufferSubData(GL_UNIFORM_BUFFER, offset[1], sizeof(glm::vec4), &mixValue); glBindBuffer(GL_UNIFORM_BUFFER, 0);
和上面std140定义的uniform block一起工作,产生的混合颜色效果如下图所示:
从上面可以看到,使用shared布局时,当变量较多时,这种查询成员变量索引和位移偏量的工作显得比较麻烦。
最后的说明
このインターフェイスブロック概念のセグメント、ならびにUBOの二つのメモリレイアウト。このより限定されたセクションでは、特定の機能のいくつかは、この展開プレゼンテーションで使用されていない、自分の必要とするかもしれない参考OpenGLのドキュメントを。一方、オフセットのUBOのstd140の複雑なレイアウトを計算するため、この論文の方法、および方法はUBO全体のサイズ、インデックスを取得し、共有モードのクエリを使用して相殺するために、我々はマスターにしようとする必要があります。