如何在RealityKit中指定双面材料?

12

我正在尝试在RealityKit中加载模型和纹理(在ARView实例中设置),但是我似乎无法弄清楚如何指定材质应该是双面的。

我已经将模型加载为ModelEntity,将纹理加载为TextureResource。模型和纹理都加载了,但是只渲染了一面。由于模型是开放的(即背面可见),所以渲染时会出现间隙。

到目前为止,我有:

let entity: ModelEntity = try .loadModel(named: "model.obj")

var material = SimpleMaterial()
material.baseColor = try .texture(.load(named: "texture.png"))
entity.model?.materials = [material]

我希望能找到类似的属性

material.twoSided = true

但到目前为止,我在RealityKit中还没有找到相应的东西。

有人知道如何在RealityKit中设置双面材质吗?


1
将 .OBJ 文件加载到 RealityKit 中可靠吗?我以为它只接受 .USDZ 格式的文件。 - HelloTimo
2
@HelloTimo 是的,使用上述方法似乎可以很好地加载OBJ文件。我认为这是因为OBJ是在USD场景描述中用于存储几何体的常见模型类型之一。 - smithco
2
谢谢@smithco,它确实有效。至少从本地资产同步,这是我刚刚尝试的。很酷 - 我认为这在RealityKit中是未记录的。 - HelloTimo
7个回答

3

正如其他人已经回答的,你无法通过编程来实现它。 但是,您可以通过检查面板手动为每个模型执行此操作。 请参见下面的图片。在底部附近,您有“双面”复选框。

enter image description here


2
这对我有用。我在Reality Composer中创建了一个球体,导出为USDZ并勾选了那个框。球体足够大,我可以往里面看。非常有趣的体验。我尝试播放视频,它也能工作,但有点扭曲。 - multitudes
这是什么应用程序?我无法在XCode或Reality Composer中找到这个界面。 - Marcus Adams
这是xCode,请先选择3D模型,然后再尝试查找它。 - Nativ

3

目前通过RealityKit API似乎没有编程实现这一点的方法。

您能更改模型定义,使其不进行背面消隐吗?例如,在我导入USDZ文件时,它将网格的一部分定义为:

def Mesh "Plane_1"
  {
    uniform bool doubleSided = 1

您可以先使用usdzconvert将obj文件转换为used文件(https://developer.apple.com/download/more/?=USDPython),然后手动编辑该文件,并将其导入到您的场景中。这种方法可能取决于模型的设置。您可以向materials数组传递多个材质,并应用于模型的不同部分,您可以查看模型需要多少材质。
entity.model?.mesh.expectedMaterialCount

不幸的是,这些是程序生成的网格,因此将其烘焙成 USDZ 文件以进行加载相当不方便。但是,作为备选方案,可以尝试对生成的文件进行后处理。 - smithco

2

在RealityKit中指定双面材料

您可以通过使用PhysicallyBasedMaterial而不是SimpleMaterial来为您的ModelEntity设置材料,并将faceCulling属性更改为.none,从而在RealityKit中本地实现这一点。

您的原始代码可能会像这样更新以具有双面材料:

let entity: ModelEntity = try! .loadModel(named: "model.obj")

var material = PhysicallyBasedMaterial()
material.baseColor.texture = // your texture here
material.faceCulling = .none
entity.model?.materials = [material]

您可以在官方文档中了解有关基于物理的材质面剔除属性的更多信息。


2

这是可行的,但需要改变思路:不需要创建双面 材质,而是创建双面网格。实现方法是通过对每个模型中的每个部分进行翻转法线(并反转三角形)来创建一个双面网格。使用以下代码,解决上述问题的方法如下:


do {
   let entity: ModelEntity = try .loadModel(named: "model.obj")
   if let model = entity.model {
       try model.mesh.addInvertedNormals()
       // or alternatively, since this model isn't onscreen yet:
       // model.mesh = try model.mesh.addingInvertedNormals()
       var material = SimpleMaterial()
       material.baseColor = try .texture(.load(named: "texture.png"))
       model.materials = [material]
       entity.model = model
   }
} catch {}

现在实体将在两侧显示材料。以下是实现它的代码:

import Foundation
import RealityKit

public extension MeshResource {
    // call this to create a 2-sided mesh that will then be displayed 
    func addingInvertedNormals() throws -> MeshResource {
        return try MeshResource.generate(from: contents.addingInvertedNormals())
    }
    
    // call this on a mesh that is already displayed to make it 2 sided
    func addInvertedNormals() throws {
        try replace(with: contents.addingInvertedNormals())
    }

    static func generateTwoSidedPlane(width: Float, depth: Float, cornerRadius: Float = 0) -> MeshResource {
        let plane = generatePlane(width: width, depth: depth, cornerRadius: cornerRadius)
        let twoSided = try? plane.addingInvertedNormals()
        return twoSided ?? plane
    }
}

public extension MeshResource.Contents {
    func addingInvertedNormals() -> MeshResource.Contents {
        var newContents = self

        newContents.models = .init(models.map { $0.addingInvertedNormals() })

        return newContents
    }
}

public extension MeshResource.Model {
    func partsWithNormalsInverted() -> [MeshResource.Part] {
        return parts.map { $0.normalsInverted() }.compactMap { $0 }
    }
    
    func addingParts(additionalParts: [MeshResource.Part]) -> MeshResource.Model {
        let newParts = parts.map { $0 } + additionalParts
        
        var newModel = self
        newModel.parts = .init(newParts)
        
        return newModel
    }
    
    func addingInvertedNormals() -> MeshResource.Model {
        return addingParts(additionalParts: partsWithNormalsInverted())
    }
}

public extension MeshResource.Part {
    func normalsInverted() -> MeshResource.Part? {
        if let normals, let triangleIndices {
            let newNormals = normals.map { $0 * -1.0 }
            var newPart = self
            newPart.normals = .init(newNormals)
            // ordering of points in the triangles must be reversed,
            // or the inversion of the normal has no effect
            newPart.triangleIndices = .init(triangleIndices.reversed())
            // id must be unique, or others with that id will be discarded
            newPart.id = id + " with inverted normals"
            return newPart
        } else {
            print("No normals to invert, returning nil")
            return nil
        }
    }
}

因此,调用addingInvertedNormals()函数会创建一个网格,可以在两侧显示相同的材质。我已经使用它来创建了一个双面网格平面。

通过一些额外的工作(留作练习!),您可以为创建的部分提供不同的材质索引,并在每一侧显示不同的材质。


1
我认为目前没有办法做到这一点。RealityKit仍处于早期阶段。RealityKit中的材料支持目前非常有限。我认为在iOS 14或之后会有计划改变这一点。文档中有评论描述了尚不存在的功能,例如Material协议表示“描述定义网格部分外观的材料(颜色、着色器、纹理等)。”目前还没有定义自定义着色器的方法。如果您查看RealityKit框架包,会发现其中有着色器图形定义和新的材料特性,但它们尚未在公共API中公开。我猜测将会有一个着色器图形编辑器、支持自定义着色器和双面材料的出现。

2
谢谢你的回答!如果您能提供陈述的链接或来源,我认为这将对此回答和问题有很大帮助。 - creyD

1

由于RealityKit仍然没有SceneKit中可以找到的isDoubleSided实例属性,因此我提供了两种解决方法来绕过这个限制。

Autodesk Maya –> 解决方案1

enter image description here

在Autodesk Maya中,创建两个多边形立方体,然后稍微缩小其中一个,选择它并从“建模”主菜单集应用网格显示 > 反转命令。此命令将面的法线反转180度。导出两个模型(第一个具有默认法线方向,第二个具有反转的法线)。以下是Maya Script Editor的Python代码:
import maya.cmds as cmds

cmds.polyCube(n='InnerCube', w=2.99, h=2.99, d=2.99)

# reversing normals of polyfaces
cmds.polyNormal(nm=0)

cmds.polyCube(n='OuterCube', w=3, h=3, d=3)
cmds.select(clear=True)

接下来,将两个模型都导入到RealityKit中。


RealityKit -> 解决方案2

enter image description here

在RealityKit中以编程方式完成此操作,需要将原始模型及其副本加载到场景中,然后对于内部模型,使用faceCulling = .front来裁剪正面多边形。稍微缩小内部模型。
此外,您还可以使用裁剪来实现轮廓边框等效果。
import UIKit
import RealityKit

class ViewController: UIViewController {
    
    @IBOutlet var arView: ARView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let boxScene = try! Experience.loadBox()
        
        // INNER
        var innerMaterial = PhysicallyBasedMaterial()
        innerMaterial.faceCulling = .front           // face culling
        innerMaterial.baseColor.tint = .green
        let box = boxScene.steelBox?.children[0] as! ModelEntity
        box.scale = [1,1,1] * 12
        box.model?.materials[0] = innerMaterial
        box.name = "Inner_Green"
        arView.scene.anchors.append(boxScene)
        
        // OUTER
        var outerMaterial = SimpleMaterial()
        let outerBox = box.clone(recursive: false)
        outerBox.model?.materials[0] = outerMaterial
        outerBox.scale = [1,1,1] * 12.001
        outerBox.name = "Outer_White"
        // boxScene.steelBox?.addChild(outerBox)     // Enable it
        
        print(boxScene)
    }
}

0
你所描述的是剔除。例如,检查MTLCullMode。从那里,你可以跳转到各种设置剔除模式的点(你感兴趣的是无剔除)。

2
如果您有关于如何为RealityKit实体中的网格设置剔除选项的具体信息,那将非常有帮助。在我阅读文档时,并没有找到任何此类选项。我假设它应该是材质选项而不是剔除选项,因为这是苹果在SceneKit API中设置的方式,但我可能是错的。 - smithco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接