From e0e40423871f811027de800cc9ad5171d77750f9 Mon Sep 17 00:00:00 2001 From: Marius Horga Date: Wed, 1 Mar 2017 23:59:33 -0600 Subject: [PATCH] added shadows --- shadows/shadows2.playground/Contents.swift | 9 ++ .../Resources/Shaders.metal | 141 ++++++++++++++++++ .../Sources/MetalView.swift | 56 +++++++ .../shadows2.playground/contents.xcplayground | 4 + .../contents.xcworkspacedata | 7 + .../UserInterfaceState.xcuserstate | Bin 0 -> 10523 bytes .../UserInterfaceState.xcuserstate | Bin 0 -> 16111 bytes .../shadows2.playground/timeline.xctimeline | 6 + 8 files changed, 223 insertions(+) create mode 100644 shadows/shadows2.playground/Contents.swift create mode 100644 shadows/shadows2.playground/Resources/Shaders.metal create mode 100644 shadows/shadows2.playground/Sources/MetalView.swift create mode 100644 shadows/shadows2.playground/contents.xcplayground create mode 100644 shadows/shadows2.playground/playground.xcworkspace/contents.xcworkspacedata create mode 100644 shadows/shadows2.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 shadows/shadows2.playground/playground.xcworkspace/xcuserdata/mhorga.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 shadows/shadows2.playground/timeline.xctimeline 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 0000000000000000000000000000000000000000..6414c11e2311742e3166080ddf2a4398e4536a9e GIT binary patch literal 10523 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

USj>r-4`M!!`6T9a z%*B|?Qi)V5l}Qy+qtq(3NgdLD(iG_c>9x|K((%#>(sHR&+9;hRoiAM|T`XN9y-RwJ zbh&h;be(jgbcb}8bf5Hu^b_eh>37l#(u>kd(qE;QV_~czHaE5~wm7ylwmh~Xwkmc? zY)$OU*rr%-?5tRSY)fowY+LN|*k@utl_41`OO&O{vSit^T-h+$^|FbwYFUkJnygO7 z%Nk_OvJTlI*=@3=vO8oeWUFMWWou>k%erJi*%sMW*)G|OvX^ChW&33ZW$((4$d1b1 zlYK3_B>PPcp<;|;tYVy^M$xKR zu6S6nQ?WM7L$)hX3wwNl+rovFS-JwlzQE>IV$$Eb_drRs8Z zg}O>TMO~wAQ{SuJt$tPgt@>vT)~GZ(O@GaGnwvCJHEo)9O^0TIrc<*}vs3eu<`vC8 z%>m6jnvXP}YCh9^q4`qto#s-UCC(lB$dW+U?ivyFL<+0DGjyv)4EoM1j@&NAni^UMY2BJ(5j zGb>}&tcA6+eb_{{KYI;3kWFKA*+OZ?+r)a=S?qlFZgv^F zf?dU~X4kU!v+LL=*e&c|*zN4o>@)0N+2`0@>|ypJ_G9)Wdy2it{>c8!{=)vI1zNdQ zqh+*OtzO$lo1{(F4$xk!P1WXW>$J^UzqUm?M>|(LPrF3BMY~hGSG!+(PT9pRBLc*XntFgWj#5t)HWxtDmP|tiMOUPXCbp5q+2b3H?_6 zHvLojSM|sAC-tZFpXxu;f1&?I|DFDV{-XXz{m%w$P#a8!bi)mX(S{ohHyOqoCK@Ii zDh<_!sRpOPWmsT%(D1V1xZ#xHwBd~5Tf+szMZ-^qONL)Lma}nvxI`|Q>(5=oWpTOO zFz$M8I5&qnoxrN+fZV7igw~~8=ThDcI8@TP<4sIv+ z9Jh;mfjhvx#=XwH$sOiC;!bmCxX-z>+&S(%_bqqHC^51|&S)}PjCNxmW1_L2F~>O4 zc(ZZ3kr>w*A2L2->@sdJzF<6HeBF55c*zuNlADw!wJFY&WJ)&mH(g^IXi78Xn1-3I zHw`z9Gfgl}GTm&dGS!)yO+Hh=G~2Yyw8FH?wA!@RwBEGI^r-1^)03wCrh}$Krq@ib zn~s`}o8C8lX!^nQFVjz^OQv53 zICF)$+C0@f&0K4)H#eDQo7bB+ncp%WH~(hAmI0O_mOM*=rO-0QGS)KAQf{fRR9U82 zYAh~Gon^jdxn-+mzvYbOoaJZBFP7ipK|G3=#7pC4@rHO~yg5ETetNt=z9qgjzAb)V z{Nea_;*Z21voh8MtKHhinrJPzI<2+Vdh2wn-#W)S*E-MIXXTkYHJJM7QdciUgG@3FsOf6M;1{fNWnxYm*C z805%wbU5yCEOV@Mtad!$c*wEd5p+D}*yec7alrAK;|<4Q$5F>|#|Ms&9G^PAa(wUj brH`hMu8%oX0|11?pIDqeiNDbAed7NI?cD$( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..501a0aa435842d0c86aea6ba4307aa987ba3ece8 GIT binary patch literal 16111 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 literal 0 HcmV?d00001 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 @@ + + + + +