有一张图片如下,想将其贴图到窗口中。
正常的效果应该就是原图的效果。
但是,我纹理贴图时的纹理顶点有四个,由两个三角形组成,当两个三角形组成矩形时,没什么问题,当其中的某个顶点移动时,就变成下图右的情况,此时就出现了问题。
在两个三角形的公共边处图像变得不连续,有明显的折痕。
这是因为渲染的时候单独对每个三角形进行插值,所以导致折痕。
问题就是这么个问题。
怎么解决呢。
可以使用投影插值和逆双线性插值。
这里使用逆双线性插值。
关于双线性插值,就是给定一个四边形的四个顶点坐标和某个uv坐标,然后通过uv计算出UV坐标对应的顶点坐标插值坐标。
逆双线性插值就是已知四边形的四个顶点坐标,和四边形内部某点的坐标,求出该点对应的UV坐标。
双线性插值相对简单,逆双线性就有点麻烦了。
关于逆双线性插值的推导,参考
纹理四边形插值2--逆双线性插值(InvBilinear Interpolation)-腾讯游戏学堂
推导就不重写了。
下面验证一下。
void main()
{
Point2d P(0.5, 0.5);
Point2d p1 = Point2d(-0.9, 1);
Point2d p2 = Point2d(1, 1);
Point2d p3 = Point2d(1, -1);
Point2d p4 = Point2d(-1, -1);
Point2d p43 = p3 - p4;
Point2d p42 = p2 - p4;
Point2d p41 = p1 - p4;
double d43 = norm(p43);
double d42 = norm(p42);
double d41 = norm(p41);
Point2d b1 = p3 - p4;
Point2d b2 = p1 - p4;
Point2d b3 = p2 - (p4 + b1 + b2);
Point2d q = P - p4;
double A = b2.cross(b3);
double B = b3.cross(q) - b1.cross(b2);
double C = b1.cross(q);
double discrim = B * B - 4 * A * C;
double v = 0.5 * (-B - sqrt(discrim))/A;
double u;
Point2d denom = b1 + b3 * v;
Point2d denom2 = q - b2 * v;
if (abs(denom.x) > abs(denom.y))
{
u = denom2.x / denom.x;
}
else
{
u = denom2.y / denom.y;
}
cout << "u = " << u << endl;
cout << "v = " << v << endl;
}
输出结果:
u = 0.74026
v = 0.75
基本差不多。
着色器中再验证一下。
#version 330 core
in vec2 TexCoord;
in vec2 aPos_out;//这个是当前处理的坐标
out vec4 FragColor;
uniform sampler2D ourTexture;
uniform mat3 transformMatrix;
uniform vec2 point1;//窗口顶点坐标
uniform vec2 point2;
uniform vec2 point3;
uniform vec2 point4;
vec2 v_q;
vec2 v_b1;
vec2 v_b2;
vec2 v_b3;
float cross2D(vec2 v, vec2 w)
{
return v.x*w.y - v.y*w.x;
}
void main()
{
v_q = aPos_out - point4;
v_b1 = point3 - point4;
v_b2 = point1 - point4;
v_b3 = point4 - point3 - point1 + point2;
float A = cross2D(v_b2, v_b3);
float B = cross2D(v_b3, v_q) - cross2D(v_b1, v_b2);
float C = cross2D(v_b1, v_q);
//float discrim = 1;
// Solve for v
vec2 uv;
if (abs(A) < 0.001)
{
// Linear form
uv.y = -C/B;
}
else
{
// Quadratic form. Take positive root for CCW winding with V-up
float discrim = B*B - 4*A*C;
//uv.y = 0.5 * (-B - sqrt(discrim)) / A;//CCW
uv.y = 0.5 * (-B - sqrt(discrim)) / A;//CW
}
// Solve for u, using largest-magnitude component
vec2 denom = v_b1 + uv.y * v_b3;
if (abs(denom.x) > abs(denom.y))
uv.x = (v_q.x - v_b2.x * uv.y) / denom.x;
else
uv.x = (v_q.y - v_b2.y * uv.y) / denom.y;
FragColor = texture2D(ourTexture, uv);
}
效果可以,符合预期。
但是上面还有点缺点,上面这种方式相当于没有使用OpenGL内部的插值,而是自己做的插值,就是找到uv坐标,直接去纹理中取对应像素值。会造成一定的锯齿。
在计算得到uv坐标后,可以接着进行一次双线性插值,这个插值,是在纹理中取值的这个过程中做的。当然也可以进行双三次插值。目的是减少锯齿。
这个后面再码吧。