Skip to content

Instantly share code, notes, and snippets.

@jamesporter
Created April 17, 2024 21:59
Show Gist options
  • Save jamesporter/1b33558b3fc2771b45630ba7e0ba5122 to your computer and use it in GitHub Desktop.
Save jamesporter/1b33558b3fc2771b45630ba7e0ba5122 to your computer and use it in GitHub Desktop.
Metal and Swift(UI) Raymarching App
import SwiftUI
struct ContentView: View {
let startDate = Date()
var body: some View {
GeometryReader { gp in
TimelineView(.animation) { ctx in
Rectangle()
.ignoresSafeArea()
.colorEffect(ShaderLibrary.raymarchB(
.float2(gp.size.width, gp.size.height),
.float(startDate.timeIntervalSinceNow)
))
}
}
}
}
#Preview {
ContentView()
}
#include <metal_stdlib>
#include <SwiftUI/SwiftUI_Metal.h>
using namespace metal;
float distanceToBall(float3 p) {
return length(p) - 1;
}
[[ stitchable ]] half4 raymarch(float2 position, half4 currentColor, float2 size, float time) {
float2 uv = (position * 2.0 - size) / size.y;
float3 ro = float3(0,0,-3.1 + cos(time));
float3 rd = normalize(float3(uv, 1));
float t = 0;
for(int i = 0; i < 80; i++) {
float3 p = ro + rd * t;
float d = distanceToBall(p);
t += d;
if(d < 0.001 || t > 100) break;
}
if(t > 100) return half4(0,0,0,1);
return half4(sin(t),cos(t),t,1);
}
float sdBox(float3 p, float3 b) {
float3 q = abs(p) - b;
return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
}
matrix<float, 2> rot2D(float angle) {
float s = sin(angle);
float c = cos(angle);
return matrix<float, 2>(c, -s, s, c);
}
// https://iquilezles.org/articles/palettes/
half3 palette(float t, half3 a, half3 b, half3 c, half3 d){
return a + b * cos( 6.28318 * (c*t+d));
}
float mod(float a, float b) {
return a - b * floor(a/b);
}
float map(float3 p, float time) {
float3 q = float3(fract(p.xy) - 0.5, mod(p.z, 0.5) - 0.25);
q.xy = q.xy * rot2D(time * 0.5 + floor(p.z));
float box = sdBox(q, float3(0.1));
return box;
}
[[ stitchable ]] half4 raymarchB(float2 position, half4 currentColor, float2 size, float time) {
float2 uv = (position * 2.0 - size) / size.y;
float3 ro = float3(sin(time),0,-3);
float3 rd = normalize(float3(uv, 1));
float t = 0;
int j = 2;
for(int i = 0; i < 80; i++) {
float3 p = ro + rd * t;
float d = map(p, time);
t += d;
j = i;
if(d < 0.001 || t > 10) break;
}
if(t > 100) return half4(0,0,0,1);
return half4(palette(t * 0.9 + time * 0.01 + float(j) * 0.01, half3(0.5), half3(0.5), half3(1.0), half3(0., 0.2, 0.2)), 1);
}
@KTRosenberg
Copy link

KTRosenberg commented Jul 6, 2024

@jamesporter Thanks for the example. I am curious what the simplest way would be to composite raymarched visuals with rasterized geometry, including lighting and correct depth occlusion. You'd probably need your own Metal pipeline beyond the SwiftUI. It'd also require writing to the depth buffer at minimum. Inigo has an article on this, but translating it to Metal is a little unclear: https://iquilezles.org/articles/raypolys/

It would be great to have a full working example of that out there if you felt like playing with it. Apple provides something for hybrid raytraced reflections, but it's not the same use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment