纹理四边形插值2--逆双线性插值(InvBilinear Interpolation)

发表于2018-01-20
评论2 1.16w浏览

之前写了一片剖析纹理映射的文章,里面简单的介绍了一下纹理映射中常用的仿射插值(Affine),处理一些特殊情况下的投影插值(Projective),作为抛砖引玉的见解之文,收到了不少好评谢谢大家的支持,当然也有不少同学反馈,我上篇文章遗留下的一个问题:连续的不规则四边形纹理贴图在四边形衔接处会出现纹理缝隙。


传送门:

投影纹理映射探讨

纹理四边形插值1--投影映射插值 


本篇文章将介绍一个新的插值解决方案来解决上篇中遗留的问题.

逆双线性插值(Inverse Bilinear Interpolation) (此处的双线性插值Bilinear不要和解决纹理锯齿而采用的多重采样双线性插值搞混淆, 此处的方案是解决多边形纹理映射接缝问题)

先来看看最终的效果:

(带线框效果)

(无线框效果)

上图红白相间纹理分别展示了 三种纹理插值的方案:

  • 仿射插值(Affine) 默认通用的纹理映射方案,算法简单高效。但在本案例的不规则四边形内两个三角形产生了不同的透视效果,不能展现一个平滑的不规则四边形纹理。

  • 投影插值(Projective) 单个四边形内有完美的透视效果,但是两个不规则四边形的衔接处因为透视角度不同,导致接缝处纹理无法衔接。

  • 逆双线性插值(InvBilinear) 很好的解决了多个不规则四边形的接缝问题,单个四边形内部也没有突兀的折边。但是四边形内的纹理产生了略微的弯曲。


好了,三个基本方案都简单的列举了优缺点。结合上图的效果,大家应该已经大概了解了这三个方案的功效。

下面开始正式的介绍 双线性纹理插值的原理和实现方案


求X的计算公式:

P是系数为u的A和B之间的线性插值: 

M = lerp(P0,P1,u) = P0 + (P1-P0)·u

同理:

N = lerp(P2,P3,u) = P2 + (P3-P2)·u

X = M + (N-M)·v

因此:

X(u,v) = P0 + (P1-P0)·u + (P2-P0)·v + (P0-P1+P3-P2)·u·v

令:
b1 = P1-P0
b2 = P2-P0
b3 = P0-P1+P3-P2
q = X-P0

得:

q = b1·u + b2·v + b3·u·v (1)

现在我们得到了一个公式,公式中两个未知数 u v. 虽然是两个未知数,但是其实uv是对应了两个坐标轴,类似于(x,y)、(i,j),我们将uv看作ij轴(不清楚的可以去了解一下格拉斯曼代数Grassmann algebra),我们得到两个公式:

qi = b1i·u + b2i·v + b3i·u·v (2)

qj = b1j·u + b2j·v + b3j·u·v (3)

由(2)求得:

u = (qi - b2i·v) / (b1i + b3i·v) (4)

然后将(4)代入(3)得:

qj·b1i + qj·b3i·v = b1j·qi - b1j·b2i·v + b2j·b1i·v + b2j·b3i·v² + b3j·qi·v - b3j·b2i·v²


(b3j·b2i-b2j·b3i)·v² + ((qj·b3i-b3j·qi)-(b2j·b1i-b1j·b2i))·v + (qj·b1i-b1j·qi) = 0

令:

A = b3j·b2i-b2j·b3i = b2 X b3
B = (qj·b3i-b3j·qi)-(b2j·b1i-b1j·b2i) = b3 X q - b1 X b2

C = qj·b1i-b1j·qi = b1Xq

得一元二次方程一般式

A·v²+B·v+C = 0 (5)

求解v得:


若X在四边形内,则B²-4AC永远大于0。若b3 = 0则四边形为平行四边形,那么A也就变成了0,方程(5)就变成了B·v+C = 0,v = -C/B,则采用仿射插值即可。

下面附上顶点着色器和片源着色器代码(GLSH)
// Attributes
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec3 a_texCoord;
attribute vec4 a_color;
// Varyings
varying vec3 v_texCoord;
varying vec4 v_fragmentColor;
varying vec2 v_q;
varying vec2 v_b1;
varying vec2 v_b2;
varying vec2 v_b3;
// Uniform
uniform	vec3 u_p0;
uniform	vec3 u_p1;
uniform	vec3 u_p2;
uniform	vec3 u_p3;
void main()
{
    gl_Position = CC_MVPMatrix * vec4(a_position, 1.0);
    v_fragmentColor = a_color;
    v_texCoord = a_texCoord;
    v_q = (a_position - u_p0).xy;
    v_b1 = (u_p1 - u_p0).xy;
    v_b2 = (u_p2 - u_p0).xy;
    v_b3 = (u_p0 - u_p1 - u_p2 + u_p3).xy;
}
uniform sampler2D u_texture;
// Varyings
varying vec3 v_texCoord;
varying vec4 v_fragmentColor;
varying vec2 v_q;
varying vec2 v_b1;
varying vec2 v_b2;
varying vec2 v_b3;
float cross2D(vec2 v, vec2 w)
{
  return v.x*w.y - v.y*w.x;
}
void main()
{
	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;
    gl_FragColor = texture2D(u_texture, uv);
}


好了,关于这个逆双线性插值的原理还实现基本介绍差不多了。写的比较仓促,有什么纰漏也请各位不吝指正。欢迎关注我的公众号(ArtStealer)进行深入探讨交流:

参考文章



如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

标签: