I saw a couple of articles earlier about the Fizzlefade effect from Wolfenstein 3D. It's a transition effect that draws over random pixels, but never draws over the same pixel twice.
I thought this was a cool trick, and I wanted to try it out and see it in action, so I implemented the Feistel network technique from antirez's article. Posting here in case anyone likes the effect and wants to recycle the code.
Nice! I used your implementation for the title screen in Picolarium. I could probably just have hardcoded a sequence too, since I did a tile-based fade there, but this way is much cooler :) Thanks!
I noticed there's sort of a visible snap when the color finishes and starts moving to the next one.
I tried switching colors every half-period instead and it looks nice, albeit a little more rough overall:
Attempt #2:
Transition colors every 3/4 period. Good balance of eliminating the snap and the induced roughness in the previous attempt.
Also might work to ease in the next color early. I tried it with just a random dither that got more dense towards the end of the cycle, but it didn't look good. Something deterministic might be better, because you'd consistently replace the previous dither with the new one, rather than possibly letting c-1 sit around too long.
Yeah, when doing multiple cyles in a row like this, the "problem" is that it always uses the same Feistel net, which is deterministic and makes the screen change the same way every time. Maybe it would be possible to randomize the numbers used in the feistel transformation (11, 7 and 5) so each fade doesn't look identical? I'm not sure how to do it without introducing more roughness though.
Anyway, I think the "snap" is acceptable, since you probably wouldn't do multiple cycles in a row in an actual use case – the point is mainly to change every pixel on the screen without having to waste computing time on checking to see if a pixel has already been changed earlier, which would make the effect slow noticeably down as more of the screen gets changed.
I don't think the problem is that the net remains the same. The problem is that one moment where there's no dithering on the screen. That's always going to be a visual hiccup, a bump in the slide through the spectrum.
The problem is that your eye is seeing a sort of sine wave or triangle wave of amount-of-dithering, from no-dithering to 50%-dithered and back to no-dithering, rinse, repeat.
It's actually a common problem with using dithering and I've seen numerous people talk about how to minimize the apparent banding when dithering images. You can dither an image so that the colors are mathematically correct, but your eyes see not just color, but level of detail, so there are still bands, they just aren't color bands. It's really tricksy to balance the two factors.
Interesting. I did an experiment where I didn't even involve dithering at all, and you can still see the same beats each time the primary color is pure:
Despite how it looks, every single pixel column there is a different color. I made certain of it. Like, the pure green column is 00FF00, and the column to its left is 01FE00 and the one to its right is 00FE01.
You probably need to do some kind of ease-in/ease-out to hide them.
Ah, never mind, that last one was a gamma issue. With a gamma curve appropriate to a typical computer monitor, I get this, which has no beats:
But I do wonder if there's some relation. Like, maybe using a power-based ease-in/out might fix the beats.
Here, I wrote some seriously janky and entirely unscientific ease-in/out code and I think it does look better.
Easing is enabled by default, but can be toggled with ❎ to compare to linear.
Slight upgrade: Uses a different offset into the feistel net after each transition to reduce visibly-repeating patterns. Also better code in general and a less-hackish n² easing curve.
Easing is enabled by default, but can be toggled with ❎ to compare to linear.
Thought I would take a crack at this. Looking at the sourcecode for the original is a bit of a logical and mathematical monster. Kudos to the fellow who thought that up ! :D
Yet PICO is so unbelievably fast that IMHO it's not necessary to go through all that.
You can do a Fizzle Fade simply - like this.
Uh, dw, I question the wisdom of calling rnd(),rnd() over and over until you find a coordinate left on the screen that you haven't hit before.
You realize that, for the last pixel, on average you're going to have to check ~8192 pixels, right? Possibly more, if the RNG isn't smoothly-distributed.
Your fader takes nearly a second to set up and then permanently consumes about 1/7th of the Lua memory.
I'm good with it. If you haven't guessed by now I code to get the thing done, not HOW they're done. :D
Now I did try to do the method of spot taken choose one next to it and continue, which is a hair faster. It's good for slow systems, but unfortunately a pattern develops rather quickly in the fade table.
Now if you just want me to swing numbers around in a bucket, I'm sure I can find a way of hitting every point on the screen without using the RND() function, yet it would LOOK random.
If that one second is bothering you, I can remind you of several carts people have written for PICO which literally lock for 2- or more seconds before anything even appears - with no indicator as to what the heck they are doing in their code.
It's 1-second in mine. You'll live.
And yes, it's good that this does actually work and not lock the system. Otherwise you could chalk up RND() to the list of flawed functions you posted recently.
... or did you just give me an idea ?
I guess that, after spending the majority of my professional career having to rewrite code that other people had written to "work" so that it wouldn't consume 10x its various CPU, GPU, or memory budgets in-situ in a game, I'm not okay with someone who presents themselves as an aged guru proposing this kind of algorithm on a forum for a product that's geared towards people learning how to write code.
"Eh, it works," is not a good motto.
Good, you learned to use shuffling to simulate random noise. Genuinely, that's good. But your table still takes up about a seventh of the available ram.
Genuinely, that's good.
Look, while you're in a good mood (I hope)
I wanted to know if someone (anyone?) has written a paint program for PICO that allows you to insert any of the sprites, draws lines, imports, and so-on and saves/loads it to and from CSTORE or strings and stuff ?
I can write it, have planned to write it, I really don't want to write it, but wanted to know if there was someone out there who already did.
... I'll work on that 7th of Ram. I need to dig up my old random number routine that uses solely math to generate random numbers.
No idea. You'd be better off asking the audience in a dedicated post. Have you searched?
I'm searching for the word "PAINT." That's not helping me very much. Pretty sure the community would rather have me write my own. Had to do that with S2.
This is the best fizzle I can come up with for the moment. It's not perfect but it's not bad, uses 256-array space instead of 16384. It bleeds 11130 which can likely be improved upon. Uses my old pseudo-random number generator method which rarely repeats itself.
Press (O) to exit, hold (X) to speed up.
Can you find one that bleeds less and gives a more accurate random representation ?
Since the original post was using the Feistel network technique, here is my take using the original LFSR technique. I basically used the C code from Wikipedia as a starting point, and cycle through all 16 bit values which means that 3 out of 4 points are outside the screen... Ouch :) we need only 14 bits for all points of the screen, but at least we can get the 0,0 point by simply subtracting one from the LFSR value.
One catch was that doing a logical shift right gave me non-integral values. This was a bit of a head scratched until I remembered Zep talking about using 16.16 fixed point values for numbers, so if you shift a number right, the lower bits become fractional values... this is taken care of by a simple flr (or I guess a band would work too.)
POM, you did a great job ! Nice small code. I like what I did yesterday, yet I did read up on this mathematical anomaly last night. Does not use random.
That's pretty impressive by itself. Based on your code let me see if I can turn something around now.
I tweaked it a bit, we can use e.g. 0x3006 as the taps value, and then choose at some point to set the color of (0, 0).
function _init() n=191 start=0x2fef taps=0x3006 lfsr=start color=1 end function _draw() for i=1,n do -- draw the current point x=band(lfsr,0x7f) -- pico8 uses 16.16 fixed point numbers!!! y=flr(lshr(lfsr,7)) pset(x,y,color) -- we need to set (0,0) at some point if (lfsr==taps) pset(0,0,color) -- get the next point lsb=band(lfsr,1) lfsr=flr(lshr(lfsr,1)) if (lsb) lfsr=bxor(lfsr,band(-lsb,taps)) -- cycle through colors when full if (lfsr==start) then color=(color+7)%16 return end end end |
Now I've got a bit of a gripe, POM, not you, Felice.
Here you are telling me my code and I'm trying out this one you guys are playing with and it loops WELL past the 16384 loops.
Tell me again the advantage of it ?
x,y,n,s,t,c=0,0,0,44257,-19456,1 l=s cls() repeat for i=1,384 do x=band(l-1,127) y=flr(lshr(l-1,7)) pset(x,y,c) n+=1 rectfill(0,0,25,7,0) print(n,1,1,7) b=band(l,1) l=flr(lshr(l,1)) if (b) l=bxor(l,band(-b,t)) if (l==s) c+=1 n=0 if (btn(4)==false) flip() end until forever |
Unless I'm mistaken it should only plot 16384 dots and THEN change color, yes ?
Otherwise these are wasted cycles, yes ?
And yes for it to exceed 16384 definitely means its plotting over the same dot more than once.
It would be better to use a 14-bit LFSR. That's just a matter of changing taps to 0x3802.
A minor issue, besides that...
if (lsb) lfsr=bxor(lfsr,band(-lsb,taps)) |
lsb will always be something true, and the band() is just compensating for that. It should be either
lfsr=bxor(lfsr,band(-lsb,taps)) |
or
if (lsb==1) lfsr=bxor(lfsr,taps) |
Here's an interesting Fizzle I just put together. No array, random pixels, draws exactly 16384 of them before changing colors, does not choose the same pixel twice in searching, and creates a curious effect besides. :)
UNREM 2-lines in the code to get a normal fizzle effect yet still only plot 16384 dots.
Saffith: I did change the taps to 0x3006 which is another value that cycles through all 14-bit numbers. 0x3802 works too and so do hundreds of others. And thanks for the bit twiddling review, that's what happens when you start with one version and switch to another without cleaning up... I turned into a one-liner for better obfuscation :)
function _init() n=191 start=0x2fef taps=0x3006 lfsr=start color=1 end function _draw() for i=1,n do pset(band(lfsr,0x7f),flr(lshr(lfsr,7)),color) if (lfsr==taps) pset(0,0,color) lfsr=bxor(flr(lshr(lfsr,1)),band(-band(lfsr,1),taps)) if (lfsr==start) then color=(color+13)%16 return end end end |
dw817, I was not criticising your version; I was just pointing out that my own initial implementation used a full 16 bit LFSR which went through more points than necessary.
POM, my ire (gone now) was directed to FELICE. If you read the comments above, she was rather acerbic with my coding and mentality for it.
I didn't like the fact she was criticizing my coding methods for plotting or searching the same point more than once so - I did the impossible. I wrote code that doesn't - that only searches and plots one time per pixel at a time. :)
I'm not recommending what she did - scolding or condescending others because of their programming ability (or inability). Usually that which doesn't kill me - does make me stronger.
That was true in this case - but that won't happen all the time.
Here is what she wrote I found uncalled for:
-
"I question the wisdom"
-
"I'm not okay with someone who presents themselves as an aged guru proposing this kind of algorithm on a forum for a product that's geared towards people learning how to write code."
- "Eh, it works," is not a good motto.
"Felice, if you don't like my code - you don't have to run it."
This time, her acerbic comments forced me to program in a better light - to improve myself. But that kind of "judgement" I received is not always going to get positive results.
It might even hurt others feelings were it directed to them.
And that's about as angry as I can get for today as I had a nice day celebrating my sister's birthday and don't want anything to cloud it.
dw--
Here's the most recent cart that I posted above, with instrumentation to show how many fades have occurred, how many times the fade has been stepped, how many times the feistel net has been permuted in the while loop inside of the step, and how many times pset() has been called.
As you can see, for each tick in the "fades" counter, the other three tick 0x4000. Neither this cart, not any of the others above, iterate "WELL past the 16,384 loops" per fade.
I'm not sure what made you say otherwise, but you really ought to be more certain of your data before speaking.
As for the other matter, I suggest you consider that social interaction is always a feedback loop.
Felice, it's very simple. I'm only adding one every time PSET is used. It's no more complex than that.
As for ticks, fades, and whatever, I'm not checking that at all.
As mentioned, I am JUST counting the number of times PSET is used in the code before the color changes, and it's more than 16384. That =seemed= to be what your grip was with my code, so I found a way to guarantee only 16384 plots.
This code, however, does not. It exceeds it.
Here is the code again:
x,y,n,s,t,c=0,0,0,44257,-19456,1 l=s cls() repeat for i=1,384 do x=band(l-1,127) y=flr(lshr(l-1,7)) pset(x,y,c) n+=1 -- increase with each plot rectfill(0,0,25,7,0) print(n,1,1,7) b=band(l,1) l=flr(lshr(l,1)) if (b) l=bxor(l,band(-b,t)) if (l==s) c+=1 n=0 -- screen filled, reset count if (btn(4)==false) flip() end until forever |
What are you on about? That's not my code. My code uses DrPete's Feistel net method.
Also, the reason why I spoke of fades is that the fade counter ticks once per every every time every pixel on the screen has finally been changed to the new color, ie. ideally every 16384 psets, which it indeed is.
Well, Felice, I did not =specifically= grab yours. I thought everyone coding here was all on the same page. Apparently not, let's see what you have.
... Seems like yours does indeed count to 16384. Well done. Well, mine does, too, so I'm content with both results.
I am certainly NOT going to bash you for anything you've written to this point. I don't do that to anyone unprovoked. But if you still feel you must harp on me for something after this, do so on my previous Matrix effect.
I think I did a nice job. But maybe you think I didn't and this is your chance to to knock me down a few notches again.
If not, have a wonderful evening ...
This is a really neat effect for fading out a scene, but it doesn't seem to lend itself to fading in a scene.
well I only needed pixel coordinates in a randomish order,
so here's yet another version, based on @pom's above
fizz={ num=0, taps=0x3006, lfsr=0x3fff, nextpixel = function(m) m.num+=1 if (m.num==0x4000) then m.num=0 return 0,0 end local x,y = band(m.lfsr,0x7f),flr(lshr(m.lfsr,7)) m.lfsr=bxor(flr(lshr(m.lfsr,1)),band(-band(m.lfsr,1),m.taps)) return x,y end } -- test function _init() cls(8) -- screen all red for _x=0,127 do for _y=0,127 do local x,y = fizz:nextpixel() pset(x,y, pget(x,y)+1) end flip() end -- wrote 128*128 pixels, screen all orange, job done end function _update60() end |
Silly Q, but is there a way to modify this routine to a specific region?
e.g. to make a large sprite fizzle in, using SGET with x, y, width, height.
(I guess what I'm aiming for is a "fizzle" equivalent to SSPR() 🤔)
UPDATE: I had a go at creating such a function (see below)
I'm sure there's lots of room for improvement, but it will probably be fine for my needs.
function fsspr(sx, sy, sw, sh, dx, dy, dw, dh, flipframe) num=0 taps=0x3006 lfsr=0x3fff for _x=0,127 do for _y=0,127 do num+=1 if (num==0x4000) then num=0 x,y = 0,0 end x,y = band(lfsr,0x7f),flr(lshr(lfsr,7)) lfsr = bxor(flr(lshr(lfsr,1)),band(-band(lfsr,1),taps)) -- within draw region? if x>=sx and x<=sx+sw and y>=sy and y<=sy+sh then pset(dx-sx+x,dy-sy+y, sget(x,y)) end end -- flip draw buffer to screen every "flipframe" # of frames if(_x%flipframe==0)flip() end end |
[Please log in to post a comment]