Как использовать 2D-преобразование 3x3 в вершинном/фрагментном шейдере (Metal)

У меня якобы простая задача, но видимо я так и не понял как работают проекции в шейдерах. Мне нужно сделать 2D-перспективное преобразование на текстурном квадрате (2 треугольника), но визуально это выглядит неправильно (например, трапеция немного выше или более растянута, чем в версии для процессора).

У меня есть эта структура:

struct VertexInOut  
{  
  float4 position [[position]];  
  float3 warp0;  
  float3 warp1;  
  float3 warp2;  
  float3 warp3;  
};  

И в вершинном шейдере я делаю что-то вроде (texCoords — это пиксельные координаты четырехугольных углов, а гомография вычисляется в пиксельных координатах):

v.warp0 = texCoords[vid] * homographies[0]; 

Затем во фрагментном шейдере вот так:

return intensity.sample(s, inFrag.warp0.xy / inFrag.warp0.z);

Результат не тот, что я ожидаю. Я потратил часы на это, но я не могу понять это. вентиляция

ОБНОВЛЕНИЕ:

Это код и результат для ЦП (он же ожидаемый результат):

// _image contains the original image
cv::Matx33d h(1.03140473, 0.0778113901, 0.000169219566,
              0.0342947133, 1.06025684, 0.000459250761,
              -0.0364957005, -38.3375587, 0.818259298);
cv::Mat dest(_image.size(), CV_8UC4);
// h is transposed because OpenCV is col major and using backwarping because it is what is used on the GPU, so better for comparison
cv::warpPerspective(_image, dest, h.t(), _image.size(), cv::WARP_INVERSE_MAP | cv::INTER_LINEAR);  

деформирован процессором

Это код и результат для графического процессора (он же неправильный результат):

// constants passed in buffers, image size 320x240
const simd::float4 quadVertices[4] =
{
  { -1.0f,  -1.0f, 0.0f, 1.0f },
  { +1.0f,  -1.0f, 0.0f, 1.0f },
  { -1.0f,  +1.0f, 0.0f, 1.0f },
  { +1.0f,  +1.0f, 0.0f, 1.0f },
};

const simd::float3 textureCoords[4] =
{
  { 0,  IMAGE_HEIGHT, 1.0f },
  { IMAGE_WIDTH, IMAGE_HEIGHT, 1.0f },
  { 0, 0, 1.0f },
  { IMAGE_WIDTH, 0, 1.0f },
};

// vertex shader
vertex VertexInOut homographyVertex(uint vid [[ vertex_id ]],
                                    constant float4 *positions [[ buffer(0) ]],
                                    constant float3 *texCoords [[ buffer(1) ]],
                                    constant simd::float3x3 *homographies [[ buffer(2) ]])
{
  VertexInOut v;
  v.position = positions[vid];

  // example homography
  simd::float3x3 h = {
    {1.03140473, 0.0778113901, 0.000169219566},
    {0.0342947133, 1.06025684, 0.000459250761},
    {-0.0364957005, -38.3375587, 0.818259298}
  };

  v.warp = h * texCoords[vid];

  return v;
}

// fragment shader
fragment int4 homographyFragment(VertexInOut inFrag [[stage_in]],
                                 texture2d<uint, access::sample> intensity [[ texture(1) ]])
{
  constexpr sampler s(coord::pixel, filter::linear, address::clamp_to_zero);
  float4 targetIntensity = intensityRight.sample(s, inFrag.warp.xy / inFrag.warp.z);
  return targetIntensity;
}

введите здесь описание изображения

Исходное изображение:

исходное изображение

ОБНОВЛЕНИЕ 2:

Вопреки распространенному мнению, что разделение перспективы должно выполняться во фрагментном шейдере, я получаю намного более похожий результат, если разделяю в вершинном шейдере (без искажений или швов между треугольниками), но почему? ?

введите здесь описание изображения

ОБНОВЛЕНИЕ 3:

Я получаю тот же (неправильный) результат, если:

  • Я перемещаю разделение перспективы на фрагментный шейдер
  • Я просто удаляю деление из кода

Очень странно, похоже, деления не происходит.


person aledalgrande    schedule 10.08.2015    source источник
comment
Выложите проект, я посмотрю. Я не могу дать совет без хотя бы скриншота.   -  person Jessy    schedule 10.08.2015
comment
@Jessy Я извлек здесь основную часть, но если вам это нужно, я могу создать полноценный проект. У меня есть собственная клиент-серверная библиотека для загрузки результатов XCTest изображений на мой Mac, поэтому извлечь эту часть кода было намного быстрее.   -  person aledalgrande    schedule 11.08.2015
comment
Я заснул, думая об этом, и проснулся с решением, что мне нужно полностью погрузиться в проект, чтобы решить его в кратчайшие сроки. Если никто другой не даст вам ответ в удобное для вас время, тогда да, пожалуйста, найдите способ загрузить минимум того, над чем мы могли бы поработать, и я найду время хотя бы попытаться помочь.   -  person Jessy    schedule 11.08.2015
comment
Привет, @Jessy, пример приложения можно найти здесь: github.com/aledalgrande/warpExample При запуске приложение на устройстве A7+, вы сможете увидеть неправильную деформацию красным цветом. Правильный зеленый, и он накладывается на неправильный. Есть некоторое растяжение из-за размера экрана, но так как он влияет на оба, это не имеет значения. Спасибо за вашу помощь.   -  person aledalgrande    schedule 11.08.2015
comment
Только что обнаружил, что результат деления был искажен... если вы попробуете приложение сейчас, вы увидите, что изображение желтое (все красные пиксели поверх зеленых).   -  person aledalgrande    schedule 11.08.2015


Ответы (1)


Хорошо, решение было, конечно, очень маленькой деталью: дивизия в simd::float3 ведет себя абсолютно безумно. На самом деле, если я разделю перспективу во фрагментном шейдере следующим образом:

float4 targetIntensity = intensityRight.sample(s, inFrag.warp.xy * (1.0 / inFrag.warp.z));

оно работает!

Это привело меня к выводу, что умножение на предварительно разделенное число с плавающей запятой отличается от деления на число с плавающей запятой. Причина этого мне до сих пор неизвестна, если кто знает, почему мы можем разгадать эту тайну.

person aledalgrande    schedule 11.08.2015
comment
Это ужасно! У меня те же результаты, что вы описываете, с вашим проектом. Я изменил поплавки на половинки, но это не изменило поведение. - person Jessy; 11.08.2015
comment
Я мог бы открыть радар, если никто не может понять, почему это происходит. - person aledalgrande; 11.08.2015
comment
Тот факт, что мы оба удивлены, и мы оба думаем, что это неправильно, означает, что он нуждается в радарной регистрации, независимо от того, вызвана ли ошибка нашим собственным недостатком знаний. Я никогда не видел ничего подобного с OpenGL/ES. - person Jessy; 12.08.2015