【iOS学习】基于CoreGraphics的3D渲染方案

来源:互联网 发布:淘宝第三方支付平台 编辑:程序博客网 时间:2024/05/19 16:03

前言

今年的首要研究对象OpenGL基本研究的差不多了,突发奇想,想用CoreGraphics根据OpenGL的渲染流水线,渲染出3D图形来。折腾了2天,写出了个demo,效果如下。

其实这种通过2D渲染引擎渲染3D的技术方案在Flash时代我就听说了,但是当时对于3D技术不是很了解,并没有做深入研究。

原理

在OpenGL中,每个顶点通过Vertex Shader的处理,被处理成基本的绘制图形,比如三角形,再通过Fragment Shader处理各个像素点的颜色。最终以透视的效果呈现在屏幕上(当然如果你用了正交矩阵就没透视啦)。根据上面的原理,我在渲染方案中定义了两种基本图形,线和多边形。线由2个顶点组成,多边形由3到多个顶点组成。通过MVP矩阵对顶点进行变换,然后用CoreGraphics绘制顶点变换后的图形。上图中的正方体就是由6个四边形组成,锥体则是4个三角形组成。

基本绘制图形

每个基本绘制图形都会实现下面的协议,material是图形的样式,包括颜色,线条粗细等,transform方法用来对组成图形的顶点进行变换,并返回变换后的图形。sortZRef会返回图形在z方向排序的参考值,这个主要用来弥补CoreGraphics中无法进行Depth Test的缺陷。不过目前的参考值计算方案还是有问题的,仅仅是计算了所有顶点变换后z的平均值而已。

1
2
3
4
5
public protocol HT3DElement {
    var material: HT3DMaterial { get set }
    func transform(matrix: GLKMatrix4) -> Self
    func sortZRef() -> Float
}

下面我们来看看图形-线的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
public var startPoint: GLKVector3
public var endPoint: GLKVector3
public var material: HT3DMaterial
public func transform(matrix: GLKMatrix4) -> HT3DLineElement {
    let newStartPoint = matrix * GLKVector4.init(vector3: startPoint, w: 1)
    let newEndPoint = matrix * GLKVector4.init(vector3: endPoint, w: 1)
    return HT3DLineElement.init(startPoint: (newStartPoint / newStartPoint.w).xyz, endPoint:
(newEndPoint
            / newEndPoint.w).xyz, material: material)
}
public func sortZRef() -> Float {
    return (startPoint.z + endPoint.z) / 2.0
}

在使用矩阵对顶点变换后,要重新把顶点变换到屏幕空间,所以将顶点除以它的w。xyz是自定义的扩展,获取4维向量的前3维。

1
(newStartPoint / newStartPoint.w).xyz

sortZRef的实现正如上面所说,求z的平均值。

几何体Geometry

Geometry由一组基本图形组成,比如一个正方体。Geometry提供modelMatrix对这一组基本图形进行变换。它的存在让我们可以为每一组基本图形提供不同的模型变换。同时它也肩负着管理图形材质的任务。可以通过它的setMaterialForElementsInRange方法为每一个基本图形设置不同的样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public var elements: [HT3DElement]?
public var materials: [HT3DMaterial] = []
public var modelMatrix: GLKMatrix4 = GLKMatrix4Identity
public var material: HT3DMaterial? {
    return materials.first
}
public init(elements: [HT3DElement], material: HT3DMaterial) {
    self.elements = elements
    self.materials.append(material)
    self.setMaterialForElementsInRange(range: Range.init(uncheckedBounds: (0,
elements.count - 1)), materialIndex: 0)
}
 
public func setMaterialForElementsInRange(range: Range, materialIndex: Int) {
    if let material = materials[cycle: materialIndex] {
        for index in range.lowerBound...range.upperBound {
            if let element = self.elements?[safe: index] {
                var ele = element
                ele.material = material
                self.elements?[index] = ele
            }
        }
    }
}

CoreGraphics渲染

为了方便其他渲染器的实现,我定义了渲染器的协议。渲染器的主要功能就是渲染基本图形的集合。

1
2
3
protocol HT3DRenderContext {
    func render(elements: [HT3DElement])
}

为了更加方便的调用渲染器代码,为该协议编写了下面的扩展方法。

1
2
3
4
5
6
7
8
9
10
11
12
extension HT3DRenderContext {
    public func render(vpMatrix: GLKMatrix4, geometries: [HT3DGeometry]) {
        var elements: [HT3DElement] = []
        for geometry in geometries {
            let _ = geometry.elements?.map {
                elements.append($0.transform(matrix: vpMatrix * geometry.modelMatrix))
            }
        }
        elements.sort { $0.sortZRef() > $1.sortZRef() }
        render(elements: elements)
    }
}

这样就可以很方便的使用VP(ProjectionMatrix * ViewMatrix)和几何体列表渲染了。在这个方法中,我们将基本图形的顶点使用VP和所属几何体的ModelMatrix进行变换,然后将这些基本图形按照z轴排序,从而模拟DepthTest,最后调用渲染器的渲染方法。这个方法的具体实现取决于你用什么样的渲染器。本文自然采用了CoreGraphics渲染器。渲染器代码在HT3DCGRenderContext.swift中。主要就是线和多边形两种基本图形的渲染,非常简单的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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())) }
    for index in 1.. CGPoint {
    return (from * (1, -1) + 1.0) * 0.5 * (canvasSize.width, canvasSize.height)
}

其中convertCoordFromGLToCG用于将OpenGL坐标转换成CoreGraphics中的坐标,如果你对OpenGL坐标不了解,可以去看我的OpenGL系列教程。

如果你对本文的其他关于OpenGL的概念也不理解,也可以去教程里面找找答案,毕竟本文中很多OpenGL的概念我只是一笔带过。

整合

目前这个方案还只是开始阶段,还有很多优化和不足的地方有待改进。比如使用更加精准的z轴排序算法,提供基本光照模型等等。


阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 错币真的值钱吗 错币收藏网 错版币图片 出售错版币 错币多少钱 错币交易市场 50元错币 错版币出手 两元错版币 古钱错币 贰分错币 错版币回收 收购错版币 错版币交易网站 错币交易网站 2角错币 错版币行情 直接收购错币 错币价格表 错版币吧 哪里可以鉴定错版币 专业私人收购错版币 生命线错开20种图解 门对门是否错开一点就行 生命线错开又长出一条 事业线错开20种图解 生命线中间错开另起一条 智慧线错开20种图解 错开多少才不算门对门 智慧线两段错开图解 红绿灯路口开错车道扣几分 错开 女右手生命线错开所有图片 女士右手生命线错开 错怪 我错怪了他作文 我错怪了他400字 让我错怪你吧 民国错恋 错恋 律轻 错恨