【iOS學(xué)習(xí)】基于CoreGraphics的3D渲染方案

前言

今年的首要研究對(duì)象OpenGL基本研究的差不多了,突發(fā)奇想,想用CoreGraphics根據(jù)OpenGL的渲染流水線,渲染出3D圖形來。折騰了2天,寫出了個(gè)demo,效果如下。

其實(shí)這種通過2D渲染引擎渲染3D的技術(shù)方案在Flash時(shí)代我就聽說了,但是當(dāng)時(shí)對(duì)于3D技術(shù)不是很了解,并沒有做深入研究。

原理

在OpenGL中,每個(gè)頂點(diǎn)通過Vertex Shader的處理,被處理成基本的繪制圖形,比如三角形,再通過Fragment Shader處理各個(gè)像素點(diǎn)的顏色。最終以透視的效果呈現(xiàn)在屏幕上(當(dāng)然如果你用了正交矩陣就沒透視啦)。根據(jù)上面的原理,我在渲染方案中定義了兩種基本圖形,線和多邊形。線由2個(gè)頂點(diǎn)組成,多邊形由3到多個(gè)頂點(diǎn)組成。通過MVP矩陣對(duì)頂點(diǎn)進(jìn)行變換,然后用CoreGraphics繪制頂點(diǎn)變換后的圖形。上圖中的正方體就是由6個(gè)四邊形組成,錐體則是4個(gè)三角形組成。

基本繪制圖形

每個(gè)基本繪制圖形都會(huì)實(shí)現(xiàn)下面的協(xié)議,material是圖形的樣式,包括顏色,線條粗細(xì)等,transform方法用來對(duì)組成圖形的頂點(diǎn)進(jìn)行變換,并返回變換后的圖形。sortZRef會(huì)返回圖形在z方向排序的參考值,這個(gè)主要用來彌補(bǔ)CoreGraphics中無法進(jìn)行Depth Test的缺陷。不過目前的參考值計(jì)算方案還是有問題的,僅僅是計(jì)算了所有頂點(diǎn)變換后z的平均值而已。

publicprotocol?HT3DElement?{

varmaterial:?HT3DMaterial?{getset}

func?transform(matrix:?GLKMatrix4)?->?Self

func?sortZRef()?->?Float

}

下面我們來看看圖形-線的實(shí)現(xiàn)。

publicvarstartPoint:?GLKVector3

publicvarendPoint:?GLKVector3

publicvarmaterial:?HT3DMaterial

publicfunc?transform(matrix:?GLKMatrix4)?->?HT3DLineElement?{

let?newStartPoint?=?matrix?*?GLKVector4.init(vector3:?startPoint,?w:1)

let?newEndPoint?=?matrix?*?GLKVector4.init(vector3:?endPoint,?w:1)

returnHT3DLineElement.init(startPoint:?(newStartPoint?/?newStartPoint.w).xyz,?endPoint:

(newEndPoint

/?newEndPoint.w).xyz,?material:?material)

}

publicfunc?sortZRef()?->?Float?{

return(startPoint.z?+?endPoint.z)?/2.0

}

在使用矩陣對(duì)頂點(diǎn)變換后,要重新把頂點(diǎn)變換到屏幕空間,所以將頂點(diǎn)除以它的w。xyz是自定義的擴(kuò)展,獲取4維向量的前3維。

1(newStartPoint?/?newStartPoint.w).xyz

sortZRef的實(shí)現(xiàn)正如上面所說,求z的平均值。

幾何體Geometry

Geometry由一組基本圖形組成,比如一個(gè)正方體。Geometry提供modelMatrix對(duì)這一組基本圖形進(jìn)行變換。它的存在讓我們可以為每一組基本圖形提供不同的模型變換。同時(shí)它也肩負(fù)著管理圖形材質(zhì)的任務(wù)。可以通過它的setMaterialForElementsInRange方法為每一個(gè)基本圖形設(shè)置不同的樣式。

publicvarelements:?[HT3DElement]?

publicvarmaterials:?[HT3DMaterial]?=?[]

publicvarmodelMatrix:?GLKMatrix4?=?GLKMatrix4Identity

publicvarmaterial:?HT3DMaterial??{

returnmaterials.first

}

publicinit(elements:?[HT3DElement],?material:?HT3DMaterial)?{

self.elements?=?elements

self.materials.append(material)

self.setMaterialForElementsInRange(range:?Range.init(uncheckedBounds:?(0,

elements.count?-1)),?materialIndex:0)

}

publicfunc?setMaterialForElementsInRange(range:?Range,?materialIndex:?Int)?{

iflet?material?=?materials[cycle:?materialIndex]?{

forindexinrange.lowerBound...range.upperBound?{

iflet?element?=?self.elements?[safe:?index]?{

varele?=?element

ele.material?=?material

self.elements?[index]?=?ele

}

}

}

}

CoreGraphics渲染

為了方便其他渲染器的實(shí)現(xiàn),我定義了渲染器的協(xié)議。渲染器的主要功能就是渲染基本圖形的集合。

protocol?HT3DRenderContext?{

func?render(elements:?[HT3DElement])

}

為了更加方便的調(diào)用渲染器代碼,為該協(xié)議編寫了下面的擴(kuò)展方法。

extension?HT3DRenderContext?{

publicfunc?render(vpMatrix:?GLKMatrix4,?geometries:?[HT3DGeometry])?{

varelements:?[HT3DElement]?=?[]

forgeometryingeometries?{

let?_?=?geometry.elements?.map?{

elements.append($0.transform(matrix:?vpMatrix?*?geometry.modelMatrix))

}

}

elements.sort?{?$0.sortZRef()?>?$1.sortZRef()?}

render(elements:?elements)

}

}

這樣就可以很方便的使用VP(ProjectionMatrix * ViewMatrix)和幾何體列表渲染了。在這個(gè)方法中,我們將基本圖形的頂點(diǎn)使用VP和所屬幾何體的ModelMatrix進(jìn)行變換,然后將這些基本圖形按照z軸排序,從而模擬DepthTest,最后調(diào)用渲染器的渲染方法。這個(gè)方法的具體實(shí)現(xiàn)取決于你用什么樣的渲染器。本文自然采用了CoreGraphics渲染器。渲染器代碼在HT3DCGRenderContext.swift中。主要就是線和多邊形兩種基本圖形的渲染,非常簡單的代碼。

func?renderElement(context:?CGContext,?element:?HT3DLineElement)?{

context.setStrokeColor(UIColor.fromVec3(glkVector3:?element.material.lineColor).cgColor)

context.setLineWidth(element.material.lineWidth)

context.beginPath()

context.move(to:?convertCoordFromGLToCG(element.startPoint.cgPoint()))

context.addLine(to:?convertCoordFromGLToCG(element.endPoint.cgPoint()))

context.strokePath()

}

func?renderElement(context:?CGContext,?element:?HT3DPolygonElement)?{

context.setFillColor(UIColor.fromVec3(glkVector3:?element.material.diffuse).cgColor)

context.setStrokeColor(UIColor.fromVec3(glkVector3:?element.material.lineColor).cgColor)

context.setLineWidth(element.material.lineWidth)

context.beginPath()

element.points.first.map?{?context.move(to:?convertCoordFromGLToCG($0.cgPoint()))?}

forindexin1..?CGPoint?{

return(from?*?(1,?-1)?+1.0)?*0.5*?(canvasSize.width,?canvasSize.height)

}

其中convertCoordFromGLToCG用于將OpenGL坐標(biāo)轉(zhuǎn)換成CoreGraphics中的坐標(biāo),如果你對(duì)OpenGL坐標(biāo)不了解,可以去看我的OpenGL系列教程。

如果你對(duì)本文的其他關(guān)于OpenGL的概念也不理解,也可以去教程里面找找答案,畢竟本文中很多OpenGL的概念我只是一筆帶過。

整合

目前這個(gè)方案還只是開始階段,還有很多優(yōu)化和不足的地方有待改進(jìn)。比如使用更加精準(zhǔn)的z軸排序算法,提供基本光照模型等等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容