iOS SceneKit 纹理贴图大小适配

961 阅读2分钟

问题背景

我现在想用scenekit写一个600米*600米的地板,地板纹理如下所示:

截屏2021-07-04 下午5.00.30.png

但是我遇到了一个问题,如果将该纹理通过下面这种方式直接赋值,会出现很严重的变形问题:

let geo = SCNPlane.init(width: 600, height: 600)
geo.firstMaterial?.diffuse.contents = UIImage.init(named:"rustediron-streaks-albedo.png")

上面代码运行了之后地板会变成如下图的马赛克一样:

Screen Shot 2021-07-04 at 4.44.21 PM.png

解决思路

因为scenekit相关的资料网上比较少,我这里是直接看官方文档,经过翻阅找到了下图所示文档:

截屏2021-07-04 下午4.49.52.png 阅读了一番官方文档,我这边锁定了三个属性:1、contentstransform 2、wrapT 3、wrapS

刚开始我觉得很简单,直接把wrapT 和 wrapS赋值为reapeat模式不就完了,然后发现赋值完了之后不起作用,然后仔细观察文档,发现了一句话:if you use the contentsTransform property to shrink a texture relative to the surface of a geometry,也就是说只有你用了contentstransform的情况下,缠绕模式才起作用。

contentstransform属性解读:

截屏2021-07-04 下午5.02.00.png 由上图我们可以看出,这个矩阵表示的是纹理贴图的映射矩阵,我们可以根据几何形状的大小改变这个矩阵,达到适配纹理的效果。

返回我之前遇到的问题:我这边是创建了一个600米的平面,但纹理实际上只能适配6米的地板,我可以将该纹理的对应的作用范围缩小100倍,然后再将wrapT和wrapS调整为repeat模式即可。 对应代码:

        geo.firstMaterial?.diffuse.contents = UIImage.init(named:"rustediron-streaks-albedo.png")
        let mat = SCNMatrix4Scale(SCNMatrix4Identity, 100, 100, 100)
        geo.firstMaterial?.diffuse.contentsTransform = mat
        geo.firstMaterial?.diffuse.wrapS = .repeat // 必须和content transform一起使用
        geo.firstMaterial?.diffuse.wrapT = .repeat

问题解决

在运行上述代码之后,得到如下所示效果:

Screen Shot 2021-07-04 at 5.16.25 PM.png

对比刚开始的马赛克形式的地板,效果好了许多。

总结

使用SCNMaterialProperty的contentstransform属性可以做到很多事情,比如可以做到镜像反转,可以旋转纹理等等,再配合wrapT和wrapS的模式,可以解决很多3D纹理适配问题。