Bei der Untersuchung von Apple-Beispiel für People Occlusion habe ich versucht, einen Effekt zu erzielen, als ich ein Super Saiyajin wurde.
Vollständiges Bild Referenzierter Artikel: ・ Anwenden von Effekten nur auf den menschlichen Körper mit ARKit3 und Metall ・ Lichteffekt mit Swift + Metal ・ GPU mit MetalKit beherrschen
Wenn Sie nach Bildern von Super Saiyajins suchen, gibt es verschiedene Variationen der Aura (?). In diesem Artikel bezog ich mich auf die Aura unmittelbar nachdem Goku auf Namek ein Super-Saiyajin geworden war (weil es einfach schien, auf Zacken zu verzichten. Die gelbe Aura macht sie heller, wenn sie sich dem menschlichen Körper nähert).
Die Basis ist Apple-Beispiel hier.
① Grenzen Sie den menschlichen Körper Vergrößern Sie den menschlichen Körperteil und entfernen Sie die Farbe des ursprünglichen menschlichen Körperteils, um ihn zu einer Kante zu machen. ② Unschärfe ① ・ Wenn Sie die gelbe Unschärfe vergrößern und die weiße Unschärfe verkleinern, wird sie heller, wenn Sie sich dem menschlichen Körper nähern. ③② und das von der Kamera aufgenommene Bild werden kombiniert
Als Methode zur dicken Kante haben wir eine Methode zur Vergrößerung des menschlichen Körperbildes und zum Löschen der ursprünglichen Farbe des menschlichen Körperteils angewendet. Wird vom Compute Shader verarbeitet.
kernel void matteConvert(texture2d<half, access::read> inTexture [[ texture(0) ]],
texture2d<half, access::write> outWhiteTexture [[ texture(1) ]],
texture2d<half, access::write> outYellowTexture [[ texture(2) ]],
uint2 gid [[thread_position_in_grid]]) {
uint2 textureIndex(gid);
if (inTexture.read(textureIndex).r > 0.1) {
//Keine Farbe am menschlichen Körper
outWhiteTexture.write(half4(0.0), gid);
outYellowTexture.write(half4(0.0), gid);
return;
}
//Erweiterung
constexpr int scale = 15;
constexpr int radius = scale / 2;
half color = 0.0;
for (int i=0; i<scale; i++) {
for (int j=0; j<scale; j++) {
uint2 textureIndex(gid.x + (i - radius), gid.y + (j - radius));
half alpha = inTexture.read(textureIndex).r;
if (alpha > 0.1) {
color = 1.0;
break;
}
}
if (color > 0.0) {
break;
}
}
outWhiteTexture.write(half4(color, color, color, 1.0), gid);
outYellowTexture.write(half4(color, color, 0.0, 1.0), gid);
}
Wenn jedes Pixel des menschlichen Körperbildes innerhalb von 15 Pixel nach oben, unten, links und rechts liegt, wird es vergrößert, indem es als "menschlicher Körper" festgelegt wird. (Es werden 225 (= 15x15) Mal pro 1px wiederholt, daher ist die Last sehr hoch. Es ist besser, die Auflösung vor der Verarbeitung zu reduzieren, da sie ohnehin unscharf wird, aber ich konnte nicht mein Bestes geben. Ich werde es eines Tages tun.) Indem der Teil, der ursprünglich ein menschlicher Körper war, ungefärbt (schwarz) gemacht wird, entsteht ein Randbild. Die Ausgabe wird in dem Texturpuffer gespeichert, der für Weiß und Gelb vorbereitet ist.
Grenzergebnis (bestätigt mit Capture GPU Frame von Xcode12)
Blur verwendet Metal Performance Shader (Bildfilter). Der Metal Performance Shader kann problemlos mit Ihrem eigenen Shader kombiniert werden.
//Ändern Sie die Größe der Unschärfe mit der Zeit
time += 1
//Unschärfe (weiß)
let whiteIntensity = Int((sin(Float(time)/3) + 2) * 30) | 0x01 //Für die Größe von MPSImageTent muss eine ungerade Zahl angegeben werden.
let kernel1 = MPSImageTent(device: device, kernelWidth: whiteIntensity, kernelHeight: whiteIntensity)
kernel1.encode(commandBuffer: commandBuffer,
inPlaceTexture: &whiteBlurTexture!, fallbackCopyAllocator: nil)
//Unschärfe (gelb)
let yellowIntensity = Int((sin(Float(time)/3) + 2) * 100) | 0x01
let kernel2 = MPSImageTent(device: device, kernelWidth: yellowIntensity, kernelHeight: yellowIntensity)
kernel2.encode(commandBuffer: commandBuffer,
inPlaceTexture: &yellowBlurTexture!, fallbackCopyAllocator: nil)
Es gibt verschiedene Filter für Metal Performance Shader, aber nachdem ich einige ausprobiert habe, scheint MPSImageTent
der beste zu sein, also habe ich es übernommen.
Die weiße Unschärfe ist klein, die gelbe Unschärfe ist groß und die Größe der Unschärfe ändert sich im Laufe der Zeit.
Fügen Sie hier einfach das von der Kamera aufgenommene Bild und das Bild hinzu, nachdem Sie den Rand verwischt haben (weiß, gelb).
fragment half4 compositeImageFragmentShader(CompositeColorInOut in [[ stage_in ]],
texture2d<float, access::sample> capturedImageTextureY [[ texture(0) ]],
texture2d<float, access::sample> capturedImageTextureCbCr [[ texture(1) ]],
texture2d<float, access::sample> whiteColorTexture [[ texture(2) ]],
texture2d<float, access::sample> yellowColorTexture [[ texture(3) ]],
texture2d<float, access::sample> alphaTexture [[ texture(4) ]])
{
constexpr sampler s(address::clamp_to_edge, filter::linear);
float2 cameraTexCoord = in.texCoordCamera;
// Sample Y and CbCr textures to get the YCbCr color at the given texture coordinate.
float4 rgb = ycbcrToRGBTransform(capturedImageTextureY.sample(s, cameraTexCoord), capturedImageTextureCbCr.sample(s, cameraTexCoord));
half4 cameraColor = half4(rgb);
half4 whiteColor = half4(whiteColorTexture.sample(s, cameraTexCoord));
half4 yellowColor = half4(yellowColorTexture.sample(s, cameraTexCoord)) * 2.0;
return cameraColor + whiteColor + yellowColor;
}
Das Additionsergebnis funktioniert ohne Klemmung.
・ Schnell
ViewController.swift
class ViewController: UIViewController, MTKViewDelegate {
var session = ARSession()
var renderer: Renderer!
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as? MTKView {
view.device = MTLCreateSystemDefaultDevice()
view.backgroundColor = UIColor.clear
view.delegate = self
renderer = Renderer(session: session, metalDevice: view.device!, mtkView: view)
renderer.drawRectResized(size: view.bounds.size)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
configuration.frameSemantics = .personSegmentation
session.run(configuration)
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
renderer.drawRectResized(size: size)
}
func draw(in view: MTKView) {
renderer.update()
}
}
Renderer.swift
let kMaxBuffersInFlight: Int = 3
let kImagePlaneVertexData: [Float] = [
-1.0, -1.0, 0.0, 1.0,
1.0, -1.0, 1.0, 1.0,
-1.0, 1.0, 0.0, 0.0,
1.0, 1.0, 1.0, 0.0
]
class Renderer {
let session: ARSession
let matteGenerator: ARMatteGenerator
let device: MTLDevice
let inFlightSemaphore = DispatchSemaphore(value: kMaxBuffersInFlight)
var mtkView: MTKView
var commandQueue: MTLCommandQueue!
var imagePlaneVertexBuffer: MTLBuffer!
//Pipeline-Status für die endgültige Bildkomposition
var compositePipelineState: MTLRenderPipelineState!
//Pipeline-Status zum Vergrößern menschlicher Körperbilder
var computeState: MTLComputePipelineState!
//Erfasste Bildtextur
var capturedImageTextureY: CVMetalTexture?
var capturedImageTextureCbCr: CVMetalTexture?
var capturedImageTextureCache: CVMetalTextureCache!
//Bildtextur des menschlichen Körpers
var alphaTexture: MTLTexture? //Bild des menschlichen Körpers
var whiteBlurTexture: MTLTexture! //Eine Textur, die das Bild des menschlichen Körpers in Weiß vergrößert und verwischt
var yellowBlurTexture: MTLTexture! //Vergrößerte / unscharfe Textur des menschlichen Körperbildes in Gelb
//Bildschirmgröße
var viewportSize: CGSize = CGSize()
var viewportSizeDidChange: Bool = false
//Berechnen Sie die Größe der Shader-Thread-Gruppe bei der Verarbeitung von Bildern des menschlichen Körpers
var threadgroupSize = MTLSizeMake(32, 32, 1)
//Anzahl der Animationen
var time = 0
init(session: ARSession, metalDevice device: MTLDevice, mtkView: MTKView) {
self.session = session
self.device = device
self.mtkView = mtkView
matteGenerator = ARMatteGenerator(device: device, matteResolution: .half)
loadMetal()
}
func drawRectResized(size: CGSize) {
viewportSize = size
viewportSizeDidChange = true
}
func update() {
_ = inFlightSemaphore.wait(timeout: DispatchTime.distantFuture)
let commandBuffer = commandQueue.makeCommandBuffer()!
//Verhindern Sie, dass von der Kamera aufgenommene Texturen während des Renderns freigegeben werden
var textures = [capturedImageTextureY, capturedImageTextureCbCr]
commandBuffer.addCompletedHandler { [weak self] commandBuffer in
if let strongSelf = self {
strongSelf.inFlightSemaphore.signal()
}
textures.removeAll()
}
//Erfassung der Kameraerfassungstextur (Y, CbCr 2))
guard let currentFrame = session.currentFrame else { return }
let pixelBuffer = currentFrame.capturedImage
if CVPixelBufferGetPlaneCount(pixelBuffer) < 2 { return }
capturedImageTextureY = createTexture(fromPixelBuffer: pixelBuffer, pixelFormat: .r8Unorm, planeIndex: 0)
capturedImageTextureCbCr = createTexture(fromPixelBuffer: pixelBuffer, pixelFormat: .rg8Unorm, planeIndex: 1)
//Stellen Sie die UV-Koordinaten entsprechend der Bildschirmgröße ein
if viewportSizeDidChange {
viewportSizeDidChange = false
// Update the texture coordinates of our image plane to aspect fill the viewport
let displayToCameraTransform = currentFrame.displayTransform(for: .portrait, viewportSize: viewportSize).inverted()
let vertexData = imagePlaneVertexBuffer.contents().assumingMemoryBound(to: Float.self)
for index in 0...3 {
let textureCoordIndex = 4 * index + 2 //kImagePlaneVertexData sind die Scheitelpunktkoordinaten(x,y) +UV-Koordinaten(u,v)Es ist geworden. Weil UV eingestellt ist+2
let textureCoord = CGPoint(x: CGFloat(kImagePlaneVertexData[textureCoordIndex]), y: CGFloat(kImagePlaneVertexData[textureCoordIndex + 1]))
let transformedCoord = textureCoord.applying(displayToCameraTransform)
//Aufgenommenes Bild
vertexData[textureCoordIndex] = Float(transformedCoord.x)
vertexData[textureCoordIndex + 1] = Float(transformedCoord.y)
}
}
//Bilderfassung des menschlichen Körpers
alphaTexture = matteGenerator.generateMatte(from: currentFrame, commandBuffer: commandBuffer)
//Ich möchte den Effekt der Unschärfe größer erscheinen lassen als den menschlichen Körper, also habe ich das Bild des menschlichen Körpers vergrößert. Außerdem werden Texturen für zwei Farben, Weiß und Gelb, generiert.
if let width = alphaTexture?.width, let height = alphaTexture?.height {
let colorDesc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgra8Unorm,
width: width, height: height, mipmapped: false)
colorDesc.usage = [.shaderRead, .shaderWrite]
whiteBlurTexture = device.makeTexture(descriptor: colorDesc)
yellowBlurTexture = device.makeTexture(descriptor: colorDesc)
let threadCountW = (width + self.threadgroupSize.width - 1) / self.threadgroupSize.width
let threadCountH = (height + self.threadgroupSize.height - 1) / self.threadgroupSize.height
let threadgroupCount = MTLSizeMake(threadCountW, threadCountH, 1)
let computeEncoder = commandBuffer.makeComputeCommandEncoder()!
computeEncoder.setComputePipelineState(computeState)
computeEncoder.setTexture(alphaTexture, index: 0)
computeEncoder.setTexture(whiteBlurTexture, index: 1)
computeEncoder.setTexture(yellowBlurTexture, index: 2)
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
computeEncoder.endEncoding()
}
//Ändern Sie die Größe der Unschärfe mit der Zeit
time += 1
//Unschärfe (weiß)
let whiteIntensity = Int((sin(Float(time)/3) + 2) * 30) | 0x01 //Für die Größe von MPSImageTent muss eine ungerade Zahl angegeben werden.
let kernel1 = MPSImageTent(device: device, kernelWidth: whiteIntensity, kernelHeight: whiteIntensity)
kernel1.encode(commandBuffer: commandBuffer,
inPlaceTexture: &whiteBlurTexture!, fallbackCopyAllocator: nil)
//Unschärfe (gelb)
let yellowIntensity = Int((sin(Float(time)/3) + 2) * 100) | 0x01
let kernel2 = MPSImageTent(device: device, kernelWidth: yellowIntensity, kernelHeight: yellowIntensity)
kernel2.encode(commandBuffer: commandBuffer,
inPlaceTexture: &yellowBlurTexture!, fallbackCopyAllocator: nil)
//Aufgenommenes Bild + Unschärfe (weiß / gelb)
guard let renderPassDescriptor = mtkView.currentRenderPassDescriptor, let currentDrawable = mtkView.currentDrawable else { return }
let compositeRenderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!
compositeImagesWithEncoder(renderEncoder: compositeRenderEncoder)
compositeRenderEncoder.endEncoding()
commandBuffer.present(currentDrawable)
commandBuffer.commit()
}
func loadMetal() {
commandQueue = device.makeCommandQueue()
let imagePlaneVertexDataCount = kImagePlaneVertexData.count * MemoryLayout<Float>.size
imagePlaneVertexBuffer = device.makeBuffer(bytes: kImagePlaneVertexData, length: imagePlaneVertexDataCount, options: [])
//Von der Kamera aufgenommener Bildcache
var textureCache: CVMetalTextureCache?
CVMetalTextureCacheCreate(nil, nil, device, nil, &textureCache)
capturedImageTextureCache = textureCache
//Zusammengesetzte Pipeline aus Kamera aufgenommenem Bild + Bild des menschlichen Körpers
let defaultLibrary = device.makeDefaultLibrary()!
let compositePipelineStateDescriptor = MTLRenderPipelineDescriptor()
compositePipelineStateDescriptor.sampleCount = 1
compositePipelineStateDescriptor.vertexFunction = defaultLibrary.makeFunction(name: "compositeImageVertexTransform")!
compositePipelineStateDescriptor.fragmentFunction = defaultLibrary.makeFunction(name: "compositeImageFragmentShader")!
compositePipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
try! compositePipelineState = device.makeRenderPipelineState(descriptor: compositePipelineStateDescriptor)
//Berechnen Sie den Shader für die Kanten des menschlichen Körpers
let edgeShader = defaultLibrary.makeFunction(name: "matteConvert")!
computeState = try! self.device.makeComputePipelineState(function: edgeShader)
}
//Generieren Sie eine MTL-Textur aus dem aufgenommenen Bild
func createTexture(fromPixelBuffer pixelBuffer: CVPixelBuffer, pixelFormat: MTLPixelFormat, planeIndex: Int) -> CVMetalTexture? {
let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex)
let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex)
var texture: CVMetalTexture? = nil
let status = CVMetalTextureCacheCreateTextureFromImage(nil, capturedImageTextureCache, pixelBuffer, nil, pixelFormat,
width, height, planeIndex, &texture)
if status != kCVReturnSuccess {
texture = nil
}
return texture
}
func compositeImagesWithEncoder(renderEncoder: MTLRenderCommandEncoder) {
guard let textureY = capturedImageTextureY, let textureCbCr = capturedImageTextureCbCr else { return }
renderEncoder.setCullMode(.none)
renderEncoder.setRenderPipelineState(compositePipelineState)
renderEncoder.setVertexBuffer(imagePlaneVertexBuffer, offset: 0, index: 0)
renderEncoder.setFragmentTexture(CVMetalTextureGetTexture(textureY), index: 0)
renderEncoder.setFragmentTexture(CVMetalTextureGetTexture(textureCbCr), index: 1)
renderEncoder.setFragmentTexture(whiteBlurTexture, index: 2)
renderEncoder.setFragmentTexture(yellowBlurTexture, index: 3)
renderEncoder.setFragmentTexture(alphaTexture, index: 4)
renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
}
}
・ Shader
Shaders.metal
typedef struct {
float2 position [[attribute(kVertexAttributePosition)]];
float2 texCoord [[attribute(kVertexAttributeTexcoord)]];
} ImageVertex;
typedef struct {
float4 position [[position]];
float2 texCoord;
} ImageColorInOut;
vertex ImageColorInOut capturedImageVertexTransform(ImageVertex in [[stage_in]]) {
ImageColorInOut out;
out.position = float4(in.position, 0.0, 1.0);
out.texCoord = in.texCoord;
return out;
}
// Convert from YCbCr to rgb
float4 ycbcrToRGBTransform(float4 y, float4 CbCr) {
const float4x4 ycbcrToRGBTransform = float4x4(
float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f)
);
float4 ycbcr = float4(y.r, CbCr.rg, 1.0);
return ycbcrToRGBTransform * ycbcr;
}
// This defines the captured image fragment function.
fragment float4 capturedImageFragmentShader(ImageColorInOut in [[stage_in]],
texture2d<float, access::sample> capturedImageTextureY [[ texture(kTextureIndexY) ]],
texture2d<float, access::sample> capturedImageTextureCbCr [[ texture(kTextureIndexCbCr) ]]) {
constexpr sampler colorSampler(mip_filter::linear,
mag_filter::linear,
min_filter::linear);
// Sample Y and CbCr textures to get the YCbCr color at the given texture coordinate.
return ycbcrToRGBTransform(capturedImageTextureY.sample(colorSampler, in.texCoord),
capturedImageTextureCbCr.sample(colorSampler, in.texCoord));
}
typedef struct {
float2 position;
float2 texCoord;
} CompositeVertex;
typedef struct {
float4 position [[position]];
float2 texCoordCamera;
} CompositeColorInOut;
// Composite the image vertex function.
vertex CompositeColorInOut compositeImageVertexTransform(const device CompositeVertex* cameraVertices [[ buffer(0) ]],
unsigned int vid [[ vertex_id ]]) {
CompositeColorInOut out;
const device CompositeVertex& cv = cameraVertices[vid];
out.position = float4(cv.position, 0.0, 1.0);
out.texCoordCamera = cv.texCoord;
return out;
}
// Composite the image fragment function.
fragment half4 compositeImageFragmentShader(CompositeColorInOut in [[ stage_in ]],
texture2d<float, access::sample> capturedImageTextureY [[ texture(0) ]],
texture2d<float, access::sample> capturedImageTextureCbCr [[ texture(1) ]],
texture2d<float, access::sample> whiteColorTexture [[ texture(2) ]],
texture2d<float, access::sample> yellowColorTexture [[ texture(3) ]],
texture2d<float, access::sample> alphaTexture [[ texture(4) ]])
{
constexpr sampler s(address::clamp_to_edge, filter::linear);
float2 cameraTexCoord = in.texCoordCamera;
// Sample Y and CbCr textures to get the YCbCr color at the given texture coordinate.
float4 rgb = ycbcrToRGBTransform(capturedImageTextureY.sample(s, cameraTexCoord), capturedImageTextureCbCr.sample(s, cameraTexCoord));
half4 cameraColor = half4(rgb);
half4 whiteColor = half4(whiteColorTexture.sample(s, cameraTexCoord));
half4 yellowColor = half4(yellowColorTexture.sample(s, cameraTexCoord)) * 2.0;
return cameraColor + whiteColor + yellowColor;
}
//(Vergrößertes Bild des menschlichen Körpers-Erstellen Sie den Rand des menschlichen Körpers mit (Bild des menschlichen Körpers) und geben Sie ihn als weiße und gelbe Textur aus
kernel void matteConvert(texture2d<half, access::read> inTexture [[ texture(0) ]],
texture2d<half, access::write> outWhiteTexture [[ texture(1) ]],
texture2d<half, access::write> outYellowTexture [[ texture(2) ]],
uint2 gid [[thread_position_in_grid]]) {
uint2 textureIndex(gid);
if (inTexture.read(textureIndex).r > 0.1) {
//Keine Farbe am menschlichen Körper
outWhiteTexture.write(half4(0.0), gid);
outYellowTexture.write(half4(0.0), gid);
return;
}
//Erweiterung
constexpr int scale = 15;
constexpr int radius = scale / 2;
half color = 0.0;
for (int i=0; i<scale; i++) {
for (int j=0; j<scale; j++) {
uint2 textureIndex(gid.x + (i - radius), gid.y + (j - radius));
half alpha = inTexture.read(textureIndex).r;
if (alpha > 0.1) {
color = 1.0;
break;
}
}
if (color > 0.0) {
break;
}
}
outWhiteTexture.write(half4(color, color, color, 1.0), gid);
outYellowTexture.write(half4(color, color, 0.0, 1.0), gid);
}