-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LEDs: add rainbow effects #11
base: main
Are you sure you want to change the base?
Conversation
Doesn't look that great with the camera I have, but this'll do for a preview. rainbow_cycle_vid.mp4rainbow_fade_vid.mp4 |
fae1b23
to
fae1b87
Compare
Here's the RGB Fade using rgb_fade_vid.mp4 |
Ok, it doesn't really go through all the colors very consistently though, I also noticed it tends to just return shades of red, green and blue. It's a deficiency of the |
yep, in that case I’ll rename it back to “Rainbow Fade” and I’ll have a crack at improving |
fae1b87
to
fae18f7
Compare
After trying out a bunch of different ways to interpolate the colours (tried smoothing RGB, tried HSV values, etc) I found that this provides a much more "natural" looking fade between the colours of the rainbow. Again, my camera isn't the best, it looks much better in person, but here's the demo of it: rainbow_fade_vid.mp4 |
fae18f7
to
fae1882
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, the colors look way better, thank you.
The change to color_wheel()
definitely deserves to be in its own commit, please.
Besides that, a couple comments. If at some point you'd like to exit the rabbit hole, let me know (I'd probably try to finish it off myself at some point).
Fix: improved accuracy of generated rainbow colors for LEDs
fae1882
to
fae191d
Compare
Alrighty, I separated the commits, and had a crack at creating an optimised version which avoids the trig functions, and instead uses a bunch of multiplications with a few divisions. It almost perfectly matches the previous implementation. I'm not really sure how I can profile these changes though? Aside from statically analysing it myself? If you have any pointers I'm all ears! I've been using this tiny tool I created to test the LED colours without actually having to write the new package to my wheel: https://github.com/acheronfail/led_test since writing it over bluetooth each time was getting rather cumbersome... |
Using "Solid" on the headlights and "Rainbow Cycle" on the taillights can replicate the "Mullet" setting on the Float package. Using "Rainbow Cycle" on both replicates the "Rave" effect from the Float package. Feature: add Rainbow Fade and Rainbow Cycle effects
You've already done more instrumentation than I'd bother with 😀 I'm not sure how representative the profiling can be on x86_64, given all the differences, and assuming you're running and measuring a lot of iterations. What you could do is running the code in a tight loop on the VESC and monitor CPU usage, which you can see either in LispBM scripting or possibly in Terminal when you list threads (where it's time-based). There may be other ways too, these are the simple ones I know of. If you could provide performance measurements for the three implementations that'd be great. IMO if you set up the loop so that you get CPU load between 20-80% for all three versions and just record the percentage from LispBM it'll be a very representative result. As for the Taylor Series, they should be good enough. I still think it could possibly be quite a bit simpler (my single quadratic function looked very close) but I haven't done the hard work so I'll leave that up to you unless the performance is somehow bad. Thanks for the work you're putting into this! |
Thinking about this, it's not correct, or rather not trivial to do right. But the old "run in a loop and measure time" can be done quite easily on the LED thread:
I measured the following: So the sinf is about 230x slower 😁 (I also tried to remove the The Taylor series implementation is faster than I thought :) Having another look at it, it seems the Otherwise, no issue with the code. Just tested it and to me it seems the Last note, on speed. I think we should set some consistent speed baseline (as makes sense for each effect). E.g. the |
Oh, awesome results!
Question, where do I see these logs? Is there somewhere in VESC Tool?
Have a look at this commit and tell me what you think: acheronfail@fae1078 - I changed
The Taylor Series logic is approximating sine, I think the reason we don't see the red/green/blue is because of the offsets between the colours. For example, to get red we'd need only the red calculation to be at the top of the curve, while the other two are lower. But in its current state, I've been offsetting each of the colors about 1/3 away from each other on the curve, so there won't be a time we see the primary colours...
I agree with that, I also changed that in acheronfail@fae1078, so if you think that looks good I'll bring that into this branch and split things into their own commits. |
Yes, in the LispBM Scripting tab under Dev Tools, which also allows to restart the package, watch CPU and RAM usage and run lisp.
Well that's one thing, another thing is you're converting input to the 0-tau range, but if the Taylor series was made to work with the input verbatim this wouldn't be needed. It's just a small thing though.
Your Taylor Series is not approximating sine, it's approximating a
I'm still a bit undecisive on the timing thing - what should be the baseline? I mean making everything have 1s period makes some sense, but for some effects it'll be too fast (e.g. your rainbow fade going through all colors in 1s will likely not be very good). Other effects, e.g. the Knight Rider have a defined "standard" speed. Maybe we should use 1s period for the blinking effects and just something that works well for the other ones. If you don't mind, update the timing of the effects in this PR to what you think is best and we can later fix up Felony to have consistent timing (other effects might need updating too, I can handle that). I'm not at home this week, my responses might be slow. I'd like to play with the |
My apologies, I didn't write that clearly enough. I was referring to my latest commit, which I linked from the previous post. If you look here: https://github.com/acheronfail/refloat/blob/fae10786a56c8ce62171f9d053447a8e2192617d/src/leds.c#L103-L123 the Taylor Series does indeed approximate sine, and I also readjusted the calculations so
I'm not 100% sure either, but we can decide that later I think.
I'd also like some improvements to |
If we assume the LEDs are linear and have the same primaries as sRGB, we could use Björn Ottosson's oklab for some nice gradients? Could look something like this: static uint32_t color_wheel(uint8_t pos) {
float lightness = 0.75f;
float chroma = 0.1275f;
float hue_rad = TAU * pos / 256.0f;
float a_ = chroma * cosf(hue_rad);
float b_ = chroma * sinf(hue_rad);
float l_ = lightness + 0.3963377774f * a_ + 0.2158037573f * b_;
float m_ = lightness - 0.1055613458f * a_ - 0.0638541728f * b_;
float s_ = lightness - 0.0894841775f * a_ - 1.2914855480f * b_;
float l = l_ * l_ * l_;
float m = m_ * m_ * m_;
float s = s_ * s_ * s_;
float r = +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s;
float b = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s;
float g = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s;
return ((uint8_t) (r * 255) << 16) | ((uint8_t) (g * 255) << 8) | (uint8_t) (b * 255);
} I haven't properly tested the code (I have an ADV), but after trying it in Blender I got this gradient: The values for lightness and chroma were eyeballed to be as large as possible without any of the output channels clipping. Trig functions could use that Bhaskara approximation or something. It doesn't go through 100% red, green and blue, because if it did it couldn't be perceptually uniform. Here's a cool website that demonstrates how it works as a color picker. |
Oh nice, I'll have to learn how to do that with Blender, being able to visualise that is really cool! Tonight I'll try out this code on the VESC and report back how it goes. |
@aeraglyx I had a crack at implementing it, this was my code: // Generate colors using the OKLAB colorspace, see: https://bottosson.github.io/posts/oklab/
static uint32_t color_wheel(uint8_t pos) {
// See: https://oklch.com/#75,0.13,360,100
float lightness = 0.75f;
float chroma = 0.1275f;
float cos_pos = 2 * (float) pos / 256.0f;
float sin_pos = fabsf(cos_pos - 0.65f);
float a = chroma * (2 * cosine_progress(cos_pos) - 1);
float b = chroma * (2 * cosine_progress(sin_pos) - 1);
float l_ = lightness + 0.3963377774f * a + 0.2158037573f * b;
float m_ = lightness - 0.1055613458f * a - 0.0638541728f * b;
float s_ = lightness - 0.0894841775f * a - 1.2914855480f * b;
float l = l_ * l_ * l_;
float m = m_ * m_ * m_;
float s = s_ * s_ * s_;
// convert to lRGB colorspace
float lr = +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s;
float lg = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s;
float lb = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s;
// and finally to RGB
float rgb_r = lr > 0.0031308f ? 1.055f * powf(lr, 1.0f / 2.4f) - 0.055f : lr * 12.92f;
float rgb_g = lg > 0.0031308f ? 1.055f * powf(lg, 1.0f / 2.4f) - 0.055f : lg * 12.92f;
float rgb_b = lb > 0.0031308f ? 1.055f * powf(lb, 1.0f / 2.4f) - 0.055f : lb * 12.92f;
return ((uint8_t) (rgb_r * 255) << 16) | ((uint8_t) (rgb_g * 255) << 8) |
(uint8_t) (rgb_b * 255);
} And here's what it looks like: oklab.mp4It does seem to cycle nicely, but I find the greens too bright compared to the rest (the glare in the video shows that a little bit). I had a decent crack at trying to tweak it, but with OKLAB it appears that those greens are quite bright. |
Hey, I don't have time to share now, but I had a go at tweaking @acheronfail's original attempt a couple days ago to reasonably satisfactory results. I also did a very quick test of the OKLCH, I'll post a comparison. |
@acheronfail Thanks for checking it out! Why is the sRGB transform at the end needed? I sort of expected the LEDs use something like PWM for brightness, in which case it should just stay linear I think. The green channel peak in the oklab sweep is noticeably darker to compensate for green being perceived too strongly, but the ~1/2.2 gamma would make them more equal at the peaks if that makes sense (so green would seem relatively brighter again). Could be wrong though.. And just for fun, here are the individual channels plotted against hue: So maybe the simplest solution would really be just scaled sine waves. If oklab ends up working, I tried to very roughly match it with cosines on Desmos. |
So, first on my tweak of @acheronfail's original approach, to me it seemed that due to nonlinearities of the LEDs (and/or possible other factors, I don't really know) the sine blending wasn't working well and I perceived the sine waves need to be kind of squished. So instead of feeding them a linear function You can see a regular sine wave as blue, orange is the "transition" of I applied this with different exponents to the three channels to create a uniform rainbow pattern. The exponents can be easily tweaked to shift the widths of the particular colors. Here's a demonstration of the result with direct comparison to @aeraglyx's okchl implementation: PXL_20240828_101805695.TS.mp4(first is my modification, which is switched to okchl and then back and forth again) So my modification is for the most part quite uniform and shows all the hues with relatively uniform widths. It shows more saturated colors and the yellow in particular is quite a bit wider than in okchl. okchl colors are more muted (mentioned by @aeraglyx as intentional). I quite like it in this particular rainbow effect, but I was thinking about this and I thought we could use this hue generating function in the future to specify the colors in the config (instead of the fixed enum of colors that is there now). In effect we'd be expressing the colors using hue (stored as 1 byte), chroma and lightness. I'd like it to cover all the possible colors that can be achieved with the RGB LEDs as well as possible. Maybe okchl is the way to go, using higher chroma to get the pure primary colors (which the proposed color_wheel doesn't do)? Maybe the hack I came up with will work better in practice? I mean ideally we'd somehow transform the LED input to make them close to linear, but I felt it's not possible with a simple gamma function (hence I settled with a simple quadratic function for gamma, which I found not being great). Anyway, code for my modification as well as the okchl function used in the demo is here: https://github.com/lukash/refloat/tree/color-wheel You can use the Hope the above makes sense, it's late, I'm tired and don't have too much time for this... 😅 |
Perhaps I implemented incorrectly at the beginning, but I found that the original transformation caused very dim and unpleasant effects - I then looked up some libraries that supported okchl and saw that they converted to sRGB after a similar transformation, which provided slightly better results... That said, looking at @lukash's demo, I think it was a case of PEBKAC. 😅 I do quite like the effect you created there, @lukash, with the LEDs showing a rainbow one by one. Would you be happy to merge this in if I took that combined with the okchl, and then made it cycle left-to-right through the rainbow? I think that would at least be a good start.
I think this is a good idea, but probably a future improvement and not for the addition of these rainbow effects? |
Oklch can do all sRGB colors (including primaries), but not all LCH combinations yield valid colors, so the final RGB values need to be clipped in some way. There is Okhsv, which pretty much solves that, but it's more complex and compromises other things. I definitely like the idea of specifying config colors in some LCH/HSV space instead of enums. Even if the typical HSV was used, rainbows could still leverage the improved method (looks pretty good!) or Oklch. The reason I brought up Oklab in the first place was that I don't really like how RGB strips and stuff usually "pulse" with changing hue (blue is dark etc.), maybe the rainbow's output channels could be scaled a bit to compensate for that? But that's just a tiny nitpick, also who knows how much this will vary across different hardware... |
Sorry, I don't have the time right now to commit to finishing this at the moment (that, and the LEDs on my board aren't working very well either, so that makes it difficult to write LED code! 😅) In the meantime I've converted this PR to a draft to indicate it's not ready. If anyone else wants to pick it up please do, but for the time being I'm putting this on the shelf! |
@acheronfail no worries. I've been slowly messing with the OKLCH implementation and wanna come up with a decent |
Setting the headlights to
Solid
with white, and then setting the taillights toRainbow Cycle
allows users to replicate theMullet
effect from the Float package.Using
Rainbow Cycle
on the front and the back replicates the "Rave" effect from the Float package.Rainbow Fade has also been added, which was inspired by the Float package's "RGB Fade" effect.Renamed to
RGB Fade
after usingcolor_wheel
.