【问题标题】:How to pack normals into GL_INT_2_10_10_10_REV如何将法线打包到 GL_INT_2_10_10_10_REV
【发布时间】:2016-03-12 17:35:35
【问题描述】:

在我的宠物项目中,视频内存开始成为一个问题,因此我研究了各种技术来最大限度地减少内存占用。我尝试使用GL_INT_2_10_10_10_REV,但我使用我的打包方法得到了照明伪影。这些伪影似乎不是不准确的结果,因为使用标准化的char[3]short[3] 可以完美地工作。由于其他无用的填充,我更愿意使用更节省空间的GL_INT_2_10_10_10_REV

这是包装代码:

union Vec3IntPacked {
    int i32;
    struct {
        int a:2;
        int z:10;
        int y:10;
        int x:10;
    } i32f3;
};

int vec3_to_i32f3(const Vec3* v) {
    Vec3IntPacked packed;
    packed.i32f3.x = to_int(clamp(v->x, -1.0f, 1.0f) * 511);
    packed.i32f3.y = to_int(clamp(v->y, -1.0f, 1.0f) * 511);
    packed.i32f3.z = to_int(clamp(v->z, -1.0f, 1.0f) * 511);
    return packed.i32;
} // NOTE: to_int is a static_cast

如果我正确阅读了spec(第 10.3.8 节,“打包顶点数据格式”和 2.1 和 2.2 中的转换规则),这应该可以工作,但它不会。

我还应该注意,上面的代码已经在多个操作系统上进行了测试(虽然都是 64 位,但int 仍然应该是 32 位)和显卡供应商检查它是否与驱动程序相关的问题。

此外,使用了 OpenGL 3.3 核心配置文件

顶点结构组成如下:

struct BasicVertex {
     float position[3];
     unsigned short uv[2];
     int normal;
     int tangent;
     int bitangent;
} // resulting in a 4-byte aligned 28 byte structure

希望我提供了足够的信息,并且有人可以阐明如何将法线正确打包到 GL_INT_2_10_10_10_REV

【问题讨论】:

  • 逐顶点内存真的会解决 VRAM 问题吗?通常处理渲染目标、MSAA、纹理压缩等使用的数据格式会更有效。
  • 我应该更清楚一点,但除了原始内存要求之外,我将大量数据流式传输到 GPU 以最小化这种流量是最终目标。很抱歉忽略了这一点,我认为我的确切动机与问题无关。
  • 不是,我只是想让您知道优化顶点缓冲区效率并不是最小化带宽或存储需求的最有效方法。当地表以下有一公里的冰层属于像素操作时,你有点像在冰山一角。

标签: opengl


【解决方案1】:

位域声明中的顺序看起来不正确。根据规范文档(3.3 规范第 32 页的“2.8.2 打包顶点数据格式”部分),每个组件的位范围为:

x: bits 0-9
y: bits 10-19
z: bits 20-29
w: bits 30-31

经过一番搜索,看起来位域中的位顺序不是 C 标准定义的。参见例如Which end of a bit field is the most significant bit?

我见过的编译器通常使用从最低到最高的位顺序。例如,微软为他们的编译器定义了这个:

位域从最低有效位到最高有效位分配在一个整数内。

如果您依赖使用具有此顺序的编译器,您的声明应如下所示:

union Vec3IntPacked {
    int i32;
    struct {
        int x:10;
        int y:10;
        int z:10;
        int w:2;
    } i32f3;
};

为了保证完全可移植性,您将使用移位运算符来构建值,而根本不使用位域。

根据您在顶点着色器中声明和使用属性的方式,您可能还需要确保将w 组件设置为1。当然,如果您不使用w 组件顶点着色器,这不是必需的。

【讨论】:

    【解决方案2】:

    我只是把它留在这里,因为我很难让它发挥作用,而且 StackOverflow 上没有完整的答案。 Reto Koradi 关于字节/位排序(OpenGL wiki 也显示布局)和使用移位是正确的,但您仍然需要正确到达那里...... 示例代码(以及 StackOverflow 上的 other questions)似乎是 rely on undefined behaviour,它对我不起作用。什么对我有用(对于 OpenGL )是

    inline uint32_t Pack_INT_2_10_10_10_REV(float x, float y, float z, float w)
    {
        const uint32_t xs = x < 0;
        const uint32_t ys = y < 0;
        const uint32_t zs = z < 0;
        const uint32_t ws = w < 0;
        uint32_t vi =
            ws << 31 | ((uint32_t)(w + (ws << 1)) & 1) << 30 |
            zs << 29 | ((uint32_t)(z * 511 + (zs << 9)) & 511) << 20 |
            ys << 19 | ((uint32_t)(y * 511 + (ys << 9)) & 511) << 10 |
            xs << 9  | ((uint32_t)(x * 511 + (xs << 9)) & 511);
        return vi;
    }
    

    我找到了here。对于法线,只需省略“w”部分。如果您有更快/更简单的方法,我很想知道。要设置属性指针,请确保使用

    glVertexAttribPointer(1, 4, GL_INT_2_10_10_10_REV, GL_TRUE, stride, dataPointer);
    

    并且您的正常数据作为 vec4 到达您的着色器,映射到 [-1,1]。如果您不需要 w 组件,您也可以方便地使用 vec3,而 OpenGL 只会为您提供 xyz,因此您可能根本不需要更改任何着色器代码。一些答案表明您必须使用“glVertexAttribIPointer”,但这是错误的。
    请注意,从 OpenGL 4.2 开始从浮点到压缩格式 was changed 的映射,因此转换是不同的,但更容易。这也是supported in OpenGL ES 3.0 及以上的顶点格式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-03
      • 1970-01-01
      • 1970-01-01
      • 2013-08-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多