阅读 139

AR贴图中 contentsTransform 的正确使用

问题说明

上次在给物体贴图时,我尝试调整了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)),最后在空间中的相对位置如图:

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

画布上印的内容如下图红色框内的内容,空白处则根据 WrapT/WrapS 的设置,进行重复贴图或者拉伸边缘
最终在屏幕上看到的贴图,就是将画布上投影的图像贴上去:

代码

那么这个代码怎么写呢?根据欧拉角的定义,我们明白:欧拉角(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)
复制代码

效果如下图

关注下面的标签,发现更多相似文章
评论