贴图
获取示例代码,本文代码在分支chapter5中。
前言
上一篇我们介绍了材质的基本概念,这一篇我们将重点介绍材质中的一个概念 - 贴图。本文将重点说明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贴图来说,也遵从相同的原理。读者可以自行构建出一些几何体,然后对其进行贴图来当作练习。