【问题标题】:How to apply a texture to a specific channel on a 3d obj model in Swift?如何在 Swift 中将纹理应用于 3d obj 模型上的特定通道?
【发布时间】:2021-12-09 06:22:09
【问题描述】:

在我的 3d obj 模型上应用特定纹理时,我现在有点卡住了。

最简单的解决方案是使用let test = SCNScene(named: "models.scnassets/modelFolder/ModelName.obj"),但这需要 mtl 文件将纹理文件直接映射到其中,这在我当前的工作流程中是不可能的。

根据我目前的理解,这让我可以选择使用散射函数将纹理应用于特定语义,例如:

if let url = URL(string: obj) {
    let asset = MDLAsset(url: url)
    guard let object = asset.object(at: 0) as? MDLMesh else {
        print("Failed to get mesh from asset.")
        self.presentAlert(title: "Warning", message: "Could not fetch the model.", firstBtn: "Ok")
        return
    }

    // Create a material from the various textures with a scatteringFunction
    let scatteringFunction = MDLScatteringFunction()
    let material = MDLMaterial(name: "material", scatteringFunction: scatteringFunction)
    let property = MDLMaterialProperty(name: "texture", semantic: .baseColor, url: URL(string: self.textureURL))
    material.setProperty(property)
    
    // Apply the texture to every submesh of the asset
    object.submeshes?.forEach {
        if let submesh = $0 as? MDLSubmesh {
            submesh.material = material
        }
    }

    // Wrap the ModelIO object in a SceneKit object
    let node = SCNNode(mdlObject: object)
    let scene = SCNScene()
    scene.rootNode.addChildNode(node)

    // Set up the SceneView
    sceneView.scene = scene
    ...
}

实际的问题是语义。 3d 模型是在 Unreal 上制作的,对于许多模型来说,有一个 png 纹理,其中包含 3 种语义,即环境光遮蔽、粗糙度和金属。需要在红色通道上应用环境光遮蔽,在贪婪通道上应用粗糙度,在蓝色通道上应用金属。

我怎样才能做到这一点? MdlMaterialSemantic 具有所有these 可能的语义,但金属、环境光遮蔽和粗糙度都是分开的。我尝试简单地在每个上应用纹理,但显然效果不佳。

考虑到我的 .png 纹理将所有这 3 个“打包”在不同的通道下,我该如何处理?我在想也许我可以以某种方式使用一个小脚本直接在应用程序中将映射添加到我端的 mtl 文件中的纹理,但这似乎很粗略哈哈..

如果无法做到这一点,我还有哪些其他选择?我也一直在尝试将 fbx 文件与 assimpKit 一起使用,但我无法加载任何纹理,只能加载黑色模型......

我愿意接受任何建议,如果需要更多信息,请告诉我!非常感谢!

【问题讨论】:

    标签: swift scenekit texture-mapping 3d-model


    【解决方案1】:

    抱歉,我没有足够的代表发表评论,但这可能更像是评论而不是答案!

    您是否尝试过单独加载纹理 png 图像(作为 NS/UI/CGImage),然后手动将其拆分为三个通道,然后分别应用这些通道? (拆分成三个独立的频道并不是那么简单……但您可以使用this grayscale conversion 进行指导,一次只做一个频道。)

    在 SceneKit 中拥有对象后,修改这些材质可能会稍微容易一些。一旦你有一个SCNNode 和一个SCNGeometry 和一个SCNMaterial,你就可以访问任何these materials 并将.contents 属性设置为几乎任何东西(包括XXImage)。

    编辑:
    这是一个扩展,您可以尝试使用 Accelerate 从 CGImage 中提取各个通道。您可以从 NSImage/UIImage 获取 CGImage,具体取决于您使用的是 Mac 还是 iOS(您可以将文件直接加载为其中一种图像格式)。

    我刚刚修改了上面链接中的代码,我对 Accelerate 框架不是很有经验,所以使用风险自负!但希望这能让您走上正确的道路。

    extension CGImage {
        enum Channel {
            case red, green, blue
        }
        
        func getChannel(channel: Channel) -> CGImage? {
            // code adapted from https://developer.apple.com/documentation/accelerate/converting_color_images_to_grayscale
            guard let format = vImage_CGImageFormat(cgImage: cgImage) else {return nil}
            guard var sourceImageBuffer = try? vImage_Buffer(cgImage: cgImage, format: format) else {return nil}
    
            guard var destinationBuffer = try? vImage_Buffer(width: Int(sourceImageBuffer.width), height: Int(sourceImageBuffer.height), bitsPerPixel: 8) else {return nil}
            
            defer {
                sourceImageBuffer.free()
                destinationBuffer.free()
            }
        
            let redCoefficient: Float = channel == .red ? 1 : 0
            let greenCoefficient: Float = channel == .green ? 1 : 0
            let blueCoefficient: Float = channel == .blue ? 1 : 0
            
            let divisor: Int32 = 0x1000
            let fDivisor = Float(divisor)
            
            var coefficientsMatrix = [
                Int16(redCoefficient * fDivisor),
                Int16(greenCoefficient * fDivisor),
                Int16(blueCoefficient * fDivisor)
            ]
            
            let preBias: [Int16] = [0, 0, 0, 0]
            let postBias: Int32 = 0
            
            vImageMatrixMultiply_ARGB8888ToPlanar8(&sourceImageBuffer,
                                                   &destinationBuffer,
                                                   &coefficientsMatrix,
                                                   divisor,
                                                   preBias,
                                                   postBias,
                                                   vImage_Flags(kvImageNoFlags))
            
            guard let monoFormat = vImage_CGImageFormat(
                bitsPerComponent: 8,
                bitsPerPixel: 8,
                colorSpace: CGColorSpaceCreateDeviceGray(),
                bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue),
                renderingIntent: .defaultIntent) else {return nil}
            
            guard let result = try? destinationBuffer.createCGImage(format: monoFormat) else {return nil}
            
            return result
        }
    }
    

    【讨论】:

    • 啊,可以把图片分成三个通道吗?我有很多麻烦这个信息。您可能知道这样做的简单方法吗?就我的知识而言,看起来有点先进,我猜也许一个 pod 或扩展可以帮助我。在此期间,我会尝试更多地研究它!
    • @David,这绝对是可能的。我在上面添加了一个扩展,您可以尝试使用我从上一个链接改编的 vImage/Accelerate 的解决方案。这应该足够快(虽然我不是这个框架的专家)。
    • 根据您的代码的其余部分,您可以通过多种其他方式执行此操作。如果您已经拥有自己的金属代码,那么使用计算着色器将很容易转换为金属纹理,这可能就是我要做的。或者,如果您使用 CoreImage,您可以使用 CIColorPolynomial 过滤器。或者你可以在 CPU 上读取 CGImage 的原始字节。
    • 好的,谢谢!我已经设法使它工作,但是按需处理它太慢了。我将不得不研究另一种解决方案,也许在将模型和纹理保存在数据库中时在 api 端进行转换,不确定。与此同时,我的另一个目标是加载带有纹理的 fbx 模型 :) 感谢您的帮助!
    • 用您的代码重新测试过,它确实可以工作并且速度足够快!非常感谢您的帮助:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-22
    • 2016-05-10
    • 2021-07-20
    • 2018-10-18
    • 1970-01-01
    • 2019-05-15
    • 1970-01-01
    相关资源
    最近更新 更多