Shadows

Blitz3D Forums/Blitz3D Programming/Shadows

The forum is locked. These are the original blitzbasic.com forums, kept so they can still be read. No new topics, replies or edits can be made here. To join in, head over to the Blitz3D+ forums.

Okay norc... I fixed your code. It took me three hours, but I fixed it.


There were a number of problems with it, some wiht your code, some with mine. I won't list them all but here are a few.

First, the object you were casting shadows did not have it's vertices in the right places. The coordinates of the vertices in the object must match their locations in the world. My code assumes you're casting shadows onto a static level so this isn't a problem. You can of course change this by modifying my code to sutract the vertex positions by the object's offset in the real world when doing the calculations.

Second, the blending mode for the texture was not set up right. The texture blend mode has to be additive. That's my fault for not informing you of that. I'd forgotten it myself. It has to be add because the colors are added to the vertex colors in order to create the effect of the shadow fading out when the surface's angle is too steep relative to the imaginary overhad light source. To do this the vertex colors start out black and go to white as the surface angle increases, and the texture of the shadow is ADDED to this, so that the areas not in shadow become white anyhow, and the areas in shadow stay whatever color the vertex colors say they should be.

Third you were scaling the entity that you wanted to cast shadows onto rather than the MESH. This whole system relies on the mesh data being set up a certain way. You can of course change this behavior by adding more math to the alogirthm, but I did not do that myself because to do so would slow the alogorithm down some.

Fourth, you were using a SOLID BLACK shadow texture. For the shadow code to work the areas outside the shadow must be white. And for them to be white yhou have to clamp the texture... but for the clmaping to make the areas outside the shadow white, ALL the edge pixels of the shadow must be white, otherwise the UV clmaping will copy the blackness everywhere outside the shadow and you don't want that.

You'll notice that the shadow is square, but not aligned properly with the cube. As I said, you have to generate an appropriate shadow each frame for the object in it's current position for the shadow to be shaped properly. Rendering a camera view from above the object to be shadowed while it is black and against a white background and then using that as the texture is one way of doing this.

Anyhow the code now works. Hope this helps you out!



Graphics3D 640,480,16,2
SetBuffer BackBuffer()

Type Shadow_Caster
	Field Caster
	Field Receiver
	Field Texture
	Field Radius#
End Type


Type Shadow
	Field Mesh
End Type


	camera=CreateCamera()
	PositionEntity camera,50,100,-200
	
	light=CreateLight(2)
	PositionEntity light,0,200,0
	AmbientLight 100,100,100

	ground=CreateSphere()
	ScaleMesh ground,100,25,100
	; Position the mesh so that the lower bottom left hand corner of it lies at 0,0,0
	PositionMesh ground,50,12.5,50
	; Update the normals because we scaled the mesh (newest versions of Blitz should not require this)
	UpdateNormals ground
	;PositionEntity ground,50,25,50
	

	cube = CreateCube()
	ScaleEntity cube,20,20,20
	RotateEntity cube,30,50,70
	PositionEntity cube,50,150,50

	PointEntity camera,cube
		
	; Create texture of black square in white background
	shd=CreateTexture(256,256,48)  ; Flags = ClampU, ClampV
	TextureBlend shd, 3
	SetBuffer TextureBuffer(shd)
	ClsColor 255, 255, 255
	Cls
	Color 0,0,0
	; Must leave border of white around shadow!
	Rect 1,1,254,254,1
	SetBuffer BackBuffer()

	Cast_Shadow(cube, ground, shd, 20.0) 

	While KeyDown(1)=0
	
		Update_Shadows() 
		UpdateWorld()
		RenderWorld()
		Flip

	Wend


End


; -------------------------------------------------------------------------------------------------------------------
; This function sets up a new shadow caster/receiver pair.
;
; Caster   = Mesh to cast shadows.
; Receiver = Mesh to receive shadows.
; Texture  = Texture to use for shadow.  (Must have UV clamping enabled for proper operation!)
; Radius#  = Radius of the shadow.
; -------------------------------------------------------------------------------------------------------------------
Function Cast_Shadow(Caster, Receiver, Texture, Radius#)

	ThisCaster.Shadow_Caster = New Shadow_Caster	
	
	ThisCaster\Caster   = Caster
	ThisCaster\Receiver = Receiver
	ThisCaster\Texture  = Texture
	ThisCaster\Radius#  = Radius#

End Function


; -------------------------------------------------------------------------------------------------------------------
; This function deletes all old shadows and creates a shadow mesh for each shadow casting object.
;
; Note:
; Shadows only need to be repositioned and recreated each time the scene is rendered, 
; not every time the physics are updated.
; -------------------------------------------------------------------------------------------------------------------
Function Update_Shadows()

	Delete_Shadows()

	For ThisCaster.Shadow_Caster = Each Shadow_Caster
		 
		Shadow_Center_X# = EntityX#(ThisCaster\Caster, True)
		Shadow_Center_Y# = EntityY#(ThisCaster\Caster, True)
		Shadow_Center_Z# = EntityZ#(ThisCaster\Caster, True)
		Shadow_Radius#   = ThisCaster\Radius#		
						
		ThisShadow.Shadow = New Shadow
		ThisShadow\Mesh = Create_Shadow(ThisCaster\Receiver, Shadow_Center_X#-Shadow_Radius#, Shadow_Center_Z#+Shadow_Radius#, Shadow_Center_X#+Shadow_Radius#, Shadow_Center_Z#-Shadow_Radius#, Shadow_Center_Y#)
	
		; full bright, use vertex colors, no fog.
		EntityFX ThisShadow\Mesh, 1+2+8

		; multiply blend mesh.
		EntityBlend ThisShadow\Mesh, 2 

		EntityTexture ThisShadow\Mesh, ThisCaster\Texture
				
	Next	
		
End Function


; -------------------------------------------------------------------------------------------------------------------
; This function deletes all active shadows.
; -------------------------------------------------------------------------------------------------------------------
Function Delete_Shadows()

	For ThisShadow.Shadow = Each Shadow
		FreeEntity ThisShadow\Mesh
		Delete ThisShadow.Shadow
	Next	

End Function


; -------------------------------------------------------------------------------------------------------------------
; This function deletes all shadow casters/recievers which reference the specified entity.
;
; When you delete an entity which is casting or receiving a shadow, you must call this function so that the game
; does not crash from trying to reference an entity which does not exist.
; -------------------------------------------------------------------------------------------------------------------
Function Delete_Shadow_Caster(Entity)

	For ThisCaster.Shadow_Caster = Each Shadow_Caster

		If (ThisCaster\Caster = Entity) Or (ThisCaster\Receiver = Entity) 
			Delete ThisCaster
		EndIf	

	Next	

End Function


; -------------------------------------------------------------------------------------------------------------------
; This function creates a shadow mesh and returns it's handle.
;
; Reciever = The entity to recieve the shadow.
; -------------------------------------------------------------------------------------------------------------------
Function Create_Shadow(Receiver, Shadow_X1#, Shadow_Z1#, Shadow_X2#, Shadow_Z2#, Shadow_Y#)

	; Get the cordinates of the lower left hand corner of the shadow.
	Shadow_Corner_X# = Shadow_X1#
	Shadow_Corner_Z# = Shadow_Z2# 

	Shadow_Scale# = Sqr((Shadow_X1#-Shadow_X2#)*(Shadow_X1#-Shadow_X2#))

	DebugLog "S: " + Str$(Shadow_Scale#)
	ShadowTris = 0

	; Create the mesh for the shadow, and give it a surface to add polygons to.
	MESH_Shadow = CreateMesh()
	SURFACE_Shadow = CreateSurface(MESH_Shadow)

	; Loop through all triangles in all surfaces of the reciever.
	Surfaces = CountSurfaces(Receiver)
	For LOOP_Surface = 1 To Surfaces

		Surface_Handle = GetSurface(Receiver, LOOP_Surface)

		Tris = CountTriangles(Surface_Handle)
		For LOOP_Tris = 0 To Tris-1

			Vertex_0 = TriangleVertex(Surface_Handle, LOOP_Tris, 0)
			Vertex_1 = TriangleVertex(Surface_Handle, LOOP_Tris, 1)
			Vertex_2 = TriangleVertex(Surface_Handle, LOOP_Tris, 2)
									
			; Check to see if the triangle is inside the shadow's bounding rectangle.
			;
			; This test works by seeing if all of a triangle's points are on a specific side of each side of the
			; rectangle.  The test is not 100% accurate... a very few triangles will pass the test but actually be
			; outside the region.  But we are concerned only with making sure we find all the triangles which ARE
			; in the region and cull the vast majority outside the region, so a little sloppiness in the test is
			; okay if that means it's really fast.
			Shadow = True

			Z0# = VertexZ#(Surface_Handle, Vertex_0)
			Z1# = VertexZ#(Surface_Handle, Vertex_1)
			Z2# = VertexZ#(Surface_Handle, Vertex_2)

			; Is polygon to north of shadow's bounding rectangle?
			If (Z0# > Shadow_Z1#) And (Z1# > Shadow_Z1#) And (Z2# > Shadow_Z1#) 
				Shadow = False 
			Else	

				; Is polygon to south of shadow's bounding rectangle?
				If (Z0# < Shadow_Z2#) And (Z1# < Shadow_Z2#) And (Z2# < Shadow_Z2#) 
					Shadow = False	
				Else
					
					X0# = VertexX#(Surface_Handle, Vertex_0)
					X1# = VertexX#(Surface_Handle, Vertex_1)
					X2# = VertexX#(Surface_Handle, Vertex_2)

					; Is polygon to west of shadow's bounding rectangle?
					If (X0# < Shadow_X1#) And (X1# < Shadow_X1#) And (X2# < Shadow_X1#) 
						Shadow = False 
					Else

						; Is polygon to east of shadow's bounding rectangle?
						If (X0# > Shadow_X2#) And (X1# > Shadow_X2#) And (X2# > Shadow_X2#) 
							Shadow = False 
						Else

							Y0# = VertexY#(Surface_Handle, Vertex_0)
							Y1# = VertexY#(Surface_Handle, Vertex_1)
							Y2# = VertexY#(Surface_Handle, Vertex_2)

							; Is shadow below the polygon?
							If (Shadow_Y# < Y0#) And (Shadow_Y# < Y1#) And (Shadow_Y# < Y2#)
								Shadow = False
							Else
								
								NY0# = VertexNY#(Surface_Handle, Vertex_0) 
								NY1# = VertexNY#(Surface_Handle, Vertex_1) 
								NY2# = VertexNY#(Surface_Handle, Vertex_2)

								FaceNY# = NY0# + NY1# + NY2#								

								; Is polygon facing downwards?
								If (FaceNY# < 0) Then
									Shadow = False
								Else
								
									If (FaceNY# = 0)
										Shadow = False
									EndIf	
									
								EndIf
							EndIf
						EndIf
					EndIf
				EndIf
			EndIf	
			
			; If the triangle is inside the bounding box then add it to the mesh.
			; Note that shadow texture will be flipped top to bottom because of how UV coordinates are assigned to the vertices from the world coordinates.
			If Shadow = True

				Shadow_Vertex_0 = AddVertex(SURFACE_Shadow, X0#, Y0#, Z0#, (X0#-Shadow_Corner_X#)/Shadow_Scale#, (Z0#-Shadow_Corner_Z#)/Shadow_Scale#)
				Shadow_Vertex_1 = AddVertex(SURFACE_Shadow, X1#, Y1#, Z1#, (X1#-Shadow_Corner_X#)/Shadow_Scale#, (Z1#-Shadow_Corner_Z#)/Shadow_Scale#)
				Shadow_Vertex_2 = AddVertex(SURFACE_Shadow, X2#, Y2#, Z2#, (X2#-Shadow_Corner_X#)/Shadow_Scale#, (Z2#-Shadow_Corner_Z#)/Shadow_Scale#)

				; Adjust shadow brightness at each vertex according to vertex normal.				
					VC = (1.0-NY0#)^3*255.0
					If (VC > 0) Then VertexColor SURFACE_Shadow, Shadow_Vertex_0, VC, VC, VC
	
					VC = (1.0-NY1#)^3*255.0
					If (VC > 0) Then VertexColor SURFACE_Shadow, Shadow_Vertex_1, VC, VC, VC
				
					VC = (1.0-NY2#)^3*255.0
					If (VC > 0) Then VertexColor SURFACE_Shadow, Shadow_Vertex_2, VC, VC, VC

				; Add the triangle to the shadow mesh.
					AddTriangle(SURFACE_Shadow, Shadow_Vertex_0, Shadow_Vertex_1, Shadow_Vertex_2)

				ShadowTris = ShadowTris + 1

			EndIf
			
		Next			
	
	Next

	Return MESH_Shadow

End Function