Ich dachte, ich möchte UIKit möglicherweise für virtuelle Objekte auf meinem Smartphone oder AR-Glas verwenden, und habe daher versucht, UIView in der Szene von SceneKit anzuzeigen. Da es einfach ist, den Betrieb zu überprüfen, verwenden Sie "WKWebView" als UIView.
Vollständiges Bild
Referenzinformationen: ・ Tweet der Operation in dieser Swift-Benutzeroberfläche
① Erfassen Sie "UIView" und konvertieren Sie es in "MTLExutre"
② ① wird als Material von SCNNode
von SceneKit festgelegt
nur das. Es funktioniert so, aber hier sind einige Abhängigkeiten.
Um in einer WebView, die ein virtuelles Objekt ist, einen Bildlauf durchzuführen oder darauf zu tippen, muss ein Berührungsereignis an eine beliebige WebView mit "ARSCNView" übergeben werden. Hier habe ich eine Methode entwickelt, um "UIResponder" -Ereignisse wie "touchBegan ()" mit "ARSCNView" zu empfangen und an "touchBegan ()" usw. eines beliebigen WebView zu senden, aber das Scrollen und Tippen funktioniert überhaupt nicht. .. Ist es überhaupt möglich, Aktionen auszuführen, die für UI-Teile spezifisch sind, wie z. B. Scrollen oder Tippen auf Schaltflächen, wenn UIEvent übergeben wird? Ich fühle mich so. Mangel an Wissen hier. .. .. Wenn jemand weiß, lassen Sie es mich bitte wissen. Wenn Sie googeln, wie Sie aus dem Programm scrollen, können Sie sehen, wie Sie es mit "contentOffset" von "UIScrollView" einstellen. Dies sollte jedoch kein komfortables Trägheits-Scrollen ermöglichen. Am Ende konnte ich keine gute Methode finden und realisierte sie mit der folgenden Methode.
Auf diese Weise können Sie durch jedes WebView scrollen. Dieses Verfahren weist jedoch die folgenden Nachteile auf.
Aus dem oben Gesagten geht hervor, dass die Fälle, in denen diese Methode verwendet werden kann, auf die Fälle beschränkt sind, in denen nur einige Informationen angezeigt werden und die Operation nur im virtuellen Objekt auf dem Bildschirm gescrollt wird. Ich werde noch einmal versuchen, wie ich es mit Swift UI wie dem obigen Tweet machen kann.
Das Capture-Timing ist auf renderer (_: updateAtTime :)
von SCNSceneRendererDelegate
eingestellt. Da der Bildschirm jedoch im Hauptthread an Capture & SceneKit übergeben werden muss, wird der Capture-Prozess bei dieser Methode in die Hauptwarteschlange eingereiht. .. Wenn die Bildschirmaufnahme schnell genug ist, kann dies ausreichen. Da es sich jedoch um einen harten Prozess beim Erfassen während des 3D-Renderns während der Verfolgung mit AR handelt, wird die Hauptwarteschlange schnell erweitert und der Bildschirm friert ein. Ich bin auf das Phänomen gestoßen. Maßnahmen, um die nächste Erfassung zu verhindern, bis die Erfassung abgeschlossen ist.
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
//Nicht erfassen, wenn die Bildschirmaufnahme in der Hauptwarteschlange nicht abgeschlossen ist
//Der Erfassungsprozess ist langsam, daher wird die Warteschlange ohne ihn nicht eingeblendet und eingefroren.
guard !isCaptureWaiting else { return }
isCaptureWaiting = true
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet weak var scnView: ARSCNView!
@IBOutlet weak var webView1: WKWebView!
@IBOutlet weak var webView2: WKWebView!
@IBOutlet weak var webView3: WKWebView!
private var device = MTLCreateSystemDefaultDevice()!
private var planeNode1: SCNNode!
private var planeNode2: SCNNode!
private var planeNode3: SCNNode!
private var node1Texture: MTLTexture?
private var node2Texture: MTLTexture?
private var node3Texture: MTLTexture?
private var viewWidth: Int = 0
private var viewHeight: Int = 0
private var isCaptureWaiting = false
override func viewDidLoad() {
super.viewDidLoad()
scnView.scene = SCNScene(named: "art.scnassets/sample.scn")!
planeNode1 = scnView.scene.rootNode.childNode(withName: "plane1", recursively: true)
planeNode2 = scnView.scene.rootNode.childNode(withName: "plane2", recursively: true)
planeNode3 = scnView.scene.rootNode.childNode(withName: "plane3", recursively: true)
//Akzeptieren Sie UI-Ereignisse nicht einmal
scnView.isUserInteractionEnabled = false
setUIEnable(webView1: false, webView2: false, webView3: false)
//ARSCN View ist immer oben
self.view.bringSubviewToFront(scnView)
//AR-Sitzung gestartet
self.scnView.delegate = self
let configuration = ARWorldTrackingConfiguration()
self.scnView.session.run(configuration, options: [.removeExistingAnchors, .resetTracking])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if node1Texture == nil || node2Texture == nil || node3Texture == nil {
viewWidth = Int(view.bounds.width)
viewHeight = Int(view.bounds.height)
//Sicherer Texturpuffer
//WebView-Größe= self.Die Größe der Ansicht
let desc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm,
width: viewWidth,
height: viewHeight,
mipmapped: false)
node1Texture = device.makeTexture(descriptor: desc)!
node2Texture = device.makeTexture(descriptor: desc)!
node3Texture = device.makeTexture(descriptor: desc)!
//Site laden
webView1.load(URLRequest(url: URL(string:"https://qiita.com")!))
webView2.load(URLRequest(url: URL(string:"https://www.apple.com")!))
webView3.load(URLRequest(url: URL(string:"https://stackoverflow.com")!))
}
}
//IsUserInteractionEnabled-Einstellung für jede WebView
func setUIEnable(webView1: Bool, webView2: Bool, webView3: Bool) {
self.webView1.isUserInteractionEnabled = webView1
self.webView2.isUserInteractionEnabled = webView2
self.webView3.isUserInteractionEnabled = webView3
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let _node1Texture = node1Texture, let _node2Texture = node2Texture, let _node3Texture = node3Texture else { return }
//Nicht erfassen, wenn die Bildschirmaufnahme in der Hauptwarteschlange nicht abgeschlossen ist
//Der Erfassungsprozess ist langsam, daher wird die Warteschlange ohne ihn nicht eingeblendet und eingefroren.
guard !isCaptureWaiting else { return }
isCaptureWaiting = true
DispatchQueue.main.async {
//Führen Sie einen Treffertest des Knotens und von WebView in der Mitte des Bildschirms durch(Knoten)Rausfinden
let bounds = self.scnView.bounds
let screenCenter = CGPoint(x: bounds.midX, y: bounds.midY)
let options: [SCNHitTestOption: Any] = [
.boundingBoxOnly: true, //Getestet mit BoundingBox
.firstFoundOnly: true //Gibt nur das vorderste Objekt zurück
]
if let hitResult = self.scnView.hitTest(screenCenter, options: options).first,
let nodeName = hitResult.node.name {
//Setzen Sie isUserInteractionEnabled der WebView, die dem Knoten in der Mitte des Bildschirms entspricht, auf true
switch nodeName {
case "plane1":
self.setUIEnable(webView1: true, webView2: false, webView3: false)
case "plane2":
self.setUIEnable(webView1: false, webView2: true, webView3: false)
case "plane3":
self.setUIEnable(webView1: false, webView2: false, webView3: true)
default:
self.setUIEnable(webView1: false, webView2: false, webView3: false)
}
} else {
self.setUIEnable(webView1: false, webView2: false, webView3: false)
}
//Aktualisieren Sie das Material des Knotens, indem Sie nur die WebView erfassen, die dem Knoten in der Mitte des Bildschirms entspricht
let setNodeMaterial: (UIView, SCNNode, MTLTexture) -> () = { captureView, node, texture in
let material = SCNMaterial()
material.diffuse.contents = captureView.takeTextureSnapshot(device: self.device,
textureWidth: self.viewWidth,
textureHeight: self.viewHeight,
textureBuffer: texture)
node.geometry?.materials = [material]
}
if self.webView1.isUserInteractionEnabled {
setNodeMaterial(self.webView1, self.planeNode1, _node1Texture)
} else if self.webView2.isUserInteractionEnabled {
setNodeMaterial(self.webView2, self.planeNode2, _node2Texture)
} else if self.webView3.isUserInteractionEnabled {
setNodeMaterial(self.webView3, self.planeNode3, _node3Texture)
}
self.isCaptureWaiting = false
}
}
}
extension UIView {
//Machen Sie einen Screenshot von UIView und konvertieren Sie ihn in MTLTexture
//Referenz-URL: https://stackoverflow.com/questions/61724043/render-uiview-contents-into-mtltexture
func takeTextureSnapshot(device: MTLDevice, textureWidth: Int, textureHeight: Int, textureBuffer: MTLTexture) -> MTLTexture? {
guard let context = CGContext(data: nil,
width: textureWidth,
height: textureHeight,
bitsPerComponent: 8,
bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil }
//Verkehrt herum vermeiden
context.translateBy(x: 0, y: CGFloat(textureHeight))
context.scaleBy(x: 1, y: -1)
guard let data = context.data else { return nil }
layer.render(in: context)
textureBuffer.replace(region: MTLRegionMake2D(0, 0, textureWidth, textureHeight),
mipmapLevel: 0,
withBytes: data,
bytesPerRow: context.bytesPerRow)
return textureBuffer
}
}
Recommended Posts