Raytracing in Blitz3d - example code and image inside

Started by Matty, January 12, 2025, 14:54:20

Previous topic - Next topic

Matty

Greetings folks,

I tried my hand at writing a simple raytracing solution (non real time) in Blitz3d tonight. You can see the code and results here:



;Example of Raytracing in Blitz3d
;Not for Real time Use
;Model used is the "felguard" model from 3drt which is available for
;free from their site, with some modifications I've added.
;This won't work for animated meshes in Blitz3d because picking
;commands only occur against the base mesh, so you can't get the
;correct normals for the lighting equations with an animated mesh.

;Some global variables
Global camera,mesh,slight1,slight2,slight3
;Base rendered blitz3d image and raytraced image, to save
Global image,renderedimage
;change this to whatever works for you
Graphics3D 512,600,0,2;256,300,0,2;512,600,0,2;256,300,0,2;920,1080,0,2;768,900,0,2;1920,1080,0,2

camera = CreateCamera()
MoveEntity camera,0,3,-10

;change this to whatever mesh you have at hand.
mesh = LoadMesh("felgardarmoured.b3d")

PointEntity camera,mesh
TurnEntity mesh,0,180,0
MoveEntity camera,0,9,-30
CameraZoom camera,3.5

slight1 = CreateSphere()
slight2 = CreateSphere()
slight3 = CreateSphere()
EntityFX slight1,1
EntityFX slight2,1
EntityFX slight3,1
;these are merely to show where the lights will be placed....
;they're not actually lights...just placeholders to visualise for
;me where I'm going to put the lights.....

PositionEntity slight1,-8,14,-8
PositionEntity slight2,8,18,-8
PositionEntity slight3,-6,17,8

campivot = CreatePivot()
EntityParent camera,campivot

TurnEntity campivot,0,20,0

Cls
RenderScene() ; this is where we do our magic...very slow however
Flip

;show the three types of render
DeleteFile "renderedimage_1.bmp"
DeleteFile "renderedimage_2.bmp"
DeleteFile "renderedimage_0.bmp"
Repeat
Cls
If KeyHit(57) Then swap = swap + 1
If swap > 2 Then swap = 0
If swap = 1 Then DrawImage image,0,0 ;ambient light directx
If swap = 0 Then DrawImage renderedimage,0,0 ;raytraced
If swap = 2 Then ;directx with 3 lights
If l1 = 0 Then l1 = CreateLight(2)
If l2 = 0 Then l2 = CreateLight(2)
If l3 = 0 Then l3 = CreateLight(2)
LightRange l1,100
LightRange l2,100
LightRange l3,100
PositionEntity l1,EntityX(slight1),EntityY(slight1),EntityZ(slight1)
PositionEntity l2,EntityX(slight2),EntityY(slight2),EntityZ(slight2)
PositionEntity l3,EntityX(slight3),EntityY(slight3),EntityZ(slight3)
EntityFX mesh,0
RenderWorld
EndIf
Color 255,255,255
If swap = 1 Then Text 0,0,"Non Raytraced"
If swap = 0 Then Text 0,0,"Raytraced"
If swap = 2 Then Text 0,0,"Directx Lights"
If FileType("renderedimage_"+swap+".bmp")=0 Then
SaveBuffer BackBuffer(),"renderedimage_"+swap+".bmp"
EndIf
Flip

Until KeyHit(1)
End

Function RenderScene()
Local lights[100]
EntityPickMode mesh,2 ;first we set our mesh to fully pickable by polygon

cube = CreateCube() ;just to act as a 'floor model'
ScaleMesh cube,7,0.05,7
EntityPickMode cube,2

;make it easier to go through our lights for our lighting calculation in a minute
lights
lights[0] = slight1
lights[1] = slight2
lights[2] = slight3
For i = 0 To 2
EntityAlpha lights[i],0
Next
For j = 3 To 100 ;more lights around our existing lights is sort of how to do 'soft lighting sometimes'
i = j Mod 3
lights[j] = CreatePivot()
PositionEntity lights[j],EntityX(lights[i]),EntityY(lights[i]),EntityZ(lights[i])
TranslateEntity lights[j],Rnd(-2,2),Rnd(-3,3),Rnd(-2,2)
Next
sw = GraphicsWidth()
sh = GraphicsHeight()
image = CreateImage(sw,sh)
renderedimage = CreateImage(sw,sh)
;FreeEntity mesh ;uncomment these 5 lines to do the test with a sphere
;mesh = CreateSphere(32)
;ScaleMesh mesh,8,8,8
;PositionEntity mesh,0,8,0
;EntityPickMode mesh,2
EntityFX mesh,1
RenderWorld ;the purpose of this is to get the mesh colours from the
;basic texture first...I'm doing a render normally first without
;proper lighting...then we're going to add the proper lighting in
;over the top of this image..
CopyRect 0,0,sw,sh,0,0,BackBuffer(),ImageBuffer(image)
CopyRect 0,0,sw,sh,0,0,BackBuffer(),ImageBuffer(renderedimage)
Cls
sw1 = sw - 1
sh1 = sh - 1
bk_red = 10 ;equivalent to ambientlight 10,10,10
bk_green = 10
bk_blue = 10
rgb_bk = bk_red Shl 16 Or bk_green Shl 8 Or bk_blue
;go through each pixel of the scene
bright# = 0.016 ;how bright our light sources are
shininess# = 51.25 ;how shiny our mesh material is
lred# = 255.0 ;colour of our light sources
lgreen# = 235.0
lblue# = 215.0
maxlights = 100
SetBuffer BackBuffer()
DrawImage renderedimage,0,0 ;set the basic fully lit image down first.
LockBuffer ImageBuffer(renderedimage)
LockBuffer
For x = 0 To sw1
For y = 0 To sh1
;do a camerapick to see if there's anything that our eye, the
;camera can see
CameraPick camera,x,y
If PickedEntity() <>0 Then ;we've hit either mesh or cube
;initialise our red/green/blue to 0 and then start
;adding colour to it
red# = 0
green# = 0
blue# = 0
;get our normals of our surface that was picked
nx# = PickedNX()
ny# = PickedNY()
nz# = PickedNZ()
;get the position in space that was picked
xx# = PickedX()
yy# = PickedY()
zz# = PickedZ()
;now go through our lights one by one and first
;of all do the diffuse component.....
For l = 0 To maxlights
If Abs(MilliSecs()-rtime)>500 Then ;let the CPU breathe....
rtime = MilliSecs()
Delay 5
EndIf
lx# = EntityX(lights[l])
ly# = EntityY(lights[l])
lz# = EntityZ(lights[l])
;the line pick is to see if the light can see this surface
;in case anything is blocking its view.
;diffuse lighting formula (let's look it up, I don't know
;it off the top of my head)
;DIFFUSE LIGHTING:
;(Normal of Surface . Light Vector ) (both normalised) * Light Color and Intensity
;lightvector:
;do the light vector from the object to the light
lvx# = xx - lx
lvy# = yy - ly
lvz# = zz - lz
ll# = Sqr(lvx*lvx + lvy*lvy + lvz*lvz)
If ll>0 Then
lvx = -lvx / ll
lvy = -lvy / ll
lvz = -lvz / ll
;dot product
;if negative light is behind the surface..so no illumination
dot# = lvx * nx + lvy * ny + lvz * nz
If dot > 0 Then
LinePick lx,ly,lz,(xx-lx),(yy-ly),(zz-lz)
If (PickedEntity()<>0 And Abs(PickedX()-xx)<0.01 And Abs(PickedY()-yy)<0.01 And Abs(PickedZ()-zz)<0.01) Or PickedEntity() = 0  Then
Else
Goto skiplight
EndIf
;there's some illumination
red = red + (Float(lred) * bright * dot)
green = green + (Float(lgreen) * bright * dot)
blue = blue + (Float(lblue) * bright * dot)
If red > 255 Then red = 255
If green > 255 Then green = 255
If blue > 255 Then blue = 255
If red < 0 Then red = 0
If green < 0 Then green = 0
If blue < 0 Then blue = 0
Else
Goto skiplight
EndIf
EndIf
;next we have to do our specular component.....
;let us look up the specular lighting model online...
cx# = EntityX(camera) - xx
cy# = EntityY(camera) - yy
cz# = EntityZ(camera) - zz
cc# = Sqr(cx*cx+cy*cy+cz*cz)
If cc>0 ;which it will be
cnx# = cx/cc
cny# = cy/cc
cnz# = cz/cc
;add the normal vector to the incoming light vector
lvx# = xx - lx
lvy# = yy - ly
lvz# = zz - lz
ll# = Sqr(lvx*lvx + lvy*lvy + lvz*lvz)
If ll>0 Then ;which it should be
sdot# = lvx * nx + lvy * ny + lvz * nz ;not normalised
snx# = nx * sdot ;this is N
sny# = ny * sdot
snz# = nz * sdot
px# = snx + lvx
py# = sny + lvy
pz# = snz + lvz
rx# = snx + px
ry# = sny + py
rz# = snz + pz
rr# = Sqr(rx*rx+ry*ry+rz*rz)
If rr > 0 Then
rx = rx / rr
ry = ry / rr
rz = rz / rr
;reflection vector, normalised
;camera vector, normalised
;find dot product to get angle
dot# = cnx * rx + cny * ry + cnz * rz
If dot > 0 And shininess >= 0 Then ;should be between 0 and 1 for dot
dot = dot ^ shininess
red = red + dot * Float(lred)
green = green + dot * Float(lgreen)
blue = blue + dot * Float(lblue)
EndIf
EndIf
EndIf
EndIf
.skiplight
Next
red = Float(bk_red + red) / 255.0
green = Float(bk_green + green) / 255.0
blue = Float(bk_blue + blue) / 255.0
If red > 1 Then red = 1
If green > 1 Then green = 1
If blue > 1 Then blue = 1
If red < 0 Then red = 0
If green < 0 Then green = 0
If blue < 0 Then blue = 0
rgb = ReadPixelFast( x,y,BackBuffer()) ;fully lit object
rgb_r = (rgb Shr 16) And 255
rgb_g = (rgb Shr 8) And 255
rgb_b = (rgb And 255)
;multiply this by our diffuse and specular values
red = red * Float(rgb_r)
green = green * Float(rgb_g)
blue = blue * Float(rgb_b)
rgb_r = red
rgb_g = green
rgb_b = blue
;now write it back out
WritePixelFast x,y,rgb_r Shl 16 Or rgb_g Shl 8 Or rgb_b,ImageBuffer(renderedimage)
Else
;draw the background colour
WritePixelFast x,y,rgb_bk,ImageBuffer(renderedimage)
EndIf
If KeyHit(1) Then End
Next
If x Mod 5 = 0 Then
UnlockBuffer
UnlockBuffer ImageBuffer(renderedimage)
DrawImage renderedimage,0,0
Color 0,0,0
Rect 0,0,100,20,1
Color 255,255,255
Text 0,0,"x:"+x
Flip
LockBuffer
LockBuffer ImageBuffer(renderedimage)
EndIf
Next
UnlockBuffer
UnlockBuffer ImageBuffer(renderedimage)
DrawImage renderedimage,0,0
End Function
;;;;;

Derron

It looks really cool.

What I see is - the raytracing algorithm ephasizes "polygon boundaries" ?! (visible because of the low-poly nature of the model I guess)



The others do not have these "cut gem"-borders that visible. Is this the algorithm (would think 100 lights shading the thing should "smooth that out")  or is it something else I am not aware of ?


a pity this is not realtime :P

bye
Ron

RemiD

Quote;This won't work for animated meshes in Blitz3d because picking
;commands only occur against the base mesh, so you can't get the
;correct normals for the lighting equations with an animated mesh.
actually it is possible to get the skinned vertices position when a rigged mesh is animating, and then reshape a 'rigid' mesh by modifying its vertices positions, and then use this mesh with linepicks, in Blitz3d, thanks to an update by Bobysait. (i can post an example, if you are interested.)



also, there is a bb code by Filax to do raytracing lighting shading on a 3d shape, somewhere...

Matty

Thanks Derron.

RemiD - if Bobysait's change means I have to use a different blitz3d.exe program then no thanks, but if it is merely a userlib, or some .bb code then I'd be happy to see it. Also, re Filax code - yeah there have been others who have done this over the years, but I'd never tried to do it myself, so wanted to have a go.

The main things that need to be worked on in my view for it to be possible to really be more useful will be the animated meshes feature AND I will need to write something to handle alpha-ed textures so that I can do it with foliage too. That's the next thing.

My next thing will be to get it to handle alpha and masking so that foliage can be rendered this way.

Derron  - yes, a pity it can't be done in real time I know.

RemiD

Quote from: Matty on January 12, 2025, 17:48:10if Bobysait's change means I have to use a different blitz3d.exe program then no thanks, but if it is merely a userlib, or some .bb code then I'd be happy to see it.
yes you have to use another blitz3d executable that Bobysait compiled ( all the codes and games that i have posted here are made with this executable, so i can say that it works well )

you can install another version of Blitz3d in another folder if you want to experiment with this.
here are the necessary files to install blitz3d 1.108 + the addons 'B003' :
http://rd-stuff.fr/blitz3d/blitz3d-1.108-installer+addons-B003+docpaks+dplayx+dx7.7z

and the bb code example to reshape a rigid mesh like a skinned rigged animated mesh :
http://rd-stuff.fr/blitz3d/reshape-rigidmesh-like-skinnedmesh-BB-20161008-1304.7z

Coder Apprentice

Hey Matty! This is cool! What would be great if you could render the same character without textures maybe putting it on a much bigger plane, both the plane and character with grey color. And the same way like you did before, one pic with regular realtime Blitz lights and the other with your raytrace lights. So the shadow and shading of your technique is more visible.

Matty

Hello Coder Apprentice, I didn't increase the size of the plane beneath the feet of the creature but I rendered it grey as you said with one image with raytraced and one with Blitz lights. I used the same number of lights in each case (3).

To be honest - you could do the top row of images in the image shown here with simple shaders really easy in realtime....it's just a diffuse and specular calculation per pixel - two lines in a real time shader function.



And I've started playing around with adding glow maps and bump mapping...

All of this would be possible easily with a shader engine though...you'd do it exactly the same in theory, same calculations mostly....



Coder Apprentice

Of course it's "easy" with a shader but the point here is that you were able to "hack" this together in a limited environment. Has a demo scene feel to the accomplishment. So awesome!

Matty

Greetings folks,

I've done a bit more work on this raytracing program for Blitz3d.
The example in the video below shows a spaceship that has diffuse, specular, illumination and bump mapping applied.
On an old 2014 Windows desktop each frame takes about 15 seconds to render, so it's not realtime.
I will explain in pseudocode the method used to generate the images:

Loop through angles (0 to 360)
First create the base image with its colours by rendering a fully lit (entityfx 1) version of the mesh
using blitz3d's normal rendering method - store this in 'image'
Change the texture to the bump map texture and do the same - store this in 'bump image'
Change the texture go the glow map texture and do the same - store this in 'glow image'
Do the same for specular image 'spec image'.
Set up the lights in their positions. (not blitz lights, but simply lights for calculation purposes below)
Set up the mesh as pickable.
Loop through each pixel on the screen
For each pixel do a camera pick.
If the pick hits nothing, draw the background color.
If the pick hits something then:
get the normal of the pixel picked (nx,ny,nz)
get the position of the pixel picked (x,y,z)
loop through each light in the scene
do a linepick between the light and the pixel coordinates (x,y,z)
if there's something blocking then don't draw a pixel.
if the view is not obstructed then:
use the bump map to calculate a slightly altered normal:
add the normalised vector to the camera multipled by the bump intensity (-1 to +1) to the normal vector and perturb the normal slightly. (renormalising the new normal)
calculate the diffuse lighting component
(diffuse is normal of pixel dotted with normalised vector from light source and then multiplied by the brightness of the light and the colours of the light)
calculate specular lighting component
(specular is calculated by dotting the reflection vector of the light around the pixel normal with the camera vector (all normalised) and then raised to a power depending on the material and multiplied by the brightness of the specular image at that x,y position)
calculate glow by adding the glow pixels multiplied by a factor at the x,y position.
add resulting r,g,b values calculated to the base image rendered from earlier.

You end up with an image that has self shadowing built in, bump, specular, diffuse and illumination done.

end loops....

The slowest parts are the picking commands.
The more polygons in the scene the slower all the picking commands get when you have to do it for every single pixel in the scene.

I cheat a little by not doing a camera pick where I know the 'base image' rendered is the background colour.

Video below:


Matty


RemiD

Quotedo a linepick between the light and the pixel coordinates (x,y,z)
if there's something blocking then don't draw a pixel.
keep in mind that the function linepick() can have a radius, so make sure to define it appropriately...


this is looking nice,

but i don't see any glow effect in your rendering... or it is very slight.


also there has been a few examples of 'dot lighting shading' done in blitz3d, which use normal maps, why not use that instead ? (only with 1 light source if i remember correctly)


pixels lighting shading looks very nice with normal maps, indeed, i have experimented a little using Xors3d and for video games who use a spotlight (lamp torch or wood torch), it looks much convincing and nicer than a simple directx light (vertices lighting shading)

Matty

Thanks RemiD - the glow wasn't quite being done properly at this stage, it's there but very faint.

I took one of my space games that I released on itch.io a while ago and built the raytracer into it to see if I could get it to render out some frames that were raytraced.

It works but for performance reasons on this particular test I had to downsample the lighting resolution significantly - so the scene renders at 1280x720 but the lighting is only rendering at 1/3 of those dimensions in x and y. When I know I'm not going to need to use the PC for a while I'll try it again at 1920x1080 without downsampling the lighting. I'll also fix the twinkling stars.

For performance reasons I had to really cut the framerate and the sample size for the lighting. Because it's a non real time render and I only have 1 PC that's 11 years old I can't afford to have it sitting around rendering all day if I need to use it for something else.

There are light sources pointing in 8 directions in space, and also coloured lights emitted from all the laser blasts and beams and explosions.

Here's a video:


MrmediamanX

Looking mighty nice, I recall there was a fun little method of using the voodooGC DX wrapper and a bunch of Reshade plugin's but it was somewhat tedious to setup ... looked pretty fancy though.
It's a thing that doe's when it don't..

RemiD

Quote from: Matty on January 16, 2025, 23:27:47There are light sources pointing in 8 directions in space, and also coloured lights emitted from all the laser blasts and beams and explosions.
frankly, for such a game with fast moving entities seen at a far distance, i don't think it is noticeable to use per pixels lighting shading....
vertex lighting shading (on subdivided meshes so that there are enough vertices on each surface) would look the same...

what could look nice and be noticeable in this case would be to add a lot of glow around the light sources...

and maybe add some blured reflections (for the metal surfaces) with precalculated cubemap textures ( of the environment around like planets and asteroids and gas clouds)...

Matty

Thanks RemiD, I'm not actually doing this for the game that's shown in the videos, I'm merely testing out the techniques and using an existing game of my own to see how difficult/easy it ends up being to do.

This is a higher quality render of the same video that was above:



And here is how it looks in full size full quality (still image)
(Render time was about 220 seconds for the image below in blitz3d on a 2014 Windows 7 PC)



You can play around with the raytracing code and software here:
https://matty77.itch.io/solar-conflict