材质
获取示例代码,本文代码在分支chapter4中。
前言
前面我们介绍了几何体的相关知识,这篇我将为大家介绍材质,那什么是材质呢?简单来说,就是你的几何体的外观,比如是什么颜色,反光强度等等。那么在SceneKit中我们可以改变几何体的哪些外观呢?接下来我将一一介绍。
光照模型
提到材质就不得不提到光照模型。在现实生活中,我们有太阳,日光灯,蜡烛等可以产生光的光源,光照射在物体上,不同的物体呈现出不同的质感,这些都是很平常的事情。但是在计算机里,想要使用光源照射3D模型,产生出想要的质感就不是那么平常了。在OpenGL中,我们需要使用Shader根据提供的材质参数和光照来计算每个像素的颜色,从而产生物体被光照射的感觉。想要了解Shader中是如何实现光照模型的可以看我的两篇文章基本光照和高级光照。其中有涉及到lambert和blinn两种光照模型。下面我将结合SceneKit简单的介绍这两种光照模型。
Lambert
在SceneKit中,使用SCNMaterial
来表示材质。SCNMaterial
的一个属性lightingModel
表示的就是使用何种光照模型。如果我们选择Lambert光照模型,并且设置如下属性,并且把material赋给球形几何体geometry
。
let material = SCNMaterial()
material.lightingModel = .lambert
material.diffuse.contents = UIColor.red
material.ambient.contents = UIColor.init(white: 0.1, alpha: 1)
material.locksAmbientWithDiffuse = false
geometry.materials = [material]
那么渲染出来的球体将会如下所示。
我们使用的灯光在(0,6,3)
处,所以球体上面是照射到灯光的,下面是灰色的。这里涉及到了两个光照分量,diffuse
和ambient
。diffuse
表示几何体的本色,所以灯光照射到的部分就是本色红色,注意我用的灯光是白色的,如果灯光是其他颜色,则会和几何体的本色混合,也就是两个颜色进行3维向量乘法。那灯光照射不到的地方呢?就是ambient
环境光。环境光的出现是为了让灯光照射不到的地方不会是全黑,你可以试试把环境光改成其他颜色,看看渲染结果如何。由于PBR光照模型中ambient
和diffuse
是锁定的,所以需要把locksAmbientWithDiffuse
设置为false
,否则ambient
只能和diffuse
取相同的值。关于PBR光照模型我会在后面的文章单独介绍。最后将material
赋值给几何体,这里material
是被放在一个数组里赋值的,如果你的几何体有多个element,系统会根据顺序为每个element提供不同的材质。第n个element会得到第n%材质个数
个材质。
diffuse还和法线相关,法线和光线的夹脚越小,则越亮。法线就是上一篇代码里的
normals
。
总的来说,Lambert模型就是最终颜色=光线和法线夹脚系数*光照颜色*本色diffuse + 环境色ambient
。
Blinn
Blinn光照模型其实就是在lambert基础上加上高光,我们将光照模型修改为.blinn
,再设置高光的属性。
material.lightingModel = .blinn
material.specular.contents = UIColor.white
material.shininess = 1.0
当光线被反射后和我们视线的夹脚比较小的时候,在金属或者玻璃等反光材质下,会看到非常亮的区域,我们称之为高光。越是光滑的物体,高光区域会越小。我们用shininess
来表示物体的表面有多闪(光滑),它的值从0到1。值越大,越光滑。下面是值为0.2和1的效果图。material.specular
表示高光的颜色,不过最终呈现的高光颜色受material.specular
和灯光的颜色共同影响,是它们颜色值的三维向量相乘。
材质参数的取值
上面我只给材质的参数赋予了颜色值,除了颜色,还可以赋予贴图,或者说是图片。diffuse
和specular
都是可以接受图片对象的。比如给diffuse
赋值一张地球的贴图。
material.diffuse.contents = UIImage.init(named: "earth.jpg")
效果如下。 specular接受的贴图就比较特殊,是一张黑白两色的图。图中黑色对应的地方将没有高光。
下面两张图分别是使用了和没使用specular贴图的效果图。第一张图中明显大海部分没有高光。
不管是什么贴图,都是需要几何体提供UV数据的,也就是所谓的贴图坐标,在上一篇的代码中有涉及到。
let uvs: [CGPoint] = [
CGPoint(x: 0, y: 1),
CGPoint(x: 0, y: 0),
CGPoint(x: 1, y: 0),
CGPoint(x: 1, y: 1),
]
let uvSource = SCNGeometrySource.init(textureCoordinates:
uvs)
系统提供的球形几何体已经有了UV数据,所以才可以轻松的进行贴图,关于贴图的更多信息,会在后面的文章中介绍,或者你也可以去看我写的基于OpenGL的贴图文章。
反射贴图
SceneKit还提供了反射贴图功能,可以使用CubeMap或者SphereMap来当作环境贴图。这里我只为大家演示一下,更深入的介绍会在后面的文章中进行。下面是设置反射贴图的代码。cube-X.jpg
等图片都在demo项目中。
material.reflective.contents = [
UIImage.init(named: "cube-1.jpg"),
UIImage.init(named: "cube-2.jpg"),
UIImage.init(named: "cube-3.jpg"),
UIImage.init(named: "cube-4.jpg"),
UIImage.init(named: "cube-5.jpg"),
UIImage.init(named: "cube-6.jpg"),
]
material.fresnelExponent = 1.7
效果图如下。是不是有一种被玻璃包裹的感觉。反射贴图主要就是通过对CubeMap或者SphereMap的反射,模拟物体反射周围环境的一种技术。
法线贴图
我们上面有说过我们会给几何体提供法线数据,但是这些数据是每个顶点才有一个,顶点之间区域的法线就只能通过线性插值来计算了。法线贴图则是通过贴图的方式来弥补这一缺陷,更多原理性质的介绍就不在这展开了,下面是例子用的法线贴图。 代码设置也很简单。
material.normal.contents = UIImage.init(named: "earth_NRM.png")
效果如下。是不是瞬间有了凹凸感。
总结
这篇文章主要介绍了材质的一些基本功能,还有很多其他的功能是没有提到的。东西其实很多,要在一篇文章中全部铺开不现实,看完这一篇读者心中对光照模型和材质有个基本了解就可以了。后面的文章会针对材质中比较复杂的特性逐一进行深入介绍。