学习SceneKit之贴图

作者: handyTOOL | 来源:发表于2017-12-13 15:26 被阅读273次

    本系列所有文章目录

    获取示例代码


    前言

    上一篇我们介绍了材质的基本概念,这一篇我们将重点介绍材质中的一个概念 - 贴图。本文将重点说明diffuse贴图的运作方式。在例子中,为之前自定义的Cube几何体每一个面贴上不同的图片。下面是效果图。


    UV

    为了能够正确的将图片贴合到几何体的面上,几何体的顶点数据中必须包含这种映射关系。在前面我们有提到顶点数据中有包含UV数据。所谓UV,就是图片映射到几何面的坐标。通常,我们将图片的尺寸规范到1x1的范围内,如下图所示。U是水平轴,V是竖直轴。


    如果一个顶点对应的UV是(0,0),那么就相当于图片的左上角会被贴到那个顶点的位置上。下面是这个面的顶点数据。
    -0.5,   0.5,  0.5,   0,  0,  1, 0, 0,
    -0.5,  -0.5,  0.5,  0,  0,  1, 0, 1,
    0.5,   -0.5,  0.5,  0,  0,  1, 1, 1,
    0.5,    -0.5, 0.5,   0,  0,  1, 1, 1,
    0.5,    0.5,  0.5,    0,  0,  1, 1, 0,
    -0.5,   0.5,  0.5,  0,  0,  1, 0, 0,
    

    每一行,前三个是顶点位置,中间三个是法线,也就是和这个面垂直的向量,后面两个就是UV数据了。这个面和xy面平行,在z=0.5处。UV为(0,0)的顶点位置是-0.5,0.5,0.5,也就是左上角的顶点,其他的顶点读者可以自行查看对应关系。通过这些UV映射,数字1的图片则刚好贴在这个面上。

    多材质

    我们要为自定义的Cube几何体每个面贴上不同的贴图,就得对之前的代码做修改。之前的代码中只使用了一个SCNGeometryElement,所以只能使用一个材质。如果我们为每一个面创建一个SCNGeometryElement,那么就可以使用6种不同的材质了。

    var elements: [SCNGeometryElement] = []
    for i in 0..<6 {
        var indices: [UInt32] = []
        (0..<6).forEach { indices.append(UInt32(i * 6) + UInt32($0)) }
        let element = SCNGeometryElement.init(indices: indices, primitiveType: .triangles)
        elements.append(element)
    }
    self.init(sources: [verticeSource, uvSource, normalSource], elements: elements)
    

    接着我们在ViewController中给Cube赋予6种材质。

    var materials: [SCNMaterial] = []
    var colors: [UIColor] = [
        UIColor.init(red: 0xff / 255.0, green: 0xe5 / 255.0, blue: 0.0, alpha: 1.0),
        UIColor.init(red: 0xe9 / 255.0, green: 0.0, blue: 0x3a / 255.0, alpha: 1.0),
        UIColor.init(red: 0x07 / 255.0, green: 0x76 / 255.0, blue: 0xa0 / 255.0, alpha: 1.0),
        UIColor.init(red: 0xf4 / 255.0, green: 0x43 / 255.0, blue: 0x36 / 255.0, alpha: 1.0),
        UIColor.init(red: 0x68 / 255.0, green: 0x9f / 255.0, blue: 0x38 / 255.0, alpha: 1.0),
        UIColor.init(red: 0xef / 255.0, green: 0x6c / 255.0, blue: 0.0, alpha: 1.0),
    ]
    for i in 0..<6 {
        let material = SCNMaterial()
        material.lightingModel = .blinn
        material.diffuse.contents = NumberImageGenerator.createImage(number: i + 1, foregroundColor: colors[(i + 1) % colors.count], backgroundColor: colors[i % colors.count], size: CGSize.init(width: 128, height: 128))
        material.shininess = 1.0
        materials.append(material)
    }
    geometry.materials = materials
    

    我编写了辅助方法NumberImageGenerator.createImage来生成写有数字的图片。我生成的图片大小是128x128,推荐使用2的次方尺寸的图片作为贴图,比如64,128,256,512,1024等。

    贴图配置

    前面我们刚好将图片贴满一个面,如果我们对UV坐标做如下修改会怎么样呢?我将UV的值放大了2倍。

    // Z轴0.5处的平面
    -0.5,   0.5,  0.5,   0,  0,  1, 0, 0,
    -0.5,  -0.5,  0.5,  0,  0,  1, 0, 2,
    0.5,   -0.5,  0.5,  0,  0,  1, 2, 2,
    0.5,    -0.5, 0.5,   0,  0,  1, 2, 2,
    0.5,    0.5,  0.5,    0,  0,  1, 2, 0,
    -0.5,   0.5,  0.5,  0,  0,  1, 0, 0,
    

    如果不做任何其他处理的话,效果如下。



    如果我们希望UV超出1的部分以重复的形式进行贴图,需要进行一些设置。

    material.diffuse.wrapS = .repeat
    material.diffuse.wrapT = .repeat
    

    这样就可以得到下面的效果。



    系统还提供了镜像模式。

    material.diffuse.wrapS = .mirror
    material.diffuse.wrapT = .mirror
    

    效果如下。


    wrapS代表U轴上的重复模式,wrapT代表V轴上的重复模式。默认情况下取值都为.clamp,也就是多出的部分重复边缘的像素点。
    我们还可以通过下面的代码控制图片放大和缩小是使用的采样算法。默认是linear算法,效率高,效果略差。

    material.diffuse.minificationFilter = .nearest
    material.diffuse.magnificationFilter = .nearest
    

    总结

    本文主要介绍了如何使用diffuse贴图以及相关配置,对于法线贴图和specular贴图来说,也遵从相同的原理。读者可以自行构建出一些几何体,然后对其进行贴图来当作练习。

    相关文章

      网友评论

      • 小沛2016:楼主你好 ,你用6个点表示一个面 但是有2个点是重复的 是否可以用4个点?
        小沛2016:@handyTOOL 嗯嗯 我也写了一个 但是上和下的贴图没有显示出来 我想法代码给你 不知道楼主是否有时间帮我看看代码呢?:flushed:
        handyTOOL:可以的,GPU是通过索引去你的顶点数组里面拿数据的,只要保证每个索引对应的顶点正确就没问题。我这么写主要是偷懒,顶点数据是我从之前写的OpenGL系列项目里面直接拷贝过来的:joy:
      • 35378e2877b2:沙发沙发

      本文标题:学习SceneKit之贴图

      本文链接:https://www.haomeiwen.com/subject/mgmaixtx.html