Layered Rendering (easy)

Blitz3D Forums/Blitz3D Tutorials/Layered Rendering (easy)

This shows an alternative to using the EntityOrder function for layering objects. The EntityOrder function has the disadvantage of disabling the Z buffer, so it can only be used for very simple objects, such as sky cubes or flat HUD elements. But it is unsuitable for complex shapes, like weapon models for First Person Shooters.

Two examples where this will be useful (there is far more, see hints and tips below):

In most FPS games, the player's weapon is always drawn over the world geometry, so that it does not clip into objects when the player gets too close to a wall or something. This effect cannot be achieved by using EntityOrder, as this will totally mess up the appearance of the weapon model.

Another example are cockpits in flight sims, space shooters etc. Let's say you have ha game where you can fly around a landscape, and you have a cloud layer. For simplicity, the clouds are simply an infinite plane with a cloudy texture on it. If you now fly through the cloud layer, you can see the clouds clip through the cockpit, which looks very ugly. You want the cockpit to always appear in front of the outside world. Using EntityOrder will mess up the appearance of the cockpit, though, as the Z buffer is disabled.

The solution to these problems is what I call "layered rendering". The key to this method is the function "CameraClsMode" (you can find it in the online help, in the "Camera" category). Using this function, you can tell Blitz that it should not clear the screen before rendering. Thus, we can render the world normally, and then render other stuff atop of it, so it will always stay in front of the world.

This method is easy to integrate into existing projects, and the following text will focus on the changes you have to make as compared to the traditional rendering method.

In the code below, you will find the following line:
CameraClsMode hCamera, False, True

This tells the camera to not clear the screen before rendering, but to clear the Z buffer normally. Put this somewhere before the main loop.

You now need to organize your world into layers. The easiest way to do this is to create a Pivot for each layer, and then make your entities childs of these pivots. That way, you can easily hide and show entire layers, to draw them one by one. You should hide all layer pivots before the main loop. You can use as many layers as you like or need.

The main loop needs only slight restructuring. Normally you just call UpdateWorld and RenderWorld and then Flip. This is the part which has to be changed, as follows:

1. Call UpdateWorld.
2. Clear the screen manually by calling Cls. You can skip this if you have the screen covered with geometry at all times, which is the case in most games.
3. For each layer:
3.1. Show the layer pivot, using ShowEntity.
3.2. Call RenderWorld.
3.3. Hide the layer pivot, using HideEntity.
4. Call Flip.

And that's it! Very easy, isn't it?

You can do far more with this technique. Here are some additional hints and tips:
- You should adapt the near and far clipping planes for each layer, as to improve the accuracy of the Z buffer (use the CameraRange function). Thanks Beaker, for pointing this out.
- You can draw layers above 2D stuff. Simply put the 2D drawing between the layers in the main loop.
- This method is compatible with alpha blending. So you can have transparency in your near layer, showing the farther layer correctly filtering through. Even 2D graphics will be filtered by alpha blending. This leads to cool possibilities: You can create fades for 2D graphics, by rendering the 3D fader plane above the 2D stuff. Cool for 2D HUDs. You could also use a fancy 3D mouse pointer above a 2D GUI system. Generally, combining 2D and 3D graphics is far more flexible if you are using this layered approach.
- AntiAliasing also works with layers.

And now for the example program. It is very simple and straight-forward. It shows three rotating objects at different distances from the camera. But they are rendered in reverse order, using layered rendering. Note that each of these objects relies on the Z buffer to render correctly.
Const Speed# = 0.1 ; Defines how fast the objects rotate.

AppTitle "Layered Rendering Test"
Graphics3D 800, 600, 32, 2

; First, we add some lights.
AmbientLight 32, 32, 32
hLight1 = CreateLight()
LightColor hLight1, 200, 200, 128
RotateEntity hLight1, 30, 15, 0
hLight2 = CreateLight()
LightColor hLight2, 128, 128, 200
RotateEntity hLight2, -30, 60, 0

; Now for the camera. Note the call to CameraClsMode.
hCamera = CreateCamera()
CameraClsMode hCamera, False, True

; In this example, we use 3 layers. We create a pivot for each layer so we can easily
; enable or disable them using ShowEntity and HideEntity.
hLayerFar = CreatePivot()
hLayerNear = CreatePivot()
hLayerNearest = CreatePivot()

; Now we create simple objects that need Z buffering to render correctly. We create
; one object for each layer to show the effect. You will have to make each entity a
; child of the layer it belongs to.
hObjectFar = CreateCube(hLayerFar)
ScaleEntity CreateCube(hObjectFar), 2, 0.25, 0.25
ScaleEntity CreateCube(hObjectFar), 0.25, 2, 0.25
ScaleEntity CreateCube(hObjectFar), 0.25, 0.25, 2

hObjectNear = CopyEntity(hObjectFar, hLayerNear)
hObjectNearest = CopyEntity(hObjectFar, hLayerNearest)

; Now we position the objects at different distances from the camera. To show the layering
; effect, we position them in the inverse order, that is, the object in the nearest layer
; is moved farthest away.
PositionEntity hObjectFar, 0, 0, 5
PositionEntity hObjectNear, 0, 0, 10
PositionEntity hObjectNearest, 0, 0, 15

; Before the main loop, we hide each layer, to later re-enable them one by one.
HideEntity hLayerFar
HideEntity hLayerNear
HideEntity hLayerNearest

; Here is the main loop.
While Not KeyHit(1)
	; To get something moving on screen, we just rotate the cubes at different angles.
	TurnEntity hObjectFar, Speed * 2, Speed * -3, Speed * 2
	TurnEntity hObjectNear, Speed * -1, Speed * 2, Speed * 3
	TurnEntity hObjectNearest, Speed * 3, Speed * -1, Speed * -1
	
	; Call UpdateWorld only once per frame, before rendering anything. It isn't necessary
	; in this example, as we don't need any collision checking, but it is shown here for
	; clarity.
	UpdateWorld
	
	; Before rendering, we have to manually clear the screen, as this is no longer done
	; by the camera.
	; NOTE: You do not need this if you always have the screen covered with 3D geometry.
	; In fact, this is the case in most games, but in this example, we don't have any
	; geometry in the background, so we need it. Try commenting it out to see a funny
	; smearing effect :-)
	Cls
	
	; Now we render our layers, back to front, so that each layer draws over the previous
	; ones.
	
	; Render far layer.
	ShowEntity hLayerFar
	RenderWorld
	HideEntity hLayerFar
	
	; Render near layer.
	ShowEntity hLayerNear
	RenderWorld
	HideEntity hLayerNear
	
	; Render nearest layer.
	ShowEntity hLayerNearest
	RenderWorld
	HideEntity hLayerNearest
		
	; Draw info text.
	Text 0, 0,  "These three objects are actually the same size but at different distances. They are just drawn in"
	Text 0, 15, "inverse order by using layered rendering. Note that the objects themselves rely on Z buffering to"
	Text 0, 30, "render correctly."
	Text 0, 50, "Hit Escape to quit."
	
	; Render finished, swapping buffers.
	Flip True
Wend
End


Couple if things worth discussing.

If you render in this order:
HUD
Gun/Cockpit
Collidable 'near' mesh world/level
Eyecandy 'distant' mesh (mountains/tall buildings etc)
Sky box/sphere/plane

..and use CameraRange to stop any overlapping/penetration (and z-buffer glitching), you should gain some speed due to z-fail.

Rendering the other direction:
Sky box/sphere/plane
Eyecandy 'distant' mesh (mountains/tall buildings etc)
Collidable 'near' mesh world/level
Gun/Cockpit
HUD

.. is easier to implement but will create much more overdraw.

Any comments?

------------------------------

It was considered very bad practise before DX9/10(?) to combine 2D and 3D every frame, because it caused a lot of slowdown/stalling on [most?] gfx cards. Now that 2D is emulated using 3D in latest DX anyway, does this mean that we can go ahead and mix 2D/3D again?

Cool, a comment from the creator of Fontext-ness! I still use Fontext, it's great :-) Thanks for making it free. I bought it years ago, but I really appreciate this generous offer.

But now, on to business:

I think that speed gain due to Z-fail is very minimal on any half-decent hardware, except if you are using pixel shaders. As B3D can't do these anyway, you practically don't win anything.

In addition, this method requires a distinct difference in distance between layers. You can't prevent a long rifle clipping into a very near wall, for example. So the only thing that you can gain is improved Z buffer precision.

Or am I wrong here? Maybe I just misunderstood your point. In that case, please, enlighten me!

But I agree that it is advisable to adjust the clipping planes for each layer, to improve Z buffer accuracy, and added this tip to my original post.