我试图绘制曼德尔布罗特集。我已经在CPU上创建了算法,但现在我想在GPU上重现它,但代码表现不同。
在CPU程序中,有一点,我取std::abs(z),其中z是一个复数,并将其值写在屏幕上的绿色通道中。
在GPU上,我取相同的z并调用以下函数(Vulkan,GLSL)。
double module(dvec2 z) {
return sqrt(z.x * z.x + z.y * z.y);
}
double color(z) {
return module(z);
}
当我把color(z)写进绿色通道时,我得到的画面和CPU程序得到的画面是一样的,所以代码的工作原理完全一样,至少在这一点上是一样的。
接下来,我把CPU代码改成了以 std::log(std::abs(z)) / 20
并把它放在绿色通道中。这就是我得到的图像(在曼德尔布罗特集合中的nubmers被染成白色)。
你可以看到绿色从来没有被剪切过 所以每个像素的结果都是在某个范围内(0,1)。
然后我把GPU代码改成这样。
double module(dvec2 z) {
return sqrt(z.x * z.x + z.y * z.y);
}
double color(z) {
return log(module(z));
}
我写道 color(z) / 20
进入绿色通道。这就是最终的图像。
正如你所看到的 color(z) / 20
必须是<=0。我试着把 color
函数来进行。
double color(z) {
return -log(module(z));
}
看看这个值是0还是负数。我还是得到了相同的图像,所以值一定是0.为了确认这一点,我再次修改代码,现在改为这样。
double color(z) {
return log(module(z)) + 0.5;
}
然后写道 color(z)
的绿色通道(将除数降为20)。我期望的结果是中绿色。
令我惊讶的是,图像没有变化,像素仍然是漆黑的。
困惑之余,我把变化恢复到了原来的样子。
double color(z) {
return log(module(z));
}
但是,我写道 color(z) + 0.5
进入绿色通道,我得到了这个。
综上所述,似乎... log(module(z))
正在返回一些未定义的值。如果你否定它或试图在它上面添加任何东西,它仍然是未定义的。当这个值从一个有类似于 double
作为返回类型,返回的值是0,现在可以加到。
为什么会出现这种情况呢?函数 module(z)
是保证返回一个正数,所以log函数应该返回一个有效的结果。这两个函数的定义是 std::log
和 GLSL log
是参数的自然对数,所以值应该是完全一样的(忽略精度误差)。
我如何使 GLSL log
乖不乖?
原来GPU并不太喜欢你让它计算一个很大的数字的log。据我所知,log(实际上是ln)是以taylor系列的形式实现的。这是不幸的,因为它包含了n个成员的n次幂的多项式。
然而,如果你有一个数字表示为 x = mantissa * 2^exp
,你可以得到 ln(x)
由以下公式得出。
ln(x) = exp * ln(2) + ln(mantissa)
不管x是什么,mantissa应该明显小一些。这是一个片段着色器的函数。
float ln(float z) {
int integerValue = floatBitsToInt(z);
int exp = ((integerValue >> mantissaBits) & (1 << expBits) - 1)
- ((1 << (expBits - 1)) - 1);
integerValue |= ((1 << expBits) - 1) << mantissaBits;
integerValue &= ~(1 << (mantissaBits + expBits - 1));
return exp * log2 + log(intBitsToFloat(integerValue));
}
请注意,在GLSL中,这个技巧只适用于floats--没有64位的积分类型,因此也没有doubleBitsToLong,反之亦然。