7

我正在研究 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);
}

还有一些其他操作可用于校正纵横比和考虑镜头偏移,但它们非常简单。期望它优于简单的纹理查找真的合理吗?

4

3 回答 3

12

GDDR 内存具有相当高的延迟,现代 GPU 架构具有大量的数字运算能力。过去恰恰相反,GPU 的计算能力太差,以至于通过从立方体贴图中获取归一化的成本更低。

考虑到您不是在这里进行常规纹理查找,而是进行依赖查找,这不足为奇。由于您从中获取的位置取决于另一个获取的结果,因此不可能预取/有效缓存(一种有效的延迟隐藏策略)着色器所需的内存。这不是“简单的纹理查找”。

更重要的是,除了进行依赖纹理查找之外,您的第二个着色器还包括discard关键字。这将有效地消除对很多硬件进行早期深度测试的可能性。

老实说,我不明白为什么要将distortionFactor (...)函数“优化”为查找。它使用squared length,所以你甚至不用处理 a sqrt,只是一堆乘法和加法。

于 2013-12-15T08:49:18.437 回答
6

Andon M. Coleman 已经解释了发生了什么。本质上,内存带宽,更重要的是内存延迟是现代 GPU 的主要瓶颈,因此从大约 2007 年到今天,简单计算通常比纹理查找快得多。

事实上,内存访问模式对效率有如此大的影响,稍微重新安排访问模式并确保正确对齐可以轻松地将性能提升 1000 倍(BT;DT,但那是 CUDA 编程)。但是,从属查找不一定是性能杀手:如果从属纹理坐标查找与控制器纹理是单调的,那么它通常不会那么糟糕。


话虽如此,您从未听说过霍纳的方法吗?你可以重写

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

为您节省一些操作。

于 2013-12-15T10:45:06.920 回答
0

GPU 是大规模并行的,可以在单个时钟周期内计算多达 1000 个结果。内存读取始终是顺序的。如果计算乘法需要 fe 5 个时钟,则可以在 5 个时钟周期内计算 1000 个结果。如果必须以每个时钟周期 fe 10 个数据集顺序读取数据,则需要 100 个时钟周期而不是 5 个时钟周期来获取数据。随机编号让您理解:)

于 2014-03-31T04:24:50.020 回答