我应该在统一缓冲区或着色器存储缓冲区对象中使用`vec3`吗?

Nic*_*las 28 opengl opengl-es glsl vulkan

vec3类型是一个非常好的类型.它只需要3个浮点数,我的数据只需要3个浮点数.我想在UBO和/或SSBO的结构中使用一个:

layout(std140) uniform UBO
{
  vec4 data1;
  vec3 data2;
  float data3;
};

layout(std430) buffer SSBO
{
  vec4 data1;
  vec3 data2;
  float data3;
};
Run Code Online (Sandbox Code Playgroud)

然后,在我的C或C++代码中,我可以这样做来创建匹配的数据结构:

struct UBO
{
  vector4 data1;
  vector3 data2;
  float data3;
};

struct SSBO
{
  vector4 data1;
  vector3 data2;
  float data3;
};
Run Code Online (Sandbox Code Playgroud)

这是一个好主意吗?

Nic*_*las 39

没有!永远不要这样做!

在声明UBO/SSBO时,假装不存在所有3元素向量和矩阵类型.假设唯一的类型是标量,2和4元素向量(和矩阵).如果你这样做,你将为自己节省很多悲伤.

如果你想要一个vec3 +一个浮点数的效果,那么你应该手动打包它:

layout(std140) uniform UBO
{
  vec4 data1;
  vec4 data2and3;
};
Run Code Online (Sandbox Code Playgroud)

是的,你必须data2and3.w用来获得另一个价值.处理它.

如果你想要vec3s的数组,那么使它们成为vec4s的数组.对于使用3元素向量的矩阵也是如此.从你的SSBO/UBO中消除3元素载体的整个概念; 从长远来看,你会好得多.

您应该避免以下两个原因vec3:

它不会做C/C++的功能

如果使用std140布局,那么您可能希望在C或C++中定义与GLSL中的定义匹配的数据结构.这使得两者之间的混合和匹配变得容易.和std140布局使得它至少可以在大多数情况下做到这一点.但是当它涉及到vec3s 时,它的布局规则与C和C++编译器的通常布局规则不匹配.

考虑以下vec3类型的C++定义:

struct vec3a { float a[3]; };
struct vec3f { float x, y, z; };
Run Code Online (Sandbox Code Playgroud)

这两种都是完全合法的类型.在sizeof这些类型的布局将匹配的大小和布局std140要求.但它与std140强加的对齐行为不匹配.

考虑一下:

//GLSL
layout(std140) uniform Block
{
    vec3 a;
    vec3 b;
} block;

//C++
struct Block_a
{
    vec3a a;
    vec3a b;
};

struct Block_f
{
    vec3f a;
    vec3f b;
};
Run Code Online (Sandbox Code Playgroud)

在大多数C++编译器,sizeof两者Block_aBlock_f会24.这意味着,offsetof b将是12.

但是,在std140布局中,vec3始终与4个字对齐.因此,Block.b将有16的偏移量.

现在,您可以尝试使用C++ 11的alignas功能(或C11的类似_Alignas功能)来解决这个问题:

struct alignas(16) vec3a_16 { float a[3]; };
struct alignas(16) vec3f_16 { float x, y, z; };

struct Block_a
{
    vec3a_16 a;
    vec3a_16 b;
};

struct Block_f
{
    vec3f_16 a;
    vec3f_16 b;
};
Run Code Online (Sandbox Code Playgroud)

如果编译器支持16字节对齐,这将起作用.或者至少,它将适用于Block_aBlock_f.

但它不会在这种情况下工作:

//GLSL
layout(std140) Block2
{
    vec3 a;
    float b;
} block2;

//C++
struct Block2_a
{
    vec3a_16 a;
    float b;
};

struct Block2_f
{
    vec3f_16 a;
    float b;
};
Run Code Online (Sandbox Code Playgroud)

根据规则std140,每个vec3必须 16字节边界开始.但是vec3消耗 16个字节的存储空间; 它只消耗12.并且因为float可以从4字节边界开始,a vec3后跟a float将占用16个字节.

但是C++对齐的规则不允许这样的事情.如果类型与X字节边界对齐,则使用该类型将消耗X个字节的倍数.

因此匹配std140的布局要求您根据其使用位置选择一种类型.如果它后面是a float,你必须使用vec3a; 如果它后跟一些超过4字节对齐的类型,你必须使用vec3a_16.

或者你可以不在vec3着色器中使用s并避免所有这些增加的复杂性.

请注意,alignas(8)基于 - vec2不会有这个问题.C/C++也不会使用正确的对齐说明符来构造和数组(尽管较小类型的数组也有自己的问题).只有在使用裸体时才会出现此问题vec3.

实施支持模糊

即使你做的一切都正确,已经知道实现错误地实现了vec3奇怪的布局规则.一些实现有效地将C++对齐规则强加给GLSL.因此,如果您使用a vec3,它会像C++一样对待16字节对齐类型.在这些实现中,a vec3后跟a float将像a vec4后跟a 一样工作float.

是的,这是实施者的错.但由于您无法修复实现,因此您必须解决它.而最合理的方法是vec3完全避免.

请注意,对于Vulkan,SDK的GLSL编译器可以正确使用,因此您无需为此担心.

  • 我在阅读这篇文章时学到的重要事情:规范允许 `float` 紧跟在内存中的 `vec3` 之后,因此它们总共占用 16 个字节。就对齐而言,这似乎是 `vec3` 与 `vec4` 的主要(如果不是唯一的话)区别特征。即 size != base 对齐仅适用于具有三个不是数组元素的组件的向量。 (3认同)
  • @krOoze:我仔细研究了所有这些,发现你是对的。所以我改变了避免它们的理由。 (2认同)
  • @AzP:关于布局规则的*only*thing`std430`变化是标量和scalars和双元素向量的结构的基本对齐.它没有改变`vec3`s,因为它们的基本对齐始终是`vec4`. (2认同)
  • @Tom:该答案中的所有示例都是正确的,但引用的文本并非来自 OpenGL 规范;这是一本书上的。那句话是不正确的。规范指出,对于 std430,数组和结构的舍入规则不适用。但 vec3 问题*不是*来自舍入规则;它的对齐不是基于数组和结构的舍入。vec3 的基本对齐直接指定为 16 字节。 (2认同)