返回未定义结果的 GLSL 日志
GLSL log returning an undefined result
我正在尝试绘制 mandelbrot 集。我已经在 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
并将其放入绿色通道。这是我得到的图像(mandelbrot 集中的数字为白色):
您可以看到绿色从未被剪裁,因此每个像素的结果都在 (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))
似乎是 returning 一些未定义的值。如果你否定它或试图向它添加任何东西,它仍然是未定义的。当此值为 return 来自具有 double
作为 return 类型的函数时,值 returned 为 0,现在可以将其添加到。
为什么会这样?函数 module(z)
保证 return 一个正数,因此对数函数应该 return 一个有效结果。 std::log
和GLSL log
的定义都是自变量的自然对数,所以值应该完全一样(忽略精度误差)。
如何让 GLSL log
正常运行?
事实证明,当你要求它计算一个非常大的数字的对数时,GPU 并不真正喜欢它。据我所知,log(实际上是 ln)是作为泰勒级数实现的。这很不幸,因为它包含 n 个成员的 n 次方的多项式。
但是,如果您有一个表示为 x = mantissa * 2^exp
的数字,您可以从以下公式得到 ln(x)
:
ln(x) = exp * ln(2) + ln(mantissa)
无论 x 是什么,尾数都应该小得多。这是片段着色器的函数:
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 中,此技巧仅适用于浮点数 - 没有 64 位整数类型,因此没有 doubleBitsToLong,反之亦然。
我正在尝试绘制 mandelbrot 集。我已经在 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
并将其放入绿色通道。这是我得到的图像(mandelbrot 集中的数字为白色):
您可以看到绿色从未被剪裁,因此每个像素的结果都在 (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))
似乎是 returning 一些未定义的值。如果你否定它或试图向它添加任何东西,它仍然是未定义的。当此值为 return 来自具有 double
作为 return 类型的函数时,值 returned 为 0,现在可以将其添加到。
为什么会这样?函数 module(z)
保证 return 一个正数,因此对数函数应该 return 一个有效结果。 std::log
和GLSL log
的定义都是自变量的自然对数,所以值应该完全一样(忽略精度误差)。
如何让 GLSL log
正常运行?
事实证明,当你要求它计算一个非常大的数字的对数时,GPU 并不真正喜欢它。据我所知,log(实际上是 ln)是作为泰勒级数实现的。这很不幸,因为它包含 n 个成员的 n 次方的多项式。
但是,如果您有一个表示为 x = mantissa * 2^exp
的数字,您可以从以下公式得到 ln(x)
:
ln(x) = exp * ln(2) + ln(mantissa)
无论 x 是什么,尾数都应该小得多。这是片段着色器的函数:
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 中,此技巧仅适用于浮点数 - 没有 64 位整数类型,因此没有 doubleBitsToLong,反之亦然。