|
// |
|
// ReflectionSession.swift |
|
// |
|
|
|
import Foundation |
|
import SceneKit |
|
import MetalPerformanceShaders |
|
|
|
public class ReflectionSession { |
|
|
|
let reflectionMapSize = 200 |
|
private let renderer: SCNRenderer |
|
private let scene: SCNScene? |
|
private let view: SCNView |
|
private let device: MTLDevice |
|
|
|
private var nodes: [SCNNode] = [] |
|
|
|
private var commandQueue: MTLCommandQueue |
|
private var blurHandlers: [MPSImageBox?] = [] |
|
|
|
// private var renderTimer: Timer |
|
|
|
public enum UpdateSpeed { |
|
case slow |
|
case normal |
|
case fast |
|
case reallyFast |
|
} |
|
|
|
public var reflectionUpdateSpeed: UpdateSpeed = .normal |
|
|
|
public init(inView view: SCNView) { |
|
|
|
device = MTLCreateSystemDefaultDevice()! |
|
renderer = SCNRenderer(device: device, options: nil) |
|
scene = view.scene |
|
self.view = view |
|
// renderTimer = Timer() |
|
commandQueue = device.makeCommandQueue()! |
|
|
|
let cubemapDescriptor = MTLTextureDescriptor.textureCubeDescriptor(pixelFormat: .rgba8Unorm, size: reflectionMapSize, mipmapped: false) |
|
cubemapDescriptor.usage = MTLTextureUsage(rawValue: MTLTextureUsage.renderTarget.rawValue | MTLTextureUsage.shaderRead.rawValue) |
|
pendingTexture = device.makeTexture(descriptor: cubemapDescriptor)! |
|
|
|
renderer.scene = scene |
|
scene?.rootNode.addChildNode(camera360Node) |
|
|
|
// renderTimer = Timer.scheduledTimer(timeInterval: 1.0 / 60.0, target: self, selector: #selector(update), userInfo: nil, repeats: true) |
|
// renderTimer.tolerance = (1.0 / 80.0) |
|
|
|
} |
|
|
|
public func addNode(_ node: SCNNode, roughness: Float = 0) { |
|
|
|
nodes.append(node) |
|
|
|
var kernelSize = Int(roughness * 95) |
|
if kernelSize > 0 { |
|
|
|
if Double(kernelSize) / 2 == Double(kernelSize / 2) { kernelSize -= 1 } |
|
if kernelSize > 95 { kernelSize = 95 } |
|
|
|
let blurHandler = MPSImageBox(device: device, kernelWidth: kernelSize, kernelHeight: kernelSize) |
|
blurHandler.edgeMode = .clamp |
|
|
|
blurHandlers.append(blurHandler) |
|
|
|
} else { |
|
|
|
blurHandlers.append(nil) |
|
|
|
} |
|
|
|
node.categoryBitMask = 2 << nodes.count |
|
|
|
} |
|
|
|
private var angles = ["x+", "x-", "y+", "y-", "z+", "z-"] |
|
private var currentAngle = 0 |
|
private var currentNode = 0 |
|
|
|
private let camera360Node = { () -> SCNNode in |
|
|
|
let rootNode = SCNNode() |
|
|
|
let xPlusCam = SCNNode() |
|
xPlusCam.name = "camera_x+" |
|
xPlusCam.camera = SCNCamera() |
|
xPlusCam.camera?.fieldOfView = 90 |
|
xPlusCam.eulerAngles.y = GLKMathDegreesToRadians(90) |
|
rootNode.addChildNode(xPlusCam) |
|
|
|
let xMinusCam = SCNNode() |
|
xMinusCam.name = "camera_x-" |
|
xMinusCam.camera = SCNCamera() |
|
xMinusCam.camera?.fieldOfView = 90 |
|
xMinusCam.eulerAngles.y = GLKMathDegreesToRadians(-90) |
|
rootNode.addChildNode(xMinusCam) |
|
|
|
let yPlusCam = SCNNode() |
|
yPlusCam.name = "camera_y+" |
|
yPlusCam.camera = SCNCamera() |
|
yPlusCam.camera?.fieldOfView = 90 |
|
yPlusCam.eulerAngles.x = GLKMathDegreesToRadians(90) |
|
rootNode.addChildNode(yPlusCam) |
|
|
|
let yMinusCam = SCNNode() |
|
yMinusCam.name = "camera_y-" |
|
yMinusCam.camera = SCNCamera() |
|
yMinusCam.camera?.fieldOfView = 90 |
|
yMinusCam.eulerAngles.x = GLKMathDegreesToRadians(-90) |
|
rootNode.addChildNode(yMinusCam) |
|
|
|
let zPlusCam = SCNNode() |
|
zPlusCam.name = "camera_z+" |
|
zPlusCam.camera = SCNCamera() |
|
zPlusCam.camera?.fieldOfView = 90 |
|
zPlusCam.eulerAngles.y = GLKMathDegreesToRadians(180) |
|
rootNode.addChildNode(zPlusCam) |
|
|
|
let zMinusCam = SCNNode() |
|
zMinusCam.name = "camera_z-" |
|
zMinusCam.camera = SCNCamera() |
|
zMinusCam.camera?.fieldOfView = 90 |
|
rootNode.addChildNode(zMinusCam) |
|
|
|
return rootNode |
|
|
|
}() |
|
//SCNScene(named: "art.scnassets/360Camera.scn")!.rootNode.childNode(withName: "360_cam", recursively: false)! |
|
|
|
private var pendingTexture: MTLTexture |
|
|
|
private func updateReflections() { |
|
|
|
let node = nodes[currentNode] |
|
|
|
let camera = camera360Node.childNode(withName: "camera_" + angles[currentAngle], recursively: false)! |
|
|
|
camera.camera?.categoryBitMask = ~node.categoryBitMask |
|
|
|
renderer.pointOfView = camera |
|
texturePending = true |
|
|
|
let renderPassDescriptor = MTLRenderPassDescriptor() |
|
renderPassDescriptor.colorAttachments[0].texture = pendingTexture |
|
renderPassDescriptor.colorAttachments[0].slice = currentAngle |
|
renderPassDescriptor.colorAttachments[0].loadAction = .clear |
|
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 1.0) |
|
renderPassDescriptor.colorAttachments[0].storeAction = .store |
|
|
|
let commandBuffer = commandQueue.makeCommandBuffer()! |
|
renderer.render(withViewport: CGRect(x: 0, y: 0, width: 200, height: 200), commandBuffer: commandBuffer, passDescriptor: renderPassDescriptor) |
|
commandBuffer.commit() |
|
|
|
commandBuffer.waitUntilCompleted() |
|
|
|
currentAngle = (currentAngle + 1) % angles.count |
|
if currentAngle == 0 { |
|
|
|
if let blurHandler = blurHandlers[currentNode] { |
|
|
|
for slice in 0..<6 { |
|
|
|
let blurBuffer = commandQueue.makeCommandBuffer()! |
|
let copyToEditable = blurBuffer.makeBlitCommandEncoder()! |
|
|
|
let copyDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: reflectionMapSize, height: reflectionMapSize, mipmapped: false) |
|
let copyTexture = device.makeTexture(descriptor: copyDescriptor)! |
|
|
|
let blurDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: reflectionMapSize, height: reflectionMapSize, mipmapped: false) |
|
blurDescriptor.usage = .shaderWrite |
|
let blurTexture = device.makeTexture(descriptor: blurDescriptor)! |
|
|
|
copyToEditable.copy(from: pendingTexture, sourceSlice: slice, sourceLevel: 0, sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0), sourceSize: MTLSize(width: reflectionMapSize, height: reflectionMapSize, depth: 1), to: copyTexture, destinationSlice: 0, destinationLevel: 0, destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0)) |
|
copyToEditable.endEncoding() |
|
|
|
blurHandler.encode(commandBuffer: blurBuffer, sourceTexture: copyTexture, destinationTexture: blurTexture) |
|
|
|
let copyBackToCube = blurBuffer.makeBlitCommandEncoder()! |
|
|
|
copyBackToCube.copy(from: blurTexture, sourceSlice: 0, sourceLevel: 0, sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0), sourceSize: MTLSize(width: reflectionMapSize, height: reflectionMapSize, depth: 1), to: pendingTexture, destinationSlice: slice, destinationLevel: 0, destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0)) |
|
copyBackToCube.endEncoding() |
|
|
|
blurBuffer.commit() |
|
blurBuffer.waitUntilCompleted() |
|
|
|
} |
|
|
|
} |
|
|
|
node.geometry?.firstMaterial?.reflective.contents = pendingTexture |
|
|
|
currentNode = (currentNode + 1) % nodes.count |
|
camera360Node.worldPosition = nodes[currentNode].worldPosition |
|
|
|
let cubemapDescriptor = MTLTextureDescriptor.textureCubeDescriptor(pixelFormat: .rgba8Unorm, size: reflectionMapSize, mipmapped: false) |
|
cubemapDescriptor.usage = MTLTextureUsage(rawValue: MTLTextureUsage.renderTarget.rawValue | MTLTextureUsage.shaderRead.rawValue) |
|
pendingTexture = device.makeTexture(descriptor: cubemapDescriptor)! |
|
texturePending = false |
|
|
|
} |
|
|
|
} |
|
|
|
private var texturePending = false |
|
|
|
@objc public func update() { |
|
|
|
if nodes.isEmpty { return } |
|
|
|
if !texturePending { |
|
|
|
for _ in 0..<nodes.count { |
|
|
|
let node = nodes[currentNode] |
|
|
|
if let pointOfView = view.pointOfView { |
|
|
|
if view.isNode(node, insideFrustumOf: pointOfView) { |
|
|
|
break |
|
|
|
} |
|
|
|
currentNode = (currentNode + 1) % nodes.count |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
switch reflectionUpdateSpeed { |
|
|
|
case .slow: |
|
updateReflections() |
|
case .normal: |
|
for _ in 1...2 { updateReflections() } |
|
case .fast: |
|
for _ in 1...3 { updateReflections() } |
|
case .reallyFast: |
|
for _ in 1...6 { updateReflections() } |
|
|
|
} |
|
|
|
} |
|
|
|
} |
Yes, I already found that with disabled SSAO problem disappear.