将数据传递给像素着色器时如何避免 int->float 转换?

How to avoid int->float conversion when passing data to pixel shader?

我有一个像素着色器:

varying vec2 f_texcoord;
uniform vec4 mycolor_mult;
uniform sampler2D mytexture;
void main(void) {
    gl_FragColor = (texture2D(mytexture, f_texcoord) * mycolor_mult);
};

和对应的C++代码:

GLint m_attr = glGetUniformLocation(m_program, "mycolor_mult");
// ...
unsigned int myColor = ...; // 0xAARRGGBB format
float a = (myColor >> 24) / 255.f;
float r = ((myColor >> 16) & 0xFF) / 255.f;
float g = ((myColor >> 8) & 0xFF) / 255.f;
float b = (myColor & 0xFF) / 255.f;
glUniform4f(m_attr, r, g, b, a);

我将精灵的颜色保持为 unsigned int 并且必须将其转换为 4 个浮点数以将它们传递给着色器。

可以优化吗?我的意思是我可以不传递浮点数,而是将无符号字符作为组件传递给着色器并避免 "divide by 255" 操作吗?我应该在着色器和 C++ 代码中更改什么才能做到这一点?

这个问题有几个方面。

是否值得优化?

我同意@Nick 的评论。您很可能正在尝试优化根本不是性能关键的东西。例如,如果这段代码每帧只执行一次,那么这段代码的执行时间是绝对微不足道的。如果每帧执行 许多 次,情况可能会有所不同。使用探查器可以告诉您这段代码花费了多少时间。

你优化的对吗?

确保 glGetUniformLocation() 调用仅在链接着色器后调用一次,而不是每次设置统一时调用。否则,该调用很可能会比其余代码昂贵得多。如果您已经这样做了,从代码中还不完全清楚。

你能使用更高效的 OpenGL 调用吗?

不是真的,如果你需要在着色器中作为浮点数的值。制服没有自动格式转换,因此您不能简单地使用来自 glUniform*() 系列的不同调用。来自规范:

For all other uniform types the Uniform* command used must match the size and type of the uniform, as declared in the shader. No type conversions are done.

代码可以优化吗?

如果你真的想做micro-optimizations,你可以用乘法代替除法。在大多数 CPU 上,除法比乘法要昂贵得多。然后代码如下所示:

const float COLOR_SCALE = 1.0f / 255.f;
float a = (myColor >> 24) * COLOR_SCALE;
float r = ((myColor >> 16) & 0xFF) * COLOR_SCALE;
float g = ((myColor >> 8) & 0xFF) * COLOR_SCALE;
float b = (myColor & 0xFF) * COLOR_SCALE;

您不能指望编译器为您执行此转换,因为更改操作可能会对操作的 precision/rounding 产生影响。一些编译器有标志来启用这些类型的优化。例如参见 [​​=14=].

使用现代 OpenGL(GLSL >= 4.1),有一个 unpackUnorm4x8 GLSL 函数可以完全满足您的需求:它采用单个 32 位 uint 并从中创建一个规范化的浮点向量。您只需调整结果以匹配您的字节顺序,该函数会将最低有效字节解释为第一个通道。

uniform uint mycolor_packed;
//...
vec4 mycolor_mult=unpackUnorm4x8(mycolor_packed).bgra;

这可能是在着色器本身中进行转换的最有效方式。然而,与在 CPU.

上的每个绘制调用只执行一次相比,在 GPU 上每个片段执行一次是否更有效仍然值得怀疑。