In der Fortsetzung des vorherigen "ARKit + SceneKit + Optische Metalltarnung ①" habe ich versucht, das Gefühl auszudrücken, dass die optische Tarnung nicht in gutem Zustand ist.
① Generieren Sie mit dem Compute Shader eine Blockrauschtextur (2) Ein Zeichenpfad für Zeichen wurde hinzugefügt, wobei (1) als Material verwendet wurde. ③ Hinzufügen ② zum zuletzt erstellten Bilderzeugungsprozess
Wenn Sie beim Ausführen der App "GPU-Frame erfassen" in Xcode ausführen, können Sie den Rendering-Pfad wie folgt überprüfen (überprüfen Sie dies mit Xcode12). Diesmal habe ich den handgeschriebenen roten Linienteil hinzugefügt. Dies ist praktisch, da Sie überprüfen können, welche Farbe und Tiefe für jeden Pfad ausgegeben werden. Tippen Sie beim Debuggen auf das Kamerasymbol, um einen "Capture GPU Frame" zu erstellen.
Alles, was Sie brauchen, um eine Rauschtextur zu erzeugen, sind die zeitlich variierenden Informationen "timeParam" und xy-Koordinaten. Der Wert von "timeParam", der jedes Mal erhöht wird, wenn er gezeichnet wird, wird an den Shader übergeben, und der Shader bestimmt die Rauschfarbe basierend auf diesen Informationen und den xy-Koordinaten. Das Timing der Rauschgenerierung ist "Renderer (_ Renderer: SCNSceneRenderer, updateAtTime time: TimeInterval)".
・ Shader
shader.metal
//Zufällige Generierung
float rand(float2 co) {
return fract(sin(dot(co.xy, float2(12.9898, 78.233))) * 43758.5453);
}
//Shader zur Erzeugung von Rauschbildern
kernel void blockNoise(const device float& time [[buffer(0)]],
texture2d<float, access::write> out [[texture(0)]],
uint2 id [[thread_position_in_grid]]) {
//8px Block
float2 uv = float2(id.x / 8, id.y / 8);
float noise = fract(rand(rand(float2(float(uv.x * 50 + time), float(uv.y * 50 + time) + time))));
float4 color = float4(0.0, noise, 0.0, 1.0);
out.write(color, id);
}
・ Swift (Shader-Call-Teil)
ViewController.swift
private func setupMetal() {
(Weggelassen)
//Berechnen Sie den Shader für die Geräuschentwicklung
let noiseShader = library.makeFunction(name: "blockNoise")!
self.computeState = try! self.device.makeComputePipelineState(function: noiseShader)
//Puffer mit Zeitinformationen, die an den Shader übergeben werden sollen
self.timeParamBuffer = self.device.makeBuffer(length: MemoryLayout<Float>.size, options: .cpuCacheModeWriteCombined)
self.timeParamPointer = UnsafeMutableRawPointer(self.timeParamBuffer.contents()).bindMemory(to: Float.self, capacity: 1)
//Thread-Gruppenraster
self.threadgroupSize = MTLSizeMake(16, 16, 1)
let threadCountW = (noiseTetureSize + self.threadgroupSize.width - 1) / self.threadgroupSize.width
let threadCountH = (noiseTetureSize + self.threadgroupSize.height - 1) / self.threadgroupSize.height
self.threadgroupCount = MTLSizeMake(threadCountW, threadCountH, 1)
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
//Inkrement für jede Zeichnung
self.timeParam += 1;
self.timeParamPointer.pointee = self.timeParam
let commandBuffer = self.commandQueue.makeCommandBuffer()!
let computeEncoder = commandBuffer.makeComputeCommandEncoder()!
computeEncoder.setComputePipelineState(computeState)
computeEncoder.setBuffer(self.timeParamBuffer, offset: 0, index: 0)
computeEncoder.setTexture(noiseTexture, index: 0)
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
computeEncoder.endEncoding()
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
}
Die Ausgabe des Shaders wird von MTLTexture
empfangen.
Der Punkt ist, wie die empfangene Textur als Material des Charakters übergeben wird.
ViewController.swift
//Textur zum Schreiben von Rauschen
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm,
width: noiseTetureSize,
height: noiseTetureSize,
mipmapped: false)
textureDescriptor.usage = [.shaderWrite, .shaderRead]
self.noiseTexture = device.makeTexture(descriptor: textureDescriptor)!
//Stellen Sie die Rauschstruktur als Material des Knotens ein, das für die optische Tarnung vorgesehen ist
let node = self.rootNode.childNode(withName: "CamouflageNode", recursively: true)!
let material = SCNMaterial()
material.diffuse.contents = self.noiseTexture!
material.emission.contents = self.noiseTexture! //Schatten verhindern
node.geometry?.materials = [material]
Alles, was Sie tun müssen, ist, das erzeugte Rauschbild (Textur) auf "diffuse.contents" von "SCNMaterial" zu setzen und es auf die Geometrie des Zeichenknotens zu setzen. SceneKit erledigt den Rest. Ich habe versucht, das SCN-Programm zu verwenden, aber die Methode ist in diesem Artikel beschrieben.
Ersetzen Sie den im vorherigen Artikel ausgegebenen optischen Tarnungsteil durch den diesmal zu zeichnenden Teil (Zeichen mit Rauschstruktur) (schalten Sie die Anzeige nach dem Zufallsprinzip um, um das Flackern auszudrücken).
Der zu "SCNTechnique" hinzugefügte Pfad lautet wie folgt.
technique.json
"pass_noise_node" : {
"draw" : "DRAW_NODE",
"includeCategoryMask" : 2,
"outputs" : {
"color" : "noise_color_node"
}
},
Das liegt daran, dass ich den Charakter nur mit der Rauschstruktur zeichne. Farbinformationen werden an "color": "Noise_color_node" ausgegeben.
Der endgültige Shader für die Bilderzeugung wurde wie folgt geändert. Die Ausgabe von NoiseColorNode
im obigen Pfad wird dem Argument hinzugefügt.
//Geräuschgenerierung Timing-Generierung
bool spike(float time) {
float flickering = 0.3; //Flackernder Zustand. Durch Erhöhen wird das Flackern erleichtert
float piriod = -0.8; //Eine flackernde Zeit. Je kleiner es ist, desto länger dauert das Flackern
if (rand(time * 0.1) > (1.0 - flickering) && sin(time) > piriod) {
return true;
} else {
return false;
}
}
//Fragment-Shader zum Zusammensetzen der gesamten Szenen- und Knotennormalen
fragment half4 mix_fragment(MixColorInOut vert [[stage_in]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]], //Rahmeninformationen zeichnen
texture2d<float, access::sample> colorScene [[texture(0)]],
depth2d<float, access::sample> depthScene [[texture(1)]],
texture2d<float, access::sample> colorNode [[texture(2)]],
depth2d<float, access::sample> depthNode [[texture(3)]],
texture2d<float, access::sample> noiseColorNode [[texture(4)]])
{
float ds = depthScene.sample(s, vert.uv); //Tiefe beim Zeichnen der gesamten Szene
float dn = depthNode.sample(s, vert.uv); //Tiefe beim Zeichnen eines Knotens
float4 fragment_color;
if (dn > ds) {
if (spike(scn_frame.time)) {
//Übernehmen Sie die Rauschtexturfarbe für das Rausch-Timing
fragment_color = noiseColorNode.sample(s, fract(vert.uv));
} else {
//Da sich das Objekt, das optisch getarnt werden soll, vor dem in der Szene gezeichneten Objekt befindet, erzeugt es einen optischen Tarneffekt.
(Weggelassen)
}
Zufällige Richtig / Falsch-Informationen werden mit "spike ()" erstellt, und die Anzeigefarbe wird zwischen einem verrauschten Zeichen und einem optischen Tarnzeichen umgeschaltet.
・ Multi-Pass-Rendering-Definition
technique.json
{
"targets" : {
"color_scene" : { "type" : "color" },
"depth_scene" : { "type" : "depth" },
"color_node" : { "type" : "color" },
"depth_node" : { "type" : "depth" },
"noise_color_node" : { "type" : "color" }
},
"passes" : {
"pass_scene" : {
"draw" : "DRAW_SCENE",
"excludeCategoryMask" : 2,
"outputs" : {
"color" : "color_scene",
"depth" : "depth_scene"
},
"colorStates" : {
"clear" : true,
"clearColor" : "sceneBackground"
},
"depthStates" : {
"clear" : true,
"func" : "less"
}
},
"pass_node" : {
"draw" : "DRAW_NODE",
"includeCategoryMask" : 2,
"metalVertexShader" : "node_vertex",
"metalFragmentShader" : "node_fragment",
"outputs" : {
"color" : "color_node",
"depth" : "depth_node"
},
"depthStates" : {
"clear" : true,
"func" : "less"
}
},
"pass_noise_node" : {
"draw" : "DRAW_NODE",
"includeCategoryMask" : 2,
"outputs" : {
"color" : "noise_color_node"
}
},
"pass_mix" : {
"draw" : "DRAW_QUAD",
"inputs" : {
"colorScene" : "color_scene",
"depthScene" : "depth_scene",
"colorNode" : "color_node",
"depthNode" : "depth_node",
"noiseColorNode" : "noise_color_node"
},
"metalVertexShader" : "mix_vertex",
"metalFragmentShader" : "mix_fragment",
"outputs" : {
"color" : "COLOR"
},
"colorStates" : {
"clear" : "true"
}
}
},
"sequence" : [
"pass_scene",
"pass_node",
"pass_noise_node",
"pass_mix"
]
}
・ Shader
#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>
// SceneKit ->Shader-Lieferart
//Die Definition lautet https://developer.apple.com/documentation/scenekit/Siehe scnprogram
struct VertexInput {
float4 position [[attribute(SCNVertexSemanticPosition)]]; //Apex-Koordinaten
float2 texCoords [[attribute(SCNVertexSemanticTexcoord0)]]; //Texturkoordinaten
float2 normal [[attribute(SCNVertexSemanticNormal)]]; //Normal
};
// SceneKit ->Shader-Lieferart(Für jeden Knoten)
//Die Definition lautet https://developer.apple.com/documentation/scenekit/Siehe scnprogram
struct PerNodeBuffer {
float4x4 modelViewProjectionTransform;
};
struct NodeColorInOut {
float4 position [[position]];
float4 normal;
};
struct MixColorInOut {
float4 position [[position]];
float2 uv;
};
//Zufällige Generierung
float rand(float2 co) {
return fract(sin(dot(co.xy, float2(12.9898, 78.233))) * 43758.5453);
}
//Geräuschgenerierung Timing-Generierung
bool spike(float time) {
float flickering = 0.3; //Flackernder Zustand. Durch Erhöhen wird das Flackern erleichtert
float piriod = -0.8; //Eine flackernde Zeit. Je kleiner es ist, desto länger dauert das Flackern
if (rand(time * 0.1) > (1.0 - flickering) && sin(time) > piriod) {
return true;
} else {
return false;
}
}
//Vertex-Shader für Knoten
vertex NodeColorInOut node_vertex(VertexInput in [[stage_in]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]], //Rahmeninformationen zeichnen
constant PerNodeBuffer& scn_node [[buffer(1)]]) //Informationen für jeden Knoten
{
NodeColorInOut out;
out.position = scn_node.modelViewProjectionTransform * in.position;
out.normal = scn_node.modelViewProjectionTransform * float4(in.normal, 1.0);
return out;
}
//Fragment-Shader für Knoten
fragment half4 node_fragment(NodeColorInOut vert [[stage_in]])
{
//Die normale Verwendung ist x,y nur. Weil es als Farbinformation behandelt wird-1.0 ~ 1.0 -> 0.0 ~ 1.In 0 konvertieren
float4 color = float4((vert.normal.x + 1.0) * 0.5 , (vert.normal.y + 1.0) * 0.5, 0.0, 0.0);
return half4(color); //Gibt normale Linien als Farbinformationen aus. Diese Informationen verzerren den Hintergrund des optischen Tarnziels
}
//Vertex-Shader zum Zusammensetzen der gesamten Szenen- und Knotennormalen
vertex MixColorInOut mix_vertex(VertexInput in [[stage_in]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]])
{
MixColorInOut out;
out.position = in.position;
//Koordinatensystem-1.0 ~ 1.0 -> 0.0 ~ 1.In 0 konvertieren. Die y-Achse ist invertiert.
out.uv = float2((in.position.x + 1.0) * 0.5 , (in.position.y + 1.0) * -0.5);
return out;
}
constexpr sampler s = sampler(coord::normalized,
address::repeat, // clamp_to_edge/clamp_to_border(iOS14)Nein.
filter::nearest);
//Fragment-Shader zum Zusammensetzen der gesamten Szenen- und Knotennormalen
fragment half4 mix_fragment(MixColorInOut vert [[stage_in]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]], //Rahmeninformationen zeichnen
texture2d<float, access::sample> colorScene [[texture(0)]],
depth2d<float, access::sample> depthScene [[texture(1)]],
texture2d<float, access::sample> colorNode [[texture(2)]],
depth2d<float, access::sample> depthNode [[texture(3)]],
texture2d<float, access::sample> noiseColorNode [[texture(4)]])
{
float ds = depthScene.sample(s, vert.uv); //Tiefe beim Zeichnen der gesamten Szene
float dn = depthNode.sample(s, vert.uv); //Tiefe beim Zeichnen eines Knotens
float4 fragment_color;
if (dn > ds) {
if (spike(scn_frame.time)) {
//Übernehmen Sie die Rauschtexturfarbe für das Rausch-Timing
fragment_color = noiseColorNode.sample(s, fract(vert.uv));
} else {
//Da sich das Objekt, das optisch getarnt werden soll, vor dem in der Szene gezeichneten Objekt befindet, erzeugt es einen optischen Tarneffekt.
float3 normal_map = colorNode.sample(s, vert.uv).rgb;
// 0.0 ~ 1.0 -> -1.0 ~ 1.Kehren Sie zu 0 zurück, damit es als Koordinate verwendet werden kann
normal_map.xy = normal_map.xy * 2 - 1.0;
//Die Position der zu übernehmenden Hintergrundfarbe liegt in der normalen Richtung des Knotens.(xy Flugzeug)Machen Sie einen verzerrten Hintergrund, um ein wenig versetzt zu werden
float2 uv = vert.uv + normal_map.xy * 0.1;
if (uv.x > 1.0 || uv.x < 0.0) {
//Vermeiden Sie es, Farben außerhalb des Bildschirms zu verwenden(Ich wollte es mit der Adressierung des Samplers lösen, aber es funktionierte nicht)
fragment_color = colorScene.sample(s, fract(vert.uv));
} else {
fragment_color = colorScene.sample(s, fract(uv));
}
}
} else {
//Da sich das Objekt, das optisch getarnt werden soll, hinter dem in der Szene gezeichneten Objekt befindet, wird die Farbe auf der Szenenseite unverändert übernommen
fragment_color = colorScene.sample(s, fract(vert.uv));
}
return half4(fragment_color);
}
//Shader zur Erzeugung von Rauschbildern
kernel void blockNoise(const device float& time [[buffer(0)]],
texture2d<float, access::write> out [[texture(0)]],
uint2 id [[thread_position_in_grid]]) {
//8px Block
float2 uv = float2(id.x / 8, id.y / 8);
float noise = fract(rand(rand(float2(float(uv.x * 50 + time), float(uv.y * 50 + time) + time))));
float4 color = float4(0.0, noise, 0.0, 1.0);
out.write(color, id);
}
・ Schnell
ViewController.swift
import ARKit
import SceneKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet weak var scnView: ARSCNView!
private var rootNode: SCNNode!
private let device = MTLCreateSystemDefaultDevice()!
private var commandQueue: MTLCommandQueue!
private var computeState: MTLComputePipelineState! = nil
private var noiseTexture: MTLTexture! = nil
private let noiseTetureSize = 256
private var threadgroupSize: MTLSize!
private var threadgroupCount: MTLSize!
private var timeParam: Float = 0
private var timeParamBuffer: MTLBuffer!
private var timeParamPointer: UnsafeMutablePointer<Float>!
override func viewDidLoad() {
super.viewDidLoad()
//Laden von Zeichen. Ausgeliehene WWDC2017 SceneKit-Demo https://developer.apple.com/videos/play/wwdc2017/604/
guard let scene = SCNScene(named: "art.scnassets/max.scn"),
let rootNode = scene.rootNode.childNode(withName: "root", recursively: true) else { return }
self.rootNode = rootNode
self.rootNode.isHidden = true
//Metallaufbau
self.setupMetal()
//Einrichtung der Szenentechnik
self.setupSCNTechnique()
//AR-Sitzung gestartet
self.scnView.delegate = self
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal]
self.scnView.session.run(configuration, options: [.removeExistingAnchors, .resetTracking])
}
private func setupMetal() {
self.commandQueue = self.device.makeCommandQueue()!
let library = self.device.makeDefaultLibrary()!
//Textur zum Schreiben von Rauschen
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm,
width: noiseTetureSize,
height: noiseTetureSize,
mipmapped: false)
textureDescriptor.usage = [.shaderWrite, .shaderRead]
self.noiseTexture = device.makeTexture(descriptor: textureDescriptor)!
//Stellen Sie die Rauschstruktur als Material des Knotens ein, das für die optische Tarnung vorgesehen ist
let node = self.rootNode.childNode(withName: "CamouflageNode", recursively: true)!
let material = SCNMaterial()
material.diffuse.contents = self.noiseTexture!
material.emission.contents = self.noiseTexture! //Schatten verhindern
node.geometry?.materials = [material]
//Berechnen Sie den Shader für die Geräuschentwicklung
let noiseShader = library.makeFunction(name: "blockNoise")!
self.computeState = try! self.device.makeComputePipelineState(function: noiseShader)
//Puffer mit Zeitinformationen, die an den Shader übergeben werden sollen
self.timeParamBuffer = self.device.makeBuffer(length: MemoryLayout<Float>.size, options: .cpuCacheModeWriteCombined)
self.timeParamPointer = UnsafeMutableRawPointer(self.timeParamBuffer.contents()).bindMemory(to: Float.self, capacity: 1)
//Thread-Gruppenraster
self.threadgroupSize = MTLSizeMake(16, 16, 1)
let threadCountW = (noiseTetureSize + self.threadgroupSize.width - 1) / self.threadgroupSize.width
let threadCountH = (noiseTetureSize + self.threadgroupSize.height - 1) / self.threadgroupSize.height
self.threadgroupCount = MTLSizeMake(threadCountW, threadCountH, 1)
}
private func setupSCNTechnique() {
guard let path = Bundle.main.path(forResource: "technique", ofType: "json") else { return }
let url = URL(fileURLWithPath: path)
guard let techniqueData = try? Data(contentsOf: url),
let dict = try? JSONSerialization.jsonObject(with: techniqueData) as? [String: AnyObject] else { return }
//Aktivieren Sie das Rendern in mehreren Durchgängen
let technique = SCNTechnique(dictionary: dict)
scnView.technique = technique
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
//Inkrement für jede Zeichnung
self.timeParam += 1;
self.timeParamPointer.pointee = self.timeParam
let commandBuffer = self.commandQueue.makeCommandBuffer()!
let computeEncoder = commandBuffer.makeComputeCommandEncoder()!
computeEncoder.setComputePipelineState(computeState)
computeEncoder.setBuffer(self.timeParamBuffer, offset: 0, index: 0)
computeEncoder.setTexture(noiseTexture, index: 0)
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
computeEncoder.endEncoding()
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
}
func renderer(_: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor, self.rootNode.isHidden else { return }
self.rootNode.simdPosition = planeAnchor.center
self.rootNode.isHidden = false
DispatchQueue.main.async {
//Zeigen Sie das Objekt in der erkannten Ebene an
node.addChildNode(self.rootNode)
}
}
}
Recommended Posts