Celshading and Outlines

BlitzMax Forums/MiniB3D Module/Celshading and Outlines

JetFireDX(Posted 2008) [#1]
I've been putting this together in between making tools to edit animated meshes in MiniB3D over the last week. (Moving, rotating, adding, and removing bones is done along with adding and removing key frames. Still need vertex assignments and weights and of course an exporter...)

Several places it seems I have seen requests for a celshading and outlining technique on here so I thought I would post it. Its been built ontop of code from the archives for Blitz3D, the Animation demo included with MiniB3D and some descriptions of how to achieve the effect using cubemapping. Also I updated the outliner to do animation.

Example on the good ol' Zombie.



Click here to get the shading texture I used here

Hope somebody finds it useful.

Import sidesign.minib3d

'A cel shading and outline example cobbled together by JetFireDX.
'The outlining code was found in the code archives for Blitz3D by
'SSwift here: http://www.blitzmax.com/Community/posts.php?topic=79166

'I ported it and extended it to support animated meshes in MiniB3D

'The cubemapping comes from the included example and a description of
'how to achive the effect in one of the forums.

'Enjoy and use as you like!


Global width=800,height=600,depth=32,mode=0,rate=60

Graphics3D width,height,depth,mode,rate
AmbientLight(255,255,255)

Local cam=CreateCamera()
CameraClsColor(cam,32,32,127)
PositionEntity cam,0,10,-15

Local light=CreateCube() 'Yes a cube and not a real light. Just an object to point at and pretend its a light.
PositionEntity light,40,20,-20
HideEntity(light)

' create texture with color + cubic environment map
Local tex:TTexture=celshade(light,"celshade2.png")
SetCubeMode tex,2 'Personally I think mode 2 looks better.

Local ent=LoadAnimMesh("media/zombie.b3d")
EntityTexture (ent,tex,0,1)

Local outLine:TEntity = LoadAnimMesh("media/zombie.b3d")

'This creates the outlining effect on the Zombie
celOutline(outLine,0.07)

EntityParent(outLine,ent)

Local anim_time#=0

' used by camera code
Local move#=0.5

While Not KeyDown(KEY_ESCAPE)		
	
	If KeyDown(KEY_W)=True Then MoveEntity cam,0,0,move# ' move camera forward
	If KeyDown(KEY_S)=True Then MoveEntity cam,0,0,-move# ' move camera back

	If KeyDown(KEY_A)=True Then MoveEntity cam,-move#,0,0 ' move camera left
	If KeyDown(KEY_D)=True Then MoveEntity cam,move#,0,0 ' move camera right

	If KeyDown(KEY_MINUS) Then anim_time#=anim_time#-0.25
	If KeyDown(KEY_EQUALS) Then anim_time#=anim_time#+0.25
	
	GroupSetAnimTime(ent,anim_time#) 'Animate an entity and its children
	
	y:Float:+0.5
	
	RotateEntity(ent,0,y,0)

	RenderWorld

	Text 0,20,"+/- to animate"
	Text 0,40,"anim_time#: "+AnimTime(ent)

	Flip

Wend
End

Function celshade:TTexture(lite:TEntity,map:String)

	texSize = 256 'Much lower than this and the resulting texture shows a lot of pixelation.

	'The texture that will be returned.
	Local retTex:TTexture=CreateTexture(texSize,texSize,1+16+32+128+256+512)
	Local shadeMap:TTexture = LoadTexture(map)
	Local celCam:TCamera = CreateCamera()
	
	Local sphere:TMesh = CreateSphere(60)
	EntityBlend(sphere,1)
	FlipMesh(sphere)
	EntityTexture(sphere,shadeMap)
	ScaleEntity(sphere,5,5,5)
	PositionEntity(sphere,0,0,0)
	RotateMesh(sphere,90,0,0)
	PointEntity(sphere,lite)
	
	CameraClsMode celCam,True,True
	
	' set the camera's viewport so it is the same size as our texture - so we can fit entire screen contents into texture
	CameraViewport celCam,0,0,texSize,texSize

	' do left view
	RenderWorld
	SetCubeFace retTex,0
	RotateEntity celCam,0,90,0
	RenderWorld
	BackBufferToTex(retTex) ' bmx

	' do forward view
	SetCubeFace retTex,1
	RotateEntity celCam,0,0,0
	RenderWorld
	BackBufferToTex(retTex) ' bmx

	' do right view	
	SetCubeFace retTex,2
	RotateEntity celCam,0,-90,0
	RenderWorld
	BackBufferToTex(retTex) ' bmx

	' do backward view
	SetCubeFace retTex,3
	RotateEntity celCam,0,180,0
	RenderWorld
	BackBufferToTex(retTex) ' bmx

	' do up view
	SetCubeFace retTex,4
	RotateEntity celCam,-90,0,0
	RenderWorld
	BackBufferToTex(retTex) ' bmx

	' do down view
	SetCubeFace retTex,5
	RotateEntity celCam,90,0,0
	RenderWorld
	BackBufferToTex(retTex) ' bmx

	FreeEntity celCam
	FreeEntity sphere
	
	Return retTex

EndFunction

Function celOutline(outLine:TEntity Var,scale:Float)
	
	If scale > 0

		UpdateNormals TMesh(outLine)
		EntityColor(outLine,0,0,0)
		EntityFX(outLine,9)
		
		For Local currSurf:TSurface = EachIn TMesh(outLine).surf_list
		
			Local verts = CountVertices(currSurf)
			
			For v:Int = 0 To verts-1
				Local VX# = VertexX (currSurf,v)
				Local VY# = VertexY (currSurf,v)
				Local VZ# = VertexZ (currSurf,v)
				Local VNX# = VertexNX (currSurf,v)
				Local VNY# = VertexNY (currSurf,v)
				Local VNZ# = VertexNZ (currSurf,v)
				If (VNX<-1) Or (VNX>1) Then VNX = 0
				If (VNY<-1) Or (VNY>1) Then VNY = 0
				If (VNZ<-1) Or (VNZ>1) Then VNZ = 0
				VX = VX + (VNX*scale)
				VY = VY + (VNY*scale)	
				VZ = VZ + (VNZ*scale)
				VertexCoords currSurf,v,VX,VY,VZ
			Next
		Next
	EndIf
	
	FlipMesh TMesh(outLine)
	
End Function

Function GroupSetAnimTime(ent:TEntity,anim_time:Float)
	
	If ent = Null Then Return

	SetAnimTime(ent,anim_time)

	For Local childEnt:TEntity = EachIn ent.child_list
	
		SetAnimTime(childEnt,anim_time)
	
	Next
EndFunction