纹理四边形插值2--逆双线性插值(InvBilinear Interpolation)
之前写了一片剖析纹理映射的文章,里面简单的介绍了一下纹理映射中常用的仿射插值(Affine),处理一些特殊情况下的投影插值(Projective),作为抛砖引玉的见解之文,收到了不少好评,谢谢大家的支持,当然也有不少同学反馈,我上篇文章遗留下的一个问题:连续的不规则四边形纹理贴图在四边形衔接处会出现纹理缝隙。
传送门:
本篇文章将介绍一个新的插值解决方案来解决上篇中遗留的问题.
逆双线性插值(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得:
// 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)进行深入探讨交流:
参考文章