Ich habe die Kombination von & Portal versucht, um das virtuelle Objekt zu texturieren, mit dem die Kameraerfassung durchgeführt wird. Lassen Sie uns den Boden als Thema zusammenbrechen.
Vollständiges Bild Wenn das aufgenommene Bild der Kamera auf diese Weise auf das virtuelle Objekt eingefügt werden kann, ist es möglich, den Boden stufenförmig zu gestalten, die Wand zu öffnen und als Tür zu verwenden usw.
(1) Bereiten Sie ein kastenförmiges 3D-Modell (im Folgenden "Gitter") vor, das in den Boden eingebettet werden soll. Eine kleine Box (im Folgenden als "Zelle" bezeichnet) ist in einer Größe von 10 x 10 oben auf dem Gitter verteilt. Stellen Sie sicher, dass die Zellen zusammenfallen und sich am unteren Rand des Rasters sammeln. (2) Führen Sie bei aktivierter Ebenenerkennung von ARKit zum Zeitpunkt des Tippens auf den Bildschirm einen Treffertest in der Mitte des Bildschirms durch. (3) Erhalten Sie die Transformation der Ebene, die im Treffertest erhalten wurde, und setzen Sie sie in das Gitter. Das Gitter passt zur Trefferposition und -haltung. ④ Nehmen Sie das Kamerabild auf ⑤ Konvertieren Sie die Eckpunkte (Weltkoordinaten der vier Ecken) jeder Zellenoberfläche in Bildschirmkoordinaten (normalisierte Gerätekoordinaten). ⑥ Da ④ als Zelltextur verwendet wird, werden die normalisierten Gerätekoordinaten von ⑤ in UV-Koordinaten konvertiert. ⑦ Stellen Sie ⑥ für die Texturkoordinaten jeder Zelle ein ⑧ Stellen Sie den SCN-Physikkörper vom Knoten der mittleren Zelle nach außen so ein, dass die 10x10-Zelle von der Mitte aus zusammenfällt.
Die Punkte werden unten beschrieben.
Verwendung des aufgenommenen Kamerabildes als Textur.
Stellen Sie für die Textur jeder Zelle das aufgenommene Bild so ein, wie es in der obigen Abbildung gezeigt ist. Für alle Zellen wird das gleiche Bild verwendet, und die UV-Koordinaten (rote Punkte) der vier Ecken der Zelloberfläche sind für jede Zelle unterschiedlich. Die Methode zum Erhalten der Scheitelpunktkoordinaten von Zellen auf dem Bild entspricht der normalen Wiedergabe eines 3D-Modells. Weltkoordinaten jedes Scheitelpunkts der Zelle → Ansichtskonvertierung → Projektionskonvertierung.
//Modelltransformationsmatrix (Zelloberflächenknoten im Weltkoordinatensystem)
let modelTransform = cellFaceNode.simdWorldTransform
//Transformationsmatrix anzeigen. Inverse Matrix des Kamera-Ansichtspunkts (Verschieben Sie alle Weltkoordinatenscheitelpunkte an die Position mit der Kamera als Ursprung).
let viewTransform = cameraNode.simdTransform.inverse
//Projektionstransformationsmatrix
let projectionTransform = simd_float4x4(camera.projectionTransform(withViewportSize: self.scnView.bounds.size))
//MVP-Matrix
let mvpTransform = projectionTransform * viewTransform * modelTransform
//Projektionskoordinatenkonvertierung
var position = matrix_multiply(mvpTransform, SIMD4<Float>(vertex.x, vertex.y, vertex.z, 1.0))
Aber! Dies kann nicht so verwendet werden, wie es ist. Ich habe den folgenden Artikel gefunden (geholfen), als ich nach etwas gesucht habe, das nicht passt.
Das Ergebnis der Projektionskoordinatenkonvertierung muss in ** "normalisierte Gerätekoordinaten" ** von -1,0 bis 0,0 konvertiert werden. Teilen Sie x, y durch die w-Komponente des Ergebnisses der Projektionskonvertierung der Modellansicht wie folgt. ..
// -1.0~1.Normalisiert auf einen Wert von 0. Teilen Sie durch w, um "normalisierte Gerätekoordinaten" zu erhalten.
position = position / position.w
Nachdem wir nun ein Koordinatensystem von -1,0 bis 0,0 haben, können wir es wie folgt als Texturkoordinaten verwenden.
//In UV-Koordinaten konvertieren
let texcordX = CGFloat(position.x + 1.0) / 2.0
let texcordY = CGFloat(-position.y + 1.0) / 2.0
texcoords.append(CGPoint(x: texcordX, y: texcordY))
Da die UV-Koordinaten jedes Scheitelpunkts des Zellknotens beliebig eingestellt werden müssen, schreiben Sie sie in Metall. .. .. Ich dachte, aber ich konnte es nur mit SceneKit leicht realisieren, indem ich auf diesen Artikel So erstellen Sie eine benutzerdefinierte Geometrie mit SceneKit + Bonus verweise. ..
//Generieren Sie die Zelloberflächengeometrie
let texcoordSource = SCNGeometrySource(textureCoordinates: texcoords)
let cellFaceGeometry = SCNGeometry(sources: [vertex, texcoordSource], elements: [element])
let cellFaceMaterial = SCNMaterial()
cellFaceMaterial.diffuse.contents = captureImage
cellFaceGeometry.materials = [cellFaceMaterial]
cellFaceNode.geometry = cellFaceGeometry //Geometrie ersetzen
Durch Erstellen einer benutzerdefinierten Geometrie mit "SCNGeometry" können Sie die Koordinaten der Textur frei angeben. Da der Zellenknoten dieses Mal bereits beim Starten der App generiert wurde, wird die Geometrie des Zellenknotens beim Tippen auf den Bildschirm erfasst und nur die Texturkoordinaten werden ersetzt, um eine benutzerdefinierte Geometrie zu erstellen.
Tips
① Debug-Option Dieses Mal war es schwierig, die Existenz und das Trennzeichen von Knoten zu verstehen, da transparente Knoten vorhanden waren und die Zellen verteilt waren. Wenn Sie in einem solchen Fall die folgende Debug-Option festlegen, wird an der Grenze des Knotens eine weiße Linie angezeigt, die leicht zu verstehen und bequem ist.
//Debug-Option, die für SCNSceneRenderer angegeben werden kann
scnView.debugOptions = [.showBoundingBoxes]
② Wenn der Knoten klein ist, funktioniert die physikalische Beurteilung nicht. Zuerst fing ich an, die Gittergröße auf 10 cm zu verkleinern, aber als die Zelle fiel, wurde nicht festgestellt, dass sie mit dem unteren Knoten kollidierte, und ich konnte das Phänomen des Durchrutschens durch den Boden nicht vermeiden. SceneKit hat eine Eigenschaft namens "ContinuousCollisionDetectionThreshold" in "SCNPhysicsBody" als Gegenmaßnahme gegen ein solches Durchrutschen. Wenn Sie die Größe des in Konflikt stehenden Knotens angeben, wird es anscheinend so berechnet, dass es nicht durchschlüpft, aber es hat nicht gut funktioniert. Apples Dokumentation sagt " Die kontinuierliche Kollisionserkennung ist mit Leistungskosten verbunden und funktioniert nur für sphärische physikalische Formen, liefert jedoch genauere Ergebnisse. " Als Gegenmaßnahme werden der Boden und die Seiten des Gitters in eine Kastenform gebracht, um es dicker zu machen (obwohl es immer noch durchrutscht, wenn die Gittergröße verringert wird).
ViewController.siwft
import ARKit
import SceneKit
class ViewController: UIViewController {
@IBOutlet weak var scnView: ARSCNView!
private let device = MTLCreateSystemDefaultDevice()!
private let gridSize = 10 //Anzahl der Gitterunterteilungen
private let gridLength: Float = 0.8 //Gittergröße[m]
private let wallThickness: Float = 0.1 //Dicke der Seite und des Bodens des Gitters
private lazy var cellSize = gridLength / Float(gridSize) //Zellgröße
private let gridRootNode = SCNNode() //Quadratischer Körper im Boden begraben
private let gridCellParentNode = SCNNode() //Route der Zellen auf dem Boden aufgereiht
//Scheitelpunktkoordinaten der Zelloberfläche
private lazy var vertices = [
SCNVector3(-cellSize/2, 0.0, -cellSize/2), //Zurückgelassen
SCNVector3( cellSize/2, 0.0, -cellSize/2), //Gleich zurück
SCNVector3(-cellSize/2, 0.0, cellSize/2), //Links vorne
SCNVector3( cellSize/2, 0.0, cellSize/2), //Rechts vorne
]
//Scheitelindex der Zelloberfläche
private let indices: [Int32] = [
0, 2, 1,
1, 2, 3
]
private var time = 0 //Zeichenzähler
private var isTouching = false //Berührungserkennung
override func viewDidLoad() {
super.viewDidLoad()
//Aufstellen einer gitterartigen Box, um sie im Boden zu vergraben
setupGridBox()
//AR-Sitzung gestartet
self.scnView.delegate = self
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal]
self.scnView.session.run(configuration, options: [.removeExistingAnchors, .resetTracking])
}
}
extension ViewController: ARSCNViewDelegate {
//
//Anker hinzugefügt
//
func renderer(_: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
//Knoten für planare Geometrie hinzugefügt
guard let geometory = ARSCNPlaneGeometry(device: self.device) else { return }
geometory.update(from: planeAnchor.geometry)
let planeNode = SCNNode(geometry: geometory)
planeNode.isHidden = true
DispatchQueue.main.async {
node.addChildNode(planeNode)
}
}
//
//Anker aktualisiert
//
func renderer(_: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
DispatchQueue.main.async {
for childNode in node.childNodes {
//Ebenengeometrie aktualisieren
guard let planeGeometry = childNode.geometry as? ARSCNPlaneGeometry else { continue }
planeGeometry.update(from: planeAnchor.geometry)
break
}
}
}
//
//Bild für Bild aufgerufen
//
func renderer(_ renderer: SCNSceneRenderer, updateAtTime _: TimeInterval) {
if isTouching {
//Der Bildschirm wurde berührt
isTouching = false
DispatchQueue.main.async {
//Überspringen, wenn ein Raster angezeigt wird
guard self.gridRootNode.isHidden else { return }
//Nehmen Sie das Kamerabild auf und wenden Sie es hier als Textur auf die Zelloberfläche an
self.setupCellFaceTexture()
//Rasteranzeige
self.gridRootNode.isHidden = false
}
}
DispatchQueue.main.async {
//Tun Sie nichts, wenn das Raster ausgeblendet ist
guard !self.gridRootNode.isHidden else { return }
//Bodeneinsturz
self.hourakuAnimation()
}
}
}
extension ViewController {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let _ = touches.first else { return }
isTouching = true
}
private func setupCellFaceTexture() {
//Hit Test in der Mitte des Bildschirms
let bounds = self.scnView.bounds
let screenCenter = CGPoint(x: bounds.midX, y: bounds.midY)
let results = self.scnView.hitTest(screenCenter, types: [.existingPlaneUsingGeometry])
guard let existingPlaneUsingGeometryResult = results.first(where: { $0.type == .existingPlaneUsingGeometry }),
let _ = existingPlaneUsingGeometryResult.anchor as? ARPlaneAnchor else {
//In der Mitte des Bildschirms befindet sich keine flache Oberfläche. Tun Sie also nichts
return
}
//Kamerabild aufnehmen. Einfacher Schnappschuss, da er nicht kontinuierlich erfasst wird()verwenden
let captureImage = self.scnView.snapshot()
//Kameraerfassung
guard let cameraNode = self.scnView.pointOfView,
let camera = cameraNode.camera else { return }
//Veröffentlichen Sie die Transformation des Trefferorts in der Transformation des Gitters
self.gridRootNode.simdTransform = existingPlaneUsingGeometryResult.worldTransform
for cellNode in self.gridCellParentNode.childNodes {
guard let cellFaceNode = cellNode.childNodes.first(where: {$0.name == "face"}) else { continue }
guard let vertex = cellFaceNode.geometry?.sources.first(where: {$0.semantic == .vertex}) else { continue }
guard let element = cellFaceNode.geometry?.elements.first else { continue }
//Modelltransformationsmatrix (Zelloberflächenknoten im Weltkoordinatensystem)
let modelTransform = cellFaceNode.simdWorldTransform
//Transformationsmatrix anzeigen. Inverse Matrix des Kamera-Ansichtspunkts (Verschieben Sie alle Weltkoordinatenscheitelpunkte an die Position mit der Kamera als Ursprung).
let viewTransform = cameraNode.simdTransform.inverse
//Projektionstransformationsmatrix
let projectionTransform = simd_float4x4(camera.projectionTransform(withViewportSize: self.scnView.bounds.size))
//MVP-Matrix
let mvpTransform = projectionTransform * viewTransform * modelTransform
//Konvertieren Sie jede Scheitelpunktkoordinate einer Zelle in Bildschirmkoordinaten und konvertieren Sie sie in UV-Koordinaten
var texcoords: [CGPoint] = []
for vertex in self.vertices {
//Projektionskoordinatenkonvertierung
var position = matrix_multiply(mvpTransform, SIMD4<Float>(vertex.x, vertex.y, vertex.z, 1.0))
// -1.0~1.Normalisiert auf einen Wert von 0. Teilen Sie durch w, um "normalisierte Gerätekoordinaten" zu erhalten.
position = position / position.w
//In UV-Koordinaten konvertieren
let texcordX = CGFloat(position.x + 1.0) / 2.0
let texcordY = CGFloat(-position.y + 1.0) / 2.0
texcoords.append(CGPoint(x: texcordX, y: texcordY))
}
//Generieren Sie die Zelloberflächengeometrie
let texcoordSource = SCNGeometrySource(textureCoordinates: texcoords)
let cellFaceGeometry = SCNGeometry(sources: [vertex, texcoordSource], elements: [element])
let cellFaceMaterial = SCNMaterial()
cellFaceMaterial.diffuse.contents = captureImage
cellFaceGeometry.materials = [cellFaceMaterial]
cellFaceNode.geometry = cellFaceGeometry //Geometrie ersetzen
}
}
private func hourakuAnimation() {
//Stoppen Sie den Zusammenbruch in einer bestimmten Zeit
guard self.time < 150 else { return }
self.time += 1
let time = Float(self.time)
let gridSize = Float(self.gridSize)
//Reduzieren Sie, um den Kreis von der Mitte aus zu verteilen
let x = sin(Float.pi * 2 * time/30.0) * time / 150
let y = cos(Float.pi * 2 * time/30.0) * time / 150
let ygrid = Int((y + 1.0) / 2 * gridSize * gridSize) / self.gridSize * self.gridSize
let xgrid = Int((x + 1.0) / 2 * gridSize) + ygrid
guard 0 <= xgrid, xgrid < self.gridCellParentNode.childNodes.count else { return }
let node = self.gridCellParentNode.childNodes[xgrid]
//Knoten überspringen, für die Physikkörper bereits festgelegt wurde
guard node.physicsBody == nil else { return }
//Passen Sie die Größe der physischen Beurteilung an die Größe der Zelle an
let bodyLength = CGFloat(self.cellSize) * 1.0
let box = SCNBox(width: bodyLength, height: bodyLength, length: bodyLength, chamferRadius: 0.0)
let boxShape = SCNPhysicsShape(geometry: box, options: nil)
let boxBody = SCNPhysicsBody(type: .dynamic, shape: boxShape)
boxBody.continuousCollisionDetectionThreshold = 0.001 //Ich habe es eingestellt, aber es fühlt sich nicht so an, als würde es funktionieren
// TODO:Der Ursprung der Zelle befindet sich in der Mitte, aber der Ursprung der im PhysicsBody festgelegten Geometrie befindet sich in der Mitte, sodass die Koordinaten der Zellenhälfte deaktiviert sind.
//Sie können entweder die PhysicsBody-Geometrie anpassen oder den Zellenursprung in die Mitte bringen, was beide mühsam sind. Beheben Sie das Problem, wenn Sie die Möglichkeit haben.
node.physicsBody = boxBody
}
private func setupGridBox() {
gridRootNode.isHidden = true
//
//Zellknoten generieren
//
//Stellen Sie sicher, dass Sie die Position jedes Knotens angeben. Ich bin auf ein Phänomen gestoßen, dass die Y-Koordinate die Erwartungen nur erfüllt, wenn sie initialisiert wird, selbst wenn es sich um die Mittelkoordinate handelt.
gridCellParentNode.simdPosition = SIMD3<Float>(x: 0.0, y: 0.0, z: 0.0)
gridRootNode.addChildNode(gridCellParentNode)
gridRootNode.simdPosition = SIMD3<Float>(x: 0.0, y: 0.0, z: 0.0)
self.scnView.scene.rootNode.addChildNode(gridRootNode)
//Erstellen Sie einen Zellknoten von gridSize x gridSize
let cellFaceGeometry = makeCellFaceGeometry()
let cellBoxGeometry = makeCellBoxGeometry()
let cellLeftBackPos = -(gridLength / 2) + cellSize / 2
for y in 0 ..< gridSize {
for x in 0 ..< gridSize {
//Jeder Zellenknoten
let cellNode = SCNNode()
cellNode.simdPosition = SIMD3<Float>(x: cellLeftBackPos + (cellSize * Float(x)), y: 0, z: cellLeftBackPos + (cellSize * Float(y)))
gridCellParentNode.addChildNode(cellNode)
//Erzeugung von Knoten auf der Ebene der Zelloberfläche
let cellFaceNode = SCNNode(geometry: cellFaceGeometry)
cellFaceNode.name = "face"
//Bestimmen Sie die Koordinaten so, dass jede Zelle verteilt ist
cellFaceNode.simdPosition = SIMD3<Float>(x: 0.0, y: 0.0, z: 0.0)
cellNode.addChildNode(cellFaceNode)
//Generieren Sie einen rechteckigen Zellenknoten
let cellBoxNode = SCNNode(geometry: cellBoxGeometry)
cellBoxNode.simdPosition = SIMD3<Float>(x: 0.0, y: -cellSize/2*1.001, z: 0.0)
cellNode.addChildNode(cellBoxNode)
}
}
//
//Gitterseitiger Knoten
//
let sideOuterBox = makeGridSideOuterGeometry()
let sideInnerPlane = makeGridSideInnerGeometry()
//Generierung von netzseitigen Knoten
for i in 0..<4 {
let x = sin(Float.pi / 2 * Float(i)) * gridLength / 2.0
let z = cos(Float.pi / 2 * Float(i)) * gridLength / 2.0
//Seite (außen)
let outerNode = SCNNode(geometry: sideOuterBox)
let nodePos = ((gridLength + wallThickness) / 2.0) / (gridLength / 2.0) * 1.001
outerNode.simdPosition = SIMD3<Float>(x: x * nodePos, y: -gridLength/2.0, z: z * nodePos)
outerNode.simdRotation = SIMD4<Float>(x: 0.0, y: 1.0, z: 0.0, w: -Float.pi / 2 * Float(i))
//Machen Sie eine physische Wand an der Seite
outerNode.physicsBody = SCNPhysicsBody.static()
//Die Außenwand ist (fast) transparent und die Renderreihenfolge-Auf 1 setzen und vor anderen Knoten zeichnen (zuerst in den Z-Puffer schreiben).
//Dadurch werden nicht die Knoten gezeichnet, die hinter dem (fast) transparenten Knoten gezeichnet werden sollen, was zu einem sichtbaren Hintergrund führt.
outerNode.renderingOrder = -1
gridRootNode.addChildNode(outerNode)
//Seite (innen)
let innerNode = SCNNode(geometry: sideInnerPlane)
innerNode.simdPosition = SIMD3<Float>(x: x, y: -gridLength/2.0, z: z)
innerNode.simdRotation = SIMD4<Float>(x: 0.0, y: 1.0, z: 0.0, w: -Float.pi / 2 * Float(i))
gridRootNode.addChildNode(innerNode)
}
//
//Unterer Knoten des Gitters
//
let bottomBox = makeGridButtomGeometry()
let bottomNode = SCNNode(geometry: bottomBox)
bottomNode.simdPosition = SIMD3<Float>(x: 0.0, y: -gridLength+Float(wallThickness), z: 0.0)
bottomNode.simdRotation = SIMD4<Float>(x: 1.0, y: 0.0, z: 0.0, w: -Float.pi / 2)
//Machen Sie eine physische Wand auf der Unterseite
bottomNode.physicsBody = SCNPhysicsBody.static()
gridRootNode.addChildNode(bottomNode)
}
private func makeCellFaceGeometry() -> SCNGeometry {
//Zelloberflächengeometrie. Fügen Sie das vom Bildschirm aufgenommene Bild als Textur in diese Geometrie ein
let cellFaceVertices = SCNGeometrySource(vertices: vertices)
let cellFaceIndices = SCNGeometryElement(indices: indices, primitiveType: .triangles)
let cellFaceGeometry = SCNGeometry(sources: [cellFaceVertices], elements: [cellFaceIndices])
let cellFaceMaterial = SCNMaterial()
cellFaceMaterial.diffuse.contents = UIColor.clear
cellFaceGeometry.materials = [cellFaceMaterial]
return cellFaceGeometry
}
private func makeCellBoxGeometry() -> SCNGeometry {
//Der kleine Kastenteil der Zelle.
let cellBox = SCNBox(width: CGFloat(cellSize), height: CGFloat(cellSize), length: CGFloat(cellSize), chamferRadius: 0.0)
let cellBoxMaterial = SCNMaterial()
cellBoxMaterial.diffuse.contents = UIColor.darkGray
cellBox.materials = [cellBoxMaterial]
return cellBox
}
private func makeGridSideOuterGeometry() -> SCNGeometry {
//Gitterseitige (äußere) Geometrie. Machen Sie eine dicke Wand, damit die Zelle beim Fallen nicht in die Seite eindringt.
let sideOuterBox = SCNBox(width: CGFloat(gridLength) * 1.001, height: CGFloat(gridLength), length: CGFloat(wallThickness), chamferRadius: 0)
let sideOuterMaterial = SCNMaterial()
sideOuterMaterial.transparency = 0.001
sideOuterMaterial.diffuse.contents = UIColor.white
sideOuterMaterial.isDoubleSided = true
sideOuterBox.materials = [sideOuterMaterial]
return sideOuterBox
}
private func makeGridSideInnerGeometry() -> SCNGeometry {
//Geometrie der Gitterseite (innen)
let sideInnerPlane = SCNPlane(width: CGFloat(gridLength), height: CGFloat(gridLength))
let sideInnerMaterial = SCNMaterial()
sideInnerMaterial.diffuse.contents = UIColor.gray
sideInnerMaterial.isDoubleSided = true
sideInnerPlane.materials = [sideInnerMaterial]
return sideInnerPlane
}
private func makeGridButtomGeometry() -> SCNGeometry {
//Die Geometrie des unteren Randes des Gitters. Machen Sie eine dicke Wand, damit die Zelle beim Fallen nicht in die Seite eindringt.
let bottomBox = SCNBox(width: CGFloat(gridLength), height: CGFloat(gridLength), length: CGFloat(wallThickness), chamferRadius: 0)
let bottomMaterial = SCNMaterial()
bottomMaterial.diffuse.contents = UIColor.black
bottomBox.materials = [bottomMaterial]
return bottomBox
}
}
Recommended Posts