Как отключить оптимизацию float arithmeti c при работе с униформой в шейдерах WebGL / WebGL2 - PullRequest
2 голосов
/ 03 февраля 2020

Я пытаюсь реализовать 64-битную арифметику c в шейдерах WebGL или WebGL2 на основе 32-битных операций с плавающей запятой. Одной из необходимых базовых c функций является функция, которая разбивает любое число с плавающей точкой на два «не перекрывающихся» числа с плавающей точкой. Первый поплавок содержит первую половину битов дроби исходного поплавка, а второй поплавок содержит вторую половину. Вот реализация этой функции:

precision highp float;
...
...
vec2 split(const float a)
{
  const float split = 4097.0; // 2^12 + 1
  vec2 result;
  float t = a * split; // almost 4097 * a
  float diff = t - a; // almost 4096 * a
  result.x = t - diff; // almost a
  result.y = a - result.x; //very small number 
  return result;
}

Эта функция работает, как и ожидалось, если я передам ей аргументы, определенные в шейдере:

precision highp float;
...
...  
float number = 0.1;
vec2 splittedNumber = split(number);
if (splittedNumber.y != 0.0)
{
    // color with white    
    // we step here and see the white screen
}
else
{
   //color with black
}

Но всякий раз, когда число зависит от в любой униформе все начинает вести себя по-разному:

precision highp float;
uniform float uniformNumber;
...
...
float number = 0.2;
if (uniformNumber > 0.0)    
{
  // uniform number is positive, 
  // so we step here
  number = 0.1;  
}  

vec2 splittedNumber = split(number);
if (splittedNumber.y != 0.0)
{
    // color with white    
}
else
{
   //color with black
   // we step here and see the black screen
}

Так что во второй ситуации, когда «число» зависит от униформы, функция разбиения каким-то образом оптимизируется и возвращает обратно vec2 со значением ноль y.

Существует аналогичный вопрос о переполнении стека для аналогичной проблемы в OpenGL Различное поведение с плавающей запятой между униформой и константами в GLSL Было предложено использовать модификатор "точный" внутри функции "split" ». К сожалению, в шейдерах WebGL / WebGL2 такого модификатора нет.

Есть ли у вас какие-либо предложения, как избавиться от оптимизаций в моем случае и реализовать функцию «split»?

1 Ответ

1 голос
/ 03 февраля 2020

Математически говоря, оба ваших примера должны выводить черные пиксели. Потому что:

float diff = t - a;
result.x = t - diff; // t - diff = t - t + a = a
result.y = a - result.x; // a - a = 0

Может случиться так, что в случае постоянного аргумента split() (заранее известного значения) компилятор выберет путь вычисления функции перед оптимизацией выражений, и вы получите splittedNumber.y != 0.0 из-за ошибок точности. Когда вы использовали униформу, компилятор пошел по пути оптимизации выражения, которое выдает математически строгий ноль.

Чтобы убедиться, что это так, попробуйте выполнить следующее сравнение:

if(abs(splittedNumber.y) > 1e-6)
{

}

В примечании стороны, highp не гарантирует 32-разрядное с плавающей точкой в ​​шейдерах WebGL. В зависимости от аппаратного обеспечения это может быть 24-разрядное число с плавающей запятой или даже 16-разрядное значение из-за возврата к среднему значению (если highp не поддерживается в фрагментных шейдерах). Чтобы увидеть фактическую точность вы можете использовать gl.getShaderPrecisionFormat

https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getShaderPrecisionFormat

...