如何将纹理应用到 Swift 中 3d obj 模型的特定通道?
How to apply a texture to a specific channel on a 3d obj model in Swift?
在我的 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 = [=11=] 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 一起使用,但我无法加载任何纹理,只能加载黑色模型...
我乐于接受任何建议,如果需要更多信息,请告诉我!非常感谢!
抱歉,我没有足够的代表发表评论,但这可能更像是评论而不是答案!
您是否尝试过单独加载纹理 png 图像(作为 NS/UI/CGImage),然后手动将其拆分为三个通道,然后分别应用这些通道? (分成三个单独的通道并不像想象的那么简单...但是您可以使用 this grayscale conversion 作为指导,一次只做一个通道。)
在 SceneKit 中拥有对象后,修改这些材质可能会稍微容易一些。一旦你有了一个 SCNNode
和一个 SCNGeometry
和一个 SCNMaterial
你就可以访问任何 these materials 并将 .contents
属性 设置为几乎任何东西(包括 XXImage)。
编辑:
这是一个扩展,您可以尝试使用 Accelerate 从 CGImage 中提取各个通道。您可以从 NSImage/UIImage 获取 CGImage,具体取决于您使用的是 Mac 还是 iOS(并且您可以将文件直接加载到其中一种图像格式)。
我刚刚改编了上面 link 的代码,我对 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
}
}
在我的 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 = [=11=] 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 一起使用,但我无法加载任何纹理,只能加载黑色模型...
我乐于接受任何建议,如果需要更多信息,请告诉我!非常感谢!
抱歉,我没有足够的代表发表评论,但这可能更像是评论而不是答案!
您是否尝试过单独加载纹理 png 图像(作为 NS/UI/CGImage),然后手动将其拆分为三个通道,然后分别应用这些通道? (分成三个单独的通道并不像想象的那么简单...但是您可以使用 this grayscale conversion 作为指导,一次只做一个通道。)
在 SceneKit 中拥有对象后,修改这些材质可能会稍微容易一些。一旦你有了一个 SCNNode
和一个 SCNGeometry
和一个 SCNMaterial
你就可以访问任何 these materials 并将 .contents
属性 设置为几乎任何东西(包括 XXImage)。
编辑:
这是一个扩展,您可以尝试使用 Accelerate 从 CGImage 中提取各个通道。您可以从 NSImage/UIImage 获取 CGImage,具体取决于您使用的是 Mac 还是 iOS(并且您可以将文件直接加载到其中一种图像格式)。
我刚刚改编了上面 link 的代码,我对 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
}
}