Real time 3draytracer.

Community Forums/Showcase/Real time 3draytracer.

AntonyWells(Posted 2004) [#1]
Inspired by the thread on it, here is a simple yet nice looking 3d tracer engine.



It uses b3d meshes so you can load and trace any entity, easily. Just call newPrim(entity) to add to the scene.
newLight() to add a colored light(Casts shadows by default)

It renders from the point of view of a blitz camera. Although the entire scene is drawn using writepixelfast, so don't expect massive speeds.)

Has shadows and true diffuse lighting.

Neither demo requires outside media.


You can set Res to any pixel size to improve the res/quality.

Low Quality Version. this one is not only real time but you can move above, wasd controls.
Still has lights/shadows.(3)
;Infini.
;
Global res=45

Graphics3D res+5,res+5,32,3
SetBuffer BackBuffer()


test=CreateCube()

;test=LoadMesh("tmedia\jeep1.3ds")
If Not test End
;FitMesh test,-3,-1,-3,6,1,6
other=CreateCube()
FitMesh other,-20,-1,-20,40,20,40
FlipMesh other
newPrim(other,2)
newPrim(test,2)
;EntityPickMode

vcam=CreateCamera()
PositionEntity vcam,3,10,-5
PointEntity vcam,test
UpdateNormals other
UpdateNormals test
TurnEntity test,0,0,-45


newLight(10,8,4,0,0,512)
newLight(-10,8,4,255,255,100)

newLight(0,3.5,-6,412,412,412)






Repeat


Cls
	;RenderWorld
  PositionEntity test,0,10+Cos(aa)*4,0
	aa=aa+20
	
	TurnEntity vcam,MouseYSpeed(),-MouseXSpeed(),0,True
	RotateEntity vcam,EntityPitch(vcam),EntityYaw(vcam),0
	MoveMouse 20,20
	FlushMouse
	
;	PointEntity vcam,test
		;FlushMouse
		
	If KeyDown(17)
		MoveEntity vcam,0,0,2
	EndIf
	
	If KeyDown(31)
		MoveEntity vcam,0,0,-2
	EndIf
	
	If KeyDown(30)
		MoveEntity vcam,1,0,0
	EndIf
	If KeyDown(32)
		MoveEntity vcam,-1,0,0
	EndIf
	TurnEntity test,0,5,0
	rayTrace(vcam,res,res)
	;Flip
;Else
	;RayTrace(vcam,64,64)
	;EndIf
	
	Flip False
Until KeyDown(1)
SaveBuffer(BackBuffer(),"RayTrace.bmp")
WaitKey
End


;End
Type prim
	Field id
End Type
Type light
	Field x,y,z
	Field r#,g#,b#
End Type
Function DotProd#(vecA#[3], vecB#[3])
   Return(vecA[0]*vecB[0] +vecA[1]*vecB[1] +vecA[2]*vecB[2]);
End Function

Function vecNormalize#(vec#[3])
   mag# = Sqr(vec[0]*vec[0] +vec[1]*vec[1] +vec[2]*vec[2]);

   ;// don't divide by zero
   If (mag=0)
      vec[0] = 0.0;f;
      vec[1] = 0.0;f;
      vec[2] = 0.0;f;
      Return(0.0);
   EndIf

   vec[0] =vec[0]/mag;
   vec[1] =vec[1]/mag;
   vec[2] =vec[2]/mag;

   Return(mag);
End Function
Function rayTrace(cam,xRes=160,yRes=160,pixelRes=1)
	fov#=5;in units
	xScan#=fov/XRes;/fov;/fov*2
	yScan#=fov/yRes;/fov;/fov*2
;	Stop
Local vecA#[3],vecB#[3]
	xo#=-2.5
	yo#=2.5
	cx#=EntityX(cam)
	cy#=EntityY(cam)
	cz#=EntityZ(cam)
LockBuffer 
	While x<xRes
		x=x+pixelRes
		xo=xo+xScan
		While y<yRes
			y=y+pixelRes
			yo=yo-yScan
			TFormVector xo*20,yo*20,100,cam,0
			totr=0
			totg=0
			totb=0
			vx#=TFormedX()
			vy#=TFormedY()
			vz#=TFormedZ()
			hit=LinePick(cx,cy,cz,TFormedX(),TFormedY(),TFormedZ(),0.2)
			If hit
			
				px#=PickedX()
				py#=PickedY()
				pz#=PickedZ()
				nx#=PickedNX()
				ny#=PickedNY()
				nz#=PickedNZ()
				Veca[0]=PickedNX()
				veca[1]=PickedNY()
				veca[2]=PickedNZ()
			HideEntity hit
			
			For l.light =Each light
				dx#=l\x-px
				dy#=l\y-py
				dz#=l\z-pz
				vecB[0]=dx
				VecB[1]=dy
				Vecb[2]=dz
				vecNormalize(vecb)
					dot#=dotProd(veca,vecb)
				If dot>0
					hit2=LinePick(px,py,pz,dx,dy,dz,0.2)
					If hit2
					Else
						totr=totR+Float(l\r*dot);*hit2
						totg=totG+Float(l\g*dot);*hit2
						totb=totB+Float(l\b*dot);*hit2
					EndIf
				EndIf
			
			Next
			ShowEntity hit

			totr=totr/3.
			totg=totg/3.
			totb=totb/3.

				If veca[0]<>0 ox#=-vx Else ox=vx
				If veca[1]<>0 oy#=-vy Else oy=vy
				If veca[2]<>0 oz#=-vz Else oz=vz

				If totr>255 totr=255
				If totg>255 totg=255
				If totb>255 totb=255
			rgb=totb Or (totg Shl 8) Or (totr Shl 16)
			
					WritePixelFast x,y,rgb
				
			EndIf
		Wend
		y=0
		yo=2.5
	Wend
UnlockBuffer
End Function

Function newLight(x,y,z,r=128,g=128,b=128)
	l.light=New light
	l\x=x
	l\y=y
	l\z=z
	l\r=r
	l\g=g
	l\b=b
End Function

Function newPrim(ent,mode)
	p.prim=New prim
	p\id=ent
	EntityPickMode ent,mode,True
	
End Function



High quality version, too hi-res to be real time.(Well, on my cput. Yours may be powerful enough).
;Infini.
;
;
;High Quality. SLOW.
Global res=512

Graphics3D res+60,res+60,32,3
SetBuffer BackBuffer()


test=CreateCube()

;test=LoadMesh("tmedia\jeep1.3ds")
If Not test End
;FitMesh test,-3,-1,-3,6,1,6
other=CreateCube()
FitMesh other,-20,-1,-20,40,20,40
FlipMesh other
newPrim(other,2)
newPrim(test,2)
;EntityPickMode

vcam=CreateCamera()
PositionEntity vcam,3,10,-5
PointEntity vcam,test
UpdateNormals other
UpdateNormals test
TurnEntity test,0,0,-45


newLight(10,8,4,0,0,512)
;newLight(-10,8,4,255,255,-100)
newLight(0,3.5,-6,412,412,412)






Repeat


Cls
	;RenderWorld
  PositionEntity test,0,10+Cos(aa)*4,0
	aa=aa+20
	
	TurnEntity vcam,MouseYSpeed(),-MouseXSpeed(),0
	RotateEntity vcam,EntityPitch(vcam),EntityYaw(vcam),0
	MoveMouse res/2,res/2
	PointEntity vcam,test
	FlushMouse
	If MouseDown(2)
		MoveEntity vcam,0,0,0.6
		EndIf
		TurnEntity test,0,30,0

	rayTrace(vcam,res,res)
	;Flip
;Else
	;RayTrace(vcam,64,64)
	;EndIf
	
	Flip False
Until KeyDown(1)
SaveBuffer(BackBuffer(),"RayTrace.bmp")
WaitKey
End


;End
Type prim
	Field id
End Type
Type light
	Field x,y,z
	Field r#,g#,b#
End Type
Function DotProd#(vecA#[3], vecB#[3])
   Return(vecA[0]*vecB[0] +vecA[1]*vecB[1] +vecA[2]*vecB[2]);
End Function

Function vecNormalize#(vec#[3])
   mag# = Sqr(vec[0]*vec[0] +vec[1]*vec[1] +vec[2]*vec[2]);

   ;// don't divide by zero
   If (mag=0)
      vec[0] = 0.0;f;
      vec[1] = 0.0;f;
      vec[2] = 0.0;f;
      Return(0.0);
   EndIf

   vec[0] =vec[0]/mag;
   vec[1] =vec[1]/mag;
   vec[2] =vec[2]/mag;

   Return(mag);
End Function
Function rayTrace(cam,xRes=160,yRes=160,pixelRes=1)
	fov#=5;in units
	xScan#=fov/XRes;/fov;/fov*2
	yScan#=fov/yRes;/fov;/fov*2
;	Stop
Local vecA#[3],vecB#[3]
	xo#=-2.5
	yo#=2.5
	cx#=EntityX(cam)
	cy#=EntityY(cam)
	cz#=EntityZ(cam)
LockBuffer 
	While x<xRes
		x=x+pixelRes
		xo=xo+xScan
		While y<yRes
			y=y+pixelRes
			yo=yo-yScan
			TFormVector xo*20,yo*20,100,cam,0
			totr=0
			totg=0
			totb=0
			vx#=TFormedX()
			vy#=TFormedY()
			vz#=TFormedZ()
			hit=LinePick(cx,cy,cz,TFormedX(),TFormedY(),TFormedZ(),0.2)
			If hit
			
				px#=PickedX()
				py#=PickedY()
				pz#=PickedZ()
				nx#=PickedNX()
				ny#=PickedNY()
				nz#=PickedNZ()
				Veca[0]=PickedNX()
				veca[1]=PickedNY()
				veca[2]=PickedNZ()
			HideEntity hit
			
			For l.light =Each light
				dx#=l\x-px
				dy#=l\y-py
				dz#=l\z-pz
				vecB[0]=dx
				VecB[1]=dy
				Vecb[2]=dz
				vecNormalize(vecb)
					dot#=dotProd(veca,vecb)
				If dot>0
					hit2=LinePick(px,py,pz,dx,dy,dz,0.2)
					If hit2
					Else
						totr=totR+Float(l\r*dot);*hit2
						totg=totG+Float(l\g*dot);*hit2
						totb=totB+Float(l\b*dot);*hit2
					EndIf
				EndIf
			
			Next
			ShowEntity hit

			totr=totr/3.
			totg=totg/3.
			totb=totb/3.

				If veca[0]<>0 ox#=-vx Else ox=vx
				If veca[1]<>0 oy#=-vy Else oy=vy
				If veca[2]<>0 oz#=-vz Else oz=vz

				If totr>255 totr=255
				If totg>255 totg=255
				If totb>255 totb=255
			rgb=totb Or (totg Shl 8) Or (totr Shl 16)
			
					WritePixelFast x,y,rgb
				
			EndIf
		Wend
		y=0
		yo=2.5
	Wend
UnlockBuffer
End Function

Function newLight(x,y,z,r=128,g=128,b=128)
	l.light=New light
	l\x=x
	l\y=y
	l\z=z
	l\r=r
	l\g=g
	l\b=b
End Function

Function newPrim(ent,mode)
	p.prim=New prim
	p\id=ent
	EntityPickMode ent,mode,True
	
End Function




Kanati(Posted 2004) [#2]
An interesting show... But the fast one is too low resolution to really do anything. And the high res version is insanely slow (though oh so much faster than I remember Turbo Silver on the amiga... like... hours faster. :) ) I was getting 1 frame every 6 seconds on the high res version (Athlon64 3000+ )

Interesting though.

Kanati


AntonyWells(Posted 2004) [#3]
Yeah, it's not got any real pratical use, just a taster of the future. ;)


slenkar(Posted 2004) [#4]
thats pretty cool, I wonder how much faster it would be in C? you could do doom3 style stuff - but low rez


Skitchy(Posted 2004) [#5]
Cool ;)


Picklesworth(Posted 2004) [#6]
That's cool. A tad useless but it's nice to see :)


AntonyWells(Posted 2004) [#7]
Yep, useless as jorden's fake knockers no doubt ;)

Just a test really, i'll be(and to answer your question too slenker) doing a much faster version in C++, as a free module(Bmax only) for vivid owners. Vivid_Tracer ;)


jfk EO-11110(Posted 2004) [#8]
I doubt doom3 uses raytracing. Anyway, this is a fascinating challenge. Elias_t posted a raytracer too some time ago in the archives. Now what you have to try is to optimize the speed again and again. Even when you think it couldn't get any faster, maybe you'll find another totally insane idea how to make it faster.

I remember my raycastimg time. I replaced the slow divisions by a bitshift OP and used the result as an index to access a value from an array. Anyway, it can be very exiting to search for speed gains.

Realtime Raytracing would make the whole shadow issue so incredible simple. Well, the number of lights still can slow down things painfully.

IMHO it would also be an option to render the scene using renderworld, and then darken the screen based on additional lightsource raytracing.

However, using writepixel is slow, but it's faster when you sequentially writepixel ONLY, reading from a bank or something, than to ReadPixel, WritePixel, ReadPixel, WritePixel etc.

An other option for dynamic shadows from static light sources is precalculated 3d lumel information. Raster the 3D space just like in a radiosity lightmapper, but instead of only painting a texture, store all lightinformation in 3D arrays, including the space that is representing Air, or "nothing". Then when a mesh is crossing the room, it's easy to calculate a realtime lightmap of any wanted resolution for the mesh. The only problem I see here is the amount of ram that is required to store the 3D light information.


slenkar(Posted 2004) [#9]
lots of possibilities for fun little games involving light,
although the raytracer doesnt do materials I spose, metal,wood etc.


AntonyWells(Posted 2004) [#10]
Nah. It did have reflections at one point, but I removed them.

Jfk, yep. To me that's half the fun..getting it to work fast ;) I did a few raycasters myself..even had a textured one going in blitz at one point, but overall blitz(In it's current incarnation..bmax will put an end to such probs most likely) hasn't got the raw speed to do it.
A great lib for trying out per pixel stuff fastly is allegro, c++ speed and basic like usage. (You still need to know C obviously, but it's 1000x easier than dx crap)

It really needs to stop using linepicks and b3d meshes, that's why it's so mind blowingly slow atm. Currently 3 picks per pixel, per light atm. So you can imagine how many picks a 640x480 res render with 5 lights would take..

In the words of the next president of america(If he had his way), I'll be back. (With a new version)


jfk EO-11110(Posted 2004) [#11]
Try the ABB-Algorithm :)

I just had this idea. Instead of camerapick to determine the 3D location of a pixel, we could do the following:

Color everything white (fullbright). Add black Fog. Do a renderworld. Based on the camera angle and the BRIGHTNESS of a pixel we could find out its 3D location, right?

Now since we know the 3d location, we should be able to calculate the 2D Screen location of this lumel from the Lights perspective, as well as it's distance to the light, is that right? If we know that, we can also calculate how bright the pixel shoud be. Not lets do a renderworld from the lights point of view. If the pixel is brighter, the lumel may be obscured from this point of view. Thus light won't reach it, of course.

This could be pretty fast. If it works.


jfk EO-11110(Posted 2004) [#12]
I just started with this. Calculating the 3D Locations of 640*480 Pixels of a rendered image took only 240 Millisecs, while 640*480 camerapicks took 108'782 Millisecs.

The Algorithm isn't pefect or very flexible by now, but it kinda works, the calculated position is about the same as the one returned by Camerapick. I guess some 3D Gurus will easily optimize this step of 3D Calculation.

That is nice, tho now I still got to find a way to check for visibility of the light, or of the lumel seen by the light.

Probably the easiest way will be to render the 6 sides of a cubemap seen from the lights position and then calculate on what side a 3D Location (they are nicely stored in arrays btw.) should be, and where exactly it should be. As I described before it should then be possible to determine the visibility of the lumel by the brightness of the pixel.

Not a single Linepick would be used that way, but unfortunately there are 7 additional Renderworlds, plus a lot of readpixel and writepixel.


AntonyWells(Posted 2004) [#13]
Yeah, if you remember my omni lightmapper, that does per pixel shadows and doesn't use a single linepick.

What I do there, is sorta like you say, but I render a low-res version of the light pointed at the tri. Then,
Break it down into a objected(Typed) grid, (by reading it per-pixel), pre-light mapping stage. The cool optimization is, since it's objected, you can do like,

if shadowMap\xRow[CheckX]=Null ;No active pixels, stop checking here.
Else if ShadowMap\xRow[CheckX]\yRoy[CheckY] return true
endif

All you have to do is point the camera at the destination and then project it into screen space, and just check the 2d map.

but bringing your idea into it, vivid has a z shader that renders textureless models colored with their Z value, for depth of field, but it could work with this...

But, what worries me is when you actually determine a hit ray(Which can be as a result of a reflection too, so it's not always tied to the camera's pov, but can be recurisive. Not in this demo, but in 'real' tracers)
anyway, is that you need the exact x,y,z location..That is easily done though, could just scale the output into a set region of space(-128,128) then do a shader that colors each pixel encoded with it's 3d location. Do a second one encoded with it's normal...

One quick transfer from the color buffer to system ram should infinitely faster than millions of linepicks.

heh getting carried away, better get back to vivid 'fore I start on this.


jfk EO-11110(Posted 2004) [#14]
Sounds good, tho I have to read this several times :) . I just tried the idea I described earlier. Unfortunately I get ugly sawteeth. I guess this is due to nonlinear Fog, or maybe floating point inaccuracy, not sure about this, but I can't get rid of the swawteeth, especially on the dark side of cubes etc. Well, since I use a conventional method for the second step (entityvisible), it's pretty slow anyway. While the first step, the calculation of the 3d locations of all pixels takes 250 Millisecs, the second part, checking for light visibility, takes more than 10 seconds.


jfk EO-11110(Posted 2004) [#15]
I just tried it again, but I didn' find a good solution yet. Calculating the 3D Locations of the Screens Pixels is fast, but it takes ages to determine the visibility of the light source from the point of view of these Locations.

Now if we could reverse the process and check for the visibility of the 3D locations, watched from the lights position, then we may be able to use something much faster:

I had the idea to position 640*480 Point Sprites and use a UNIQUE COLOR for each one of them. The Color will be produced by x (shl 12) or (y+10). Then we position a cubemap camera at the lights position and take 6 renders, just like a cubemap. Then we only have to readpixelfast all pixels. If they are not black, we can use their color to calculate the xy screen position of this location, this means if such a coloured point sprite is visible from the light location, it will not be inside the shadow.

Well, using this ICU Method with point sprites, with the help of the new Direct3D7 Extensions of Version 1.88 it may be possible.

I already tried it with ordinary sprites, but the polycount und number of surfaces was way too much for my system and it started to use harddrive swapspace and finally MAVed.

At least it would be true raytracing. I'd really like to test this, and I will test it as soon as someone offers a solution for point sprites.


AntonyWells(Posted 2004) [#16]
I've been thinking about it too...What I've come up with (In theory, havn't had time to code it yet) is..

Think of any pixel in the scene, what is the one
that each pixel has in common with a light? Vector.
360x360x360 that defines any light/pixel combination.

A lot, over 16Million...but do we really need such accuracy?
Why not compress space...

Reduce it all to the aspect of say... 32x32x32

Now the obvious next step is to determine this at each lumel position..but when you think about it, each unique angle can only cast a shadow or not..there cannot be two shadow casters along the same vector...so, instead..we keep local to the light..

now wiht a simple 32x32x32 array, we can very very quickly determine if any lumel is shadowed, even if it's from a single pixel within the same model or differant.
As in that 32x32x32 little array, we encode the distance from light to lumel..now as the look up is a vector, the distance in effect becomes the z value.

Some come runtime, all we have to do to determine if a lumel is shadowed is,

Entity_PointAt LumelPivot,LightX,LightY,LightZ ;Point towards the light.
Pitch=Entity_Pitch LumelPivot
Yaw=Entity_Yaw LumelPivot
Roll=Entity_Yaw lumelPivot,

The vector is made up from the angles got from pointing the lumel at the light. a quick easy way that doesn't degrade over distances.

Now compress to within 32x32x32 (Or higher for a higher res shadow detail.)
we do this by doing this,

CompressedPitch = 32*(Pitch/360.)
CompressedYaw== 32*(Yaw/360)
CompressedRoll 32*(Roll/360)

In our light, create a fake multi-dimension array in objects,

Pdist=Light\Lut[ CompressedRoll]\Lut[CompressedPitch]\Lut[CompressedYaw]

get the current set shade distance.
if Entity_Distance( lightPiv,Lumel) < pDist ;This is a closer lumel, so is the one that casts the shadow along this vector.


now run time, to determine if anything being rendered is in a shadow, we just quickly get the vector in the same manner, and check if the distance is greater than the encoded one in the light..if so, it has to be shadowed.

The downside is moving lights...they'ed need to be recalced on the fly..but i'm sure that could be optimized to be just as quick.


BlitzSupport(Posted 2004) [#17]
Go on, do it... this is very cool!


GW(Posted 2004) [#18]
Actually James, You do it!!
port that original post over to BMax and show us the goods! :-)


BlitzSupport(Posted 2004) [#19]
I would, but it uses Blitz 3D, and we still need the 3D engine.

(I had another excuse written down but the dog ate it.)

This would of course be even cooler if it wasn't dependent on Blitz 3D commands, but beggars (eg. me) can't be choosers!


GW(Posted 2004) [#20]
hmm, i forgot about the 3d commands..
Well here is a nice little 2K raytracer written in C (with comments)and uses no external libs ;)

http://www.mysterystudio.com/2k_raytracer.php


AntonyWells(Posted 2004) [#21]
No worry, won't need it for the next version..
in fact james I'll make a deal with ya then, I'll do a version in b3d without reliance on b3d's engine..and you ask mark if you can post an exe of it running in bmax...Everyone's a winner :)


jfk EO-11110(Posted 2005) [#22]
what happened with this?


big10p(Posted 2005) [#23]
A.D.D.

I prescribe Ritalin!


Picklesworth(Posted 2005) [#24]
Lol!

Let's hope not though...