零. 前言
之前一段時(shí)間一直和團(tuán)隊(duì)的其他小伙伴研究很炫酷的特效荐吵,遇到了很多掉頭發(fā)的問題,幸好大家都很給力秉宿,一一給解決了。今天抽空來(lái)復(fù)盤和總結(jié)一下當(dāng)時(shí)遇到的一些難點(diǎn)吧屯碴,就是標(biāo)題所說(shuō)的描睦,透視糾正和坐標(biāo)系這兩個(gè)大難題~
一. 透視糾正
我們的特效需求支持了不規(guī)則的圖形遮罩,也就意味著我們不能直接簡(jiǎn)單粗暴地取一個(gè)圖形的最大最小四個(gè)頂點(diǎn)坐標(biāo)导而。
于是我們想到初步的方案是用OpenCV識(shí)別出四邊形的四個(gè)頂點(diǎn)位置忱叭,直接傳給業(yè)務(wù)端進(jìn)行渲染,想法很不錯(cuò)今艺,確實(shí)識(shí)別出來(lái)了韵丑,但是卻發(fā)現(xiàn)渲染出來(lái)的圖像完全扭曲了
有種百思不得其解的感覺,后面查到文章才知道是透視糾正的原因虚缎。撵彻。
在我們執(zhí)行渲染操作的時(shí)候,頂點(diǎn)著色器會(huì)要求我們返回一個(gè)含(x, y, z, w)的四維坐標(biāo),w稱為比例因子陌僵,當(dāng)w不為0時(shí)(一般設(shè)1)轴合,表示一個(gè)坐標(biāo),一個(gè)三維坐標(biāo)的三個(gè)分量x碗短,y受葛,z用齊次坐標(biāo)表示為變?yōu)閤,y豪椿,z奔坟,w的四維空間,變換成三維坐標(biāo)是方式是(x/w, y/w, z/w)搭盾。
w的作用可不僅僅是使一個(gè)頂點(diǎn)等比例壓縮咳秉,他還有透視糾正的功能,如下面公式所示鸯隅,當(dāng)渲染操作進(jìn)行紋理渲染的時(shí)候澜建,他會(huì)根據(jù)當(dāng)前渲染點(diǎn)到兩個(gè)頂點(diǎn)的距離、以及兩個(gè)頂點(diǎn)的w值進(jìn)行透視糾正蝌以,可以看到炕舵,某個(gè)點(diǎn)w值越大,就離a點(diǎn)越遠(yuǎn)跟畅。w的設(shè)置符合近大遠(yuǎn)小的透視變換咽筋。
如果我們直接傳入頂點(diǎn)坐標(biāo),使w=1徊件,則原透視糾正公式就會(huì)變?yōu)榫€性插值奸攻,最終導(dǎo)致了紋理變形:
因此,為了使得紋理不變形虱痕,我們需要獲取兩個(gè)參數(shù)睹耐,一個(gè)是圖像的外接矩形坐標(biāo),一個(gè)是將外接矩形變換為真實(shí)頂點(diǎn)坐標(biāo)的透視變換矩陣部翘。當(dāng)透視變換矩陣(4*4)乘以外接矩形坐標(biāo)(4*1)時(shí)硝训,即可得到真實(shí)的頂點(diǎn)坐標(biāo),紋理插值也不會(huì)變形了新思。
至于怎么得到透視變換矩陣嘛窖梁,那是工具的事兒啦,大概原理就是根據(jù)外接矩形坐標(biāo)表牢、真實(shí)頂點(diǎn)坐標(biāo)窄绒,調(diào)用OpenCV的透視變換函數(shù)求出來(lái)的。
二. 坐標(biāo)系
通過上一章崔兴,我們可以知道彰导,需要用工具產(chǎn)生的頂點(diǎn)坐標(biāo)蛔翅、透視矩陣確定最終的頂點(diǎn)坐標(biāo)坐標(biāo)。
但由于這個(gè)特效是Web位谋、Android山析、iOS三端的,而且iOS端渲染還包含Metal渲染和OpenGL渲染掏父,各種渲染機(jī)制的坐標(biāo)系不完全相同笋轨,可以簡(jiǎn)單地區(qū)分為左手坐標(biāo)系和右手坐標(biāo)系,我們需要根據(jù)這些坐標(biāo)系來(lái)為工具定制出具體的透視變換矩陣求解方案赊淑。
什么是左手坐標(biāo)系和右手坐標(biāo)系呢爵政?顧名思義,需要伸出你的左手和右手陶缺,并作出兩兩垂直的手勢(shì)钾挟,如下圖所示,可以發(fā)現(xiàn)饱岸,當(dāng)x軸和y軸方向一致的時(shí)候掺出,z軸會(huì)朝向兩個(gè)相反的地方。
OpenGL和OpenCV同屬右手坐標(biāo)系苫费,工具正常求透視矩陣即可汤锨,但對(duì)于Metal的透視矩陣,我們?cè)谟?jì)算的過程中需要將y坐標(biāo)置反百框,這樣就相當(dāng)于z軸翻轉(zhuǎn)了闲礼,才可以適配左手坐標(biāo)系。
再來(lái)看看他們x铐维、y軸的方向位仁,可以發(fā)現(xiàn)OpenGL和Metal是以中間點(diǎn)為原點(diǎn),往右方椎、往上遞增,而OpenCV是以左上角為原點(diǎn)钧嘶,往右棠众、往下遞增。所以工具求出頂點(diǎn)位置后還需要一發(fā)x有决、y坐標(biāo)轉(zhuǎn)換操作闸拿。
這里的originX和originY就是將OpenCV的坐標(biāo)系y坐標(biāo)取反后,歸一化得到的真實(shí)坐標(biāo)书幕。
GLfloat originX, originY, width, height;
originX = -1 + 2 * rect.origin.x / videoSize.width;
originY = 1 - 2 * rect.origin.y / videoSize.height;
width = 2 * rect.size.width / videoSize.width;
height = 2 * rect.size.height / videoSize.height;
在解決完頂點(diǎn)坐標(biāo)翻轉(zhuǎn)后新荤,我們還需要留意OpenGL和Metal之間的紋理坐標(biāo)系的差異,OpenGL紋理坐標(biāo)系以左下角為原點(diǎn)台汇,而Metal以左上角為原點(diǎn)苛骨。
OpenGL渲染——GPUImage提供的默認(rèn)紋理坐標(biāo)如下:
static const GLfloat noRotationTextureCoordinates[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
};
由此可見篱瞎,對(duì)于OpenGL渲染,頂點(diǎn)的構(gòu)建四個(gè)點(diǎn)分別是左下痒芝、右下俐筋、左上、右上严衬。
而自研Metal鏈?zhǔn)戒秩疽膊捎昧讼嗤募y理坐標(biāo)數(shù)值:
- (NSArray *)defaultTextureCoordinates {
return @[
@0.0f, @0.0f,
@1.0f, @0.0f,
@0.0f, @1.0f,
@1.0f, @1.0f,
];
}
但由于紋理坐標(biāo)系與OpenGL不一致澄者,因此對(duì)于Metal渲染,頂點(diǎn)的構(gòu)建四個(gè)點(diǎn)分別是左上请琳、右上粱挡、左下、右下俄精。
因此询筏,對(duì)于同一個(gè)點(diǎn)來(lái)說(shuō),OpenGL的頂點(diǎn)y坐標(biāo)還需要再翻轉(zhuǎn)一次嘀倒。得到以下代碼:
OpenGL的四個(gè)頂點(diǎn):
float oriVertices[] = {
originX, -originY,
originX + width, -originY,
originX, height - originY,
originX + width, height - originY,
};
Metal的四個(gè)頂點(diǎn):
NSArray *result = @[
@(originX), @(originY),
@(originX+width), @(originY),
@(originX), @(originY-height),
@(originX+width), @(originY-height),
];
三. 總結(jié)
w坐標(biāo)是透視變換的重要因子屈留,以近大遠(yuǎn)小的方式?jīng)Q定了渲染圖形的坐標(biāo)和紋理糾正。
OpenGL测蘑、Metal的頂點(diǎn)坐標(biāo)系灌危、紋理坐標(biāo)系各不相同,需要根據(jù)具體坐標(biāo)系去決定頂點(diǎn)坐標(biāo)和紋理坐標(biāo)碳胳。