With the module you can then create illuminated sprites (IlluminatedSprite) and display them on the screen like normal sprites, with the benefit that they can be illuminated using lights placed in the scene (SpriteLight) (examples in the next post):

Internally, the module create a set of images for different lighting directions from the Diffuse Map and the Normal Map and then mix them in real time when displaying:



When DisplayIlluminatedSprite() is executed, one lighting version is mixed from all lights and is displayed. More lights (i.e. many lights) do not mean more sprites that need to be displayed. A maximum of 30 DisplaySprite commands are executed per DisplayIlluminatedSprite command.
Of course, this is anything but efficient and accurate compared to real solutions with shaders, but that is not the topic here. It is just a simple "trick" and is intended to show what is possible with "pure"-Basic without a 3D engine. And perhaps, a nice add-on for a small game.
Code: Select all
;- Illuminated Sprites
DeclareModule IlluminatedSprites
Enumeration
#SpriteLight_Ambient ; An ambient light source that illuminates the whole sprite angle independent.
#SpriteLight_Directional ; A directional light source that illuminates the sprite from the specified direction.
#SpriteLight_Point ; A point light source that illuminates the sprite from the specified position and range.
EndEnumeration
; Procedure for the illuminated sprites:
Declare.i CreateIlluminatedSprite(Sprite_DiffuseMap.i, Sprite_NormalMap.i, WeightingZ.f = 1.0) ; Creates an IlluminatedSprite from a diffuse and normal map.
Declare.i DisplayIlluminatedSprite(*IlluminatedSprite, X.f, Y.f, Z.f=0.0) ; Displays the IlluminatedSprite at the given position.
Declare.i RotateIlluminatedSprite(*IlluminatedSprite, Angle.f) ; Rotates the IlluminatedSprite to the given angle in degree.
Declare.i ZoomIlluminatedSprite(*IlluminatedSprite, Factor.f) ; Zooms the IlluminatedSprite by the given factor relative to the original size.
Declare.i FreeIlluminatedSprite(*IlluminatedSprite) ; Deletes the given IlluminatedSprite
; Procedure for the light sources:
Declare.i CreateSpriteLight(Type.i, Color.q, Intensity.f=1.0, X.f=0, Y.f=0, Z.f=0, Range.f=1000.0) ; Create a SpriteLight with the given color and parameters:
; - #SpriteLight_Ambient: X, Y, Z and Range have no meaning.
; - #SpriteLight_Directional: X, Y, and Z are the direction, Range has no meening.
; - #SpriteLight_Point: X, Y, and Z are the position, Range is the max distance of illumination.
Declare.i FreeSpriteLight(*SpriteLight) ; Deletes the given SpriteLight
Declare.i SpriteLightIntensity(*SpriteLight, Intensity.f) ; Changes the intensity of a light source
Declare.i SpriteLightRange(*SpriteLight, Range.f) ; Changes the range of a point light source
Declare.i SpriteLightDirection(*SpriteLight, X.f, Y.f, Z.f) ; Changes the direction of a directional light source
Declare.i SpriteLightPosition(*SpriteLight, X.f, Y.f, Z.f=0) ; Changes the position of a point light source
EndDeclareModule
Module IlluminatedSprites
EnableExplicit
#Inverse255 = 1.0/255.0
#TwoInverse255 = 2.0/255.0
#SquaredInverse255 = 1.0/(255.0*255.0)
#LightDirections = 10
Structure Color
Channel.f[0]
R.f
G.f
B.f
A.f
EndStructure
Structure Vector
X.f
Y.f
Z.f
EndStructure
Structure IlluminatedSprite
Sprite_Diffuse.i
Sprite_Color.i
Rotation.f
Zoom.f
Width.i
Height.i
EndStructure
Global NewList IlluminatedSprite.IlluminatedSprite()
Structure SpriteLight
Type.i
Color.Color
StructureUnion
Position.Vector
Direction.Vector
EndStructureUnion
Intensity.f
Range.f
EndStructure
Global NewList SpriteLight.SpriteLight()
;- Some helper vector math functions
Procedure.f Vector_Length(*Vector.Vector)
ProcedureReturn Sqr(*Vector\X**Vector\X+*Vector\Y**Vector\Y+*Vector\Z**Vector\Z)
EndProcedure
Procedure.f Vector_Dot(*Vector1.Vector, *Vector2.Vector)
ProcedureReturn *Vector1\X * *Vector2\X + *Vector1\Y * *Vector2\Y + *Vector1\Z * *Vector2\Z
EndProcedure
Procedure.i Vector_Normalize(*Vector.Vector)
Protected Length.f = Vector_Length(*Vector)
*Vector\X / Length : *Vector\Y / Length : *Vector\Z / Length
ProcedureReturn *Vector
EndProcedure
Procedure.i Vector_Rotate(*Vector.Vector, Angle.f)
Protected OldVector.Vector
OldVector\X = *Vector\X : OldVector\Y = *Vector\Y
*Vector\X = Cos(Angle) * OldVector\X - Sin(Angle) * OldVector\Y
*Vector\Y = Sin(Angle) * OldVector\X + Cos(Angle) * OldVector\Y
ProcedureReturn *Vector
EndProcedure
;- Procedures for the lights
Procedure.i CreateSpriteLight(Type.i, Color.q, Intensity.f=1.0, X.f=0, Y.f=0, Z.f=0, Range.f=1000.0)
Select Type
Case #SpriteLight_Ambient
AddElement(SpriteLight())
SpriteLight()\Type = #SpriteLight_Ambient
Case #SpriteLight_Directional
AddElement(SpriteLight())
SpriteLight()\Type = #SpriteLight_Directional
SpriteLight()\Direction\X = X
SpriteLight()\Direction\Y = Y
SpriteLight()\Direction\Z = Z
Vector_Normalize(SpriteLight()\Direction)
Case #SpriteLight_Point
AddElement(SpriteLight())
SpriteLight()\Type = #SpriteLight_Point
SpriteLight()\Position\X = X
SpriteLight()\Position\Y = Y
SpriteLight()\Position\Z = Z
Default
ProcedureReturn #False
EndSelect
With SpriteLight()
\Color\R = #Inverse255 * Red(Color)
\Color\G = #Inverse255 * Green(Color)
\Color\B = #Inverse255 * Blue(Color)
\Range = Range
\Intensity = Intensity
EndWith
ProcedureReturn SpriteLight()
EndProcedure
Procedure FreeSpriteLight(*SpriteLight.SpriteLight)
ChangeCurrentElement(SpriteLight(), *SpriteLight)
DeleteElement(SpriteLight())
EndProcedure
Procedure.i SpriteLightPosition(*SpriteLight.SpriteLight, X.f, Y.f, Z.f=0.0)
*SpriteLight\Position\X = X
*SpriteLight\Position\Y = Y
*SpriteLight\Position\Z = Z
EndProcedure
Procedure.i SpriteLightDirection(*SpriteLight.SpriteLight, X.f, Y.f, Z.f)
*SpriteLight\Direction\X = X
*SpriteLight\Direction\Y = Y
*SpriteLight\Direction\Z = Z
Vector_Normalize(*SpriteLight\Direction)
EndProcedure
Procedure.i SpriteLightIntensity(*SpriteLight.SpriteLight, Intensity.f)
*SpriteLight\Intensity = Intensity
EndProcedure
Procedure.i SpriteLightRange(*SpriteLight.SpriteLight, Range.f)
*SpriteLight\Range = Range
EndProcedure
;- Procedures for the illuminated sprites
Procedure.i CreateIlluminatedSprite(Sprite_DiffuseMap.i, Sprite_NormalMap.i, WeightingZ.f = 1.0)
Protected Dim Color.Color(0, 0), *Color.Color
Protected Dim Normal.Vector(0, 0), *Normal.Vector
Protected *IlluminationDirection.Vector
Protected Sprite_ColorMap.i, Color.l
Protected X.i, Y.i, Width.i, Height.i
Protected Dot.f, DotXY.f, DotWeighted.f
Protected Temp.Vector
Protected Part.i
If Not IsSprite(Sprite_DiffuseMap) Or Not IsSprite(Sprite_NormalMap)
ProcedureReturn #False
EndIf
Width = SpriteWidth(Sprite_DiffuseMap)
Height = SpriteHeight(Sprite_NormalMap)
If Width <> SpriteWidth(Sprite_NormalMap) Or Height <> SpriteHeight(Sprite_NormalMap)
ProcedureReturn #False
EndIf
Dim Color(Width-1, Height-1)
Dim Normal(Width-1, Height-1)
; Import color data from the diffuse map.
If StartDrawing(SpriteOutput(Sprite_DiffuseMap))
DrawingMode(#PB_2DDrawing_AllChannels)
For Y = Height-1 To 0 Step -1
For X = Width-1 To 0 Step -1
Color = Point(X, Y)
*Color = Color(X, Y)
*Color\R = #SquaredInverse255 * Alpha(Color) * Red(Color)
*Color\G = #SquaredInverse255 * Alpha(Color) * Green(Color)
*Color\B = #SquaredInverse255 * Alpha(Color) * Blue(Color)
Next
Next
StopDrawing()
EndIf
; Import normal vector data from the normal map.
If StartDrawing(SpriteOutput(Sprite_NormalMap))
DrawingMode(#PB_2DDrawing_AllChannels)
For Y = Height-1 To 0 Step -1
For X = Width-1 To 0 Step -1
Color = Point(X, Y)
*Normal = Normal(X, Y)
*Normal\X = #TwoInverse255 * Red(Color) - 1.0
*Normal\Y = -(#TwoInverse255 * Green(Color) - 1.0)
*Normal\Z = (#TwoInverse255 * Blue(Color) - 1.0) * WeightingZ
Vector_Normalize(*Normal)
Next
Next
StopDrawing()
EndIf
; Create the special illuminated Sprite, a grid of three colors,
; each having the ambient light version and nine light directions.
*IlluminationDirection = ?IlluminationDirections
Sprite_ColorMap = CreateSprite(#PB_Any, #LightDirections*Width, 3*Height)
If StartDrawing(SpriteOutput(Sprite_ColorMap))
For Part = 0 To #LightDirections-1
For Y = Height-1 To 0 Step -1
For X = Width-1 To 0 Step -1
*Normal = Normal(X, Y)
*Color = Color(X, Y)
If Part > 0
Dot = Vector_Dot(*Normal, *IlluminationDirection)
Temp\X = *Normal\X
Temp\Y = *Normal\Y
Vector_Normalize(Temp)
DotXY = Vector_Dot(Temp, *IlluminationDirection)
If Part = 1
DotWeighted = Dot
Else
DotWeighted = DotXY * Pow(Dot,2.01)
EndIf
If Dot > 0.0 And DotWeighted > 0
Plot(X+Width*Part, Y+Height*0, RGB(*Color\R*DotWeighted*255, 0, 0))
Plot(X+Width*Part, Y+Height*1, RGB(0, *Color\G*DotWeighted*255, 0))
Plot(X+Width*Part, Y+Height*2, RGB(0, 0, *Color\B*DotWeighted*255))
EndIf
Else
Plot(X, Y+Height*0, RGB(*Color\R*255, 0, 0))
Plot(X, Y+Height*1, RGB(0, *Color\G*255, 0))
Plot(X, Y+Height*2, RGB(0, 0, *Color\B*255))
EndIf
Next
Next
*IlluminationDirection + SizeOf(Vector)
Next
StopDrawing()
EndIf
AddElement(IlluminatedSprite())
With IlluminatedSprite()
\Sprite_Diffuse = Sprite_DiffuseMap
\Sprite_Color = Sprite_ColorMap
\Width = Width
\Height = Height
\Rotation = 0
\Zoom = 1.0
EndWith
ProcedureReturn IlluminatedSprite()
EndProcedure
Procedure FreeIlluminatedSprite(*IlluminatedSprite.IlluminatedSprite)
FreeSprite(*IlluminatedSprite\Sprite_Color)
ChangeCurrentElement(IlluminatedSprite(), *IlluminatedSprite)
DeleteElement(IlluminatedSprite())
EndProcedure
Procedure.i DisplayColorChannels(*IlluminatedSprite.IlluminatedSprite, *Color.Color, Part.i, X.f, Y.f)
Protected I.i
With *IlluminatedSprite
For I = 0 To 2
If *Color\Channel[I] > 0
ClipSprite(\Sprite_Color, \Width*Part, \Height*I, \Width, \Height)
RotateSprite(\Sprite_Color, \Rotation, #PB_Absolute)
ZoomSprite(\Sprite_Color, \Width*\Zoom, \Height*\Zoom)
DisplayTransparentSprite(\Sprite_Color, X, Y, 255 * *Color\Channel[I])
;If *Color\Channel[I] > 1 ; One more drawing if the intensity is brighter than 1.0, not used at the moment
; DisplayTransparentSprite(\Sprite_Color, X, Y, 255 * (*Color\Channel[I]-1))
;EndIf
EndIf
Next
EndWith
EndProcedure
Procedure.i RotateIlluminatedSprite(*IlluminatedSprite.IlluminatedSprite, Angle.f)
*IlluminatedSprite\Rotation = Angle
EndProcedure
Procedure.i ZoomIlluminatedSprite(*IlluminatedSprite.IlluminatedSprite, Factor.f)
*IlluminatedSprite\Zoom = Factor
EndProcedure
Procedure.i DisplayIlluminatedSprite(*IlluminatedSprite.IlluminatedSprite, X.f, Y.f, Z.f=0.0)
Protected *IlluminationDirections.Vector
Protected LightColor.Color
Protected Part.i, Direction.Vector, Intensity.f, Distance.f, Weighting.f
Protected View.Vector
With *IlluminatedSprite
RotateSprite(\Sprite_Diffuse, \Rotation, #PB_Absolute)
ZoomSprite(\Sprite_Diffuse, \Width*\Zoom, \Height*\Zoom)
DisplayTransparentSprite(\Sprite_Diffuse, X-\Zoom*\Width/2, Y-\Zoom*\Height/2, 255, 0)
SpriteBlendingMode(#PB_Sprite_BlendSourceAlpha, #PB_Sprite_BlendOne)
*IlluminationDirections = ?IlluminationDirections
For Part = 0 To #LightDirections-1
ResetStructure(@LightColor, Color)
ForEach SpriteLight()
Intensity = 0.0
Select SpriteLight()\Type
Case #SpriteLight_Ambient
If Part = 0
Intensity = SpriteLight()\Intensity
EndIf
Case #SpriteLight_Directional
Direction = SpriteLight()\Direction
Vector_Rotate(Direction, -Radian(\Rotation))
If Part = 1
Intensity = Vector_Dot(Direction, *IlluminationDirections) * SpriteLight()\Intensity
Else
Intensity = Vector_Dot(Direction, *IlluminationDirections) * SpriteLight()\Intensity * 0.7
EndIf
Case #SpriteLight_Point
Direction\X = SpriteLight()\Position\X - X
Direction\Y = SpriteLight()\Position\Y - Y
Direction\Z = SpriteLight()\Position\Z - Z
Distance = Vector_Length(Direction)
If Distance < SpriteLight()\Range
Vector_Normalize(Direction)
Vector_Rotate(Direction, -Radian(\Rotation))
If Part = 1
Intensity = Vector_Dot(Direction, *IlluminationDirections) * SpriteLight()\Intensity * (1 - Distance/SpriteLight()\Range)
Else
Intensity = Vector_Dot(Direction, *IlluminationDirections) * SpriteLight()\Intensity * (1 - Distance/SpriteLight()\Range) * 0.7
EndIf
EndIf
EndSelect
If Intensity > 0
LightColor\R + Intensity * SpriteLight()\Color\R
LightColor\G + Intensity * SpriteLight()\Color\G
LightColor\B + Intensity * SpriteLight()\Color\B
EndIf
Next
DisplayColorChannels(*IlluminatedSprite, LightColor, Part, X-\Zoom*\Width/2, Y-\Zoom*\Height/2)
*IlluminationDirections + SizeOf(Vector)
Next
SpriteBlendingMode(#PB_Sprite_BlendSourceAlpha, #PB_Sprite_BlendInvertSourceAlpha)
EndWith
EndProcedure
DataSection
IlluminationDirections:
Data.f 0.0, 0.0, 0.0 ; Ambient
Data.f 0.0, 0.0, 1.0 ; Front
Data.f 1.0, 0.0, 0.0 ; Right
Data.f 0.0, 1.0, 0.0 ; Bottom
Data.f -1.0, 0.0, 0.0 ; Left
Data.f 0.0, -1.0, 0.0 ; Top
Data.f 0.7, 0.7, 0.0 ; Right-Bottom
Data.f -0.7, 0.7, 0.0 ; Left-Bottom
Data.f -0.7, -0.7, 0.0 ; Left-Top
Data.f 0.7, -0.7, 0.0 ; Right-Top
EndDataSection
EndModule