メインコンテンツまでスキップ

Add a custom rendered layer

This example demonstrates how to create a custom layer over the MapView using the Mapbox Maps SDK for iOS. The custom layer utilizes Metalkit, a 3D graphics pipeline for rendering the vertex and fragment shaders. This custom map style layer is then hosted by the CustomLayerHost protocol and applied to the MapView.

The custom layer is created and added to the map using the setMapStyleContent function. This function specifies which runtime styling components will be added to the style. The custom layer is then repainted with the new color scheme using the triggerRepaint() function.

iOS Examples App Available

This example code is part of the Maps SDK for iOS Examples App, a working iOS project available on Github. iOS developers are encouraged to run the examples app locally to interact with this example in an emulator and explore other features of the Maps SDK.

See our Run the Maps SDK for iOS Examples App tutorial for step-by-step instructions.

CustomLayerExample.swift
import UIKit
@_spi(Experimental) import MapboxMaps
import MetalKit

final class ViewController: UIViewController {
private var mapView: MapView!

var colorArray = [
simd_float4(1, 0, 0, 0.5),
simd_float4(0.5, 0, 0, 0.5),
simd_float4(0, 1, 0, 0.5),
simd_float4(0, 0.5, 0, 0.5),
simd_float4(0, 0, 1, 0.5),
simd_float4(0, 0, 0.5, 0.5),
]

// The ViewControllerCustomLayerHost() should be created and stored outside of MapStyleContent so that it is not recreated with every style update.
let renderer = ViewControllerCustomLayerHost()

override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Update", style: .plain, target: self, action: #selector(barButtonTap(_:)))

let cameraOptions = CameraOptions(center: CLLocationCoordinate2D(latitude: 58, longitude: 20), zoom: 3)

mapView = MapView(frame: view.bounds, mapInitOptions: MapInitOptions(cameraOptions: cameraOptions))
mapView.mapboxMap.mapStyle = .streets
if #available(iOS 13.0, *) {
mapView.mapboxMap.setMapStyleContent {
StyleProjection(name: .mercator)
CustomLayer(id: "custom-layer-example", renderer: renderer)
.position(.below("waterway"))
}
}

mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(mapView)
}

@objc func barButtonTap(_ barButtonItem: UIBarButtonItem) {
renderer.colors = colorArray.shuffled()

// You must trigger a repaint manually to re-draw the updated Custom Layer
mapView.mapboxMap.triggerRepaint()
}


}

final class ViewControllerCustomLayerHost: NSObject, CustomLayerHost {
var colors = [
simd_float4(1, 0, 0, 0.5),
simd_float4(0, 1, 0, 0.5),
simd_float4(0, 0, 1, 0.5),
]

var depthStencilState: MTLDepthStencilState!
var pipelineState: MTLRenderPipelineState!

func renderingWillStart(_ metalDevice: MTLDevice, colorPixelFormat: UInt, depthStencilPixelFormat: UInt) {
guard let library = metalDevice.makeDefaultLibrary() else {
fatalError("Failed to create shader")
}

guard let vertexFunction = library.makeFunction(name: "vertexShader") else {
fatalError("Could not find vertex function")
}

guard let fragmentFunction = library.makeFunction(name: "fragmentShader") else {
fatalError("Could not find fragment function")
}

// Set up vertex descriptor
let vertexDescriptor = MTLVertexDescriptor()

// Set up pipeline descriptor
let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
pipelineStateDescriptor.label = "Test Layer"
pipelineStateDescriptor.vertexFunction = vertexFunction
pipelineStateDescriptor.vertexDescriptor = vertexDescriptor
pipelineStateDescriptor.fragmentFunction = fragmentFunction

// Set up color attachment
let colorAttachment = pipelineStateDescriptor.colorAttachments[0]
colorAttachment?.pixelFormat = MTLPixelFormat(rawValue: colorPixelFormat)!
colorAttachment?.isBlendingEnabled = true
colorAttachment?.rgbBlendOperation = colorAttachment?.alphaBlendOperation ?? .add
colorAttachment?.sourceAlphaBlendFactor = colorAttachment?.sourceAlphaBlendFactor ?? .one
colorAttachment?.destinationRGBBlendFactor = .oneMinusSourceAlpha

// Configure render pipeline descriptor
pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormat(rawValue: depthStencilPixelFormat)!
pipelineStateDescriptor.stencilAttachmentPixelFormat = MTLPixelFormat(rawValue: depthStencilPixelFormat)!

// Configure the depth stencil
let depthStencilDescriptor = MTLDepthStencilDescriptor()
depthStencilDescriptor.isDepthWriteEnabled = false
depthStencilDescriptor.depthCompareFunction = .always

depthStencilState = metalDevice.makeDepthStencilState(descriptor: depthStencilDescriptor)

do {
pipelineState = try metalDevice.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
} catch {
fatalError("Could not make render pipeline state: \(error.localizedDescription)")
}
}

func render(_ parameters: CustomLayerRenderParameters, mtlCommandBuffer: MTLCommandBuffer, mtlRenderPassDescriptor: MTLRenderPassDescriptor) {

let zoomScale = pow(2, parameters.zoom)
let projectedHelsinki = Projection.project(.helsinki, zoomScale: zoomScale)
let projectedBerlin = Projection.project(.berlin, zoomScale: zoomScale)
let projectedKyiv = Projection.project(.kyiv, zoomScale: zoomScale)
let positions = [
simd_float2(Float(projectedHelsinki.x), Float(projectedHelsinki.y)),
simd_float2(Float(projectedBerlin.x), Float(projectedBerlin.y)),
simd_float2(Float(projectedKyiv.x), Float(projectedKyiv.y))
]

guard let renderCommandEncoder = mtlCommandBuffer.makeRenderCommandEncoder(descriptor: mtlRenderPassDescriptor) else {
fatalError("Could not create render command encoder from render pass descriptor.")
}

let projectionMatrix = parameters.projectionMatrix.map(\.floatValue)
let vertices = zip(positions, colors).map(VertexData.init)
let viewport = MTLViewport(
originX: 0,
originY: 0,
width: parameters.width,
height: parameters.height,
znear: 0,
zfar: 1
)

renderCommandEncoder.label = "Custom Layer"
renderCommandEncoder.pushDebugGroup("Custom Layer")
renderCommandEncoder.setDepthStencilState(depthStencilState)
renderCommandEncoder.setRenderPipelineState(pipelineState)
renderCommandEncoder.setVertexBytes(
vertices,
length: MemoryLayout<VertexData>.size * vertices.count,
index: Int(VertexInputIndexVertices.rawValue)
)
renderCommandEncoder.setVertexBytes(
projectionMatrix,
length: MemoryLayout<simd_float4x4>.size,
index: Int(VertexInputIndexTransformation.rawValue)
)
renderCommandEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)

renderCommandEncoder.setViewport(viewport)
renderCommandEncoder.popDebugGroup()
renderCommandEncoder.endEncoding()
}

func renderingWillEnd() {
// Unimplemented
}
}
このexampleは役に立ちましたか?