Lua code which hardcodes a glsl raytracer demo

Showcase your libraries, tools and other projects that help your fellow love users.
Post Reply
AxisAngles
Prole
Posts: 12
Joined: Mon Feb 22, 2016 9:02 am

Lua code which hardcodes a glsl raytracer demo

Post by AxisAngles » Wed Feb 24, 2016 12:21 am

Okay. So I thought, "wouldn't it be fun if I wrote a Lua program to program a hardcoded binary search tree in GLSL?" So I did it, and then before I implemented stuff other than the sphere solver, I realized that you only have a small number of words for gpu instructions to be stored. So you can't actually go over like 200 spheres in my raycaster...

Image
Image

ANYWAY, here's the code.

Code: Select all

--How to create the raycast GLSL code
--	call generateglslcaster(data)
--	returns a string of glsl code
--	the code contains a function called raycast

--How to call the raycast function
--bool raycast(in vec3 o,in vec3 d,out vec3 pos,out vec3 norm,out vec4 color)
--	Returns whether the ray hit anything
--	o is the origin of the ray
--	d is the direction of the ray
--	pos is where the ray hits the object
--	norm is the normal of where the ray hits the object
--	color is the color of the object the ray hit




--Groups nearest spheres together
--This make a log-time ray search
--Way better than octrees... if you don't need to move anything.
local function partition(spheres)
	if #spheres==1 then
		return spheres[1]
	else
		local newspheres={}
		local taken={}
		for i=1,#spheres do
			if not taken[i] then
				local b0=spheres[i]
				local bestr=1/0
				local bestj
				for j=i+1,#spheres do
					if not taken[j] then
						local b1=spheres[j]
						local r
						local d=((b1.x-b0.x)*(b1.x-b0.x)
							+(b1.y-b0.y)*(b1.y-b0.y)
							+(b1.z-b0.z)*(b1.z-b0.z))^0.5
						if d+b0.r<b1.r then
							r=b1.r
						elseif d+b1.r<b0.r then
							r=b0.r
						else
							r=(d+b0.r+b1.r)/2
						end
						if r<bestr then
							bestr=r
							bestj=j
						end
					end
				end
				if bestj then
					taken[i]=true
					taken[bestj]=true
					local b1=spheres[bestj]
					local d=((b1.x-b0.x)*(b1.x-b0.x)
							+(b1.y-b0.y)*(b1.y-b0.y)
							+(b1.z-b0.z)*(b1.z-b0.z))^0.5
					if d+b0.r<b1.r then
						newspheres[#newspheres+1]={
							a=b0;
							b=b1;
							x=b1.x;
							y=b1.y;
							z=b1.z;
							r=b1.r;
						}
					elseif d+b1.r<b0.r then
						newspheres[#newspheres+1]={
							a=b0;
							b=b1;
							x=b0.x;
							y=b0.y;
							z=b0.z;
							r=b0.r;
						}
					else
						local c0=((b0.r-b1.r)/d+1)/2
						local c1=((b1.r-b0.r)/d+1)/2
						newspheres[#newspheres+1]={
							a=b0;
							b=b1;
							x=c0*b0.x+c1*b1.x;
							y=c0*b0.y+c1*b1.y;
							z=c0*b0.z+c1*b1.z;
							r=bestr;
						}
					end
				end
			end
		end
		for i=1,#spheres do
			if not taken[i] then
				newspheres[#newspheres+1]=spheres[i]
			end
		end
		return partition(newspheres)
	end
end

--Bounds all the objects into minimum size spheres.
--Because the only datatype is sphere, this is kind of useless, but whatever.
--I _was_ planning on adding more shapes
local function makespheres(data)
	local spheres={}
	for i=1,#data do
		local g=data[i]
		if g.type=="sphere" then
			spheres[i]={
				x=g.cx;
				y=g.cy;
				z=g.cz;
				r=g.r;
				g=g;
			}
		end
	end
	return spheres
end

--code to check if the ray intersects a partition sphere
local pcheck=[[
$tco=$c-o;
$tu=dot(d,co);
$tv2=u*u-dot(co,co)+$r;
$tif(0<v2&&0<u+sqrt(v2)&&u-sqrt(v2)<bestt){
$t	//Optimization: &&0<u+sqrt(v2)&&u-sqrt(v2)<bestt
$t	//I haven't tested it but I think square roots are significantly faster than more if statements
]]

--Solves the intersection of the ray and a sphere object
--sets hit distance, hit position, hit norm, and color
local solvesphere=[[
$t	vec3 co=$c-o;
$t	float u=dot(d,co);
$t	float v2=u*u-dot(co,co)+$r;
$t	if(0<v2){
$t		float t=u-sqrt(v2);
$t		if(0<t&&t<bestt){
$t			bestt=t;
$t			pos=o+t*d;
$t			norm=normalize(pos-$c);
$t			color=$q;
$t		}
$t	}
]]

--recursively hardcodes the partition checks and object solvers.
local function writecode(p,tabs)
	tabs=(tabs or "").."\t"
	local s0=string.gsub(pcheck,"$(.)",function(c)
		if c=="c" then
			return "vec3("..p.x..","..p.y..","..p.z..")"
		elseif c=="r" then
			return p.r*p.r
		elseif c=="t" then
			return tabs
		end
	end)
	local g=p.g
	if g then
		if g.type=="sphere" then
			local s1=string.gsub(solvesphere,"$(.)",function(c)
				if c=="c" then
					return "vec3("..g.cx..","..g.cy..","..g.cz..")"
				elseif c=="r" then
					return g.r*g.r
				elseif c=="t" then
					return tabs
				elseif c=="q" then
					return "vec4("..g.cr..","..g.cg..","..g.cb..",1)"
				end
			end)
			return s0..s1..tabs.."}\n"
		--elseif g.type=="convexmesh" then LOLOLOL Not going to happen
		end
	else
		return s0..writecode(p.a,tabs)..writecode(p.b,tabs)..tabs.."}\n"
	end
end

--Makes the glsl ray caster from the given data
local function generateglslcaster(data)
	local part=partition(makespheres(data))
	return [[
bool raycast(in vec3 o,in vec3 d,out vec3 pos,out vec3 norm,out vec4 color){
	d=normalize(d);
	float bestt=1.0f/0.0f;
	vec3 co;
	float u;
	float v2;
]]..writecode(part)..[[
	return bestt<1.0/0.0;
}
]]
end







--Trash code after this point.








--Make a bunch of spheres.
local data={}
for i=1,64 do
	--shitty non-uniform distribution.
	local cx=math.random()-0.5
	local cy=math.random()-0.5
	local cz=math.random()-0.5
	local c=7.5/(cx*cx+cy*cy+cz*cz)^0.5
	cx,cy,cz=c*cx,c*cy,c*cz

	data[i]={
		type="sphere";
		cx=cx;
		cy=cy;
		cz=cz;
		cr=math.random();
		cg=math.random();
		cb=math.random();
		r=math.random();
	}
end

data[65]={
	type="sphere";
	cx=0;
	cy=0;
	cz=0;
	cr=0.25;
	cg=0.25;
	cb=0.75;
	r=5;
}




--Create the caster
local caster=generateglslcaster(data)
--Create the tracer
--This is the part you rewrite to make less cruddy
local effecttracer=[[

extern float w=800;
extern float h=600;
extern vec3 cp=vec3(0,0,0);
extern vec3 cx=vec3(1,0,0);
extern vec3 cy=vec3(0,1,0);
extern vec3 cz=vec3(0,0,1);
extern vec3 lightdir=normalize(vec3(1,1,1));

vec4 effect( vec4 _color, Image texture, vec2 p, vec2 screen_coords )
{
	vec3 throwaway3;
	vec4 throwaway4;
	vec3 pos=cp;
	//Notice how I'm not subtracting cz. It's because left hand rule is for cool peeps.
	vec3 dir=w/h*(2*p.x-1)*cx+(1-2*p.y)*cy+cz;
	vec3 norm;
	vec4 color;
	vec4 sumcolor=vec4(0,0,0,1);
	vec4 mulcolor=vec4(1,1,1,1);
	for(int i=0;i<10;i++){
		bool hit=raycast(pos,dir,pos,norm,color);
		if(!hit){
			return sumcolor;
		}
		//Some craptastic light stuff.
		hit=raycast(pos,lightdir,throwaway3,throwaway3,throwaway4);
		if(!hit){
			sumcolor+=0.5*max(0,dot(norm,lightdir))*mulcolor*color;
		}
		mulcolor*=color;
		dir-=2*dot(dir,norm)*norm;
	}
	return sumcolor;
}
]]

--Compile the fragment shader
local effect=love.graphics.newShader(caster..effecttracer)
















--In case you want to do something cool, hve a canvas
local canvas=love.graphics.newCanvas(800,600,"hdr")
love.graphics.setShader(effect)

--Position and angles... sort of.
local pos={10,10,10}
local mx,my=0,0

local sin=math.sin
local cos=math.cos
local function anglesyx(x,y)
	local cx,sx=cos(x),sin(x)
	local cy,sy=cos(y),sin(y)
	--Inefficient. Oh well. This is trash code.
	return {cy,0,-sy},{sx*sy,cx,cy*sx},{cx*sy,-sx,cx*cy}
end

function love.draw()
	love.graphics.draw(canvas)
end

love.mouse.setRelativeMode(true)

function love.mousemoved(x,y,dx,dy)
	mx=mx+dx
	my=my+dy
	local cx,cy,cz=anglesyx(my/256,mx/256)
	effect:send("cx",cx)
	effect:send("cy",cy)
	effect:send("cz",cz)
end

local t=0
function love.update(dt)
	t=t+dt
	effect:send("lightdir",{0.5^0.5*sin(t),0.5^0.5,0.5^0.5*cos(t)})
	if love.keyboard.isDown("escape") then
		love.event.quit()
	end
	local cx,cy,cz=anglesyx(my/256,mx/256)
	local dx=0;
	local dz=0;
	if love.keyboard.isDown("w") then
		dz=dz+1
	end
	if love.keyboard.isDown("a") then
		dx=dx-1
	end
	if love.keyboard.isDown("s") then
		dz=dz-1
	end
	if love.keyboard.isDown("d") then
		dx=dx+1
	end
	local d2=dx*dx+dz*dz
	if 0<d2 then
		local d=d2^0.5
		dx=dx/d
		dz=dz/d
	end
	if love.keyboard.isDown("lshift") then
		dx=dx/16
		dz=dz/16
	end
	pos[1]=pos[1]+dt*4*(dx*cx[1]+dz*cz[1])
	pos[2]=pos[2]+dt*4*(dx*cx[2]+dz*cz[2])
	pos[3]=pos[3]+dt*4*(dx*cx[3]+dz*cz[3])
	effect:send("cp",pos)
end

User avatar
ReFreezed
Citizen
Posts: 72
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: Lua code which hardcodes a glsl raytracer demo

Post by ReFreezed » Thu Feb 25, 2016 11:17 pm

Just seeing 3D graphics in LÖVE is awesome enough, but a raytracer? Super cool! :o lol
Tools: LuaPreprocess, LuaHotLoader
Games: Momento Temporis: Light from the Deep, Momento Temporis: Arena, Energize!
"If each mistake being made is a new one, then progress is being made."


User avatar
alberto_lara
Party member
Posts: 331
Joined: Wed Oct 30, 2013 8:59 pm

Re: Lua code which hardcodes a glsl raytracer demo

Post by alberto_lara » Mon Mar 07, 2016 5:56 pm

So you wrote a lua program which writes the GLSL code? did I get it right? Anyway this look awesome.

EDIT: I just read it and I see you indeed are generating glsl code, cool :)
LÖVE Projects: GOOi, Süsse and Katsudö
Thank you for taking the time to read this

AxisAngles
Prole
Posts: 12
Joined: Mon Feb 22, 2016 9:02 am

Re: Lua code which hardcodes a glsl raytracer demo

Post by AxisAngles » Tue Mar 08, 2016 10:40 am

Yes, that's exactly what I did!

Post Reply

Who is online

Users browsing this forum: No registered users and 3 guests