且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2023-11-09 22:16:04

否! 永远不要这样做!

NO! Never do this!

在声明UBO/SSBO时,假装所有3元素向量类型都不存在.这包括具有3行的列主矩阵或具有3列的行主矩阵.假设唯一的类型是标量,2和4个元素向量(和矩阵).如果这样做,您将为自己节省很多悲痛.

When declaring UBOs/SSBOs, pretend that all 3-element vector types don't exist. This includes column-major matrices with 3 rows or row-major matrices with 3 columns. Pretend that the only types are scalars, 2, and 4 element vectors (and matrices). You will save yourself a very great deal of grief if you do so.

如果您想要vec3 +浮子的效果,则应该手动包装它:

If you want the effect of a vec3 + a float, then you should pack it manually:

layout(std140) uniform UBO
{
  vec4 data1;
  vec4 data2and3;
};

是的,您必须使用data2and3.w来获取其他值.处理它.

Yes, you'll have to use data2and3.w to get the other value. Deal with it.

如果需要vec3的数组,则使它们成为vec4 s的数组.使用3元素向量的矩阵也是如此.只需消除SSBO/UBO中3元素向量的整个概念即可;从长远来看,你会变得更好.

If you want arrays of vec3s, then make them arrays of vec4s. Same goes for matrices that use 3-element vectors. Just banish the entire concept of 3-element vectors from your SSBOs/UBOs; you'll be much better off in the long run.

应避免使用vec3的原因有两个:

There are two reasons why you should avoid vec3:

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

If you use std140 layout, then you will probably want to define data structures in C or C++ that match the definition in GLSL. That makes it easy to mix&match between the two. And std140 layout makes it at least possible to do this in most cases. But its layout rules don't match the usual layout rules for C and C++ compilers when it comes to vec3s.

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

Consider the following C++ definitions for a vec3 type:

struct vec3a { float a[3]; };
struct vec3f { float x, y, z; };

这两个都是完全合法的类型.这些类型的sizeof和布局将匹配std140所需的大小和布局.但这与std140施加的对齐方式不匹配.

Both of these are perfectly legitimate types. The sizeof and layout of these types will match the size&layout that std140 requires. But it does not match the alignment behavior that std140 imposes.

考虑一下:

//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;
};

在大多数C ++编译器中,Block_aBlock_fsizeof均为24.这意味着offsetof b的值为12.

On most C++ compilers, sizeof for both Block_a and Block_f will be 24. Which means that the offsetof b will be 12.

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

In std140 layout however, vec3 is always aligned to 4 words. And therefore, Block.b will have an offset of 16.

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

Now, you could try to fix that by using C++11's alignas functionality (or C11's similar _Alignas feature):

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;
};

如果编译器支持16字节对齐,则可以使用.或者至少在Block_aBlock_f的情况下有效.

If the compiler supports 16-byte alignment, this will work. Or at least, it will work in the case of Block_a and Block_f.

但是在这种情况下它不会起作用:

But it won't work in this case:

//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;
};

根据std140的规则,每个vec3必须 start 在16字节边界上.但是vec3不会消耗 16个字节的存储空间;它仅消耗12个字节.由于float可以从4字节边界开始,因此vec3后跟float会占用16个字节.

By the rules of std140, each vec3 must start on a 16-byte boundary. But vec3 does not consume 16 bytes of storage; it only consumes 12. And since float can start on a 4-byte boundary, a vec3 followed by a float will take up 16 bytes.

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

But the rules of C++ alignment don't allow such a thing. If a type is aligned to an X byte boundary, then using that type will consume a multiple of X bytes.

因此,匹配std140的布局需要您根据使用的确切位置选择一种类型.如果后跟float,则必须使用vec3a;否则,必须使用vec3a.如果后面跟着大于4个字节对齐的某种类型,则必须使用vec3a_16.

So matching std140's layout requires that you pick a type based on exactly where it is used. If it's followed by a float, you have to use vec3a; if it's followed by some type that is more than 4 byte aligned, you have to use vec3a_16.

否则,您就不能在着色器中使用vec3并避免所有这些增加的复杂性.

Or you can just not use vec3s in your shaders and avoid all this added complexity.

请注意,基于alignas(8)vec2不会出现此问题. C/C ++结构和数组也不会使用适当的对齐说明符(尽管较小类型的数组有其自身的问题).当使用裸露的vec3时,只会出现 这个问题.

Note that an alignas(8)-based vec2 will not have this problem. Nor will C/C++ structs&arrays using the proper alignment specifier (though arrays of smaller types have their own issues). This problem only occurs when using a naked vec3.

即使您做对了所有事情,也知道一些实现会错误地实现vec3的奇数球布局规则.一些实现有效地将C ++对齐规则强加给GLSL.因此,如果使用vec3,它将像对待C ++那样对待16字节对齐类型一样.在这些实现上,在vec3后跟float的方式将与在vec4后跟float的方式一样.

Even if you do everything right, implementations have been known to incorrectly implement vec3's oddball layout rules. Some implementations effectively impose C++ alignment rules to GLSL. So if you use a vec3, it treats it like C++ would treat a 16-byte aligned type. On these implementations, a vec3 followed by a float will work like a vec4 followed by a float.

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

Yes, it's the implementers' fault. But since you can't fix the implementation, you have to work around it. And the most reasonable way to do that is to just avoid vec3 altogether.

请注意,对于Vulkan(以及使用SPIR-V的OpenGL),SDK的GLSL编译器可以正确解决此问题,因此您不必为此担心.

Note that, for Vulkan (and OpenGL using SPIR-V), the SDK's GLSL compiler gets this right, so you don't need to be worried about it for that.