How to properly render a 3d model using Metal IOS? - ios

I created a 3D object using blender and exported it as an OBJ file and I tried to render it using Metal by following this http://metalbyexample.com/modern-metal-1 tutorial. But some of my 3D object parts are missing. They are not rendered properly.
Here is my 3D object in blender :-
Here is my rendered object in Metal :-
Here is my blender file :-
https://gofile.io/?c=XfQYLK
How should i fix this?
I already rendered some other shapes like, rectangle, Circle, Star successfully. But the problem is with this shape. I did not change the way i create the shape nor the way it is exported from the blender. Even though I did everything in same way problem still there.
Here is how i load the OBJ file
private var vertexDescriptor: MTLVertexDescriptor!
private var meshes: [MTKMesh] = []
private func loadResource() {
let modelUrl = Bundle.main.url(forResource: self.meshName, withExtension: "obj")
let vertexDescriptor = MDLVertexDescriptor()
vertexDescriptor.attributes[0] = MDLVertexAttribute(name: MDLVertexAttributePosition, format: .float3, offset: 0, bufferIndex: 0)
vertexDescriptor.attributes[1] = MDLVertexAttribute(name: MDLVertexAttributeNormal, format: .float3, offset: MemoryLayout<Float>.size * 3, bufferIndex: 0)
vertexDescriptor.attributes[2] = MDLVertexAttribute(name: MDLVertexAttributeTextureCoordinate, format: .float2, offset: MemoryLayout<Float>.size * 6, bufferIndex: 0)
vertexDescriptor.layouts[0] = MDLVertexBufferLayout(stride: MemoryLayout<Float>.size * 8)
self.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(vertexDescriptor)
let bufferAllocator = MTKMeshBufferAllocator(device: self.device)
let asset = MDLAsset(url: modelUrl, vertexDescriptor: vertexDescriptor, bufferAllocator: bufferAllocator)
(_, meshes) = try! MTKMesh.newMeshes(asset: asset, device: device)
}
Here is my vertex and fragment shaders :-
struct VertexOut {
float4 position [[position]];
float4 eyeNormal;
float4 eyePosition;
float2 texCoords;
};
vertex VertexOut vertex_3d(VertexIn vertexIn [[stage_in]])
{
VertexOut vertexOut;
vertexOut.position = float4(vertexIn.position, 1);
vertexOut.eyeNormal = float4(vertexIn.normal, 1);
vertexOut.eyePosition = float4(vertexIn.position, 1);
vertexOut.texCoords = vertexIn.texCoords;
return vertexOut;
}
fragment float4 fragment_3d(VertexOut fragmentIn [[stage_in]]) {
return float4(0.33, 0.53, 0.25, 0.5);
}
And here my CommandEncoder :-
func render(commandEncoder: MTLRenderCommandEncoder) {
commandEncoder.setRenderPipelineState(self.renderPipelineState)
let mesh = meshes[0]
let vertexBuffer = mesh.vertexBuffers.first!
commandEncoder.setVertexBuffer(vertexBuffer.buffer, offset: vertexBuffer.offset, index: 0)
let indexBuffer = mesh.submeshes[0].indexBuffer
commandEncoder.drawIndexedPrimitives(type: mesh.submeshes[0].primitiveType,
indexCount: mesh.submeshes[0].indexCount,
indexType: mesh.submeshes[0].indexType,
indexBuffer: indexBuffer.buffer,
indexBufferOffset: indexBuffer.offset)
commandEncoder.endEncoding()
}
Presenting to the drawable is handled in a different place.
How should i properly render my 3D object using Metal?

I made this public repo: https://github.com/danielrosero/ios-touchingMetal, and I think is a great starting point for 3d Rendering with Metal, with textures and a compute function.
You should just change the models inside, check Renderer.swift init(view: MTKView) method.
// Create the MTLTextureLoader options that we need according to each model case. Some of them are flipped, and so on.
let textureLoaderOptionsWithFlip: [MTKTextureLoader.Option : Any] = [.generateMipmaps : true, .SRGB : true, .origin : MTKTextureLoader.Origin.bottomLeft]
let textureLoaderOptionsWithoutFlip: [MTKTextureLoader.Option : Any] = [.generateMipmaps : true, .SRGB : true]
// ****
// Initializing the models, set their position, scale and do a rotation transformation
// Cat model
cat = Model(name: "cat",vertexDescriptor: vertexDescriptor,textureFile: "cat.tga", textureLoaderOptions: textureLoaderOptionsWithFlip)
cat.transform.position = [-1, -0.5, 1.5]
cat.transform.scale = 0.08
cat.transform.rotation = vector_float3(0,radians(fromDegrees: 180),0)
// ****
// Dog model
dog = Model(name: "dog",vertexDescriptor: vertexDescriptor,textureFile: "dog.tga", textureLoaderOptions: textureLoaderOptionsWithFlip)
dog.transform.position = [1, -0.5, 1.5]
dog.transform.scale = 0.018
dog.transform.rotation = vector_float3(0,radians(fromDegrees: 180),0)
// ****
This is the way I import models in my implementation, check Model.swift
//
// Model.swift
// touchingMetal
//
// Created by Daniel Rosero on 1/8/20.
// Copyright © 2020 Daniel Rosero. All rights reserved.
//
import Foundation
import MetalKit
//This extension allows to create a MTLTexture attribute inside this Model class
//in order to be identified and used in the Renderer. This is to ease the loading in case of multiple models in the scene
extension Model : Texturable{
}
class Model {
let mdlMeshes: [MDLMesh]
let mtkMeshes: [MTKMesh]
var texture: MTLTexture?
var transform = Transform()
let name: String
//In order to create a model, you need to pass a name to use it as an identifier,
// a reference to the vertexDescriptor, the imagename with the extension of the texture,
//the dictionary of MTKTextureLoader.Options
init(name: String, vertexDescriptor: MDLVertexDescriptor, textureFile: String, textureLoaderOptions: [MTKTextureLoader.Option : Any]) {
let assetUrl = Bundle.main.url(forResource: name, withExtension: "obj")
let allocator = MTKMeshBufferAllocator(device: Renderer.device)
let asset = MDLAsset(url: assetUrl, vertexDescriptor: vertexDescriptor, bufferAllocator: allocator)
let (mdlMeshes, mtkMeshes) = try! MTKMesh.newMeshes(asset: asset, device: Renderer.device)
self.mdlMeshes = mdlMeshes
self.mtkMeshes = mtkMeshes
self.name = name
texture = setTexture(device: Renderer.device, imageName: textureFile, textureLoaderOptions: textureLoaderOptions)
}
}

If the 3D model is not triangulated properly it will miss behave in Metal. In order to render 3D model correctly, When exporting from modeling software to an OBJ file turn on Triangulate Faces option. This will turn all the faces to triangles. So Metal will not have to re triangulate the faces. But this process may change the vertex order. But 3D model will not change. Only the order of vertices will change.

Related

How do I remove noise from an audio signal? And what threshold/threshold range should I use?

I have loaded an audio file and have created an input and output buffer.
But when I follow Apple's post I get an output signal that has distorted sounds.
private func extractSignal(input: AVAudioPCMBuffer, output: AVAudioPCMBuffer) {
let count = 256
let forward = vDSP.DCT(previous: nil, count: count, transformType: .II)!
let inverse = vDSP.DCT(previous: nil, count: count, transformType: .III)!
// Iterates over the signal.
input.iterate(signalCount: 32000) { step, signal in
var series = forward.transform(signal)
series = vDSP.threshold(series, to: 0.0003, with: .zeroFill) // What should this threshold be?
var inversed = inverse.transform(series)
let divisor: Float = Float(count / 2)
inversed = vDSP.divide(inversed, divisor)
// Code: write inversed to output buffer.
output.frameLength = AVAudioFrameCount(step * signal.count + signal.count)
}
}

Metal core image kernel with sampler

I am trying to use a CIColorKernel or CIBlendKernel with sampler arguments but the program crashes. Here is my shader code which compiles successfully.
extern "C" float4 wipeLinear(coreimage::sampler t1, coreimage::sampler t2, float time) {
float2 coord1 = t1.coord();
float2 coord2 = t2.coord();
float4 innerRect = t2.extent();
float minX = innerRect.x + time*innerRect.z;
float minY = innerRect.y + time*innerRect.w;
float cropWidth = (1 - time) * innerRect.w;
float cropHeight = (1 - time) * innerRect.z;
float4 s1 = t1.sample(coord1);
float4 s2 = t2.sample(coord2);
if ( coord1.x > minX && coord1.x < minX + cropWidth && coord1.y > minY && coord1.y <= minY + cropHeight) {
return s1;
} else {
return s2;
}
}
And it crashes on initialization.
class CIWipeRenderer: CIFilter {
var backgroundImage:CIImage?
var foregroundImage:CIImage?
var inputTime: Float = 0.0
static var kernel:CIColorKernel = { () -> CIColorKernel in
let url = Bundle.main.url(forResource: "AppCIKernels", withExtension: "ci.metallib")!
let data = try! Data(contentsOf: url)
return try! CIColorKernel(functionName: "wipeLinear", fromMetalLibraryData: data) //Crashes here!!!!
}()
override var outputImage: CIImage? {
guard let backgroundImage = backgroundImage else {
return nil
}
guard let foregroundImage = foregroundImage else {
return nil
}
return CIWipeRenderer.kernel.apply(extent: backgroundImage.extent, arguments: [backgroundImage, foregroundImage, inputTime])
}
}
It crashes in the try line with the following error:
Fatal error: 'try!' expression unexpectedly raised an error: Foundation._GenericObjCError.nilError
If I replace the kernel code with the following, it works like a charm:
extern "C" float4 wipeLinear(coreimage::sample_t s1, coreimage::sample_t s2, float time)
{
return mix(s1, s2, time);
}
So there are no obvious errors in the code, such as passing incorrect function name or so.
For your use case, you actually can use a CIColorKernel. You just have to pass the extent of your render destination to the kernel as well, then you don't need the sampler to access it.
The kernel would look like this:
extern "C" float4 wipeLinear(coreimage::sample_t t1, coreimage::sample_t t2, float4 destinationExtent, float time, coreimage::destination destination) {
float minX = destinationExtent.x + time * destinationExtent.z;
float minY = destinationExtent.y + time * destinationExtent.w;
float cropWidth = (1.0 - time) * destinationExtent.w;
float cropHeight = (1.0 - time) * destinationExtent.z;
float2 destCoord = destination.coord();
if ( destCoord.x > minX && destCoord.x < minX + cropWidth && destCoord.y > minY && destCoord.y <= minY + cropHeight) {
return t1;
} else {
return t2;
}
}
And you call it like this:
let destinationExtent = CIVector(cgRect: backgroundImage.extent)
return CIWipeRenderer.kernel.apply(extent: backgroundImage.extent, arguments: [backgroundImage, foregroundImage, destinationExtent, inputTime])
Note that the last destination parameter in the kernel is passed automatically by Core Image. You don't need to pass it with the arguments.
Yes, you can't use samplers in CIColorKernel or CIBlendKernel. Those kernels are optimized for the use case where you have a 1:1 mapping from input pixel to output pixel. This allows Core Image to execute multiple of these kernels in one command buffer since they don't require any intermediate buffer writes.
A sampler would allow you to sample the input at arbitrary coordinates, which is not allowed in this case.
You can simply use a CIKernel instead. It's meant to be used when you need to sample the input more freely.
To initialize the kernel, you need to adapt the code like this:
static var kernel: CIKernel = {
let url = Bundle.main.url(forResource: "AppCIKernels", withExtension: "ci.metallib")!
let data = try! Data(contentsOf: URL)
return try! CIKernel(functionName: "wipeLinear", fromMetalLibraryData: data)
}()
When calling the kernel, you now need to also provide a ROI callback, like this:
let roiCallback: CIKernelROICallback = { index, rect -> CGRect in
return rect // you need the same region from the input as the output
}
// or even shorter
let roiCallback: CIKernelROICallback = { $1 }
return CIWipeRenderer.kernel.apply(extent: backgroundImage.extent, roiCallback: roiCallback, arguments: [backgroundImage, foregroundImage, inputTime])
Bonus answer:
For this blending effect, you actually don't need any kernel at all. You can achieve all that with simple cropping and compositing:
class CIWipeRenderer: CIFilter {
var backgroundImage:CIImage?
var foregroundImage:CIImage?
var inputTime: CGFloat = 0.0
override var outputImage: CIImage? {
guard let backgroundImage = backgroundImage else { return nil }
guard let foregroundImage = foregroundImage else { return nil }
// crop the foreground based on time
var foregroundCrop = foregroundImage.extent
foregroundCrop.size.width *= inputTime
foregroundCrop.size.height *= inputTime
return foregroundImage.cropped(to: foregroundCrop).composited(over: backgroundImage)
}
}

Metal Depth Clamping

I want to disable clamping between far and close points. Already tred to modify sampler to disable clamp to edge (constexpr sampler s(address::clamp_to_zero) and it worked as expected for the edges, but coordinates between most far and close points are still clamping.
Current unwanted result:
https://gph.is/g/ZyWjkzW
Expected result:
https://i.imgur.com/GjvwgyU.png
Also tried encoder.setDepthClipMode(.clip) but it didn't worked.
Some portions of code:
let descriptor = MTLRenderPipelineDescriptor()
descriptor.colorAttachments[0].pixelFormat = .rgba16Float
descriptor.colorAttachments[1].pixelFormat = .rgba16Float
descriptor.depthAttachmentPixelFormat = .invalid
let descriptor = MTLRenderPassDescriptor()
descriptor.colorAttachments[0].texture = outputColorTexture
descriptor.colorAttachments[0].clearColor = clearColor
descriptor.colorAttachments[0].loadAction = .load
descriptor.colorAttachments[0].storeAction = .store
descriptor.colorAttachments[1].texture = outputDepthTexture
descriptor.colorAttachments[1].clearColor = clearColor
descriptor.colorAttachments[1].loadAction = .load
descriptor.colorAttachments[1].storeAction = .store
descriptor.renderTargetWidth = Int(drawableSize.width)
descriptor.renderTargetHeight = Int(drawableSize.height)
guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else { throw RenderingError.makeDescriptorFailed }
encoder.setDepthClipMode(.clip)
encoder.setRenderPipelineState(pipelineState)
encoder.setFragmentTexture(inputColorTexture, index: 0)
encoder.setFragmentTexture(inputDepthTexture, index: 1)
encoder.setFragmentBuffer(uniformsBuffer, offset: 0, index: 0)
encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
encoder.endEncoding()

Generating random data using Metal Performance Shaders

I am trying to generate some random integer data for my app with the GPU using MPSMatrixRandom, and I have two questions.
What is the difference between MPSMatrixRandomMTGP32 and MPSMatrixRandomPhilox?
I understand that these two shaders use different algorithms, but what are the differences between them? Does the performance or output of these two algorithms differ, and if so, how?
What code can you use to implement these shaders?
I tried to implement them myself, but my app consistently crashes with vague error messages. I'd like to see an example implementation of this being done properly.
Here's a sample demonstrating how to generate random matrices using these two kernels:
import Foundation
import Metal
import MetalPerformanceShaders
let device = MTLCreateSystemDefaultDevice()!
let commandQueue = device.makeCommandQueue()!
let rows = 8
let columns = 8
let matrixDescriptor = MPSMatrixDescriptor(rows: rows,
columns: columns,
rowBytes: MemoryLayout<Float>.stride * columns,
dataType: .float32)
let mtMatrix = MPSMatrix(device: device, descriptor: matrixDescriptor)
let phMatrix = MPSMatrix(device: device, descriptor: matrixDescriptor)
let distribution = MPSMatrixRandomDistributionDescriptor.uniformDistributionDescriptor(withMinimum: -1.0, maximum: 1.0)
let mtKernel = MPSMatrixRandomMTGP32(device: device,
destinationDataType: .float32,
seed: 0,
distributionDescriptor: distribution)
let phKernel = MPSMatrixRandomPhilox(device: device,
destinationDataType: .float32,
seed: 0,
distributionDescriptor: distribution)
let commandBuffer = commandQueue.makeCommandBuffer()!
mtKernel.encode(commandBuffer: commandBuffer, destinationMatrix: mtMatrix)
phKernel.encode(commandBuffer: commandBuffer, destinationMatrix: phMatrix)
#if os(macOS)
mtMatrix.synchronize(on: commandBuffer)
phMatrix.synchronize(on: commandBuffer)
#endif
commandBuffer.commit()
commandBuffer.waitUntilCompleted() // Only necessary to ensure GPU->CPU sync for display
print("Mersenne Twister values:")
let mtValues = mtMatrix.data.contents().assumingMemoryBound(to: Float.self)
for row in 0..<rows {
for col in 0..<columns {
print("\(mtValues[row * columns + col])", terminator: " ")
}
print("")
}
print("")
print("Philox values:")
let phValues = phMatrix.data.contents().assumingMemoryBound(to: Float.self)
for row in 0..<rows {
for col in 0..<columns {
print("\(phValues[row * columns + col])", terminator: " ")
}
print("")
}
I can't comment on the statistical properties of these generators; I'd refer you to the papers mentioned in the comments.

Custom layer with two parameters function on Core ML

Thanks to this great article(http://machinethink.net/blog/coreml-custom-layers/), I understood how to write converting using coremltools and Lambda with Keras custom layer.
But, I cannot understand on the situation, function with two parameters.
#python
def scaling(x, scale):
return x * scale
Keras layer is here.
#python
up = conv2d_bn(mixed,
K.int_shape(x)[channel_axis],
1,
activation=None,
use_bias=True,
name=name_fmt('Conv2d_1x1'))
x = Lambda(scaling, # HERE !!
output_shape=K.int_shape(up)[1:],
arguments={'scale': scale})(up)
x = add([x, up])
On this situation, how can I write func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) in custom MLCustomLayer class on Swift? I understand just in one parameter function situation, like this,
#swift
func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws {
for i in 0..<inputs.count {
let input = inputs[i]
let output = outputs[i]
for j in 0..<input.count {
let x = input[j].floatValue
let y = x / (1 + exp(-x))
output[j] = NSNumber(value: y)
}
}
}
How about two parameters function, like x * scale?
Full code is here.
Converting to Core ML model with custom layer
https://github.com/osmszk/dla_team14/blob/master/facenet/coreml/CoremlTest.ipynb
Network model by Keras
https://github.com/osmszk/dla_team14/blob/master/facenet/code/facenet_keras_v2.py
Thank you.
It looks like scale is a hyperparameter, not a learnable parameter, is that correct?
In that case, you need to add scale to the parameters dictionary for the custom layer. Then in your Swift class, scale will also be inside the parameters dictionary that is passed into your init(parameters) function. Store it inside a property and then in evaluate(inputs, outputs) read from that property again.
My blog post actually shows how to do this. ;-)
I solved this problem on this way thanks to hollance's blog. On converting func, in this case, in convert_lambda, I should have added a scale parameter for the custom layer.
python code(converting Core ML)
def convert_lambda(layer):
if layer.function == scaling:
params = NeuralNetwork_pb2.CustomLayerParams()
params.className = "scaling"
params.description = "scaling input"
# HERE!! This is important.
params.parameters["scale"].doubleValue = layer.arguments['scale']
return params
else:
return None
coreml_model = coremltools.converters.keras.convert(
model,
input_names="image",
image_input_names="image",
output_names="output",
add_custom_layers=True,
custom_conversion_functions={ "Lambda": convert_lambda })
swift code(Custom layer)
//custom MLCustomLayer `scaling` class
let scale: Float
required init(parameters: [String : Any]) throws {
if let scale = parameters["scale"] as? Float {
self.scale = scale
} else {
self.scale = 1.0
}
print(#function, parameters, self.scale)
super.init()
}
func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws {
for i in 0..<inputs.count {
let input = inputs[i]
let output = outputs[i]
for j in 0..<input.count {
let x = input[j].floatValue
let y = x * self.scale
output[j] = NSNumber(value: y)
}
//faster
/*
let count = input.count
let inputPointer = UnsafeMutablePointer<Float>(OpaquePointer(input.dataPointer))
let outputPointer = UnsafeMutablePointer<Float>(OpaquePointer(output.dataPointer))
var scale = self.scale
vDSP_vsmul(inputPointer, 1, &scale, outputPointer, 1, vDSP_Length(count))
*/
}
}
Thank you.

Resources