Method for displaying text with alpha in Blitz3d (code inside)

Started by Matty, December 30, 2024, 17:49:22

Previous topic - Next topic

Matty

This is how I am planning on doing it in my current game I'm developing. I've tested it in game but not released it yet. It does have a performance hit and can be optimised, but it demonstrates how to do it:

This is for displaying alpha-ed text in 3d mode using the standard text commands syntax, or close to it.

Function TextA(x#,y#,txt$,cx=0,cy=0,r=255,g=255,b=255,alpha#=1.0,fontsize=30)
;This function draws text with alpha
;by creating a sprite, texture and camera to do it with
;it uses two images and two textures and blends them
;using multiply blending first to black out the space where the
;text is, and then additive blending over the top of the text that
;is now black....
;because it uses renderworld it assumes no other cameras are currently active
If Len(txt)<=0 Then Return
image = CreateImage(Len(txt)*fontsize*1,fontsize*1)
image2 = CreateImage(Len(txt)*fontsize*1,fontsize*1)
SetBuffer ImageBuffer(image)
Color 0,0,0
Rect 0,0,ImageWidth(image),ImageHeight(image),1
Color r,g,b
Text 0,0,txt,0,0
SetBuffer ImageBuffer(image2)
Color 255,255,255
Rect 0,0,ImageWidth(image2),ImageHeight(image2),1
Color 255 - alpha*255.0,255 - alpha*255.0,255 - alpha*255.0
Text 0,0,txt,0,0
SetBuffer BackBuffer()
tx = 1
ty = 1
While tx < ImageWidth(image)
tx = tx Shl 1
Wend
While ty < ImageHeight(image)
ty = ty Shl 1
Wend
If tx > ty Then ty = tx
If ty > tx Then tx = ty
tex = CreateTexture(tx,ty,1+16+32)
tex2 = CreateTexture(tx,ty,1+16+32)
SetBuffer TextureBuffer(tex2)
Color 255,255,255
Rect 0,0,tx,ty,1
SetBuffer BackBuffer()
CopyRect 0,0,ImageWidth(image),ImageHeight(image),StringWidth(txt)/2+(tx - ImageWidth(image))/2,(ty - ImageHeight(image))/2,ImageBuffer(image),TextureBuffer(tex)
CopyRect 0,0,ImageWidth(image2),ImageHeight(image2),StringWidth(txt)/2+(tx - ImageWidth(image2))/2,(ty - ImageHeight(image2))/2,ImageBuffer(image2),TextureBuffer(tex2)
sprite = CreateSprite()
SpriteViewMode sprite,2
ncamera = CreateCamera()
CameraRange ncamera,GraphicsWidth()/3,GraphicsWidth()*2
CameraClsMode ncamera,False,True
ScaleSprite sprite,TextureWidth(tex),TextureHeight(tex)
PositionEntity sprite,2.0*Float((StringWidth(txt)/2)*(1-cx)+x-(GraphicsWidth()/2)),2.0*Float((GraphicsHeight()/2)-y-(1-cy)*StringHeight(txt)/2),GraphicsWidth()
RotateEntity sprite,0,0,0
EntityFX sprite,16+1
EntityTexture sprite,tex2
EntityAlpha sprite,1
EntityBlend sprite,2
RenderWorld
EntityTexture sprite,tex
EntityBlend sprite,3
EntityAlpha sprite,alpha
RenderWorld
FreeImage image2
FreeTexture tex2
FreeEntity sprite
FreeEntity ncamera
FreeTexture tex
FreeImage image
End Function

Derron

Just a reminder: if your text does not change, you can reuse a created texture.

In other words: render your text into an image and only recreate that image if the text changes. Should remove a lot of "cpu load". Instead of (not blitz3d syntax) DrawText("unit name") you have an object "unit.labelName" which handles all this. 

unit.labelName.SetValue("Matty") would set the text variable of "labelName" and recreate the corresponding texture (if old and new text variable differ...).


bye
Ron

Matty

Thanks Derron, I'd forgotten about that (I used something very similar in my C#/SDL2 engine for text as well to reduce how many textures it needs to create/and can reuse them)

The code can be seen in my game download but I'll post it here as well:

Global ncamera
Type textsprite
Field txt$
Field r,g,b
Field sprite
Field tex
Field tex2
Field image
Field image2
Field timestamp
Field alphaint
End Type

Function managetextsprites() ;call this every frame in the main loop
For t.textsprite = Each textsprite
If t\image<>0 Then FreeImage t\image:t\image = 0
If t\image2<>0 Then FreeImage t\image2:t\image2 = 0
If Abs(MilliSecs()-t\timestamp)>1000 Then
FreeEntity t\sprite
FreeTexture t\tex
FreeTexture t\tex2
If t\image<>0 Then FreeImage t\image
If t\image<>0 Then FreeImage t\image2
Delete t
EndIf
Next
End Function

Function gettextsprite.textsprite(txt$,r,g,b,alphaint) ;no need to call directly, handled by TextA function
For t.textsprite = Each textsprite
If t\txt = txt And t\r = r And t\g = g And t\b = b And t\alphaint = alphaint Then
t\timestamp = MilliSecs()
Return t.textsprite
EndIf
Next
Return Null
End Function

Function TextA(x#,y#,txt$,cx=0,cy=0,r=255,g=255,b=255,alpha#=1.0,fontsize=30)
;This function draws text with alpha
;by creating a sprite, texture and camera to do it with
;it uses two images and two textures and blends them
;using multiply blending first to black out the space where the
;text is, and then additive blending over the top of the text that
;is now black....
If Len(txt)<=0 Then Return
t.textsprite = gettextsprite(txt,r,g,b,255.0*alpha)
If t.textsprite = Null Then
image = CreateImage(Len(txt)*fontsize*1,fontsize*1)
image2 = CreateImage(Len(txt)*fontsize*1,fontsize*1)
SetBuffer ImageBuffer(image)
Color 0,0,0
Rect 0,0,ImageWidth(image),ImageHeight(image),1
Color r,g,b
Text 0,0,txt,0,0
SetBuffer ImageBuffer(image2)
Color 255,255,255
Rect 0,0,ImageWidth(image2),ImageHeight(image2),1
Color 255 - alpha*255.0,255 - alpha*255.0,255 - alpha*255.0
Text 0,0,txt,0,0
SetBuffer BackBuffer()
tx = 1
ty = 1
While tx < ImageWidth(image)
tx = tx Shl 1
Wend
While ty < ImageHeight(image)
ty = ty Shl 1
Wend
If tx > ty Then ty = tx
If ty > tx Then tx = ty
tex = CreateTexture(tx,ty,1+16+32)
tex2 = CreateTexture(tx,ty,1+16+32)
SetBuffer TextureBuffer(tex2)
Color 255,255,255
Rect 0,0,tx,ty,1
SetBuffer BackBuffer()
CopyRect 0,0,ImageWidth(image),ImageHeight(image),StringWidth(txt)/2+(tx - ImageWidth(image))/2,(ty - ImageHeight(image))/2,ImageBuffer(image),TextureBuffer(tex)
CopyRect 0,0,ImageWidth(image2),ImageHeight(image2),StringWidth(txt)/2+(tx - ImageWidth(image2))/2,(ty - ImageHeight(image2))/2,ImageBuffer(image2),TextureBuffer(tex2)
sprite = CreateSprite()
SpriteViewMode sprite,2
ScaleSprite sprite,TextureWidth(tex),TextureHeight(tex)
RotateEntity sprite,0,0,0
EntityFX sprite,16+1
t.textsprite = New textsprite
t\txt = txt
t\r = r
t\g = g
t\b = b
t\timestamp = MilliSecs()
t\tex = tex
t\tex2 = tex2
t\sprite = sprite
t\image = image
t\image2 = image2
t\alphaint = 255.0 * alpha
Else
sprite = t\sprite
tex = t\tex
tex2 = t\tex2
t\timestamp = MilliSecs()
alpha = Float(t\alphaint)/255.0
EndIf
;HideEntity camera ;you won't need to do this if you do it elsewhere
PositionEntity sprite,2.0*Float((StringWidth(txt)/2)*(1-cx)+x-(GraphicsWidth()/2)),2.0*Float((GraphicsHeight()/2)-y-(1-cy)*StringHeight(txt)/2),GraphicsWidth()
If ncamera = 0 Then ncamera = CreateCamera()
ShowEntity ncamera
CameraRange ncamera,GraphicsWidth()/3,GraphicsWidth()*2
CameraClsMode ncamera,False,True
EntityTexture sprite,tex2
EntityAlpha sprite,1
EntityBlend sprite,2
ShowEntity sprite
RenderWorld
EntityTexture sprite,tex
EntityBlend sprite,3
EntityAlpha sprite,alpha
RenderWorld
HideEntity sprite
HideEntity ncamera
; ShowEntity camera ;you won't need to do this if you do it elsewhere
End Function

RemiD

@Matty >> you may be interested in this code example :
https://www.syntaxbomb.com/blitz2d-blitzplus-blitz3d/help-error-somewhere-to-position-a-quad-precisely-at-mousex-mousey/

to draw text with an image or with a rectangle mesh + texture, with the exact same texel size on the screen.
and of course you can then change the alpha of the mesh or the alpha of some texels of the texture.

(there is a little bug to position the rectangle mesh but otherwise it works well)