Last active
October 8, 2018 18:26
-
-
Save hcorion/3cfc1e11fac888ab6fd2b853009c275a to your computer and use it in GitHub Desktop.
A port of a raytracer, programmed in Nim, built by someone else, to sdl2
This file contains 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
#Originally created by AdrianV and can be found here: https://gist.github.com/AdrianV/5774141 | |
#Modified by me, hcorion to add sdl2 support and bring it up to speed with version 0.14.2 of Nim (nim-lang.org). | |
import math | |
import sequtils | |
import sdl2 | |
import times | |
const | |
width = 1280 | |
height = 720 | |
fov = 45.0 | |
max_depth = 6 | |
type | |
TVec3 = array[3,float] | |
TRay {.pure, final.} = object | |
start: TVec3 | |
dir: TVec3 | |
TSphere {.pure, final.} = object | |
center : TVec3 | |
radius : float | |
color : TVec3 | |
reflection: float | |
transparency: float | |
TLight {.pure, final.} = object | |
position: TVec3 | |
color: TVec3 | |
TScene {.pure, final.} = object | |
objects: seq[ref TSphere] | |
lights: seq[ref TLight] | |
proc newRay(start, dir: TVec3): TRay {.noInit, inline.} = | |
result.start = start | |
result.dir = dir | |
template newVec3(x: float): TVec3 = [x,x,x] | |
proc newLight(position, color: TVec3): ref TLight = | |
new result | |
result.position = position | |
result.color = color | |
template `-` (me: TVec3): TVec3 = [-me[0], -me[1], -me[2]] | |
template declOpBinary(op: expr) = | |
proc op(me, rhs: TVec3): TVec3 {.inline, noInit.} = [op(me[0], rhs[0]), op(me[1], rhs[1]), op(me[2], rhs[2])] | |
proc op(me: TVec3, rhs: float): TVec3 {.inline, noInit.} = [op(me[0], rhs), op(me[1], rhs), op(me[2], rhs)] | |
template declOpBinaryAssign(op: expr) = | |
proc op(me: var TVec3, rhs: TVec3) {.inline.} = | |
op(me[0], rhs[0]) | |
op(me[1], rhs[1]) | |
op(me[2], rhs[2]) | |
proc op(me: var TVec3, rhs: float) {.inline.} = | |
op(me[0], rhs) | |
op(me[1], rhs) | |
op(me[2], rhs) | |
#Those commented out are unused | |
declOpBinary(`+`) | |
declOpBinary(`-`) | |
declOpBinary(`*`) | |
#declOpBinary(`/`) | |
declOpBinaryAssign(`+=`) | |
#declOpBinaryAssign(`-=`) | |
declOpBinaryAssign(`*=`) | |
#declOpBinaryAssign(`/=`) | |
proc dot(v1, v2: TVec3): float {.inline.} = v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2] | |
proc magnitude(v: TVec3) : float {.inline.} = sqrt(dot(v,v)) | |
proc normalize(v: TVec3): TVec3 {.inline, noInit.} = | |
let m = v.magnitude() | |
return [v[0] / m, v[1] / m, v[2] / m] | |
proc newSphere(center: TVec3, radius: float, color: TVec3, reflection: float = 0.0, transparency: float = 0.0): ref TSphere = | |
new(result) | |
result.center = center | |
result.radius = radius | |
result.color = color | |
result.reflection = reflection | |
result.transparency = transparency | |
proc normalize(me: ref TSphere, v: TVec3): TVec3 {.inline, noInit.} = normalize(v - me.center) | |
template intersectImpl(me: ref TSphere, ray: expr) : expr {.immediate, dirty.} = | |
var vl = me.center - ray.start | |
var a = dot(vl, ray.dir) | |
if (a < 0) : # opposite direction | |
return false | |
var b2 = dot(vl, vl) - a * a | |
var r2 = me.radius * me.radius | |
if (b2 > r2) : # perpendicular > r | |
return false | |
proc intersect(me: ref TSphere, ray: TRay) : bool {.inline.} = | |
intersectImpl(me, ray) | |
return true | |
proc intersect(me: ref TSphere, ray: TRay, distance: var float) : bool {.inline.} = | |
intersectImpl(me, ray) | |
var c = sqrt(r2 - b2) | |
var near = a - c | |
var far = a + c | |
distance = if (near < 0) : far else : near | |
return true | |
proc trace(ray: TRay, scene: TScene, depth: int): TVec3 = | |
var nearest = INF | |
var obj : ref TSphere | |
# // search the scene for nearest intersection | |
for o in scene.objects : | |
var distance = INF | |
if o.intersect(ray, distance) : | |
if distance < nearest : | |
nearest = distance | |
obj = o | |
if obj.isNil : return #newVec3(0) | |
var point_of_hit = ray.dir * nearest | |
point_of_hit += ray.start | |
var normal = obj.normalize(point_of_hit) | |
var inside = false | |
var dot_normal_ray = dot(normal, ray.dir) | |
if dot_normal_ray > 0 : | |
inside = true | |
normal = -normal | |
dot_normal_ray = -dot_normal_ray | |
#result = newVec3(0.0) | |
var reflection_ratio = obj.reflection | |
let normE5 = normal * 1.0e-5 | |
for lgt in scene.lights : | |
let light_direction = normalize(lgt.position - point_of_hit) | |
let r = newRay(point_of_hit + normE5, light_direction) | |
# go through the scene check whether we're blocked from the lights | |
var blocked = false | |
for it in scene.objects: | |
blocked = it.intersect(r) | |
if blocked: break | |
if not blocked : | |
when true : | |
var temp = lgt.color | |
temp *= max(0.0, dot(normal, light_direction)) | |
temp *= obj.color | |
temp *= (1.0 - reflection_ratio) | |
result += temp | |
else : | |
result += lgt.color * | |
max(0.0, dot(normal, light_direction)) * | |
obj.color * (1.0 - reflection_ratio) | |
var facing = max(0.0, - dot_normal_ray) | |
var fresneleffect = reflection_ratio + (1.0 - reflection_ratio) * pow((1.0 - facing), 5.0) | |
# compute reflection | |
if depth < max_depth and reflection_ratio > 0 : | |
var reflection_direction = ray.dir - normal * 2.0 * dot_normal_ray | |
var reflection = trace(newRay(point_of_hit + normE5, reflection_direction), scene, depth + 1) | |
result += reflection * fresneleffect | |
# compute refraction | |
if depth < max_depth and (obj.transparency > 0.0) : | |
var ior = 1.5 | |
let CE = ray.dir.dot(normal) * -1.0 | |
ior = if inside : 1.0 / ior else: ior | |
let eta = 1.0 / ior | |
let GF = (ray.dir + normal * CE) * eta | |
let sin_t1_2 = 1.0 - CE * CE | |
let sin_t2_2 = sin_t1_2 * (eta * eta) | |
if sin_t2_2 < 1.0 : | |
let GC = normal * sqrt(1 - sin_t2_2) | |
let refraction_direction = GF - GC | |
let refraction = trace(newRay(point_of_hit - normal * 1.0e-4, refraction_direction), | |
scene, depth + 1) | |
result += refraction * (1.0 - fresneleffect) * obj.transparency | |
proc render (scene: TScene, surface: SurfacePtr, renderer: RendererPtr) = | |
discard lockSurface(surface) | |
let eye = newVec3(0.0) | |
var h = tan(fov / 360.0 * 2.0 * PI / 2.0) * 2.0 | |
var | |
w = h * width.float / height.float | |
const | |
ww = width.float | |
hh = height.float | |
for y in 0 .. < height : | |
let yy = y.float | |
var row: ptr int32 = cast[ptr int32](cast[ByteAddress](surface.pixels) + surface.pitch.int32 * y) | |
for x in 0 .. < width : | |
let xx = x.float | |
var dir = normalize([(xx - ww / 2.0) / ww * w, | |
(hh/2.0 - yy) / hh * h, | |
-1.0]) | |
let pixel = trace(newRay(eye, dir), scene, 0) | |
#macFor x -> [0,1,2], col -> [r,g,b] : | |
let r = min(255, round(pixel[0] * 255.0)).byte | |
let g = min(255, round(pixel[1] * 255.0)).byte | |
let b = min(255, round(pixel[2] * 255.0)).byte | |
#auto rgb = map!("cast(ubyte)min(255, a*255+0.5)")(pixel[]); | |
row[] = (int32)sdl2.mapRGB(surface.format, r, g, b) | |
row = cast[ptr int32](cast[ByteAddress](row) + sizeof(int32)) | |
unlockSurface(surface) | |
#sdl2.clear(renderer) | |
#sdl2.setDrawColor(renderer, 25, 02 ,0, 255) | |
sdl2.present(renderer) | |
proc test() = | |
discard sdl2.init(INIT_EVERYTHING) | |
var screen = sdl2.createWindow("Raycast", | |
SDL_WINDOWPOS_UNDEFINED, | |
SDL_WINDOWPOS_UNDEFINED, | |
width, height, | |
SDL_WINDOW_OPENGL); | |
var renderer = sdl2.createRenderer(screen, -1, 1) | |
var window = sdl2.getsurface(screen) | |
if screen.isNil: | |
quit($sdl2.getError()) | |
var scene: TScene | |
scene.objects = @[newSphere([0.0, -10002.0, -20.0], 10000.0, [0.8, 0.8, 0.8]), | |
newSphere([0.0, 2.0, -20.0], 4.0, [0.8, 0.5, 0.5], 0.5), | |
newSphere([5.0, 0.0, -15.0], 2.0, [0.3, 0.8, 0.8], 0.2), | |
newSphere([-5.0, 0.0, -15.0], 2.0, [0.3, 0.5, 0.8], 0.2), | |
newSphere([-2.0, -1.0, -10.0], 1.0, [0.1, 0.1, 0.1], 0.1, 0.8)] | |
scene.lights = @[newLight([-10.0, 20.0, 30.0], [2.0, 2.0, 2.0]) ] | |
render(scene, window, renderer) | |
var initTime = cpuTime() | |
test() | |
var time = cpuTime() - initTime | |
echo("It took exactly ", time, " seconds to render.") | |
var quit: bool = false | |
var event = sdl2.defaultEvent | |
while quit == false: | |
while (pollEvent(event)): | |
if event.kind == QuitEvent: | |
quit = true | |
sdl2.quit() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment