Last active
March 9, 2021 00:44
-
-
Save wcarss/a6ff897fb9c50ac34875dbd5d8599927 to your computer and use it in GitHub Desktop.
diffuse raymarcher from https://ch-st.de/its-ray-marching-march/ ported to python (tested in 2.7 and ~3.8.6)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# original C code from https://ch-st.de/its-ray-marching-march/ | |
# retrieved, roughly ported, and refactored a little on 2021-03-08 | |
from __future__ import print_function | |
import math | |
import time | |
import sys | |
class Vec3: | |
def __init__(self, x, y, z): | |
self.x = x | |
self.y = y | |
self.z = z | |
def length(self): | |
return math.sqrt( | |
self.x*self.x + self.y*self.y + self.z*self.z | |
) | |
def normalize(self): | |
l = self.length() | |
self.x = self.x / l | |
self.y = self.y / l | |
self.z = self.z / l | |
def __mul__(self, scale_factor): | |
new = Vec3(self.x, self.y, self.z) | |
new.x *= scale_factor | |
new.y *= scale_factor | |
new.z *= scale_factor | |
return new | |
def __add__(self, other): | |
new = Vec3(self.x, self.y, self.z) | |
new.x += other.x | |
new.y += other.y | |
new.z += other.z | |
return new | |
def __sub__(self, other): | |
new = Vec3(self.x, self.y, self.z) | |
new.x -= other.x | |
new.y -= other.y | |
new.z -= other.z | |
return new | |
def raymarch(t, framebuffer, width, height): | |
pixels = " .:+|0#" | |
for y in range(height): | |
for x in range(width): | |
pos = Vec3(0.0, 0.0, -3.0) | |
target = Vec3( | |
x / (1.0 * width) - 0.5, | |
(y / (1.0 * height) - 0.5) * (height / (1.0 * width)) * 1.5, | |
-1.5 | |
) | |
ray = target - pos | |
ray.normalize() | |
pxl = pixels[0] | |
dist = 0 | |
max = 9999.0 | |
for i in range(15000): | |
if (abs(pos.x) > max or | |
abs(pos.y) > max or | |
abs(pos.z) > max): | |
break | |
dist = sdf(pos) | |
if dist < 1e-6: | |
pxl = shade(pos, t, pixels) | |
break | |
pos = pos + ray * dist | |
framebuffer[y * width + x] = pxl | |
def sdf(pos): | |
center = Vec3(0.0, 0.0, 0.0) | |
return (pos - center).length() - 0.2 | |
def shade(pos, t, pixels): | |
L = Vec3( | |
50*math.sin(t), | |
20, | |
50*math.cos(t) | |
) | |
L.normalize() | |
dt = 1e-6 | |
current_val = sdf(pos) | |
x = Vec3(pos.x + dt, pos.y, pos.z) | |
dx = sdf(x) - current_val | |
y = Vec3(pos.x, pos.y + dt, pos.z) | |
dy = sdf(y) - current_val | |
z = Vec3(pos.x, pos.y, pos.z + dt) | |
dz = sdf(z) - current_val | |
N = Vec3(0,0,0) | |
N.x = (dx - pos.x) / dt | |
N.y = (dy - pos.y) / dt | |
N.z = (dz - pos.z) / dt | |
if N.length() < 1e-9: | |
return pixels[0] | |
N.normalize() | |
diffuse = L.x * N.x + L.y * N.y + L.z * N.z | |
diffuse = (diffuse + 1.0) / 2.0 * len(pixels) | |
return pixels[int(math.floor(diffuse)) % len(pixels)] | |
def cls(): | |
# Terminal clear sequence | |
cls_seq = u"\u001b[1;1H\u001b[2J" | |
sys.stdout.write(cls_seq) | |
sys.stdout.flush() | |
def printfb(framebuffer, width, height): | |
fb = 0 | |
cls() | |
for y in range(height): | |
sys.stdout.write(''.join(framebuffer[fb:fb+width])) | |
sys.stdout.write('\n') | |
sys.stdout.flush() | |
fb += width | |
def main(): | |
width = 80 | |
height = 40 | |
framebuffer = [] | |
for i in range(width * height): | |
framebuffer.append('') | |
t = 0 | |
while(True): | |
raymarch(t, framebuffer, width, height) | |
printfb(framebuffer, width, height) | |
# change these numbers as desired for spin+update speed | |
time.sleep(0.08) | |
t += 0.35 | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
below, I've also extended the original by adding some very hacky object representations, a more varied gradient representation, and implemented an awful background parallax effect, to make a silly space scene: