Non-Shader Old-School Plasma Effect using Texture2D.SetData

Just wanted to share this as I’m kinda chuffed about it. Obviously, yes, I know, please don’t tell me, a shader is better for this. But nontheless here’s my monogame plasma effect based on this old tutorial:
https://lodev.org/cgtutor/plasma.html

You can import colors from a texture or use HSV to create a palette. I made my palette in photoshop using the gradient tool on a 256 x 1px image. If you create an instance and pass in a texture, the colors of that texture will be used to color the plasma, otherwise it will use straight max saturation rainbow colors. The for loop to create the palette in this way can be manipulated to restrain the colors used.

I’ve also included a HSV to RGB color space function. I’m not totally sure it’s correct however.

The plasma pattern is generated once in the constructor and then animated with palette cycling. You can also regenerate the Sin functions with time as a factor every frame for more impressive animation, but I’m not doing that here. Performance tips to this end would be appreciated (lookup tables??)

You could get very creative here, maybe adding the alpha channel to the mix. In the version I’m using in my game I’m masking the final texture as well by passing in an extra texture, but I’m not totally happy with my implementation yet as it has to re-mask every frame.

Also, there’s only a Draw function. You can break out the work from the Draw function into an update function, but then you have to worry about timing a bit more, so it may or may not make sense to do so.

This is the first time I’ve ever tried something like this, so any tips are appreciated.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using static System.MathF;
namespace Graphix {
public class Plasma
{
	public readonly int width, height;

	Texture2D texture;

	int[] plasma;
	Color[] palette, buffer;

	public Plasma(int width, int height, GraphicsDevice graphicsDevice, Texture2D paletteImport = null) {
		this.width = width;
		this.height = height;
		
		//create the texture to write to each frame. Using the Color struct makes our lives easy.
		texture = new(graphicsDevice, width, height, false, SurfaceFormat.Color);

		if (paletteImport != null) {
			palette = new Color[paletteImport.Width * paletteImport.Height];
			paletteImport.GetData(palette);
		} else {
			//Rainbow palette. Try limiting the values and doing different stuff here.
			palette = new Color[360];
			for (int i = 0; i < palette.Length; i++) {
				palette[i] = HSVtoRGB((i) % 360, 1, 1);
			}
		}

		plasma = new int[width * height];
		buffer = new Color[width * height];

		float gray = 128.0f; // h

		//initial plasma pattern generated here, once, by adding Sin functions.
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				int value = (int)(
					gray + (gray * Sin(x / 30.0f))
				  + gray + (gray * Sin(y / 50.0f))
				  + gray + (gray * Sin((Sqrt(((x - width / 2.0f) * (x - width / 2.0f) + (y - height / 2.0f) * (y - height / 2.0f))) / 20.37f)))
				  + gray + (gray * Sin((Sqrt(x * x + y * y) / 30.4f)))
				) / 8;
				plasma[(y * width) + x] = value;
			}
		}

		texture.SetData(plasma);

	}

	public void Draw(SpriteBatch spriteBatch, GameTime gameTime, Rectangle destination, Rectangle source) {
		//30fps. This can be optimized so the for loop only computes when there's a new t value.
		int t = (int)(gameTime.TotalGameTime.TotalSeconds * 30.0f);
		//animation created through palette cycling
		for (int i = 0; i < width * height; i++) {
			buffer[i] = palette[(plasma[i] + t) % palette.Length];
		}
		texture.SetData(buffer);
		spriteBatch.Draw(texture, destination, source, Color.White);
	}

	/// <summary>
	/// Takes a colour in HSV colour space and returns it in RGB space as an XNA Color object.
	/// </summary>
	/// <param name="H">'Hue' between 0 and 360.</param>
	/// <param name="S">'Saturation' between 0 and 1</param>
	/// <param name="V">'Value' between 0 and 1</param>
	/// <returns></returns>
	public static Color HSVtoRGB(int H, double S, double V) {
		double C = V * S;
		double X = C * (1 - Math.Abs((H / 60.0) % 2 - 1));
		double m = V - C;

		int r, g, b;
		if (H < 60) {
			r = (int)((C + m) * 255);
			g = (int)((X + m) * 255);
			b = (int)(m * 255);
		} else if (H < 120) {
			r = (int)((X + m) * 255);
			g = (int)((C + m) * 255);
			b = (int)(m * 255);
		} else if (H < 180) {
			r = (int)(m * 255);
			g = (int)((C + m) * 255);
			b = (int)((X + m) * 255);
		} else if (H < 240) {
			r = (int)(m * 255);
			g = (int)((X + m) * 255);
			b = (int)((C + m) * 255);
		} else if (H < 300) {
			r = (int)((X + m) * 255);
			g = (int)(m * 255);
			b = (int)((C + m) * 255);
		} else {
			r = (int)((C + m) * 255);
			g = (int)(m * 255);
			b = (int)((X + m) * 255);
		}

		return new Color(r, g, b);
	}

} }

So apart from this being terrible idea, why is there GetData? You already created those data on CPU, why you are retrieving them from GPU?

While I agree the OP doesn’t need the GetData, it seems like is a fun experiment. It’s like writing a software 3d renderer, it’s about the learning experience.

I doubt this is better for him than learning how to do it in actual shader

GetData is only used to make an array of Colors from a normal PNG. As I said, I’ve never attempted anything like this before so please let me know how I should have done this oh wise shader master.

Ppl say it’s best to do as much computing as possible in the update method and then use that data to blast out the drawing as fast as you can in the Draw method.
So; I might try moving this part to the Update method (it may only be a tiny performance improvement):

Otherwise, I would expect it to perform ok (I seem to remember ppl saying that using SetData could cause a slight performance drop - maybe not as much as it used to though).

Btw - I’m glad I stumbled across this. Now I want to try one myself - plasma is cool :wink:

EDIT:
Er wait… would it be better to SetData in Update or Draw? I’m not sure. It’s locking VRAM to write to… so maybe that is better to do in Draw method? Anyone?
I suppose you could test to see with has better FPS (with 60fps limit turned off).

1 Like

Ideally yah, this might be fun to do on a shader once you know it does what you want. I might try that out too if I find time. :slight_smile:

Since the frames are generated sequentially (at least with my simple setup), if you want to put the work into an Update function, you have to make sure the timing is correct with a countdown or something, so the benefit is a bit dubious maybe, because you won’t gain anything from cramming more update calls between draw calls. Maybe batching frames somehow or pre-computing them would be best. Especially with the palette cycling, it’s more or less a loop so re-generating over and over again is certainly inefficient, but the idea of course is to introduce more generative variables / randomness.

1 Like

Here’s what it looks like btw, with this custom palette:
palette_pinknoise

https://imgur.com/a/LgQK12a

I used to program little graphical demos in QBasic so I did stuff like this all the time. Palette rotating is fun, nice work!

As a next step, you might actually want to try doing something like this in a pixel shader just for fun. You can pass your palette in as a parameter, as well as an index offset, and output the colour based on a lookup conversion.

Have fun!

1 Like