16位浮点数和GL_HALF_FLOAT

时间:2014-03-05 22:23:24

标签: c++ opengl floating-point

我正在寻找/编写一个16位浮点数的C ++实现,用于OpenGL顶点缓冲区(纹理坐标,法线等)。以下是我目前的要求:

  • 必须是16位(显然)。
  • 必须能够使用GL_HALF_FLOAT上传到OpenGL顶点缓冲区。
  • 必须能够代表超过-1.0 - +1.0的数字(否则我只会使用GL_SHORT标准化)。
  • 必须能够转换为正常的32位浮点数。
  • 算术运算并不重要 - 我只关心存储。
  • 速度不是主要问题,但正确性是。

这是我到目前为止的界面:

class half
{
public:
    half(void) : data(0) {}
    half(const half& h) : data(h.data) {}
    half(const unsigned short& s) : data(s) {}
    half(const float& f) : data(fromFloat(f)) {}
    half(const double& d) : data(fromDouble(d)) {}

    inline operator const float() { return toFloat(data); }
    inline operator const double() { return toDouble(data); }

    inline const half operator=(const float& rhs) { data = fromFloat(rhs); return *this; }
    inline const half operator=(const double& rhs) { data = fromDouble(rhs); return *this; }

private:
    unsigned short data;

    static unsigned short fromFloat(float f);
    static float toFloat(short h);

    inline static unsigned short fromDouble(double d) { return fromFloat((float)d); }
    inline static double toDouble(short h) { return (double)toFloat(h); }
};

std::ostream& operator<<(std::ostream& os, half h) { os << (float)h; }
std::istream& operator>>(std::istream& is, half& h) { float f; is >> f; h = f; }

最终,班级的真正含义在于toFloat()fromFloat()函数,这是我需要帮助的。我已经能够找到很多16位浮点实现的例子,但没有一个提到它们是否可以上传到OpenGL。

将16位浮点数上传到OpenGL时,我应该注意哪些问题?是否存在专门解决这些问题的半浮动实现?

编辑:根据大众需求,以下是我的顶点数据的生成,上传和渲染方式。

以下是如何在WireCubeEntity类中定义数据:

VertexHalf vertices[8] = {
        vec3(-1.0f, -1.0f, -1.0f),
        vec3(1.0f, -1.0f, -1.0f),
        vec3(1.0f, 1.0f, -1.0f),
        vec3(-1.0f, 1.0f, -1.0f),
        vec3(-1.0f, -1.0f, 1.0f),
        vec3(1.0f, -1.0f, 1.0f),
        vec3(1.0f, 1.0f, 1.0f),
        vec3(-1.0f, 1.0f, 1.0f)
    };

    unsigned char indices[24] = {
        0, 1,
        1, 2,
        2, 3,
        3, 0,
        4, 5,
        5, 6,
        6, 7,
        7, 4,
        0, 4,
        1, 5,
        2, 6,
        3, 7
    };

    va.load(GL_LINES, VF_BASICHALF, 8, vertices, GL_UNSIGNED_BYTE, 24, indices);

其中va是VertexArray的一个实例。 va.load定义为:

MappedBuffers VertexArray::load(GLenum primitive, VertexFormat vertexFormat, unsigned int vertexCount, void* vertices,
                                                  GLenum indexFormat, unsigned int indexCount, void* indices)
{
    MappedBuffers ret;

    /* Check for invalid primitive types */
    if (primitive > GL_TRIANGLE_FAN)
    {
        error("in VertexFormat::load():\n");
        errormore("Invalid enum '%i' passed to 'primitive'.\n", primitive);
        return ret;
    }

    /* Clean up existing data */
    clean();

    /* Set up Vertex Array Object */
    glGenVertexArrays(1, &vao);
    bindArray();

    /* Create Vertex Buffer Object */
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, vertexSize(vertexFormat) * vertexCount, vertices, GL_STATIC_DRAW);
    if (!vertices) ret.vmap = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

    /* Save variables for later usage */
    prim = primitive;
    vformat = vertexFormat;
    vcount = vertexCount;

    /* If we've been given index data, handle it */
    if (indexSize(indexFormat) != 0)
    {
        glGenBuffers(1, &ibo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize(indexFormat) * indexCount, indices, GL_STATIC_DRAW);
        if (!indices) ret.imap = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);

        iformat = indexFormat;
        icount = indexCount;
    }

    /* Handle the vertex format */
    switch (vformat)
    {
    case VF_BASIC:
        /* VF_BASIC only has a position - a 3-component float vector */
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
        break;
    case VF_32:
        /* VF_32 has 3 components for position, 2 for texture coordinates, and 3 for a normal.
        Position is at offset 0, TextureCoordinate is at offset 12, and Normal is at offset 20 */
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(1);
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertexSize(VF_32), (void*)0);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, vertexSize(VF_32), (void*)12);
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, vertexSize(VF_32), (void*)20);
        break;
    case VF_BASICHALF:
        /* VF_BASICHALF is very similar to VF_BASIC, except using half-floats instead of floats. */
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_HALF_FLOAT, GL_FALSE, 0, (void*)0);
        break;
    case VF_WITHTANGENTS:
        /* VF_WITHTANGENTS is similar to VF_32, but with additional components for a Tangent. */
        /* Tangent is at offset 32 */
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(1);
        glEnableVertexAttribArray(2);
        glEnableVertexAttribArray(3);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertexSize(VF_WITHTANGENTS), (void*)0);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, vertexSize(VF_WITHTANGENTS), (void*)12);
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, vertexSize(VF_WITHTANGENTS), (void*)20);
        glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, vertexSize(VF_WITHTANGENTS), (void*)32);
        break;
    default:
        error("In VertexFormat::load():\n");
        errormore("Invalid enum '%i' passed to vertexFormat.\n", (int)vformat);
        clean();
        return MappedBuffers();
    }

    /* Unbind the vertex array */
    unbindArray();

    if (vertices) ready = true;

    return ret;
}
我知道,这是一个非常重要的功能。 MappedBuffers只是一个包含2个指针的结构,因此如果我将NULL数据传递给VertexArray::load(),我可以使用指针将数据直接从文件加载到缓冲区(可能来自另一个线程) )。 vertexSize是一个函数,它返回我传入的顶点格式的sizeof(),或者对于无效格式返回0。

VertexHalf结构是:

struct VertexHalf
{
    VertexHalf(void) {}
    VertexHalf(vec3 _pos) :x(_pos.x), y(_pos.y), z(_pos.z) {}
    VertexHalf(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}

    half x, y, z, padding;
};

最后,使用我们之前加载的VertexArray呈现数据:

void VertexArray::draw(void)
{
    if (ready == false)
        return;

    /* Bind our vertex array */
    bindArray();

    /* Draw it's contents */
    if (ibo == 0)
        glDrawArrays(prim, 0, vcount);
    else
        glDrawElements(prim, icount, iformat, NULL);

    unbindArray();
}

3 个答案:

答案 0 :(得分:11)

编辑 :VertexHalf结构中出现最明显的错误。你有一个填充元素。然而,当你指定你的glVertexAttribPointer时,你在步幅中指定一个0表示它是紧密打包的。因此,您可以更改VertexHalf以删除填充或更改glVertexAttribPointer以使其具有8个字节的步幅。

我使用以下类与DirectX for float16支持,它完美地工作。

Float16.h:

#ifndef THE__FLOAT_16_H_
#define THE__FLOAT_16_H_

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

extern short FloatToFloat16( float value );
extern float Float16ToFloat( short value );

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

class Float16
{
protected:
    short mValue;
public:
    Float16();
    Float16( float value );
    Float16( const Float16& value );

    operator float();
    operator float() const;

    friend Float16 operator + ( const Float16& val1, const Float16& val2 );
    friend Float16 operator - ( const Float16& val1, const Float16& val2 );
    friend Float16 operator * ( const Float16& val1, const Float16& val2 );
    friend Float16 operator / ( const Float16& val1, const Float16& val2 );

    Float16& operator =( const Float16& val );
    Float16& operator +=( const Float16& val );
    Float16& operator -=( const Float16& val );
    Float16& operator *=( const Float16& val );
    Float16& operator /=( const Float16& val );
    Float16& operator -();
};

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16::Float16()
{
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16::Float16( float value )
{
    mValue  = FloatToFloat16( value );
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16::Float16( const Float16 &value )
{
    mValue  = value.mValue;
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16::operator float()
{
    return Float16ToFloat( mValue );
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16::operator float() const
{
    return Float16ToFloat( mValue );
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16& Float16::operator =( const Float16& val )
{
    mValue  = val.mValue;
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16& Float16::operator +=( const Float16& val )
{
    *this   = *this + val;
    return *this;
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16& Float16::operator -=( const Float16& val )
{
    *this   = *this - val;
    return *this;

}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16& Float16::operator *=( const Float16& val )
{
    *this   = *this * val;
    return *this;
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16& Float16::operator /=( const Float16& val )
{
    *this   = *this / val;
    return *this;
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16& Float16::operator -()
{
    *this   = Float16( -(float)*this );
    return *this;
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
/*+----+                                 Friends                                       +----+*/
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16 operator + ( const Float16& val1, const Float16& val2 )
{
    return Float16( (float)val1 + (float)val2 );
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16 operator - ( const Float16& val1, const Float16& val2 )
{
    return Float16( (float)val1 - (float)val2 );
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16 operator * ( const Float16& val1, const Float16& val2 )
{
    return Float16( (float)val1 * (float)val2 );
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

inline Float16 operator / ( const Float16& val1, const Float16& val2 )
{
    return Float16( (float)val1 / (float)val2 );
}

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/


#endif

Float16.cpp:

#include "Types/Float16.h"

//#include <d3dx9.h>

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

short FloatToFloat16( float value )
{
    short   fltInt16;
    int     fltInt32;
    memcpy( &fltInt32, &value, sizeof( float ) );
    fltInt16    =  ((fltInt32 & 0x7fffffff) >> 13) - (0x38000000 >> 13);
    fltInt16    |= ((fltInt32 & 0x80000000) >> 16);

    return fltInt16;
}

/ + ---- ---- + ---- + ---- + ---- + ---- + ---- + ---- + + - - + ---- ---- + ---- + ---- + ---- + ---- + ---- + ---- + ---- + + /

float Float16ToFloat( short fltInt16 )
{
    int fltInt32    =  ((fltInt16 & 0x8000) << 16);
    fltInt32        |= ((fltInt16 & 0x7fff) << 13) + 0x38000000;

    float fRet;
    memcpy( &fRet, &fltInt32, sizeof( float ) );
    return fRet;
 }

/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/

答案 1 :(得分:4)

GLM library支持half-float types。使用的前缀是'h',因此glm::vec3是浮点值的3元素向量,glm::hvec3是半浮点数的3元素向量。

答案 2 :(得分:0)

我只知道具有half实现的OpenEXR库。好处是half的实现具有您正在寻找的功能,甚至可以与NVIDIA CG工具包一起使用。

糟糕的是我不知道half类型是否与您使用的opengl版本开箱即用(理论上应该是这样),所以在决定之前应该做一些测试使用它。