问题说明
上次在给物体贴图时,我尝试调整了node.geometry.firstMaterial.diffuse.contentsTransform,结果并不如我想象的那样好用,甚至完全搞不明白。这个旋转到底是怎么回事,应该怎么理解才对。。。。
坐标
首先,不管贴图本身的尺寸是多少,当被应用到几何体上时,都会按照材质球的方式进行贴图,也就是铺满几何体表面。也就是范围是 x 轴方向 0~1,y 方向也是 0~1


理解这些后,就可以进行简单的变换操作了:
如果你想将贴图上下颠倒,应该反转 y 轴,再向 y 轴正方向平移 1 个单位;
利用画布理解 contentsTransform
但是,只知道上面这些内容是不够的,contentsTransform是个 4x4 的矩阵,这意味着它可以完成更复杂的变换操作,那么怎么理解这个矩阵对显示内容的影响呢?经过我的反复试验,我认为:最恰当的方式是利用“画布”来理解,contentsTransform本质是对画布的操作。
如下图,贴图是 1x1 的方块,画布也是 1x1 的方块,位于贴图前面,它的坐标原点在左上角,x 轴指向右侧,y 轴指向下方,z 轴指向屏幕内::

contentsTransform时,本质就是在操作画布,比如将画布先绕 y 轴旋转 50 度,再绕 x 轴旋转 50 度(等同于欧拉角(x:50, y:50, z:0)),最后在空间中的相对位置如图:

最终将贴图利用平行投影,将图像印在画布上:



代码
那么这个代码怎么写呢?根据欧拉角的定义,我们明白:欧拉角(x:50, y:50, z:0),等同于先绕 y 轴旋转 50 度,再绕 x 轴旋转 50 度
// 苹果文档中对欧拉角的说明
/**
@property eulerAngles
@abstract Determines the receiver's euler angles. Animatable.
@dicussion The order of components in this vector matches the axes of rotation:
1. Pitch (the x component) is the rotation about the node's x-axis (in radians)
2. Yaw (the y component) is the rotation about the node's y-axis (in radians)
3. Roll (the z component) is the rotation about the node's z-axis (in radians)
SceneKit applies these rotations in the reverse order of the components:
1. first roll
2. then yaw
3. then pitch
*/
open var eulerAngles: SCNVector3
从欧拉角到矩阵,这个矩阵怎么写呢?其实有多种方法,如下面这样:
// 创建一个临时的 SCNNode 进行欧拉角转矩阵,永远不会出错,但是可能有性能损失
let tempNode = SCNNode()
tempNode.eulerAngles = SCNVector3Make(50/180*Float.pi, 50/180*Float.pi, 0)
let rotationYXtemp = tempNode.transform
// 根据欧拉角定义,用SCNMatrix4Rotate,先进行绕 y 旋转,再绕 x 轴旋转
let rotationY = SCNMatrix4MakeRotation(50/180*Float.pi, 0, 1, 0)
let rotationYX = SCNMatrix4Rotate(rotationY, 50/180*Float.pi, 1, 0, 0)
// 根据欧拉角定义,用矩阵乘法,先进行绕 y 旋转,再绕 x 轴旋转
let rotationX = SCNMatrix4MakeRotation(50/180*Float.pi, 1, 0, 0)
let rotationYX2 = SCNMatrix4Mult(rotationX, rotationY)
// 根据欧拉角定义,用四元数乘法,先进行绕 y 旋转,再绕 x 轴旋转
let qx = simd_quaternion(50/180*Float.pi, simd_make_float3(1, 0, 0))
let qy = simd_quaternion(50/180*Float.pi, simd_make_float3(0, 1, 0))
let q = simd_mul(qy, qx) //这里顺序与矩阵乘法不同
let rotationYX3 = SCNMatrix4(simd_matrix4x4(q))
// 打印 rotationYXtemp, rotationYX, rotationYX2 三者值完全相等, rotationYX3 浮点数最后两位有计算误差
planeNode?.geometry?.firstMaterial?.diffuse.contentsTransform = rotationYX
最终在屏幕上看到的贴图,就是将画布上投影的图像贴上去:

动画支持
contentsTransform这个属性是 Animatable 的,也就是支持动画,我们来试一试动画效果。
let anim = CABasicAnimation(keyPath: "contentsTransform")
anim.isRemovedOnCompletion = false;
anim.fillMode = .forwards
anim.duration = 5
anim.toValue = rotationYX
planeNode?.geometry?.firstMaterial?.diffuse.addAnimation(anim, forKey: nil)
效果如下图
