Журнал GLSL, возвращающий неопределенный результат - PullRequest
1 голос
/ 04 мая 2020

Я пытаюсь нарисовать набор Мандельброта. Я создал алгоритм на CPU, но теперь я хочу воспроизвести его на GPU, но код ведет себя по-другому.

В программе CPU, в какой-то момент, я беру std :: abs ( z), где z - комплексное число, и запишите значение в зеленый канал на экране.

В графическом процессоре я беру тот же z и вызываю следующую функцию (Vulkan, GLSL):

double module(dvec2 z) {
    return sqrt(z.x * z.x + z.y * z.y);
}

double color(z) {
    return module(z);
}

Когда я записываю цвет (z) в зеленый канал, я получаю ту же самую картинку, что и для программы ЦП, поэтому код работает точно так же, по крайней мере, до этого момента.

Затем я изменил код процессора, взяв вместо него std::log(std::abs(z)) / 20 и поместив его в зеленый канал. Это изображение, которое я получаю (номера в наборе Мандельброта окрашены в белый цвет): enter image description here

Вы можете видеть, что зеленый цвет никогда не обрезается, поэтому результат для каждого pixel находится где-то в диапазоне (0, 1).

Затем я изменил код графического процессора следующим образом:

double module(dvec2 z) {
    return sqrt(z.x * z.x + z.y * z.y);
}

double color(z) {
    return log(module(z));
}

Я написал color(z) / 20 в зеленый канал. Это результирующее изображение: enter image description here

Как видите, значение color(z) / 20 должно быть <= 0. Я попытался изменить функцию <code>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 в зеленый канал и получил это: enter image description here

Подводя итог, кажется, что log(module(z)) возвращая какое-то неопределенное значение. Если вы отрицаете это или пытаетесь добавить что-либо к нему, оно остается неопределенным. Когда это значение возвращается из функции, которая имеет double в качестве возвращаемого типа, возвращаемое значение равно 0, которое теперь можно добавить к.

Почему это происходит? Функция module(z) гарантированно возвращает положительное число, поэтому функция журнала должна возвращать действительный результат. Определения std::log и GLSL log являются натуральным логарифмом аргумента, поэтому значение должно быть точно таким же (игнорируя ошибку точности).

Как заставить GLSL log вести себя правильно?

1 Ответ

1 голос
/ 04 мая 2020

Оказывается, что GPU не очень нравится, когда вы просите его вычислить лог очень большого числа. Из того, что я понял, log (фактически ln) реализован как ряд Тейлора. К сожалению, он содержит полиномы от n-й степени для n членов.

Однако, если у вас есть число, представленное как x = mantissa * 2^exp, вы можете получить ln(x) из следующей формулы:

ln(x) = exp * ln(2) + ln(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 этот трюк работает только с числами с плавающей точкой - нет 64-битного целочисленного типа и, следовательно, не doubleBitsToLong или наоборот.

...