我正在寻找/编写一个 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()
and 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);
where 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();
}