Wave Rendering

A place for Ren'Py tutorials and reusable Ren'Py code.
Forum rules
Do not post questions here!

This forum is for example code you want to show other people. Ren'Py questions should be asked in the Ren'Py Questions and Announcements forum.
Post Reply
User avatar
Posts: 11
Joined: Sun Nov 01, 2020 10:50 pm

Wave Rendering

#1 Post by XT9K » Tue Apr 20, 2021 4:39 pm

Howdy ya'll I'm back after several months with a new little thing I've been tinkering with.
Think I'ma call it Wave Rendering.
For those who want it, you can get a copy of the code from either the Wave Rendering Itch Page or from the Github.

Important Notes:
  • For reasons that I'll get into later in this post, this was made without the use of Shaders. So all of this runs on the CPU instead of the GPU. The effects made by this code would be much more efficient as a Shader and I'd recommend for anyone using Ren'py 7.4 and above, specifically the model-based rendering, that you use Shaders instead. You'll likely get far better performance out of those than with this.
  • Given the above, be aware that this can cause a lot of lag on some systems. If it's only having to deal with a couple of strips, it should be fine. But when you get into the hundreds of strips to handle, it can become a performance bottleneck.
  • Later into this I'll provide a rundown on ways to mitigate these issues should they arise for you.
Couple months, back was inspired by this video.
Got me thinking about if I could replicate those effects in Ren'py as well. Moreover, the project (shameless plug) I've been working on the last couple of months has opted to stick to Ren'py 7.3.5 for various reasons. So I had to develop this without the use of Renpy 7.4's shaders. But I think my solution is at least moderately clever and some may be able to get some usage out of it.

So the effects to emulate were:
  • Palette Cycling
  • Translation
  • Horizontal Oscillation
  • Interleaved Oscillation
  • Vertical Oscillation
  • Transparency
Now we can cross off Transparency pretty easily since Ren'py already supports alpha channels in images and what not. So I won't go into that much. Similarly, Translation can be achieved in a number of ways. While I did implement it, I feel the exact way one might want to tackle that can vary. Some may prefer to use ATL for that and all the power to you. So that leaves us with the various Oscillations
How it Works
The basic idea is to emulate the scanlines of a CRT. By breaking up the render of an image (and later, any other displayable we want) into several thin strips, we can apply offsets to each one. Then it's just a matter of having the offsets follow a wave pattern. Allowing for interesting distortions upon the render. This constitutes the Horizontal Oscillation in the video. To achieve Interleaved Oscillation, we just need to flip the sine wave over 0. And for the Vertical Oscillation, we need only offset which row we take the strip from.
Additional Functionality
All that is well and good. Now let's get into some additional functionality that Ren'py allows us.
  • Unlike the SNES, we're not limited to only horizontal strips. We can apply this principle with vertical strips as well! You can enable this style with the WaveImage class by providing horizontal = False as a parameter.
  • Interleaved Oscillation usually creates holes in the image. While interesting when used on sprites or things shown above a background, this can create issues when used as your primary background. A good way to get around this though is to apply the same strip on both waves so they can mask each other. You can enable this by providing the double = True in WaveImage. If you just want Interleaved Oscillation however, just have to provide double = "interleaved" as a parameter instead.
  • I've also included a way to reduce or increase the amount of the horizontal offset with each strip with the damp parameter for WaveImage. This works by multiplying the offset by a variable called extreme each strip. Extreme starts out at 1.0 but will approach damp with each strip.
  • Extreme can come in and out as well. Making it so the wavey effect starts and stops on an interval. You can enable this by including extreme_sine = True. However, to simplify things, if damp is set to around 1, the default, then the starting value of extreme is used throughout instead of extreme trying to target damp when it's at 0. If damp is set to anything else, extreme will continue to target it, regardless of the starting value. If you'd prefer different behaviour, you may want to modify this.
  • Since extreme changes with each strip as the process proceeds, I've made it so you can specify a direction boolean, which will dictate which side the stripping process begins on.
  • We can also do the stripping at arbitrary angles by making use of Transforms. By rotating the initial render, then applying our strip offsets, and then undoing our original rotation, all the strips will have been applied at an angle. I've included the WaveImageUnrotate and WaveImageRotate classes to demonstrate this.

    It should be noted though that the use of Transforms adds some overhead to the rendering process. So one may want to set strip_height to > 1 for this. There can also be some artifacts as well. Mostly in the form of slight holes between the strips. If I find a better fix for this in the future I'll update the project.
For more on the parameters and functionality, feel free to check the code. As I've documented all the parameters for each of the classes and what each one does.
Lag Reduction
In the Important Notes section, I mentioned that much of this can cause lag when used on large images or on lower-end devices. If you're finding this to be an issue for you. Here's a couple of ways you can try and reduce the lag it generates.
  • Increasing the "height" of each strip will cut down substantially on the amount of time it takes to render, at the cost of some smoothness. But even increasing the strip height from 1 to 2 will half the amount of time it takes. For the WaveImage class, just include strip_height = [int] as a parameter to make use of this. It's also recommended that if you're targeting mobile, to maybe even automatically do this within the class.
  • Make sure the strips are going across the smallest side. If an image is wider than it is tall, horizontal strips are recommended. And vertically if it's taller than it is wide.
  • Only strip as much as you need. If you only want to have the effect cover part of an image, then provide a start and end number so it only covers that range.
  • Applying this to a smaller image and then scaling it up to the size you want can help a lot. Especially if you're doing pixel art for your game since using nearest_neighbor during the zoom will keep each pixel crisp. This is what I did with the Earthbound backgrounds I used in the demo.
Bonus: Palette Cycling
Some of you may have noticed I omitted Palette Cycling up till now... That's mostly because I didn't know how to fit it in with the other wave stuff, which became more and more the focus as I worked on this. And calling it just "Earthbound Battle Effects for Ren'py" felt a bit too simplistic. As you can see, this is capable of a bit more than Earthbound was capable of on the SNES. The wave stuff really felt like the focus so decided to lean into that angle.

However, in order to be feature-complete with the video, I've decided to include it as well. It's pretty basic. Just need to have your image broken down into several layers of pixels that will take on each color in the palette. And then provide the list of colors to cycle through. You can check out the example in the project if you'd like to give it a try.
Phew... This took me a while to write. If any of you have any questions that I failed to address, please feel free to ask or check the code for yourself. And let me know of any bugs you'd like to see fixed or additional ideas on what to do with it. In the future, I'll likely make a shader that can do all of this as well. I already have started work on that. But for some reason, the shader wasn't working. Whenever I do get it working though, I'll probably put it's own forum post and call it Wave Shaders or something. If you missed the links to where you can get the code for all this: In addition, I've put out an update to my Kinetic Text Tag stuff. I'll be updating the forum post to that soon. But if you'd want I've already uploaded the new version onto itch here.

Post Reply

Who is online

Users browsing this forum: No registered users