diff --git a/shadows/shadows2.playground/Contents.swift b/shadows/shadows2.playground/Contents.swift new file mode 100644 index 0000000..694d377 --- /dev/null +++ b/shadows/shadows2.playground/Contents.swift @@ -0,0 +1,9 @@ + +import MetalKit +import PlaygroundSupport + +let frame = NSRect(x: 0, y: 0, width: 400, height: 400) +let delegate = MetalView() +let view = MTKView(frame: frame, device: delegate.device) +view.delegate = delegate +PlaygroundPage.current.liveView = view diff --git a/shadows/shadows2.playground/Resources/Shaders.metal b/shadows/shadows2.playground/Resources/Shaders.metal new file mode 100644 index 0000000..aea4c8c --- /dev/null +++ b/shadows/shadows2.playground/Resources/Shaders.metal @@ -0,0 +1,141 @@ + +#include +using namespace metal; + +struct Ray { + float3 origin; + float3 direction; + Ray(float3 o, float3 d) { + origin = o; + direction = d; + } +}; + +struct Sphere { + float3 center; + float radius; + Sphere(float3 c, float r) { + center = c; + radius = r; + } +}; + +struct Plane { + float yCoord; + Plane(float y) { + yCoord = y; + } +}; + +struct Light { + float3 position; + Light(float3 pos) { + position = pos; + } +}; + +float unionOp(float d0, float d1) { + return min(d0, d1); +} + +float differenceOp(float d0, float d1) { + return max(d0, -d1); +} + +float distToSphere(Ray ray, Sphere s) { + return length(ray.origin - s.center) - s.radius; +} + +float distToPlane(Ray ray, Plane plane) { + return ray.origin.y - plane.yCoord; +} + +float distToScene(Ray r) { + Plane p = Plane(0.0); + float d2p = distToPlane(r, p); + Sphere s1 = Sphere(float3(2.0), 1.9); + Sphere s2 = Sphere(float3(0.0, 4.0, 0.0), 4.0); + Sphere s3 = Sphere(float3(0.0, 4.0, 0.0), 3.9); + Ray repeatRay = r; + repeatRay.origin = fract(r.origin / 4.0) * 4.0; + float d2s1 = distToSphere(repeatRay, s1); + float d2s2 = distToSphere(r, s2); + float d2s3 = distToSphere(r, s3); + float dist = differenceOp(d2s2, d2s3); + dist = differenceOp(dist, d2s1); + dist = unionOp(d2p, dist); + return dist; +} + +float3 getNormal(Ray ray) { + float2 eps = float2(0.001, 0.0); + float3 n = float3(distToScene(Ray(ray.origin + eps.xyy, ray.direction)) - + distToScene(Ray(ray.origin - eps.xyy, ray.direction)), + distToScene(Ray(ray.origin + eps.yxy, ray.direction)) - + distToScene(Ray(ray.origin - eps.yxy, ray.direction)), + distToScene(Ray(ray.origin + eps.yyx, ray.direction)) - + distToScene(Ray(ray.origin - eps.yyx, ray.direction))); + return normalize(n); +} + +float lighting(Ray ray, float3 normal, Light light) { + float3 lightRay = normalize(light.position - ray.origin); + float diffuse = max(0.0, dot(normal, lightRay)); + float3 reflectedRay = reflect(ray.direction, normal); + float specular = max(0.0, dot(reflectedRay, lightRay)); + specular = pow(specular, 200.0); + return diffuse + specular; +} + +float shadow(Ray ray, float k, Light l) { + float3 lightDir = l.position - ray.origin; + float lightDist = length(lightDir); + lightDir = normalize(lightDir); + float eps = 0.1; + float distAlongRay = eps * 2.0; + float light = 1.0; + for (int i=0; i<100; i++) { + Ray lightRay = Ray(ray.origin + lightDir * distAlongRay, lightDir); + float dist = distToScene(lightRay); + light = min(light, 1.0 - (eps - dist) / eps); + distAlongRay += dist * 0.5; + eps += dist * k; + if (distAlongRay > lightDist) { break; } + } + return max(light, 0.0); +} + +kernel void compute(texture2d output [[texture(0)]], + constant float &time [[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; + uv.y = -uv.y; + Ray ray = Ray(float3(0.0, 4.0, -12.0), normalize(float3(uv, 1.0))); + float3 col = float3(1.0); + bool hit = false; + for (int i=0; i<200; i++) { + float dist = distToScene(ray); + if (dist < 0.001) { + hit = true; + break; + } + ray.origin += ray.direction * dist; + } + if (!hit) { + col = float3(0.5); + } else { + float3 n = getNormal(ray); + Light light = Light(float3(sin(time) * 10.0, 5.0, cos(time) * 10.0)); + float l = lighting(ray, n, light); + float s = shadow(ray, 0.3, light); + col = col * l * s; + } + Light light2 = Light(float3(0.0, 5.0, -15.0)); + float3 lightRay = normalize(light2.position - ray.origin); + float fl = max(0.0, dot(getNormal(ray), lightRay) / 2.0); + col = col + fl; + output.write(float4(col, 1.0), gid); +} diff --git a/shadows/shadows2.playground/Sources/MetalView.swift b/shadows/shadows2.playground/Sources/MetalView.swift new file mode 100644 index 0000000..bf69602 --- /dev/null +++ b/shadows/shadows2.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/shadows2.playground/contents.xcplayground b/shadows/shadows2.playground/contents.xcplayground new file mode 100644 index 0000000..a93d484 --- /dev/null +++ b/shadows/shadows2.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/shadows/shadows2.playground/playground.xcworkspace/contents.xcworkspacedata b/shadows/shadows2.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/shadows/shadows2.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/shadows/shadows2.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate b/shadows/shadows2.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..6414c11 Binary files /dev/null and b/shadows/shadows2.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/shadows/shadows2.playground/playground.xcworkspace/xcuserdata/mhorga.xcuserdatad/UserInterfaceState.xcuserstate b/shadows/shadows2.playground/playground.xcworkspace/xcuserdata/mhorga.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..501a0aa Binary files /dev/null and b/shadows/shadows2.playground/playground.xcworkspace/xcuserdata/mhorga.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/shadows/shadows2.playground/timeline.xctimeline b/shadows/shadows2.playground/timeline.xctimeline new file mode 100644 index 0000000..bf468af --- /dev/null +++ b/shadows/shadows2.playground/timeline.xctimeline @@ -0,0 +1,6 @@ + + + + +