Skip to content

Commit

Permalink
added shadows
Browse files Browse the repository at this point in the history
  • Loading branch information
mhorga committed Mar 2, 2017
1 parent c340f80 commit e0e4042
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 0 deletions.
9 changes: 9 additions & 0 deletions shadows/shadows2.playground/Contents.swift
Original file line number Diff line number Diff line change
@@ -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
141 changes: 141 additions & 0 deletions shadows/shadows2.playground/Resources/Shaders.metal
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@

#include <metal_stdlib>
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<float, access::write> 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);
}
56 changes: 56 additions & 0 deletions shadows/shadows2.playground/Sources/MetalView.swift
Original file line number Diff line number Diff line change
@@ -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<Float>.size, options: [])
}

func update() {
timer += 0.01
let bufferPointer = timerBuffer.contents()
memcpy(bufferPointer, &timer, MemoryLayout<Float>.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()
}
}
}
4 changes: 4 additions & 0 deletions shadows/shadows2.playground/contents.xcplayground
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='macos' executeOnSourceChanges='false'>
<timeline fileName='timeline.xctimeline'/>
</playground>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.
6 changes: 6 additions & 0 deletions shadows/shadows2.playground/timeline.xctimeline
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>

0 comments on commit e0e4042

Please sign in to comment.