【问题标题】:How to use a 3x3 2D transformation in a vertex/fragment shader (Metal)如何在顶点/片段着色器(金属)中使用 3x3 2D 变换
【发布时间】:2015-11-02 17:21:21
【问题描述】:

我有一个据说很简单的任务,但显然我仍然不明白投影在着色器中是如何工作的。我需要对纹理四边形(2 个三角形)进行 2D 透视变换,但在视觉上它看起来不正确(例如,梯形比 CPU 版本中的稍高或拉伸更多)。

我有这个结构:

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);

结果不是我所期望的。我花了几个小时在这上面,但我无法弄清楚。 通风

更新:

这些是 CPU 的代码和结果(又名预期结果):

// _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);  

这些是 GPU 的代码和结果(又名错误结果):

// 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:

如果出现以下情况,我会得到相同(错误)的结果:

  • 我将透视分割移动到片段着色器
  • 我只是从代码中删除了分隔符

很奇怪,好像没有分水岭。

【问题讨论】:

  • 立个项目,我看看。至少没有截图,我无法给出建议。
  • @Jessy 我在这里提取了核心部分,但如果您需要它,我可以创建一个完整的项目。我有一个自定义客户端-服务器库,可以将图像 XCTest 结果上传到我的 Mac,因此提取这部分代码要快得多。
  • 我想着它就睡着了,醒来后我决定我需要全身心地投入到项目中,这样我才能及时有效地解决它。如果没有其他人在您满意的时间范围内为您提供答案,那么是的,请找到一种方法来上传我们可以做的最低限度的工作,我会抽出时间至少尝试提供帮助。
  • 嘿@Jessy,您可以在此处找到示例应用程序:github.com/aledalgrande/warpExample 当您在 A7+ 设备上运行该应用程序时,您将能够看到错误的红色扭曲。正确的一个是绿色的,它叠加到错误的那个上。由于屏幕尺寸,有一些拉伸,但由于两者都受它的影响,所以没有区别。感谢您的帮助。
  • 刚刚发现除法的结果很糟糕...如果您现在尝试该应用程序,您会看到图像是黄色的(绿色像素顶部的所有红色像素)。

标签: ios shader texture-mapping metal


【解决方案1】:

好的,解决方案当然是一个非常小的细节:simd::float3division 表现得非常疯狂。事实上,如果我像这样在片段着色器中进行透视划分:

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

有效!

这让我发现乘以预除的浮点数与除以浮点数不同。我仍然不知道其中的原因,如果有人知道我们为什么可以解开这个谜。

【讨论】:

  • 太糟糕了!对于您的项目,我的结果与您描述的相同。我将浮点数更改为一半,但这并没有改变行为。
  • 我可能会打开雷达,如果没人能弄清楚为什么会发生这种情况。
  • 我们俩都很惊讶,而且我们都认为这是错误的,这意味着它需要一个雷达文件,不管这个错误是否是我们自己缺乏知识造成的。我从来没有在 OpenGL/ES 中看到过这样的事情。
猜你喜欢
  • 2016-11-13
  • 2017-10-05
  • 1970-01-01
  • 2019-01-24
  • 2011-05-24
  • 1970-01-01
  • 2023-03-25
  • 2017-06-13
  • 1970-01-01
相关资源
最近更新 更多