我正在研究oculus Rift失真着色器的OpenGL实现。着色器通过获取输入纹理坐标(包含先前渲染的场景的纹理)并使用失真系数对其进行变换,然后使用变换的纹理来确定片段颜色来工作。
我希望通过预先计算失真并将其存储在第二个纹理中来提高性能,但结果实际上比直接计算慢。
直接计算版看起来基本上是这样的:
float distortionFactor(vec2 point) {
float rSq = lengthSquared(point);
float factor = (K[0] + K[1] * rSq + K[2] * rSq * rSq + K[3] * rSq * rSq * rSq);
return factor;
}
void main()
{
vec2 distorted = vRiftTexCoord * distortionFactor(vRiftTexCoord);
vec2 screenCentered = lensToScreen(distorted);
vec2 texCoord = screenToTexture(screenCentered);
vec2 clamped = clamp(texCoord, ZERO, ONE);
if (!all(equal(texCoord, clamped))) {
vFragColor = vec4(0.5, 0.0, 0.0, 1.0);
return;
}
vFragColor = texture(Scene, texCoord);
}
其中K是以制服传递的vec4。
另一方面,置换贴图查找如下所示:
void main() {
vec2 texCoord = vTexCoord;
if (Mirror) {
texCoord.x = 1.0 - texCoord.x;
}
texCoord = texture(OffsetMap, texCoord).rg;
vec2 clamped = clamp(texCoord, ZERO, ONE);
if (!all(equal(texCoord, clamped))) {
discard;
}
if (Mirror) {
texCoord.x = 1.0 - texCoord.x;
}
FragColor = texture(Scene, texCoord);
}
还有一些其他操作可用于校正纵横比和计算镜头偏移,但它们非常简单。期望它优于简单的纹理查找是否合理?
答案 0 :(得分:12)
GDDR内存具有相当高的延迟性,现代GPU架构具有大量的数字运算功能。它曾经是另一种方式,GPU无法进行计算,通过从立方体图中提取,规范化的成本更低。
请注意,您这里没有进行 常规 纹理查找,而是 依赖 查找,毫不奇怪。由于您从中获取的位置取决于另一次获取的结果,因此无法预先获取/有效缓存(有效的延迟隐藏策略)着色器所需的内存。这不是简单的纹理查找。"
此外,除了执行从属纹理查找外,您的第二个着色器还包含discard
关键字。这将有效地消除在许多硬件上进行早期深度测试的可能性。
老实说,我不明白为什么你要"优化"将distortionFactor (...)
函数放入查找中。它使用 平方长度 ,所以你甚至没有处理sqrt
,只是一堆乘法和加法。
答案 1 :(得分:6)
Andon M. Coleman已经解释了发生了什么。基本上内存带宽和更重要的内存延迟是现代GPU的主要瓶颈,因此在大约2007年到今天之间建立的外观简单计算通常比纹理查找更快。
实际上,内存访问模式对效率有如此大的影响,稍微重新排列访问模式并确保正确对齐可以轻松地提供1000倍的性能提升(BT; DT,但这是CUDA编程)。然而,依赖查找不一定是性能杀手:如果依赖纹理坐标查找与控制器纹理是单调的,那么它通常不会那么糟糕。
话虽如此,你有没有听说过Horner's Method?你可以重写
float factor = (K[0] + K[1] * rSq + K[2] * rSq * rSq + K[3] * rSq * rSq * rSq);
琐事
float factor = K[0] + rSq * (K[1] + rSq * (K[2] + rSq * K[3]) );
为您节省一些操作。
答案 2 :(得分:0)
GPU是大规模并行的,可以在一个时钟周期内计算多达1000个结果。内存读取始终是顺序的。如果需要f.e. 5个时钟用于计算乘法,可以在5个时钟周期内计算1000个结果。如果必须用f.e顺序读取数据。每个时钟周期10个数据集,需要100个时钟周期而不是5个来获取数据。数字只是随机让你明白:)