single surface particles concept ?

Started by RemiD, December 28, 2020, 16:46:35

Previous topic - Next topic

RemiD

hi !  :)

I am trying to understand the concept of single surface particles, in order to decrease render time.

my idea on how i would do it :
-have a premade surface with premade quads in it (4 vertices, 2 triangles)
-turn move 3d pivotsto update particles positions / orientations
-after each pivot has been turned moved, calculate the appropriate position of the 4 vertices of the quad (for this particle), and reposition them.
-only one surface with one material would be rendered instead of 1000 surfaces / materials.

maybe have one surface one material for all particles of an emitter, so that the emitters (and associated particles) not in view or too far don't have to be updated / rendered.

correct ?

any other idea ?

thanks!

Kryzon

If we're talking about the optimization behind single-surface particles/billboards as they were discussed in the Blitz3D forums, the name is "batching": preparing big arrays with all the geometry --arrays for vertex attributes (position, normal, UV coordinates), and an array for triangle vertex indices-- and then using these arrays in a single draw call, instead of doing a draw call per particle. This is what batching is.

OpenGL 3.1+ even has instanced drawing that helps simplifying your arrays. This tutorial uses batching and instanced drawing: 
http://www.opengl-tutorial.org/intermediate-tutorials/billboards-particles/particles-instancing/

Sending as much work as possible to be done in the vertex shader makes these particles very very fast. One test I did had a 10x speed increase after this.

RemiD

#2
Quote
preparing big arrays with all the geometry --arrays for vertex attributes (position, normal, UV coordinates), and an array for triangle vertex indices-- and then using these arrays in a single draw call, instead of doing a draw call per particle. This is what batching is.
ok but my plan was to premake the surface with all the quads (4 vertices, 2 triangles) or tris (3 vertices, 1 triangle), and only update the vertices positions.
or
maybe to recreate only the necessary vertices / triangles each frame, depending on which particles are visible

not sure which method is faster...


i have read somewhere on the blitzbasic forum that to "hide" a particle (of a single surface), an approach is to position the vertices of this particle (quad or tri) at the same position... (or maybe out of the camera FOV ?)


thanks for the link, i will take a look :)


Kryzon

If you're using Blitz3D your only resort is the surface functions indeed. But if you're using an engine or framework that gives you access to the graphics API then you can optimize your graphics further by setting the geometry array usage mode to "STREAM" (expected to change at every frame) for example, so the GPU can handle the memory for it in a more optimal way.

The fastest single surface demo for Blitz3D that I could program was this (taken from my thread in the old Blitz forums):

;Example of a fast alignment of billboards with the camera plane.
;
;Optimised 03/06/2016
;------------------------------------------------------

;The amount of single-surface quadrilateral billboards in the scene.

Const BILLBOARD_AMOUNT% = 5000

Const LARGE_NUMBER% = 10000000 ;Used for setting the cullbox of the single-surface mesh.

Const FX_FORCEALPHA% = 32 ;EntityFX flags used with the billboards to enable per-vertex colour and alpha.
Const FX_FULLBRIGHT% = 1
Const FX_VERTEXCOLOUR% = 2

Const BILLBOARD_ROTATION_SPEED# = 2.0 ;Speed with which all billboards are rotated.

;Type for a billboard object (a camera-aligned quadricular mesh).
;Holds some properties so billboards can be different from one another, like size and rotation.

Type TBillboard
  Field posX#, posY#, posZ#
  Field roll#
  Field scaleX#
  Field scaleY#

  Field r#, g#, b#, alpha#

  Field surf%
  Field vID%[3]
End Type


AppTitle("Fast Billboard Alignment Example")
Graphics3D(800, 600, 0, 2)

Local camera = CreateCamera()
MoveEntity(camera, 0, 5, 0)

;Single surface elements needed:
;-------------------------------------------------------------------------

;- A mesh. Setting the cullbox to a very large size so it's never culled by the camera (i.e it's always visible).

Local billboardMesh = CreateMesh()
MeshCullBox(billboardMesh, -LARGE_NUMBER, -LARGE_NUMBER, -LARGE_NUMBER, 2 * LARGE_NUMBER, 2 * LARGE_NUMBER, 2 * LARGE_NUMBER)
EntityFX(billboardMesh, FX_FULLBRIGHT + FX_VERTEXCOLOUR + FX_FORCEALPHA)

;- A surface to hold the billboards (there can be more than one).

Local billboardSurface = CreateSurface(billboardMesh)

;- A simple pivot without modifications. This pivot is used to align the billboards.

Local billboardPivot = CreatePivot()

;-------------------------------------------------------------------------


;Populate the scene with billboards.

If BILLBOARD_AMOUNT * 4 > 65534 Then RuntimeError "Too many vertices on a single surface."

Const MIN_RADIUS# = 30
Const MAX_RADIUS# = 200
Local angle

For n = 1 To BILLBOARD_AMOUNT

  ;Create random billboards around the scene.
  ;The billboards are added to the surface specified by the programmer.

  angle = Rand(0, 359)

  addBillboard(billboardSurface, Cos(angle) * Rand(MIN_RADIUS, MAX_RADIUS), Rand(-5, 15), Sin(angle) * Rand(MIN_RADIUS, MAX_RADIUS), Rand(0, 359), Rnd(0.25, 2.0), Rnd(0.25, 2.0))

Next

MoveMouse(400, 300)
HidePointer()

Local fastMethod = 1
Local fpsTimer = CreateTimer(30)
Local deltaCount = 0
Local lastDelta
Local delta

While Not (KeyHit(1) Or (KeyDown(56) + KeyDown(62)))
  WaitTimer(fpsTimer)

  navigation(camera)

  ;During the update cycle, align all the billboards with the camera.
  alignBillboards(camera, billboardPivot)

  UpdateWorld()
  RenderWorld()

  Flip()
Wend

End


Function navigation(camera)
  MoveEntity(camera, KeyDown(32) - KeyDown(30), 0, KeyDown(17) - KeyDown(31))

  Local mY = MouseYSpeed()
  If Abs(mY + EntityPitch(camera, True)) > 88.9 Then mY = 0

  TurnEntity(camera, mY, -MouseXSpeed(), 0)
  RotateEntity(camera, EntityPitch(camera, True), EntityYaw(camera, True), 0, True) ;Doesn't let the camera tilt with time.

  MoveMouse(400, 300)

  MouseXSpeed() ;Avoids the camera rotating right? I think my mouse is broken.

End Function


Function addBillboard.TBillboard(surface, x#, y#, z#, roll#, scaleX# = 1.0, scaleY# = 1.0)
  b.TBillboard = New TBillboard

  b\surf = surface

  b\posX = x
  b\posY = y
  b\posZ = z
  b\roll = roll
  b\scaleX = scaleX
  b\scaleY = scaleY

  b\r = Rand(0, 255) ; 0 ~ 255.
  b\g = Rand(0, 255) ; 0 ~ 255.
  b\b = Rand(0, 255) ; 0 ~ 255.
  b\alpha = Rand(20, 100) / 100.0 ; 0.0 ~ 1.0

  b\vID[0] = AddVertex(surface, -1, 1, 0) ;Top left.
  b\vID[1] = AddVertex(surface, 1, 1, 0) ;Top right.
  b\vID[2] = AddVertex(surface, 1, -1, 0) ;Bottom right.
  b\vID[3] = AddVertex(surface, -1, -1, 0) ;Bottom left.

  AddTriangle(surface, b\vID[0], b\vID[1], b\vID[2])
  AddTriangle(surface, b\vID[2], b\vID[3], b\vID[0])

  Return b
End Function


Function alignBillboards(camera, pivot)
  ;Retrieve the (normalised) 'up' and 'right' vectors of the camera in world space.
  ;This is only needed once per frame instead of per billboard as it is usually done.

  RotateEntity(pivot, EntityPitch(camera, True), EntityYaw(camera, True), 0, True)

  Local camRightX#, camRightY#, camRightZ#
  TFormNormal(1, 0, 0, pivot, 0)
  camRightX = TFormedX()
  camRightY = TFormedY()
  camRightZ = TFormedZ()

  Local camUpX#, camUpY#, camUpZ#
  TFormNormal(0, 1, 0, pivot, 0)
  camUpX = TFormedX()
  camUpY = TFormedY()
  camUpZ = TFormedZ()

  Local bCos#, bSin#
  Local bRightX#, bRightY#, bRightZ#
  Local bUpX#, bUpY#, bUpZ#

  ;Update the billboard objects.

  For b.TBillboard = Each TBillboard

    ;1) Update the properties of the billboard.

    b\roll = b\roll + BILLBOARD_ROTATION_SPEED

    ;b\life,
    ;b\scale,
    ;b\alpha etc.

    ;----------------------------------------------------------------------------------------------

    ;2) Update the geometry of the billboard based on the properties and the camera.

    ;Get the cosine and sine for rotating the "up" and "right" vectors of the billboard.

    bCos = Cos(b\roll)
    bSin = Sin(b\roll)

    ;Right vector.

    bRightX = ((camRightX * bCos) - (camUpX * bSin)) * b\scaleX
    bRightY = ((camRightY * bCos) - (camUpY * bSin)) * b\scaleX
    bRightZ = ((camRightZ * bCos) - (camUpZ * bSin)) * b\scaleX

    ;Up vector.

    bUpX = ((camRightX * bSin) + (camUpX * bCos)) * b\scaleY
    bUpY = ((camRightY * bSin) + (camUpY * bCos)) * b\scaleY
    bUpZ = ((camRightZ * bSin) + (camUpZ * bCos)) * b\scaleY

    ;Position and rotation.
    ;The vertices of the billboard quad are placed based on the sum (or subtraction) of the rotated right and up vectors.

    VertexCoords(b\surf, b\vID[0], b\posX - bRightX + bUpX, b\posY - bRightY + bUpY, b\posZ - bRightZ + bUpZ) ;Top left.
    VertexCoords(b\surf, b\vID[1], b\posX + bRightX + bUpX, b\posY + bRightY + bUpY, b\posZ + bRightZ + bUpZ) ;Top right.
    VertexCoords(b\surf, b\vID[2], b\posX + bRightX - bUpX, b\posY + bRightY - bUpY, b\posZ + bRightZ - bUpZ) ;Bottom right.
    VertexCoords(b\surf, b\vID[3], b\posX - bRightX - bUpX, b\posY - bRightY - bUpY, b\posZ - bRightZ - bUpZ) ;Bottom left.

    ;Colour.

    VertexColor(b\surf, b\vID[0], b\r, b\g, b\b, b\alpha)
    VertexColor(b\surf, b\vID[1], b\r, b\g, b\b, b\alpha)
    VertexColor(b\surf, b\vID[2], b\r, b\g, b\b, b\alpha)
    VertexColor(b\surf, b\vID[3], b\r, b\g, b\b, b\alpha)

    ;Texture.

    ;VertexTexCoords(...)
    ;VertexTexCoords(...)
    ;VertexTexCoords(...)
    ;VertexTexCoords(...)

    ;Etc.

  Next
End Function


RemiD

impressive ! :o

thanks for the link, and code example. :)


i am going to code my own version anyway (more primitive than yours, but still faster than using many separate surfaces / meshes...)