Another per pixel lighting system :)

BlitzMax Forums/BlitzMax Programming/Another per pixel lighting system :)

Hi,

As mentioned in the other thread: http://www.blitzbasic.com/Community/posts.php?topic=49403
You can use the accumulation buffer for lighting as well. It should be a lot faster on newish graphics cards, but maybe not on older ones?

Anyway, it's OpenGL only. If someone can do it in DX then show us!

Here's some simple code:
Strict

SetGraphicsDriver(GLMax2DDriver(),GRAPHICS_BACKBUFFER|GRAPHICS_ACCUMBUFFER)
Graphics 640,480,0,0

SeedRnd MilliSecs()

'
' Box type...just for testing
'
Type Box
	Global BoxList:TList

	Field x,y,w,h
	Field r,g,b
	
	Method New()
		If BoxList= Null
			BoxList= New TList
		EndIf
		BoxList.AddLast Self
	EndMethod
	
	Method Delete()
		BoxList.Remove Self
	EndMethod
	
	Function Create:Box(x=0,y=0,w=10,h=10,r=255,g=255,b=255)
		Local o:Box = New Box
		o.x		= x
		o.y		= y
		o.w		= w
		o.h		= h
		o.r		= r
		o.g		= g
		o.b		= b
		Return o
	End Function

	Function Render()

		SetRotation 0
		SetScale 1,1
		SetOrigin 0,0
		
		SetBlend ALPHABLEND
		
		For Local o:Box = EachIn BoxList
			SetColor o.r,o.g,o.b
			DrawRect o.x,o.y,o.w,o.h
		Next
	EndFunction

End Type

'
' Light type
'
Type Light
	Global LightList:TList

	Field x:Float,y:Float
	Field rad:Float,size:Float
	Field a:Float,spd:Float
	Field r:Float,g:Float,b:Float
	Field img:TImage
	
	Method New()
		If LightList = Null
			LightList = New TList
		EndIf
		LightList.AddLast Self
	EndMethod
	
	Method Delete()
		LightList.Remove Self
	EndMethod
	
	Method Free()
		LightList.Remove Self
	EndMethod
	
	Function Create:Light(image:TImage,x:Float=0,y:Float=0,size:Float,a:Float=0,spd:Float=0,rad:Float=0.0,r:Float=255,g:Float=255,b:Float=255)
		Local l:Light = New Light
		l.img	= image
		l.x		= x
		l.y		= y
		l.size	= size
		l.a		= a
		l.spd	= spd
		l.rad	= rad
		l.r		= r
		l.g		= g
		l.b		= b
		Return l
	End Function
	
	Function Render(ambience:Float,intensity:Float)

		'
		' Clear Accum buffer
		'
		glClear(GL_ACCUM_BUFFER_BIT)

		'
		' Grab backbuffer and place it in accumbuffer
		' (note subtraction "ambience-1.0")
		'
		glAccum(GL_ACCUM,1.0)
		glAccum(GL_ADD,ambience-1.0)
		
		'
		' Draw Lights
		'
		Cls
		SetRotation 0
		SetScale 1,1
		SetOrigin 0,0
		
		SetBlend LIGHTBLEND
		
		For Local l:Light = EachIn LightList
			l.a :+ l.spd
	
			SetScale l.size,l.size
			SetColor l.r,l.g,l.b
			DrawImage l.img,l.x+Sin(l.a)*l.rad,l.y+Cos(l.a)*l.rad
		Next

		'
		' Mix lights with accumbuffer (note multiply by intensity)
		'
		glAccum(GL_ACCUM,intensity)

		'
		' Draw accumulated result to backbuffer
		'
		glAccum(GL_RETURN,1.0)
	EndFunction
	
EndType


Global lightimg:TImage = CreateLightImage(256,256)

'
' Create a few lights
'
For Local i = 0 To 5
	Light.Create(lightimg,Rnd(640),Rnd(480),Rnd(1,2),Rnd(360),Rnd(-2,2),Rnd(5,50),Rnd(128,255),Rnd(128,255),Rnd(128,255))
Next

'
' Create some ugly backdrop
For Local i = 0 To 100
	Box.Create(Rnd(640),Rnd(480),Rnd(50,250),Rnd(50,250),Rnd(128,255),Rnd(128,255),Rnd(128,255))
Next

'
' Main loop
'
Global lighton:Int = True
Global lightpow:Float = 1.5
Global ambpow:Float = 0.0
Repeat
	
	If KeyHit(KEY_SPACE)
		lighton = Not lighton
	EndIf
	
	If KeyDown(KEY_UP)
		lightpow :+ 0.01
		If lightpow>3 Then lightpow = 3
	EndIf
	If KeyDown(KEY_DOWN)
		lightpow :- 0.01
		If lightpow<0 Then lightpow = 0
	EndIf

	If KeyDown(KEY_RIGHT)
		ambpow :+ 0.01
		If ambpow>1 Then ambpow = 1
	EndIf
	If KeyDown(KEY_LEFT)
		ambpow :- 0.01
		If ambpow<0 Then ambpow = 0
	EndIf

	Cls
	
	Box.Render()

	If lighton Then Light.Render(ambpow,lightpow)

	SetScale 1,1
	SetColor 255,255,255
	Local t:String = "Space - Lights On/Off | Up/Down - Intensity | Left/Right - Ambient"
	DrawText t,(GraphicsWidth()-TextWidth(t))*0.5,0
	
	If lighton
		Local t2:String = "Intensity - "+lightpow+"| Ambient - "+ambpow
		DrawText t2,(GraphicsWidth()-TextWidth(t2))*0.5,GraphicsHeight()-15
	EndIf
		
	Flip
Until KeyHit(KEY_ESCAPE)

End

Function CreateLightImage:TImage(width,height,kapow:Float = 2.0)

	SetScale 1,1
	SetRotation 0
	SetOrigin 0,0
	
	Local xrad:Float = width*0.5
	Local yrad:Float = height*0.5

	For Local x = 0 To width-1
		For Local y = 0 To height-1
			Local dx:Float = (x-xrad)/xrad
			Local dy:Float = (y-yrad)/yrad
			Local c:Float = 1.0-Sqr(dx*dx + dy*dy)
			If c<0.0 Then c = 0.0
			c = (c^kapow)*255
			SetColor c,c,c
			Plot x,y
		Next		
	Next

	Local img:TImage = CreateImage(width,height,1,FILTEREDIMAGE|MIPMAPPEDIMAGE)
	GrabImage(img,0,0,0)
	SetImageHandle img,xrad,yrad
	
	Return img

End Function


[edit]Screenie of how it should look with an image, instead of the boxes...


Works great ... *somehow beeing a little shocked that it can be done "that simple" ...*

How is it supposed to look?
I imadgine it doesnt look like it should on my computer here at work. awfully slow and colors that doesnt seem to be correct. ascreenshot of a working one pls :)

Looks a bit of a mess here as well (on my old machine at least - GF4 MX 440).

ditto slow mess, ermmmmmm o_O

It's probably dog slow on GeForce 4 and older. It will need a hardware accumulation buffer to work properly, otherwise it will chug along and probably not work correctly.

Nice effect from the king of lightmapping! :)

Runs very fast here, I'm on what's probably an average card these days FX5600.

Great job fredborg!

Very fast!

WinXP SP1, AMD-XP 2400+, 512 MB, GF-FX5200

Hi Fredborg, very nice but how can you stop the lights 'blobbing' together? If I have two lights and shine them on the same wall the result is a light equal to the brightest of the two not the sum of them (if you know what I mean). I'm guessing this is what the accum buffer does.

Um, I would think decreasing the brightness of the individual lights, while pumping up the overall light intensity would do the trick.

It happens because the values the accum buffer can store are limited (from -1 to 1)...

I think you'll find two lights together equals their sum, tonyg.

Surely not! Shine a very bright light on a wall and then a dim torch. The result is the same very bright light and not a bright light with a brighter section where the torch is shining.
e.g. A 'force 5' torch shining with a 'force 6' torch makes a force 6 light not a force 11 light.... surely?

perhaps using alphablend with Alpha instead of LightBlend works? *haven't tried as I am trying to kick my windows system ;-)*

If you scale your light colors so that the sum of them will not exceed 255,255,255 and then increase the Intensity, you shouldn't have any problems. Example:
Strict

SetGraphicsDriver(GLMax2DDriver(),GRAPHICS_BACKBUFFER|GRAPHICS_ACCUMBUFFER)
Graphics 640,480,0,0

SeedRnd MilliSecs()

'
' Box type...just for testing
'
Type Box
	Global BoxList:TList

	Field x,y,w,h
	Field r,g,b
	
	Method New()
		If BoxList= Null
			BoxList= New TList
		EndIf
		BoxList.AddLast Self
	EndMethod
	
	Method Delete()
		BoxList.Remove Self
	EndMethod
	
	Function Create:Box(x=0,y=0,w=10,h=10,r=255,g=255,b=255)
		Local o:Box = New Box
		o.x		= x
		o.y		= y
		o.w		= w
		o.h		= h
		o.r		= r
		o.g		= g
		o.b		= b
		Return o
	End Function

	Function Render()

		SetRotation 0
		SetScale 1,1
		SetOrigin 0,0
		
		SetBlend ALPHABLEND
		
		For Local o:Box = EachIn BoxList
			SetColor o.r,o.g,o.b
			DrawRect o.x,o.y,o.w,o.h
		Next
	EndFunction

End Type

'
' Light type
'
Type Light
	Global LightList:TList

	Field x:Float,y:Float
	Field rad:Float,size:Float
	Field a:Float,spd:Float
	Field r:Float,g:Float,b:Float
	Field img:TImage
	
	Method New()
		If LightList = Null
			LightList = New TList
		EndIf
		LightList.AddLast Self
	EndMethod
	
	Method Delete()
		LightList.Remove Self
	EndMethod
	
	Method Free()
		LightList.Remove Self
	EndMethod
	
	Function Create:Light(image:TImage,x:Float=0,y:Float=0,size:Float,a:Float=0,spd:Float=0,rad:Float=0.0,r:Float=255,g:Float=255,b:Float=255)
		Local l:Light = New Light
		l.img	= image
		l.x		= x
		l.y		= y
		l.size	= size
		l.a		= a
		l.spd	= spd
		l.rad	= rad
		l.r		= r
		l.g		= g
		l.b		= b
		Return l
	End Function
	
	Function Render(ambience:Float,intensity:Float)

		'
		' Clear Accum buffer
		'
		glClear(GL_ACCUM_BUFFER_BIT)

		'
		' Grab backbuffer and place it in accumbuffer
		' (note subtraction "ambience-1.0")
		'
		glAccum(GL_ACCUM,1.0)
		glAccum(GL_ADD,ambience-1.0)
		
		'
		' Draw Lights
		' (note multiply by intensity)
		'
		Local intenslight:Float = 1' intensity
		If intenslight>1.0 Then intenslight = 1.0
		
		Cls
		SetRotation 0
		SetScale 1,1
		SetOrigin 0,0
		
		SetBlend LIGHTBLEND
		
		For Local l:Light = EachIn LightList
			l.a :+ l.spd
	
			SetScale l.size,l.size
			SetColor l.r*intenslight,l.g*intenslight,l.b*intenslight
			DrawImage l.img,l.x+Sin(l.a)*l.rad,l.y+Cos(l.a)*l.rad
		Next

		'
		' Mix lights with accumbuffer 
		'
		'If intensity<1.0 Then intensity = 1.0
		glAccum(GL_ACCUM,intensity)

		'
		' Draw accumulated result to backbuffer
		'
		glAccum(GL_RETURN,1.0)
	EndFunction
	
	
	Function ScaleColors(maxbright:Float)
		'
		' Scale colors so they cannot exceed maxbright
		' when drawn on top of each other
		'
		Local red:Float		= 0
		Local green:Float	= 0
		Local blue:Float	= 0
	
		For Local l:Light = EachIn LightList
			red :+ l.r
			green :+ l.g
			blue :+ l.b
		Next
		
		Local cmax:Float = Max(red,green)
		cmax = Max(cmax,blue)

		For Local l:Light = EachIn LightList
			l.r = (l.r*maxbright) / cmax
			l.g = (l.g*maxbright) / cmax
			l.b = (l.b*maxbright) / cmax
		Next
	
	EndFunction
	
EndType


Global lightimg:TImage = CreateLightImage(256,256)

'
' Create a few lights
'
Local lcount:Float = 5
For Local i = 0 To lcount-1
	Local hue:Float = (i/lcount)*180.0
	Local r:Float = 40+Sin(hue)*255
	Local g:Float = 40+Sin(hue+45)*255
	Local b:Float = 40+Sin(hue+90)*255
	If r<0 Then r = 0
	If g<0 Then g = 0
	If b<0 Then b = 0
	Light.Create(lightimg,Rnd(640),Rnd(480),Rnd(1.5,2),Rnd(360),Rnd(-2,2),Rnd(25,50),r,g,b)
Next

'
' Scale colors
'
Light.ScaleColors(255)

'
' Create some ugly backdrop
For Local i = 0 To 100
	Box.Create(Rnd(640),Rnd(480),Rnd(50,250),Rnd(50,250),Rnd(128,255),Rnd(128,255),Rnd(128,255))
Next

'
' Main loop
'
Global lighton:Int = True
Global lightpow:Float = 1.5
Global ambpow:Float = 0.0
Repeat
	
	If KeyHit(KEY_SPACE)
		lighton = Not lighton
	EndIf
	
	If KeyDown(KEY_UP)
		lightpow :+ 0.01
		If lightpow>10 Then lightpow = 10
	EndIf
	If KeyDown(KEY_DOWN)
		lightpow :- 0.01
		If lightpow<0 Then lightpow = 0
	EndIf

	If KeyDown(KEY_RIGHT)
		ambpow :+ 0.01
		If ambpow>1 Then ambpow = 1
	EndIf
	If KeyDown(KEY_LEFT)
		ambpow :- 0.01
		If ambpow<0 Then ambpow = 0
	EndIf

	Cls
	
	Box.Render()

	If lighton Then Light.Render(ambpow,lightpow)

	SetScale 1,1
	SetColor 255,255,255
	Local t:String = "Space - Lights On/Off | Up/Down - Intensity | Left/Right - Ambient"
	DrawText t,(GraphicsWidth()-TextWidth(t))*0.5,0
	
	If lighton
		Local t2:String = "Intensity - "+lightpow+"| Ambient - "+ambpow
		DrawText t2,(GraphicsWidth()-TextWidth(t2))*0.5,GraphicsHeight()-15
	EndIf
		
	Flip
Until KeyHit(KEY_ESCAPE)

End

Function CreateLightImage:TImage(width,height,kapow:Float = 2.0)

	SetScale 1,1
	SetRotation 0
	SetOrigin 0,0
	
	Local xrad:Float = width*0.5
	Local yrad:Float = height*0.5

	For Local x = 0 To width-1
		For Local y = 0 To height-1
			Local dx:Float = (x-xrad)/xrad
			Local dy:Float = (y-yrad)/yrad
			Local c:Float = 1.0-Sqr(dx*dx + dy*dy)
			If c<0.0 Then c = 0.0
			c = (c^kapow)*255
			SetColor c,c,c
			Plot x,y
		Next		
	Next

	Local img:TImage = CreateImage(width,height,1,FILTEREDIMAGE|MIPMAPPEDIMAGE)
	GrabImage(img,0,0,0)
	SetImageHandle img,xrad,yrad
	
	Return img

End Function

As you can see this results in some artifacts, but if you tweak it, it'll look fine.

wow, great work fredborg !

Wow! Its cool! ...how use it in DX 7? ;)
Please...

Accumulation Buffer Techniques with DirectX® 7
http://www.ati.com/developer/sdk/RadeonSDK/Html/Samples/Samples.html