问题
I'm using SCNLevelOfDetails with SceneKit.
I was wondering if there's a way to know when the engine changes the levelOfDetails for a particular node's geometry.
Many thanks.
回答1:
I'm answering my own question. To give more context, we needed to know when the LOD would change in order to stop an idle animation, our model is skinned. What happens if you don't disable this animation when the LOD changes is that the geo goes all over the place and it looks wrong.
The solution for us was to clone the rendered node geo, create a node using this geo clone and give it our different LOD for when it's far, add this node to the hierarchy. This way the node is not affected by the joints and the animation. We call this node lowLODNode.
Your rendered node will have LOD of nil when it's too far so it disappear and the lowLODNode kicks in. Note the the lowLODNode has a LOD of nil when it's close.
To disable the animation we use the renderer loop to compute distance from POV and disable it if the LOD threshold is reached. Note that we are iterating through more than a 1000 nodes every 0.25 seconds, this calculation only takes 0.0016 second in average thanks to the simd library.
I'm adding a code example to show off the technique:
private func setupLOD() {
// Keep animation and LOD working with a rigged node.
// Scene graph
// YourNodeSubclass
// - loadedDAENode
// - rigNode
// - geoGroupNode
// - geoNode -> this one has a SCNGeometry, empty geo when far LOD = [ geoNode.geo, nil ]
// - lowLODNode -> this one has the low poly versions and an empty geo when close LOD = [ nil, lowPoly1, lowPoly2, ... ]
// LOD for the geometry affected by the rig. The geo disappear when far enough. Animations are working.
let lowLevelMain = SCNLevelOfDetail(geometry: nil, worldSpaceDistance: 3.0)
renderedNode.childNode(withName: "\(renderedNodeName)_geo", recursively: true)?.geometry?.levelsOfDetail = [lowLevelMain]
// 1) Load low poly geo and eventual LOD geos from folder(s).
// 2) Copy each LOD geo.
// 3) Create a geo, make it transparent. This will be the high LOD geo the default one.
// 4) Create a lowLODNode using this geo.
// 5) Ceate your [LOD]
// 6) Assign LOD to lowLODNode geometry
// 7) Add the lowLODNode as a child to the rendered node
// 1) 2)
if let lowVersionScene = SCNScene(named: "art.scnassets/yourScene.dae"), let itemNode = lowVersionScene.rootNode.childNode(withName: "rootNodeName", recursively: false), let geo = itemNode.childNode(withName: "nodeWithGeo", recursively: true)?.geometry?.copy() as? SCNGeometry {
// 3)
let tranparentCube = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.0)
tranparentCube.firstMaterial?.diffuse.contents = UIColor.clear
// 4)
let lowLODNode = SCNNode(geometry: tranparentCube)
// 5)
let lowLOD = SCNLevelOfDetail(geometry: geo, worldSpaceDistance: 3.0)
// 6)
lowLODNode?.levelsOfDetail = [lowLOD]
// 7)
renderedNode.addChildNode(lowLODNode)
}
}
The only downside to this is that we have to use LOD based on distance NOT pixel size because we need to know when to disable the animation and don't want to compute the size of the object in pixels.
We were able to really lower the CPU and GPU usage using this technique.
来源:https://stackoverflow.com/questions/55936873/scnlevelofdetails-delegate-notification