Dark Bit Factory & Gravity
PROGRAMMING => Freebasic => Topic started by: Stonemonkey on August 09, 2007
-
Since the discussion in blitz about raytracing i've given it a little go. It's not got shadows yet and still needs some work on shading.
option explicit
const magic=6755399441055744.0
union float_to_int_conversion
_in as double
_out as integer
end union
union recip_sqr_conversion
_float as single
_int as integer
end union
type sphere_struct
x as single
y as single
z as single
rad as single
rad_recip as single
rad_recip2 as single
red as single
gre as single
blu as single
reflective as single
end type
type light_struct
x as single
y as single
z as single
red as single
gre as single
blu as single
end type
type vector_struct
x as single
y as single
z as single
end type
type raytrace_struct
x as single
y as single
z as single
vx as single
vy as single
vz as single
affect as single
red as single
gre as single
blu as single
sphere_list as sphere_struct pointer
sphere_count as integer
current_sphere as sphere_struct pointer
light_list as light_struct pointer
light_count as integer
display as integer pointer
vectors as vector_struct pointer
end type
sub raytrace (ray as raytrace_struct pointer)
dim as sphere_struct pointer sphere,this_sphere
dim as light_struct pointer light
dim as single current_distance=1000000.0,vx,vy,vz,d,d2,nx,ny,nz
dim as integer i,j
dim as recip_sqr_conversion dd
for i=0 to ray->sphere_count-1
sphere=@ray->sphere_list[i]
if ray->current_sphere<>sphere then
vx=sphere->x-ray->x
vy=sphere->y-ray->y
vz=sphere->z-ray->z
d=vx*ray->vx+vy*ray->vy+vz*ray->vz
If (d>0.0)and((d-sphere->rad)<current_distance) Then
vx-=ray->vx*d
vy-=ray->vy*d
vz-=ray->vz*d
d2=vx*vx+vy*vy+vz*vz
If d2<=sphere->rad*sphere->rad Then
d-=Sqr(sphere->rad*sphere->rad-d2)
if d<current_distance then
this_sphere=sphere
current_distance=d
end if
end if
end if
end if
next
if this_sphere then
ray->x+=ray->vx*current_distance
ray->y+=ray->vy*current_distance
ray->z+=ray->vz*current_distance
vx=ray->x-this_sphere->x
vy=ray->y-this_sphere->y
vz=ray->z-this_sphere->z
d=(ray->vx*vx+ray->vy*vy+ray->vz*vz)*this_sphere->rad_recip2
ray->vx-=vx*d
ray->vy-=vy*d
ray->vz-=vz*d
nx=-(this_sphere->x-ray->x)*this_sphere->rad_recip
ny=-(this_sphere->y-ray->y)*this_sphere->rad_recip
nz=-(this_sphere->z-ray->z)*this_sphere->rad_recip
for j=0 to ray->light_count-1
light=@ray->light_list[j]
vx=light->x-ray->x
vy=light->y-ray->y
vz=light->z-ray->z
dd._float=vx*vx+vy*vy+vz*vz
d2=0.5*dd._float
dd._int=&h5f3759df-(dd._int shr 1)
dd._float=dd._float*(1.5-d2*dd._float*dd._float)
vx*=dd._float
vy*=dd._float
vz*=dd._float
'test if point facing away from light
i=0
while(i<ray->sphere_count-1)
'test for shadow, if in shadow then i=ray->sphere_count
i+=1
wend
if i=ray->sphere_count then
'do lighting(no shadow)
end if
d=vx*ray->vx+vy*ray->vy+vz*ray->vz
if d>0.0 then
d=d*d*d*ray->affect
ray->red+=light->red*d
ray->gre+=light->gre*d
ray->blu+=light->blu*d
end if
next
d=vx*nx+vy*ny+vz*nz
if d>0.0 then
ray->red+=this_sphere->red*ray->affect*light->red*d
ray->gre+=this_sphere->gre*ray->affect*light->gre*d
ray->blu+=this_sphere->blu*ray->affect*light->blu*d
end if
ray->affect*=this_sphere->reflective
ray->current_sphere=this_sphere
if ray->affect>0.01 then raytrace(ray)
end if
End sub
sub render_world(ray as raytrace_struct pointer)
dim as integer w,h,x,y
dim as float_to_int_conversion red,gre,blu
dim as single d
screeninfo(w,h)
dim as integer pointer display=ray->display
dim as vector_struct pointer vector=ray->vectors
for y=0 to h-1
for x=0 to w-1
ray->x=0.0
ray->y=0.0
ray->z=0.0
ray->red=0.0
ray->gre=0.0
ray->blu=0.0
ray->vx=vector->x
ray->vy=vector->y
ray->vz=vector->z
ray->current_sphere=0
ray->affect=1.0
raytrace(ray)
if ray->red>1.0 then ray->red=1.0
if ray->gre>1.0 then ray->gre=1.0
if ray->blu>1.0 then ray->blu=1.0
red._in=ray->red*255.0+magic
gre._in=ray->gre*255.0+magic
blu._in=ray->blu*255.0+magic
*display=(red._out shl 16)or(gre._out shl 8)or blu._out
display+=1
vector+=1
next
next
end sub
sub set_screen_vectors(ray as raytrace_struct pointer,z as single=200.0)
dim as integer w,h,x,y
dim as single d
screeninfo(w,h)
ray->vectors=callocate(len(vector_struct)*w*h)
dim as vector_struct pointer vector=ray->vectors
for y=0 to h-1
for x=0 to w-1
vector->x=x-(w shr 1)
vector->y=(h shr 1)-y
vector->z=z
d=1.0/sqr(vector->x^2+vector->y^2+vector->z^2)
vector->x*=d
vector->y*=d
vector->z*=d
vector+=1
next
next
end sub
sub set_sphere(ray as raytrace_struct pointer,index as integer,_
x as single,y as single,z as single,radius as single,_
red as single,green as single,blue as single,reflective as single)
dim as sphere_struct pointer sphere=@ray->sphere_list[index]
sphere->x=x
sphere->y=y
sphere->z=z
sphere->rad=radius
sphere->rad_recip=1.0/radius
sphere->rad_recip2=sphere->rad_recip*sphere->rad_recip*2.0
sphere->red=red
sphere->gre=green
sphere->blu=blue
sphere->reflective=reflective
end sub
sub set_light(ray as raytrace_struct pointer,index as integer,_
x as single,y as single,z as single,_
red as single,green as single,blue as single)
dim as light_struct pointer light=@ray->light_list[index]
light->x=x
light->y=y
light->z=z
light->red=red
light->gre=green
light->blu=blue
end sub
sub main()
screenres 320,240,32,2
screenset 1,0
dim as raytrace_struct pointer ray=callocate(len(raytrace_struct))
ray->display=screenptr()
set_screen_vectors(ray)
ray->sphere_count=4
ray->light_count=1
ray->sphere_list=callocate(len(sphere_struct)*ray->sphere_count)
ray->light_list=callocate(len(light_struct)*ray->light_count)
set_sphere(ray,0,50.0,20.0,300.0,50.0,.9,.0,.0,.5)
set_sphere(ray,1,0.0,20.0,200.0,50.0,.0,.0,.2,.5)
set_sphere(ray,2,.0,-5040.0,.0,5000.0,.2,.2,.2,.24)
set_sphere(ray,3,.0,5080,.0,5000.0,.2,.2,.2,.1)
set_light(ray,0,100.0,20.0,.0,1.0,1.0,0.5)
dim as single angle=0
do
ray->sphere_list[0]->z=200.0+100*sin(angle)
ray->sphere_list[0]->x=0.0+100*cos(angle)
ray->sphere_list[1]->z=200.0-30*sin(angle)
ray->sphere_list[1]->x=0.0-30*cos(angle)
angle+=0.1
render_world(ray)
flip
cls
loop while inkey$=""
end sub
main()
-
Is this FB 1.6 mate?
I can't run it here on my opld version of Freebasic.. Any chance of an exe please ? :) Cheers.
-
It's .17b I've got but this also doesn't work with the latest version of the compiler :( there seems to have been a fair bit of messing around with the types over the last few builds.
Here's the exe.
-
Messed about code you may say but that looks very good to me. Thanks for posting the exe.
-
Excellent stuff its runnin smooth here. unfinished you say? well i cant wait to see the finished one :D
-
Fixed up the shadows and lighting a bit, It's pertty slow but it doesn't use any interpolation or anything to keep the speed up so I'm not too bothered about that.
It only does spheres and has no refraction.
Source and exe included:
-
"Finished" was running at a couple of frames a second, "Finished_mod" was a frame every few seconds but like you said, it's not something to be bothered about as the rendering is really nice quality.
Now if that was my code I would leave it running during a demo during static text displays and precalculate a nice animation for the end part :)
Lovely stuff Stonemonkey and source included too, have some Karma :)
-
"Finished" was running at a couple of frames a second, "Finished_mod" was a frame every few seconds
Strange, it's the other way round for me.
-
Here's another edit, should work in the latest version of FB with or without -lang deprecated:
-
It looks great!
Jim
-
Thanks, just a shame it's not a bit faster. Maybe of use for some animated textures or something though.
-
Is it possible to create 2 (or more) threads to do it? That would double the speed on an P4 HT or Core2.
Jim
-
Should be, what sort of thing would you recommend? dividing the screen up and letting each thread deal with a section of it?
-
Yeah, how I'd do it is start by dividing the screen into 2 (or more) horizontal strips and render each on a different thread.
Then you could consider dividing it into many, many strips (scanlines?) and allocating them to any thread that's finished its work already.
Then you could do the same with 2d blocks instead of strips.
This way, if the top half of the screen is empty, that thread should render really fast and complete quickly, so that thread should apply itself to another part of the screen.
I'm working on something similar for a Mandelbrot renderer. Simple multithread easily doubles the speed.
Jim
-
Divided the screen into 4 areas each rendered with a different thread, as you say though some threads will finish quicker than others but I'm not sure if FB can detect whether a thread has finished other than waiting with threadwait.
doesn't work with -lang deprecated.
-
I reckon that's 2-3x faster than finished_mod.exe. Nice work, but still a bit of a slideshow :D
Jim
-
Yep it is, still might find some use for it though.
-
Here's the code for one I did some time ago, takes a min or 2 to do the render.
-
Stonemonkey,
cant you just use a variable? When the thread finishes it sets the variable to "done" value that the main thread (task manager as used to call them) reads?
Chris
-
Hmm, global? or maybe another field in the world type 'world->thread_busy' or something?
-
something liek this:
sub render_thread(world as world_struct pointer)
dim as integer x,y
dim as single d
dim as integer pointer display=world->display+(world->y_start*world->wwidth)
dim as vector_struct pointer vector=world->vectors+(world->y_start*world->wwidth)
for y=world->y_start to world->y_end
for x=0 to world->wwidth-1
*display=raytrace(world,vector)
display+=1
vector+=1
next
next
world->busy=0
end sub
sub render_world(world as world_struct pointer)
dim as integer y=0,i=0
dim world_thread(0 to 3) as world_struct
dim as any pointer thread(0 to 3)
world_thread(0)=*world
world_thread(1)=*world
world_thread(2)=*world
world_thread(3)=*world
while y<world->height
i=(i+1)and 3
if (@world_thread(i))->busy=0 then
(@world_thread(i))->y_start=y
(@world_thread(i))->y_end=y+9
(@world_thread(i))->busy=1
thread(i)=threadcreate(@render_thread,@world_thread(i))
y+=10
end if
wend
threadwait(thread(0))
threadwait(thread(1))
threadwait(thread(2))
threadwait(thread(3))
end sub
with the field 'busy as integer' added to the world_struct type.
Does it in blocks of 10 rows at a time, the current values need the screen size to be some multiple of 10 in height.
-
You have to watch out for that - the while loop that is checking for busy is very tight - it has no sleep in it so it will take up 100% cpu doing very little. The correct way to do this is to use semaphores, but I'm not sure FB supports them directly - you might have to use Windows API.
eg. Windows has a function that says
Sleep until one or more of the threads completes and then tell me which one(s) it was.
Jim
-
yep, i was wondering about that, i don't think fb can do that and sleep wouldn't be much help either.
-
Is it something like this:
WaitForMultipleObjectsEx(4,@thread(0),0,1,0)
added into the loop?
-
Yes, something very like that - then check the return code (or the busy flags of all threads) to see which one to start next. Is it known whether FB's thread handles are compatible with Windows ones?
Jim
-
I have no idea and i'm just trying to make some sense of the standard access rights although i'm just looking at it kind of blankly atm.
-
Something I'm not to sure about is bAlertable, the description is a little confusing.
-
Set it to false. There are many things that can be waited on - thread and process handles, semaphores, events, and overlapped IO, etc. Overlapped IO is where you tell Windows to go and read or write a load of data, and then go to sleep until the read or write is completed.
You want to set the timeout to INFINITE.
Jim
-
Now, if i put in the for loop to test each thread, it crashes/closes. doesn't do that if i just loop i in the while wend loop though. (not just with putting in infinite etc. was the same just before that too)
-
ok, found why it crashes, the for loop was letting it draw beyond the screen boundary.
-
I think I'm getting there now:
-
any chance of an exe for the C weenies??
-
->Fryer, it might be interesting as an experiment to colour code which areas are rendered by which cpu/thread. I see you've made each thread do 10 lines at a time. Good for debugging :)
->chris, it's exactly the same as one of the previous exes, except it's now using 4 concurrent threads to do the rendering. This is making it 2-3x faster on my Core2, and it should help all the P4 HT people, and anyone lucky enough to have a Core2 Quad!
Jim
-
Stonemonkey dude, it crashes my end.
-
Nice idea to see what goes on Jim, I'll give that a go.
Sorry Clyde, I take it you're not running the latest version of FB atm?
-
Won't run here either (FB1.5) so cant give feedback on speed and such, sorry :) I bet it looks nice though.
-
Sorry about that, it still just looks the same as before. The only thing is now it's multithreaded so it'd hopefully run a bit better on muti core/cpu machines although mine isn't. It's just turnde into a bit of an experiment tbh, here's how it stands atm with colour banding showing up which thread is rendering.
.exe
-
Doh! Silly me I'll need to bung on V17b.
And Mate thats mega impressive welldone indeed dude.
:o
-
looks great and seems to run quicker on dual core too :)
-
Cool and thanks.
Another thing I'm wondering about this is sharing the data between the threads, there's stuff about locking memory (with mutexlock). I can see the point in doing that when a thread is writing to memory but in this case I don't write to any of the shared memory so to me that's not really an issue, is that right or is there anything else that should be taken care of?
-
@Stonemonkey: I've tried your exe and it run very slow here, it took around 7 seconds to render one frame :( , probably because I've only one processor core ??? (Pentium 4 2.8Ghz)
-
->rbraz - don't worry, it only does about 2fps on my Core2 Duo 2.66GHz!
->fryer - you almost certainly don't have to worry about data locking in this program - multiple threads can share the model data with no problems, and since they're each writing to different bits of the screen it's OK too.
You need the locking when one thread writes some data and another thread needs to read it back. Usually you create a 'semaphore' (not a mutex, which is slightly different). One thread then 'locks' the semaphore, does the writes and unlocks it again. If the other thread tries to lock the semaphore while the first thread still has it locked, it will block until the semaphore is released. Clever eh? :)
Jim
-
Thanks Jim.
rbraz, that does seem a bit slow to me. I have a single core amd64 @ 2.1GHz and get somewhere around 1fps
-
i've got a amd athlon xp 2600+ 1.9 ghz and running a little more than 1 fps
-
Thanks Jim.
rbraz, that does seem a bit slow to me. I have a single core amd64 @ 2.1GHz and get somewhere around 1fps
Might be my graphics board then, I've always got bad FPS with software rendered pixel.