diff --git a/README.md b/README.md index b7abaae..57bc338 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,5 @@ Repository to accompany the following blog posts: - [Using MetalKit part 16](http://metalkit.org/2016/07/06/using-metalkit-part-16.html) - [Using MetalKit part 17](http://metalkit.org/2016/09/24/using-metalkit-part-17.html) - [Using MetalKit part 18](http://metalkit.org/2016/10/01/using-metalkit-part-2-3-2.html) -- [Raymarching in Metal](http://metalkit.org/2016/12/30/raymarching-in-metal.html) \ No newline at end of file +- [Raymarching in Metal](http://metalkit.org/2016/12/30/raymarching-in-metal.html) +- [Shadows in Metal part 1](http://metalkit.org/2017/01/31/shadows-in-metal-part-1.html) \ No newline at end of file diff --git a/shadows/shadows.playground/Contents.swift b/shadows/shadows.playground/Contents.swift new file mode 100644 index 0000000..d12f060 --- /dev/null +++ b/shadows/shadows.playground/Contents.swift @@ -0,0 +1,9 @@ + +import MetalKit +import PlaygroundSupport + +let frame = NSRect(x: 0, y: 0, width: 300, height: 300) +let delegate = MetalView() +let view = MTKView(frame: frame, device: delegate.device) +view.delegate = delegate +PlaygroundPage.current.liveView = view diff --git a/shadows/shadows.playground/Resources/Shaders.metal b/shadows/shadows.playground/Resources/Shaders.metal new file mode 100755 index 0000000..8ac339e --- /dev/null +++ b/shadows/shadows.playground/Resources/Shaders.metal @@ -0,0 +1,57 @@ + +#include + +using namespace metal; + +float differenceOp(float d0, float d1) { + return max(d0, -d1); +} + +float distanceToRect( float2 point, float2 center, float2 size ) { + point -= center; + point = abs(point); + point -= size / 2.; + return max(point.x, point.y); +} + +float distanceToScene( float2 point ) { + float d2r1 = distanceToRect( point, float2(0.), float2(0.45, 0.85) ); + float2 mod = point - 0.1 * floor(point / 0.1); + float d2r2 = distanceToRect( mod, float2( 0.05 ), float2(0.02, 0.04) ); + float diff = differenceOp(d2r1, d2r2); + return diff; +} + +float getShadow(float2 point, float2 lightPos) { + float2 lightDir = normalize(lightPos - point); + float dist2light = length(lightDir); + float distAlongRay = 0.0; + for (float i=0.0; i < 80.; i++) { + float2 currentPoint = point + lightDir * distAlongRay; + float d2scene = distanceToScene(currentPoint); + if (d2scene <= 0.001) { return 0.0; } + distAlongRay += d2scene; + if (distAlongRay > dist2light) { break; } + } + return 1.; +} + +kernel void compute(texture2d output [[texture(0)]], + constant float &timer [[buffer(0)]], + uint2 gid [[thread_position_in_grid]]) +{ + int width = output.get_width(); + int height = output.get_height(); + float2 uv = float2(gid) / float2(width, height); + uv = uv * 2.0 - 1.0; + float d2scene = distanceToScene(uv); + bool i = d2scene < 0.0; + float4 color = i ? float4( .1, .5, .5, 1. ) : float4( .7, .8, .8, 1. ); + float2 lightPos = float2(1.3 * sin(timer), 1.3 * cos(timer)); + float dist2light = length(lightPos - uv); + color *= max(0.0, 2. - dist2light ); + float shadow = getShadow(uv, lightPos); + shadow = shadow * 0.5 + 0.5; + color *= shadow; + output.write(color, gid); +} diff --git a/shadows/shadows.playground/Sources/MetalView.swift b/shadows/shadows.playground/Sources/MetalView.swift new file mode 100755 index 0000000..a3a47b1 --- /dev/null +++ b/shadows/shadows.playground/Sources/MetalView.swift @@ -0,0 +1,56 @@ + +import MetalKit + +public class MetalView: NSObject, MTKViewDelegate { + + public var device: MTLDevice! = nil + var queue: MTLCommandQueue! = nil + var cps: MTLComputePipelineState! = nil + var timerBuffer: MTLBuffer! = nil + var timer: Float = 0 + + override public init() { + super.init() + device = MTLCreateSystemDefaultDevice() + queue = device.makeCommandQueue() + registerShaders() + } + + func registerShaders() { + guard let path = Bundle.main.path(forResource: "Shaders", ofType: "metal") else { return } + do { + let input = try String(contentsOfFile: path, encoding: String.Encoding.utf8) + let library = try device.makeLibrary(source: input, options: nil) + guard let kernel = library.makeFunction(name: "compute") else { return } + cps = try device.makeComputePipelineState(function: kernel) + } catch let e { + Swift.print("\(e)") + } + timerBuffer = device.makeBuffer(length: MemoryLayout.size, options: []) + } + + func update() { + timer += 0.01 + let bufferPointer = timerBuffer.contents() + memcpy(bufferPointer, &timer, MemoryLayout.size) + } + + public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {} + + public func draw(in view: MTKView) { + if let drawable = view.currentDrawable { + let commandBuffer = queue.makeCommandBuffer() + let commandEncoder = commandBuffer.makeComputeCommandEncoder() + commandEncoder.setComputePipelineState(cps) + commandEncoder.setTexture(drawable.texture, at: 0) + commandEncoder.setBuffer(timerBuffer, offset: 0, at: 0) + update() + let threadGroupCount = MTLSizeMake(8, 8, 1) + let threadGroups = MTLSizeMake(drawable.texture.width / threadGroupCount.width, drawable.texture.height / threadGroupCount.height, 1) + commandEncoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupCount) + commandEncoder.endEncoding() + commandBuffer.present(drawable) + commandBuffer.commit() + } + } +} diff --git a/shadows/shadows.playground/contents.xcplayground b/shadows/shadows.playground/contents.xcplayground new file mode 100644 index 0000000..9f9eecc --- /dev/null +++ b/shadows/shadows.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/shadows/shadows.playground/playground.xcworkspace/contents.xcworkspacedata b/shadows/shadows.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100755 index 0000000..919434a --- /dev/null +++ b/shadows/shadows.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/shadows/shadows.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate b/shadows/shadows.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100755 index 0000000..b3cea73 Binary files /dev/null and b/shadows/shadows.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/shadows/shadows.playground/playground.xcworkspace/xcuserdata/mhorga.xcuserdatad/UserInterfaceState.xcuserstate b/shadows/shadows.playground/playground.xcworkspace/xcuserdata/mhorga.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..8c75744 Binary files /dev/null and b/shadows/shadows.playground/playground.xcworkspace/xcuserdata/mhorga.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/shadows/shadows.playground/timeline.xctimeline b/shadows/shadows.playground/timeline.xctimeline new file mode 100644 index 0000000..bf468af --- /dev/null +++ b/shadows/shadows.playground/timeline.xctimeline @@ -0,0 +1,6 @@ + + + + +