MetalByTutorials/19-shadows/final/PCF/Shadows/Renderer.swift

317 lines
12 KiB
Swift
Executable File

/**
* Copyright (c) 2019 Razeware LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
* distribute, sublicense, create a derivative work, and/or sell copies of the
* Software in any work that is designed, intended, or marketed for pedagogical or
* instructional purposes related to programming, coding, application development,
* or information technology. Permission for such use, copying, modification,
* merger, publication, distribution, sublicensing, creation of derivative works,
* or sale is expressly withheld.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import MetalKit
class Renderer: NSObject {
static var device: MTLDevice!
static var commandQueue: MTLCommandQueue!
static var colorPixelFormat: MTLPixelFormat!
static var library: MTLLibrary!
var renderPipelineState: MTLRenderPipelineState!
var depthStencilState: MTLDepthStencilState!
var shadowTexture: MTLTexture!
let shadowRenderPassDescriptor = MTLRenderPassDescriptor()
var shadowPipelineState: MTLRenderPipelineState!
var albedoTexture: MTLTexture!
var normalTexture: MTLTexture!
var positionTexture: MTLTexture!
var depthTexture: MTLTexture!
var gBufferPipelineState: MTLRenderPipelineState!
var gBufferRenderPassDescriptor: MTLRenderPassDescriptor!
var compositionPipelineState: MTLRenderPipelineState!
var quadVerticesBuffer: MTLBuffer!
var quadTexCoordsBuffer: MTLBuffer!
let quadVertices: [Float] = [
-1.0, 1.0,
1.0, -1.0,
-1.0, -1.0,
-1.0, 1.0,
1.0, 1.0,
1.0, -1.0,
]
let quadTexCoords: [Float] = [
0.0, 0.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0
]
var uniforms = Uniforms()
var fragmentUniforms = FragmentUniforms()
lazy var camera: Camera = {
let camera = Camera()
camera.position = [0, 0, -5]
camera.rotation = [-0.5, -0.5, 0]
return camera
}()
lazy var sunlight: Light = {
var light = buildDefaultLight()
light.position = [1, 2, -2]
light.intensity = 1.5
return light
}()
var lights: [Light] = []
var models: [Model] = []
var lightsBuffer: MTLBuffer!
init(metalView: MTKView) {
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("GPU not available")
}
metalView.device = device
Renderer.device = device
Renderer.commandQueue = device.makeCommandQueue()!
Renderer.colorPixelFormat = metalView.colorPixelFormat
Renderer.library = device.makeDefaultLibrary()
super.init()
metalView.clearColor = MTLClearColor(red: 1.0, green: 1.0, blue: 0.8, alpha: 1)
metalView.depthStencilPixelFormat = .depth32Float
metalView.delegate = self
metalView.framebufferOnly = false
mtkView(metalView, drawableSizeWillChange: metalView.bounds.size)
lights.append(sunlight)
fragmentUniforms.lightCount = UInt32(lights.count)
let train = Model(name: "train")
train.position = [-0.5, 0, 0]
train.rotation = [0, Float(45).degreesToRadians, 0]
models.append(train)
let tree = Model(name: "treefir")
tree.position = [1.4, 0, 3]
tree.position = [1.4, 0, 0]
models.append(tree)
let plane = Model(name: "plane")
plane.scale = [8, 8, 8]
plane.position = [0, 0, 0]
models.append(plane)
buildRenderPipelineState()
buildDepthStencilState()
buildShadowTexture(size: metalView.drawableSize)
buildShadowPipelineState()
quadVerticesBuffer = Renderer.device.makeBuffer(bytes: quadVertices, length: MemoryLayout<Float>.size * quadVertices.count)
quadVerticesBuffer.label = "Quad vertices"
quadTexCoordsBuffer = Renderer.device.makeBuffer(bytes: quadTexCoords, length: MemoryLayout<Float>.size * quadTexCoords.count)
quadTexCoordsBuffer.label = "Quad texCoords"
lightsBuffer = Renderer.device.makeBuffer(bytes: lights, length: MemoryLayout<Light>.stride * lights.count)
}
func buildShadowPipelineState() {
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = Renderer.library.makeFunction(
name: "vertex_depth")
pipelineDescriptor.fragmentFunction = nil
pipelineDescriptor.colorAttachments[0].pixelFormat = .invalid
pipelineDescriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(Model.defaultVertexDescriptor)
pipelineDescriptor.depthAttachmentPixelFormat = .depth32Float
do {
shadowPipelineState = try Renderer.device.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch let error {
fatalError(error.localizedDescription)
}
}
func buildRenderPipelineState() {
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = Renderer.library.makeFunction(name: "vertex_main")
pipelineDescriptor.fragmentFunction = Renderer.library.makeFunction(name: "fragment_main")
pipelineDescriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(Model.defaultVertexDescriptor)
pipelineDescriptor.colorAttachments[0].pixelFormat = Renderer.colorPixelFormat
pipelineDescriptor.depthAttachmentPixelFormat = .depth32Float
do {
renderPipelineState = try Renderer.device.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch let error {
fatalError(error.localizedDescription)
}
}
func buildDepthStencilState() {
let descriptor = MTLDepthStencilDescriptor()
descriptor.depthCompareFunction = .less
descriptor.isDepthWriteEnabled = true
depthStencilState = Renderer.device.makeDepthStencilState(descriptor: descriptor)
}
func buildDefaultLight() -> Light {
var light = Light()
light.position = [0, 0, 0]
light.color = [1, 1, 1]
light.intensity = 1
light.attenuation = float3(1, 0, 0)
light.type = Sunlight
return light
}
func buildTexture(pixelFormat: MTLPixelFormat, size: CGSize, label: String) -> MTLTexture {
let descriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: pixelFormat, width: Int(size.width), height: Int(size.height), mipmapped: false)
descriptor.usage = [.shaderRead, .renderTarget]
descriptor.storageMode = .private
guard let texture = Renderer.device.makeTexture(descriptor: descriptor) else {
fatalError()
}
texture.label = "\(label) texture"
return texture
}
func buildShadowTexture(size: CGSize) {
shadowTexture = buildTexture(pixelFormat: .depth32Float, size: size, label: "Shadow")
shadowRenderPassDescriptor.setUpDepthAttachment(texture: shadowTexture)
}
func renderShadowPass(renderEncoder: MTLRenderCommandEncoder) {
renderEncoder.pushDebugGroup("Shadow pass")
renderEncoder.label = "Shadow encoder"
renderEncoder.setCullMode(.none)
renderEncoder.setDepthStencilState(depthStencilState)
renderEncoder.setDepthBias(0.01, slopeScale: 10.0, clamp: 0.01)
uniforms.projectionMatrix = float4x4(orthoLeft: -8, right: 8, bottom: -8, top: 8, near: 0.1, far: 16)
let position: float3 = [sunlight.position.x,
sunlight.position.y,
sunlight.position.z]
let center: float3 = [0, 0, 0]
let lookAt = float4x4(eye: position, center: center, up: [0,1,0])
uniforms.viewMatrix = lookAt
uniforms.shadowMatrix = uniforms.projectionMatrix * uniforms.viewMatrix
renderEncoder.setRenderPipelineState(shadowPipelineState)
for model in models {
draw(renderEncoder: renderEncoder, model: model)
}
renderEncoder.endEncoding()
renderEncoder.popDebugGroup()
}
}
extension Renderer: MTKViewDelegate {
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
camera.aspect = Float(view.bounds.width)/Float(view.bounds.height)
uniforms.projectionMatrix = camera.projectionMatrix
buildShadowTexture(size: size)
}
func draw(in view: MTKView) {
guard let descriptor = view.currentRenderPassDescriptor,
let commandBuffer = Renderer.commandQueue.makeCommandBuffer(),
let drawable = view.currentDrawable else {
return
}
models[0].rotation.y += 0.01
// shadow pass
guard let shadowEncoder = commandBuffer.makeRenderCommandEncoder(
descriptor: shadowRenderPassDescriptor) else {
return
}
renderShadowPass(renderEncoder: shadowEncoder)
// main pass
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
return
}
renderEncoder.pushDebugGroup("Main pass")
renderEncoder.label = "Main encoder"
uniforms.viewMatrix = camera.viewMatrix
uniforms.projectionMatrix = camera.projectionMatrix
fragmentUniforms.cameraPosition = camera.position
renderEncoder.setRenderPipelineState(renderPipelineState)
renderEncoder.setDepthStencilState(depthStencilState)
renderEncoder.setFragmentBytes(&lights, length: MemoryLayout<Light>.stride * lights.count, index: 2)
renderEncoder.setFragmentBytes(&fragmentUniforms, length: MemoryLayout<FragmentUniforms>.stride, index: 3)
renderEncoder.setFragmentTexture(shadowTexture, index: 0)
for model in models {
uniforms.modelMatrix = model.modelMatrix
uniforms.normalMatrix = float3x3(normalFrom4x4: model.modelMatrix)
renderEncoder.setVertexBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
renderEncoder.setVertexBuffer(model.vertexBuffer, offset: 0, index: 0)
for modelSubmesh in model.submeshes {
let submesh = modelSubmesh.submesh
renderEncoder.setFragmentBytes(&modelSubmesh.material, length: MemoryLayout<Material>.stride, index: 1)
renderEncoder.drawIndexedPrimitives(type: .triangle, indexCount: submesh.indexCount, indexType: submesh.indexType, indexBuffer: submesh.indexBuffer.buffer, indexBufferOffset: submesh.indexBuffer.offset)
}
}
renderEncoder.endEncoding()
renderEncoder.popDebugGroup()
commandBuffer.present(drawable)
commandBuffer.commit()
}
func draw(renderEncoder: MTLRenderCommandEncoder, model: Model) {
uniforms.modelMatrix = model.modelMatrix
uniforms.normalMatrix = float3x3(normalFrom4x4: model.modelMatrix)
renderEncoder.setVertexBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
renderEncoder.setVertexBuffer(model.vertexBuffer, offset: 0, index: 0)
for modelSubmesh in model.submeshes {
let submesh = modelSubmesh.submesh
renderEncoder.setFragmentBytes(&modelSubmesh.material, length: MemoryLayout<Material>.stride, index: 1)
renderEncoder.drawIndexedPrimitives(type: .triangle, indexCount: submesh.indexCount, indexType: submesh.indexType, indexBuffer: submesh.indexBuffer.buffer, indexBufferOffset: submesh.indexBuffer.offset)
}
}
}
private extension MTLRenderPassDescriptor {
func setUpDepthAttachment(texture: MTLTexture) {
depthAttachment.texture = texture
depthAttachment.loadAction = .clear
depthAttachment.storeAction = .store
depthAttachment.clearDepth = 1
}
func setUpColorAttachment(position: Int, texture: MTLTexture) {
let attachment: MTLRenderPassColorAttachmentDescriptor = colorAttachments[position]
attachment.texture = texture
attachment.loadAction = .clear
attachment.storeAction = .store
attachment.clearColor = MTLClearColorMake(0.73, 0.92, 1, 1)
}
}