简体   繁体   中英

16-bit floats and GL_HALF_FLOAT

I'm looking for/writing a C++ implementation of a 16-bit floating point number to use with OpenGL vertex buffers (texture coordinates, normals, etc). Here are my requirements so far:

  • Must be 16-bit (obviously).
  • Must be able to be uploaded to an OpenGL vertex buffer using GL_HALF_FLOAT.
  • Must be able to represent numbers beyond -1.0 - +1.0 (Otherwise I would just use GL_SHORT normalized).
  • Must be able to convert to and from a normal 32-bit float.
  • Arithmetic operations do not matter - I only care about storage.
  • Speed is not a primary concern, but correctness is.

Here's what I have so far for an interface:

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

Ultimately, the real meat of the class lies in the toFloat() and fromFloat() functions, which is what I need help with. I've been able to find quite a few examples of 16-bit float implementations, but none of them mention whether or not they can be uploaded to OpenGL or not.

What are some concerns I should be aware of when uploading a 16-bit float to OpenGL? Is there a half-float implementation that specifically addresses these concerns?

EDIT: By popular demand, here is how my vertex data is generated, uploaded, and rendered.

Here is how the data is defined within the WireCubeEntity class:

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

where va is an instance of VertexArray. va.load is defined as:

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

I'ts a pretty heavy function, I know. MappedBuffers is simply a struct that contains 2 pointers so that if I pass NULL data into VertexArray::load() , I can use the pointers to load the data directly from file into buffers (possibly from another thread). vertexSize is a function that returns the sizeof() of whichever vertex format I pass in, or 0 for an invalid format.

The VertexHalf struct is:

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

And finally the data is rendered using the VertexArray we loaded earlier:

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

Edit : The most obvious error appears in your VertexHalf structure. You have an element of padding. Yet when you specify your glVertexAttribPointer you specify a 0 in the stride which indicates it is tightly packed. So you can either change VertexHalf to remove the padding or change your glVertexAttribPointer to have a stride of 8 bytes.

I use the following class with DirectX for float16 support and it works perfectly.

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

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

The GLM library supports half-float types . The prefix used is 'h' so where glm::vec3 is a 3 element vector of floating points values, glm::hvec3 is a 3 element vector of half-floats.

I only know of OpenEXR library that has an implementation for half . The good thing is that the implementation of half has the functions you are looking for, and it even works with NVIDIA CG toolkit.

The bad thing is that i don't know if the half type is compatible out of the box with the opengl version you use (in theory it should be), so you should do some testing before you decide to use it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM