Classic 2D water ripple

Previous topic - Next topic

Wampus

So, I did a quick routine to create a classic 2D water ripple effect. The code, exe and test image are all included in the attachment.



On my 32GB iPod Touch (3rd generation - quite powerful) it really struggles, reducing framerate to a crawl and is thus not usable for anything that requires 60 FPS.

Is anyone aware of a way to do this kind of ripple effect without having to load and then draw a sprite as a line-by-line animation? Although covering the screen line by line could be very fast, if that was all that was involved, having to use the DRAWSPRITE command to do it is considerably CPU expensive. This kind of ripple effect was possible without too much trouble even on 16-bit computers but calling DRAWSPRITE 480 times to fill the screen even slows the FPS on my netbook.

I don't need any alpha-blending, collision detection or anything like that so is there any way to do what I've done without DRAWSPRITE?

Code (glbasic) Select
LOADANIM "test.png", 1, 340, 1

GLOBAL waters[]
DIM waters[480]
GLOBAL f = 0

FOR n = 0 TO 3

FOR i = 0 TO 119

waters[i+(n*120)] = 5*SIN(f)

INC f
INC f
INC f

IF f >= 360 THEN f = f-360

NEXT

NEXT


f = 0


// MAIN LOOP
WHILE KEY(01) = FALSE
//MAIN LOOP


FOR i = 0 TO 479           

    DRAWANIM 1, i+5-waters[f], waters[f]-5, i            // Draw every single line as a sprite

INC f

IF f = 480

    f = f - 480

ENDIF

NEXT

DEC f

SHOWSCREEN

WEND

END


[attachment deleted by admin]

matchy

That's nice although I would not use it for a game cycle. Maybe for a menu or title screen!!!!

Cliff3D

Hmmm... sounds like you could do with a "cheaper, faster" sprite routine, with much of the cleverness ripped out for speed. Or inline C/C++ to achieve the same results...

but how about this. You're doing a fair bit of math there, and I suspect that the CPU isn't that great at math. If you move the math out to a function and the sprite drawing to another function you could use the profiler to see if there is significant time being spent on the math. If there IS significant time being spent on the math, you MIGHT be able to improve on the time taken per frame by pre-calculating values and storing them in an array? Maybe a 2-dimensional array of floats would be quicker to access than recalculating each time?

Other little optimisations all help. For example, change:

INC f
INC f
INC f

to

INC f,3

Wampus

I think I'll mention a cheaper, cruder and faster image copying routine at the requests forum. It could be helpful in circumstances like these.

Good suggestions Re: the math. In this case I already use an array to hold pre-calculated Sine multiples for how far the screen should distort. However, there are always a few things that can be done to optimise.

What I've been avoiding and should now think about facing is learning some C++! Many years ago I used to compliment my STOS Gaming Basic programs with 68000 assembly routines where I needed a speed boost on something simple. These are oddly similar circumstances. I'm reasonably experienced with Java and OOP so how hard could C++ be?

Ian Price

I came. I saw. I played.

Scott_AW

#5
Try using polyvector and newpolystrip.

You can load the image as a solid sprite and in your for-loop, change the texture position with the x position.

Surround the loop with the startpoly and endpoly, then put this in place of the sprite bit


newpolystrip
polyvector  i+5-waters[f], waters[f]-5, i-1,0, rgb(255,255,255)
polyvector  i+10-waters[f], waters[f]-5, i,0, rgb(50,50,155) 
polyvector  i+10-waters[f], waters[f]-5+480, i,480, rgb(50,50,155)
polyvector  i+5-waters[f], waters[f]-5+480, i-1,480, rgb(255,255,255)

Also using the color you create a interesting fadeoff.


Okay I was wrong about the fadeoff and used one additional poly vector more than  I should of, but here's the same code again with polyvector and a gradient routine simply applied.

Code (glbasic) Select
// --------------------------------- //
// Project: watereffect2d
// Author: Scott A. Williams
// Based off code provided by 'Ragaril' from GLBasic forums
// Start: Wednesday, December 31, 1969
// IDE Version: 8.078

LOADSPRITE "test.png", 1

SETSCREEN 340, 480, 0

GLOBAL water[]

DIM water[480]

GLOBAL f = 0, c1 = 255, c2 = 0

FOR n = 0 TO 3
  FOR i = 0 TO 119
    water[i+(n*120)] = 5*SIN(f)
    INC f, 3
   
    IF f >= 360 THEN DEC f, 360
   
  NEXT
NEXT

f = 0

WHILE KEY(1) = FALSE
  STARTPOLY 1, 2
  FOR i = 0 TO 479
    c1 = 255 - (255 * (i/480))
    c2 = 0 + (255 * (i/480))
    IF i > 0 THEN POLYNEWSTRIP
      POLYVECTOR water[f]-5-340, i+1, -340, i+5-water[f]+1, RGB(c1,c1,c2)
      POLYVECTOR water[f]-5+340, i, 340, i+5-water[f],  RGB(c1,c1,c2)
      POLYVECTOR water[f]-5+340, i+1, 340, i+5-water[f]+1,  RGB(c1,c1,c2)
   
    INC f, 1
   
    IF f = 480 THEN DEC f, 480
   
  NEXT
  ENDPOLY
  DEC f, 1
  SHOWSCREEN
WEND

END
[/cude]
Current Project, Orbital Contract Defense
http://gamejolt.com/games/adventure/code-name-ocd/9887/

BlackShadow now open source/resource(requires duke3d)
http://gamejolt.com/games/adventure/black-shadow-3d/9885/

Schranz0r

I <3 DGArray's :D

PC:
AMD Ryzen 7 3800X 16@4.5GHz, 16GB Corsair Vengeance LPX DDR4-3200 RAM, ASUS Dual GeForce RTX™ 3060 OC Edition 12GB GDDR6, Windows 11 Pro 64Bit, MSi Tomahawk B350 Mainboard

Wampus

Scott_AW your changes work great!  8)

Not only is the wavy pattern now totally smooth in appearance but its significantly faster than calling drawsprite 480 times. Using a wavy background for my PowFish game is now netbook friendly! My iPod Touch is still dropping to half frame rate but heck, I was starting to overload onscreen detail anyway.

Thanks!  =D

Scott_AW

Thanks for the great example.
Current Project, Orbital Contract Defense
http://gamejolt.com/games/adventure/code-name-ocd/9887/

BlackShadow now open source/resource(requires duke3d)
http://gamejolt.com/games/adventure/black-shadow-3d/9885/

Wampus

Thought I could update this thread with an improved routine. Using the code below still looks as nice but is far more optimised since it draws polys in steps of 16. Its even fast enough that I use a variation of it to display the backdrop for my PowFish game on the iPhone.

Code (glbasic) Select
// --------------------------------- //
// Project: Water Effect
// Start: Wednesday, September 08, 2010
// IDE Version: 7.341



SETCURRENTDIR("Media") // seperate media and binaries?
SETSCREEN 320,480,0 // iPhone size 320x480 Window
LIMITFPS 60 // refresh at 60 frames per second

LOADANIM "test.png", 1, 340, 1  // should be 340x by 500y

GLOBAL waters[]
DIM waters[640]
GLOBAL f = 0

FOR n = 0 TO 4

FOR i = 0 TO 119

waters[i+(n*120)] = 5*SIN(f)

INC f
INC f
INC f

IF f >= 360 THEN f = f-360

NEXT

NEXT


f = 0


// MAIN LOOP
WHILE KEY(01) = FALSE
//MAIN LOOP

STARTPOLY 1,1

FOR i = 0 TO 479 STEP 16

POLYVECTOR 0, i, waters[f]+5, i+5-waters[f], RGB(255,255,255)
POLYVECTOR 0, i+16, waters[f+16]+5, i+21-waters[f+16], RGB(255,255,255)
POLYVECTOR 320, i+16, waters[f+16]+325, i+21-waters[f+16], RGB(255,255,255)

POLYVECTOR 0, i, waters[f]+5, i+5-waters[f], RGB(255,255,255)
POLYVECTOR 320, i, waters[f]+325, i+5-waters[f], RGB(255,255,255)
POLYVECTOR 320, i+16, waters[f+16]+325, i+21-waters[f+16], RGB(255,255,255)

INC f, 16

IF f >= 480 THEN DEC f, 480

NEXT

DEC f

ENDPOLY

SHOWSCREEN

WEND

END

Albert

I've modified it a bit. Added some shade and optimized a bit (using triangle strip)

Note that you really want to use an image with 340x500!
Quote// --------------------------------- //
// Project: Water Effect
// Start: Wednesday, September 08, 2010
// IDE Version: 7.341



SETCURRENTDIR("Media") // seperate media and binaries?
SETSCREEN 320,480,0 // iPhone size 320x480 Window
LIMITFPS 60 // refresh at 60 frames per second

LOADANIM "test3.png", 1, 340, 1  // should be 340x by 500y

GLOBAL waters1[]
DIM waters1[640]
GLOBAL waters2[]
DIM waters2[640]
GLOBAL f = 0

FOR n = 0 TO 4
   FOR i = 0 TO 119
      waters1[i+(n*120)] = 5*SIN(f)
      waters2[i+(n*120)] = 5*COS(360-f)      
      INC f
      INC f
      INC f
      IF f >= 360 THEN f = f-360
   NEXT
NEXT

f = 0

// MAIN LOOP
WHILE KEY(01) = FALSE
   //MAIN LOOP
   STARTPOLY 1,2
   LOCAL c=waters1[f]*20+(255-100)
   LOCAL color1a=RGB(c,c,c)
   c=waters1[f+16]*20+(255-100)
   LOCAL color1b=RGB(c,c,c)
   c=waters2[f/2]*20+(255-100)
   LOCAL color2a=RGB(c,c,c)
   c=waters2[f/2+16]*20+(255-100)
   LOCAL color2b=RGB(c,c,c)   
   POLYVECTOR 320,          0,          waters2[f]+325,       5-waters2[f],         color2a
   POLYVECTOR 0,             0,          waters1[f]+5,          5-waters1[f],         color1a
   FOR i = 0 TO 479 STEP 16
      c=waters1[f]*20+(255-100)
      color1a=RGB(c,c,c)
      c=waters1[f+16]*20+(255-100)
      color1b=RGB(c,c,c)
      c=waters2[f/2]*20+(255-100)
      color2a=RGB(c,c,c)
      c=waters2[f/2+16]*20+(255-100)
      color2b=RGB(c,c,c)            
      POLYVECTOR 320,          i+16,       waters2[f+16]+325,       i+21-waters2[f+16],      color2b
      POLYVECTOR 0,            i+16,       waters1[f+16]+5,       i+21-waters1[f+16],      color1b
      INC f, 16
      IF f >= 480 THEN DEC f, 480
   NEXT
   DEC f
   ENDPOLY
   
   SHOWSCREEN
WEND
END

Wampus

#11
Lovely effect Albert.  :)

For some reason there were some jagged edges occurring. Look at the light beams going down into to water on the different versions for comparison. I wasn't sure why that was occuring but instead of working out why I just de-optimised the routine (sorry - I'm lazy) by modifying the older code with your new shading changes so that the POLY section looks like this: -

Code (glbasic) Select
STARTPOLY 1,1
FOR i = 0 TO 479 STEP 16

   LOCAL c=waters[f]*6+(255-30)
   LOCAL color1a=RGB(c,c,c)
   c=waters[f+16]*6+(255-30)
   LOCAL color1b=RGB(c,c,c)

POLYVECTOR 0, i, waters[f]+5, i+5-waters[f], color1a
POLYVECTOR 0, i+16, waters[f+16]+5, i+21-waters[f+16], color1b
POLYVECTOR 320, i+16, waters[f+16]+325, i+21-waters[f+16], color1b

POLYVECTOR 0, i, waters[f]+5, i+5-waters[f], color1a
POLYVECTOR 320, i, waters[f]+325, i+5-waters[f], color1a
POLYVECTOR 320, i+16, waters[f+16]+325, i+21-waters[f+16], color1b

INC f, 16
IF f >= 480 THEN DEC f, 480
NEXT
DEC f
ENDPOLY