Skip to content

Instantly share code, notes, and snippets.

@StefanPetrick
Last active September 11, 2023 14:05
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save StefanPetrick/e6aeee3a7d75ee9f713b0d451fba6607 to your computer and use it in GitHub Desktop.
Save StefanPetrick/e6aeee3a7d75ee9f713b0d451fba6607 to your computer and use it in GitHub Desktop.
FastLED simplex noise and colormapping fully modulated by itself
void noise_noise1() {
CRGBPalette16 Pal( pit );
/* here is how the palette looks like:
DEFINE_GRADIENT_PALETTE( pit ) {
0, 3, 3, 3,
64, 13, 13, 255, // blue
128, 3, 3, 3,
192, 255, 130, 3, // orange
255, 3, 3, 3
};
*/
//modulate the position so that it increases/decreases x
//(here based on the top left pixel - it could be any position else)
x[0] = x[0] + (2 * noise[0][0][0]) - 255;
//modulate the position so that it increases/decreases y
//(here based on the top right pixel - it could be any position else)
y[0] = y[0] + (2 * noise[0][15][0]) - 255;
//z just in one direction but with the additional "1" to make sure to never get stuck
//(here based on the down left pixel - it could be any position else)
z[0] += 1 + ((noise[0][0][15]) / 4);
//set the scaling based on left and right pixel of the middle line
scale_x[0] = 8000 + (noise[0][0][7] * 16);
scale_y[0] = 8000 + (noise[0][15][7] * 16);
//calculate new noise data
uint8_t layer = 0;
for (uint8_t i = 0; i < Width; i++) {
uint32_t ioffset = scale_x[layer] * (i - CentreX);
for (uint8_t j = 0; j < Height; j++) {
uint32_t joffset = scale_y[layer] * (j - CentreY);
uint16_t data = inoise16(x[layer] + ioffset, y[layer] + joffset, z[layer]);
if (data < 11000) data = 11000;
if (data > 51000) data = 51000;
data = data - 11000;
data = data / 161;
noise[layer][i][j] = data;
}
}
//map the colors
for (uint8_t y = 0; y < Height; y++) {
for (uint8_t x = 0; x < Width; x++) {
//I will add this overlay CRGB later for more colors
//it´s basically a rainbow mapping with an inverted brightness mask
CRGB overlay = CHSV(noise[0][y][x], 255, noise[0][x][y]);
//here the actual colormapping happens - notice the additional colorshift caused by the down right pixel noise[0][15][15]
leds[XY(x, y)] = ColorFromPalette( Pal, noise[0][15][15] + noise[0][x][y]) + overlay;
}
}
//make it looking nice
adjust_gamma();
//and show it
FastLED.show();
}
// cheap correction with gamma 2.0
void adjust_gamma()
{
for (uint16_t i = 0; i < NUM_LEDS; i++)
{
leds[i].r = dim8_video(leds[i].r);
leds[i].g = dim8_video(leds[i].g);
leds[i].b = dim8_video(leds[i].b);
}
}
//find the right led index within a serpentine matrix
uint16_t XY( uint8_t x, uint8_t y) {
uint16_t i;
if ( y & 0x01) {
uint8_t reverseX = (Width - 1) - x;
i = (y * Width) + reverseX;
} else {
i = (y * Width) + x;
}
return i;
}
@TomFrost
Copy link

TomFrost commented Sep 1, 2023

This animation is really incredible. I'm running it on a WS2812b matrix and it's mesmerizing. I know it's been awhile since you wrote this, but I'm curious if you can point me in the right direction: The WS28xx family doesn't use a clock signal, it simply recognizes a small gap in transmission to indicate that the next bitstream should start at the first pixel again. I'm getting significant flicker in dimmer pixels, and there's no way to reduce the clock speed as your comment suggests.

From my testing it looks like the noise algorithm makes dimmer colors more susceptible to larger percentages of relative change, but I've had no luck in smoothing it out. I've tried tweaking the calculations directly, as well as a few over-the-top solutions to ignore rapid changes in dim pixels. Nothing I've tried thus far succeeds in averaging out that flicker without also negatively impacting the beauty of the rest of the transitions.

If you had any recommendations for what to play with to stabilize these flickers a little more, I'd deeply appreciate it! Thanks for sharing this :)

@StefanPetrick
Copy link
Author

StefanPetrick commented Sep 9, 2023

Hi Tom! The problem that you are encountering is the limitation of only 8 bit per color chanel. This means there are 256 brightness levels possible. Those steps are spaced in a linear fashion while the perception of the human eye is logarithmic. (A gamma correction trys to compensate for this) Even at full master brightness the resolution at the low end is not good - the difference between the lowest brightness levels are clearly visible. If now the master brightness gets reduced the remaining color resolution gets smaller and smaller - and the low and steps become more and more visible. This is a systemic limitation of 8 bit leds. Fancy techniques like temporal dithering are a possible workaround - but that needs way faster chipsets than WS2812.

Long story short there is not much you can do about it. This whole problem is unrelated to the data transmission speed which affects only the possible framerate. For high end visual quality it needs eighter 16 bit leds (provides a better dynamic range) or superfast 8 bit leds (like APA102) where you can run temporal dithering - basically an extra layer of pulsewidth modulation which smoothes out the brightness difference between neighboring brightness values.

I hope that helps to understand what causes the unsteady results you are seeing a low brightness levels.

@TomFrost
Copy link

TomFrost commented Sep 9, 2023

Thanks for the comprehensive answers, that's hugely enlightening!

The bigger (at least perceptually) steps between shades of dim colors are something I've had success smoothing out in the diffuser design that I'm overlaying on top of my matrix, but what's still very evident is when an individual LED will be updated to be brighter, then darker, then brighter, and so on extremely rapidly over the course of a second or more as its overall brightness level changes. That's why I was a bit more focused on the noise algorithm, as the LED is being told "1, 4, 2, 5, 3, 6" instead of "1, 2, 3, 4, 5, 6". But my attempts to detect and smooth such changes range from "not smooth enough to be effective" to "so smooth that intentional changes in direction are delayed enough to ruin the effect". But I suppose if it were possible to write an over-the-top algorithm to predict which changes are incidental and likely to reverse vs. not before the future data is known, the stock market would never be the same.

Appreciate your help and incredible work!

@StefanPetrick
Copy link
Author

I've 3 thoughs regarding this.

A) The quality of the implementation of the noise algorithm matters. FastLED noise is optimized for performance on 8 bit AVRs, not for quality. Currently I'm a huge fan of this floating point implementation for 32 Bit ARM:
https://github.com/StefanPetrick/animartrix/blob/main/noise.ino
(Please note that this runs only on a hardware FPU performant (Teensy 4, ESP32 S3), when the float functions are emulated in software it's really slow.)

B) I always aim for a high framerate = efficient code that runs as quick as possible. Not because I really care if the result is 30 fps or 300 fps, but with a low fps rate the brightness could be "1, 5, 2", while on a higher fps rate the same sequence would be "1, 2, 3, 4, 5, 4, 4, 3, 2" which looks smoother, even when it's very fast. Basically I aim that the difference between 2 frames for the same led is a brightness difference of only 1.

C) When working with noise I aim to not zoom out too far because this leads to pixels jumping in and out of existence. The smallest visual detail should not be smaller then the distance between 2 leds - so that a moving dot is always visible and never appears black because it's temporary inbetween 2 pixels.

I hope that helps to write smoother animations. And thanks for your kind words.

@TomFrost
Copy link

TomFrost commented Sep 11, 2023

Fantastic! Your Animartrix work is mindblowing and hugely educational. Thanks so much for open sourcing that! I'm working on a v1 ESP32 right now-- I have a couple S3s headed my way though, so I'll be interested to see how they perform with the implementation you linked.

Great thoughts on B and C as well-- B in particular might be the cause of some of my smoothing issues. If my calculations slow down the FPS enough, transitions that were normally smoother might start flashing without the FPS drop being entirely obvious yet. I hadn't considered that at all.

Really appreciate you taking the time to share your wisdom here -- thank you!

@StefanPetrick
Copy link
Author

You're welcome! When you get some nice results please show it to the community in the r/FastLED subreddit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment