Build a basic 3D Render Engine

Started by stevmjon, May 03, 2022, 10:25:50 PM

Previous topic - Next topic

kevin


  Steve,

     Looking good dude..     The bouncing costs a bit more (50%)  but only handful seconds longer than one of the previous versions. 

     For the scan render (pass #1) you can do updates once per second rather than per scan line updates..  Which give the benefit of showing the user it's working but with minimal system impact.    This makes the two methods run in about the same time here.   

PlayBASIC Code: [Select]
;-----------------
;--- method #1 ---
;-----------------

If render_option = 1 ; see one line at a time drawn to screen (slower)

start = Timer()

RefreshTime$=""
;--- start at the top left of the screen, and go across each scanline to the end, then move down to the next scanline till reach the bottom of the screen ---
;--- draw one whole scanline at a time so user can see progress (will be slower though) ---
For lp = 0 To screen_h-1 ; go down the screen

RenderToImage 1 ; focus drawing to a screen size FX image (draws faster for lots of repeated smaller draw commands)

;*********
LockBuffer
;*********

seed = Point(0,0) ; ensures address of the surface is seeded for 'fastdot'

For lp2 = 0 To screen_w-1 ; go across the screen

Gosub Ray ; check ray collision with objects

Next lp2

;***********
UnLockBuffer
;***********

if RefreshTime$<>CurrentTime$()
ShowRender()
RefreshTime$=CurrentTime$()
endif

Next lp

ShowRender()
time = Timer() - start

EndIf


Function ShowRender()
RenderToScreen ; focus back to screen
DrawImage 1,0,0,0 ; draw FX image to screen to see line by line progress
Sync
EndFunction



stevmjon

i was thinking about displaying the scanlines every 100 lines, but i like the every line method cause it looks cool.
that is why i added the second render method, but this could be updated to draw to screen every second, to make it look better than watching the progress bar.

glad you like it.
It's easy to start a program, but harder to finish it...

I think that means i am getting old and get side tracked too easy.

stevmjon

hey uncle kev

just a few questions i want to ask, as i have progressed well with the editor i have written for the ray tracer. i am nearly ready to post a demo.

> if i have lots of lines, dots, GouraudQuad, and text to draw, should i draw these to the screen or an FX image?

* currently i am drawing these to the screen using lockbuffer. this works well on my newer computer, but will it work on an older one?

when i 'render' the scene, this is drawn to an FX image. this helps the 'one line at a time' method draw smoothly.
i do know that text/print doesn't like lockbuffer, so i can unlock then draw text last. there is not much text so i am not too concerned.

i am not sure about gouraudquad. is this gpu or cpu?
i am considering drawing everything to an FX image, then draw this to the screen. so main scene to FX image, editor to FX image, and render to FX image...

also with text, what i am doing is drawing gouraudquad then text on top for buttons. i am thinking of just making this into a small image, then just drawing this where the button is.

thanks, stevmjon
It's easy to start a program, but harder to finish it...

I think that means i am getting old and get side tracked too easy.

kevin


Hey Steve,

Quote
just a few questions i want to ask, as i have progressed well with the editor i have written for the ray tracer. i am nearly ready to post a demo.
> if i have lots of lines, dots, GouraudQuad, and text to draw, should i draw these to the screen or an FX image?
   
* currently i am drawing these to the screen using lockbuffer. this works well on my newer computer, but will it work on an older one?

when i 'render' the scene, this is drawn to an FX image. this helps the 'one line at a time' method draw smoothly.
i do know that text/print doesn't like lockbuffer, so i can unlock then draw text last. there is not much text so i am not too concerned.


   For things like the rendering loop,  you could just write to a 1D array across the row.   Then at the end of the row,  copy the row to the buffer / screen (ie. copymemory
 
   For GUI stuff you can draw the FX image to the screen, then draw the GUI elements to screen over the top.  This does work but your bound to run into a system that doesn't like all the locking / unlocking of the screen. 

   I generally don't draw anything to the screen, in favor of drawing it all to an FX version, then rendering that to the screen.   Which requires one lock/unlock. 

   For TEXT, you should convert your fonts to CRF.   These can blend (FontBendMode) to the backdrop and respond to Alpha channel which gives greater control and the rendering code is optimized for rendering text, unlike bitmap / fx fonts modes. 

 
Quote
i am not sure about gouraudquad. is this gpu or cpu?
i am considering drawing everything to an FX image, then draw this to the screen. so main scene to FX image, editor to FX image, and render to FX image...

    Direct draw only supports filling rectangles and image blits (none rotated).   So gouraud rect is all cpu driven, same as it's texture cousins.


Quote
also with text, what i am doing is drawing gouraudquad then text on top for buttons. i am thinking of just making this into a small image, then just drawing this where the button is.

   If you're drawing a bunch of stuff to an area, like a button or something then it's most likely a better idea to draw it all to an AFX surface and just draw the cached image in it's place.   




stevmjon

thanks kev

i will draw everything to an FX image. seems a better method.

out of interest which is better / less problematic:
> if i draw a background FX image to the screen, then draw an object as many lines to the screen over the top of this using lockbuffer.
> draw the background in an FX image, then draw the object as many lines also in this FX image using lockbuffer.

the reason i ask is i am not sure by DEFAULT (not using lockbuffer) if drawing to the screen causes locking/unlocking per object line drawn AND drawing to an FX image by DEFAULT (not using lockbuffer) causes locking/unlocking per object line drawn in the same way.
i know using lockbuffer at start, then draw all objects lines, then use unlockbuffer at end helps resolve this by only a single lock. at the moment i am using about 500 small lines to draw each individual object (spheres).
i want to change this wireframe mode as a choice for the user to also show the objects in 'shaded' mode (color without wireframe). so i am experimenting on different techniques, hence so many questions.

i appreciate the knowledge you share with us, even though we ask similar questions one after the other (at least i do, lol).

  thanks stevmjon
It's easy to start a program, but harder to finish it...

I think that means i am getting old and get side tracked too easy.

kevin

#20
Quote
out of interest which is better / less problematic:
> if i draw a background FX image to the screen, then draw an object as many lines to the screen over the top of this using lockbuffer.
> draw the background in an FX image, then draw the object as many lines also in this FX image using lockbuffer.

 I don't really follow.   But every time you draw .. everything.... the surface is locked and released as part of the command.      The lockbuffer / unlockbuffer commands just allow us to batch these to avoid any potential stalls.   You can think of locking as owning a resource and when we unlock we're giving it back to the system.   For things like video resources (including the screen which is just a pair of of video images) the system only allows us to modify / read the surface when we're the owner of it.    



Quote
the reason i ask is i am not sure by DEFAULT (not using lockbuffer) if drawing to the screen causes locking/unlocking per object line drawn AND drawing to an FX image by DEFAULT (not using lockbuffer) causes locking/unlocking per object line drawn in the same way.
i know using lockbuffer at start, then draw all objects lines, then use unlockbuffer at end helps resolve this by only a single lock. at the moment i am using about 500 small lines to draw each individual object (spheres).
i want to change this wireframe mode as a choice for the user to also show the objects in 'shaded' mode (color without wireframe). so i am experimenting on different techniques, hence so many questions.


  The same rule apply..  whatever the replacement for the lines, the routine would need to manage the lock state of the surface or suffer the penalties when drawing to video (gpu) memory.  

  But if your drawing everything to an FX image and then draw the final to the screen..  It's irrelevant.  


  This is how all my demos / programs work. From Thesius XIII - Forest Blast Tech  to Blitz 2 PlayBASIC convertor


   

stevmjon

ok kev, i think i am getting it.

* a draw command to a video surface (screen) gets locked > draw > unlock
* a draw command to an FX surface just draws

so the advantage of drawing everything to an FX image, is there is no locking with each draw command, and when finished you draw this FX image to the screen as a single large rectangle block (which video surface excels at).
It's easy to start a program, but harder to finish it...

I think that means i am getting old and get side tracked too easy.

kevin


Quote
* a draw command to a video surface (screen) gets locked > draw > unlock
* a draw command to an FX surface just draws

so the advantage of drawing everything to an FX image, is there is no locking with each draw command, and when finished you draw this FX image to the screen as a single large rectangle block (which video surface excels at).


  FX surfaces also need to be locked / unlocked.   So we always need to own the surface we're drawing to.   The difference is that video anything is passed off to the underlying GFX driver to do that task.   This might be done immediately, or in the background asynchronously..  The later is best as it off loads the rendering time to an external device.   In ters of performance this works bets for big stuff.

  BUT.... When we overlap calls..  we introduce wait states.     


  picture this..

   rendertoscreen   
   Cls rgb(0,0,255)

   box 100,100,200,200,true


  if CLS and BOX are both to be drawn by the driver in the background.   The driver has to wait for the CLS to be done and released (unlocked) before the BOX can even start.

  The system manages video memory for us, we have no control over it.. So when a command is called,  it will request to be owner the of surface(s) it's about to use.   If something else is working on them, we have to wait.. 

  if we do

   Cls  then DOT  .. Dot includes a lock/unlock (a request to own and release it's ownership of the surface).   This process is not a fixed time operation as the system is sandwiched int he middle of this conversation.   So the locking/unlocking is slower than drawing the dot.   Hence if we group the locking we can remove potential stalls.       


   








 

stevmjon

thanks very much kev for the video explanation.
i could watch videos like this all day. i love the detail of how things work.

so a lot is going on when doing drawing operations. now i understand more about FX and video surface.
so, the so called 'issue' is when drawing to a video surface the video card driver takes over and we have no control over how it works. meaning different drivers can be efficient, and others slower/less efficient.
now i see that drawing to an FX surface, then when finished, draw this to the screen allows for a more controlled/smoother outcome.

yay i am understanding more, stevmjon
It's easy to start a program, but harder to finish it...

I think that means i am getting old and get side tracked too easy.

stevmjon

i actually applied a trick to the scene to now have 'shaded mode' drawn to the screen live (this is when the objects are drawn solid).
how i did this is when you select shaded mode it calculates the scene objects to be drawn solid (diffuse only), and this is drawn to a separate FX image, then when done this image is now drawn as the background.
this way all the buttons etc work as normal. when the user moves the camera, the objects are temporarily changed to wireframe mode (this mode is fast and keeps up with the framerate), then when the camera stops the objects are re-calculated as solid again.

the only decision i need to make is if i should have the light source of the diffuse 'solid mode' as the actual lighting in the scene, or have all objects evenly shaded.
the difference in look is in actual lighting it shades the objects depending on where the scene lights are, so if the light is on the other side of the objects from the camera point of view, you are on the shadow side so the objects will look dark.
otherwise i can evenly light each object so the shading looks the same on all objects, regardless of camera angle.

anyway, i am having fun with this and i shall keep coding, stevmjon
It's easy to start a program, but harder to finish it...

I think that means i am getting old and get side tracked too easy.

stevmjon

hey kev, hope you're feeling better.

i have now added enough to the editor to release a demo, but...it only has spheres.
you can now edit surfaces, clone objects, move the objects around and also move the main scene camera to take a render from a different angle.
it also has load / save panels for the scene, and color panel for surface editing, all using #include "PBDialogs".

most of the buttons now work (the ones that don't work are related to non-sphere objects eg. rotate won't work on the spheres, so i left it not active, for now).

* so, i am wondering if you want to see a demo now, or do you want to wait while i add other object types, like box, cone, disc (cylinder)?

   stevmjon
It's easy to start a program, but harder to finish it...

I think that means i am getting old and get side tracked too easy.

kevin


Steve,


   
Quoteso, i am wondering if you want to see a demo now

    yeah.. the more the better...


stevmjon

#27
ok kev, just a few adjustments and i will upload a demo.

> could you have a look at 'PBDialogs2' ColorDialog, as i cannot get it to work. so i use the one in 'PBDialogs' ?

> also, i noticed with 'PBDialogs' ColourDialog that if i use a custom color that the red, green, blue values are reversed (see pic).
   i wondered why the colours didn't look right, lol. i can just reverse the red & blue in the meantime.

  thanks stevmjon
It's easy to start a program, but harder to finish it...

I think that means i am getting old and get side tracked too easy.

kevin

#28
 Your asking me ? :)


Looking at the picture it's return the colour in BGR rather than RGB.  So you can just swap the R and B and life goes on


PlayBASIC Code: [Select]
   print Hex$( BGR_to_RGB($4080FF) )
Sync
waitkey


function BGR_to_RGB(ThisColour)
NewRGB = rgb( rgbb(ThisColour) , rgbg(ThisColour) , rgbr(ThisColour) )
EndFunction NewRGB






stevmjon

#29
time for a demo.

this demo as v0.20 has most features added into it, but only has spheres objects that can be duplicated and modified.
the main scene camera can also be moved around to find a better render position.

the inactive buttons, that i intend to add features to in the future, have the buttons in dark text. that way i can see what i intend to add later.

anyway, have fun testing this demo, and if there are any bugs please post, and i am also happy to take requests also.
it has been a longer journey to get this far than i expected, but i have enjoyed it.

  stevmjon
It's easy to start a program, but harder to finish it...

I think that means i am getting old and get side tracked too easy.