Sign in

Pekka Väänänen

@pekkavaa.bsky.social
1.2K followers 404 following 348 posts

Avid reader, computer graphics fan and atmospheric jungle beats enjoyer. Demoscene: cce/Peisik. Blog at 30fps.net

PostsRepliesMedia
Pekka Väänänen @pekkavaa.bsky.social · 17/09/2026
I enjoyed this 41 minute documentary filmed before the E3 2005 reveal of Elder Scrolls IV: Oblivion www.youtube.com/watch?v=zvm0... Found it funny how they staged a scene of artists traveling to Washington D.C. to capture textures for the game's Imperial City.
youtube.com
The Making of Oblivion (FULL | HQ | ENG)
Making of GOTY 2006
130
Reposted by Pekka Väänänen
Max (VGCartography) @vgcartography.com · 11/09/2026
nintendo 64 desertification 🏖️
shifting sand landangry aztecgobi's valleydindom dries
27823
Reposted by Pekka Väänänen
Don Williamson @donw.bsky.social · 09/09/2026
In ~2007 when coding the ambient lighting of Splinter Cell, I had the idea of storing dynamic SH probes in Voronoi cells, where a) dynamic objects could walk/lerp the face graph, and b) static meshes "palettised" probes per vertex and lerped to give Realtime GI. It never shipped but look how pretty!
410515
Reposted by Pekka Väänänen
Jason Scott @textfiles.com · 08/09/2026
Of all the formats for newsletters, zines and information indexes, I'd honestly have not thought people would use WinHelp format. But Vannevar K has collected hundreds of examples of WinHelp formated documents, all conveniently emulated in Windows 3.1.
2377
Pekka Väänänen @pekkavaa.bsky.social · 04/09/2026
I've been racking my brain over linear space dithering. Adding noise sounds simple but how much, exactly? Finally, I found a trick: scale the dither down near zero. Found from Timothy Lottes 2016 slide deck. Attached is an exaggarated example.
A two-pane comparison of Floyd-Steinberg dithering with threshold noise. Left: linear space noise is added with constant magnitude. Right: linear space noise but magnitude is scaled down near zero.A slide from "Advanced Techniques
and Optimization of
VDR Color Pipelines" by
Timothy Lottes (2016)

https://gpuopen.com/download/GdcVdrLottes.pdf
1302
Reposted by Pekka Väänänen
Collin Allen @command-tab.com · 03/09/2026
The #N64 development scene has been making huge strides recently. I blogged about where things stand right now: Also, that chunky header image took a *while* to make 🥹
command-tab.com
The Nintendo 64 Renaissance
The N64 homebrew scene now has better developer tools and support than ever
0143
Reposted by Pekka Väänänen
Nelarius @nelarius.bsky.social · 31/08/2026
While investigating #voxel rendering alternatives that don't involve generating triangles, I ran into the "Voxel GI with Nested Axis-Aligned Distance Fields" paper. github.com/cg-tuwien/NA... Super promising method, with adapted code, I can render RTAO with 1 billion voxels on an M2 Macbook Air!
091
Reposted by Pekka Väänänen
Nexxtic (Selaco Lead Dev) @selaco.bsky.social · 27/08/2026
Being able to bake shadows into the Doom Engine is so freaking cool.
2136773
Pekka Väänänen @pekkavaa.bsky.social · 25/08/2026
Hexagonal-grid dithering looks pretty wild up close, especially the 50% case here. A crop of a figure from "Alpha stable modeling of human visual systems for digital halftoning in rectangular and hexagonal grids" by González et al. 2008.
0130
Reposted by Pekka Väänänen
Robin Green @fatlimey.bsky.social · 03/08/2026
Ever wondered what a test card is for? TV signals are encoded in the YUV or YCbCr color spaces. A vectorscope is just the CbCr or UV part of the signal in 2D. Running a SMTPE test card through the scope gives you a zigzag pattern where it visits the center of each primary box in a specific order.
The SMTPE test card, electronically generated.An oscilloscope image with lines and boxes printed on the screen. The green lines of the oscilloscope are tracing out a pair of interlocking zigzags that exactly hit the centers of the boxes. Science!
181
Pekka Väänänen @pekkavaa.bsky.social · 04/08/2026
What do you call the act of inserting an element to a sorted list? "Insort" of course... A snippet of a guide in the Python language docs.
030
Pekka Väänänen @pekkavaa.bsky.social · 04/08/2026
An interesting insider view from the SGI side to Nintendo 64's development and launch: steveshepard.com/blog/making-...
060
Pekka Väänänen @pekkavaa.bsky.social · 29/07/2026
"Kodak Lab notebook entry showing Bayer Pattern" en.wikipedia.org/wiki/Bryce_B...
en.wikipedia.org
Bryce Bayer - Wikipedia
Bryce Edward Bayer (/ˈbaɪər/; pronounced BYE-er, August 15, 1929[1] – November 13, 2012) was an American scientist who invented the Bayer filter pattern,[1] which is used in most modern color digital…
010
Pekka Väänänen @pekkavaa.bsky.social · 13/07/2026
Another good find from Bouman's image processing course: CIELAB DeltaE is intended for comparing larges swathes of color, not individual pixels. Source: engineering.purdue.edu/~bouman/ece6...
Warning: Don't Abuse Lab Space

Lab color space is designed for low spatial frequencies.
- It is very good for measuring color differences in objects.
- It is the standard for measuring paint color.
- It is good for measuring color accuracy of printers and displays.

Direct application to images works poorly.
- Lab does not account for high spatial frequency content of images.
- Lab formulation ignores different form of CSF for luminance and chrominance spaces.
- Image coding in Lab space will produce poor results.

C. A. Bouman: Digital Image Processing - January 11, 2026
090
Pekka Väänänen @pekkavaa.bsky.social · 13/07/2026
Useful dithering (digital halftoning) related notes: engineering.purdue.edu/~bouman/ece6... They are from Charles A. Bouman's Spring 2026 "Digital Image Processing I" course, Purdue University.
engineering.purdue.edu
090
Pekka Väänänen @pekkavaa.bsky.social · 09/07/2026
I've read that newspaper-like halftone patterns are laid out in a 45-degree angle because the human eye is less sensitive to that than to straight grid lines. But wouldn't a simpler explanation be that newspaper photos of streets and buildings have straight lines, so a rotated pattern works better?
160
Pekka Väänänen @pekkavaa.bsky.social · 08/07/2026
The Mona Lisa mapped to the Commodore 64 palette by taking only pixel lightness into account. Two algorithms: N-Convex (left) always tries to dither the result while in Knoll's algorithm (right) dither strength can be tuned.
081
Pekka Väänänen @pekkavaa.bsky.social · 29/06/2026
A new article: Revisiting Yliluoma’s ordered dither algorithm 30fps.net/pages/revisi... It's a deep dive to a single "color selection algorithm" used for ordered dithering. Nothing groundbreaking but I did uncover a neat simplified version that works OK. It's technical but with visual examples.
A graphic showing a side-by-side comparison of three versions of the same image, each processed with a different dithering algorithm.
The image consists of three vertical panels arranged horizontally, with a text summary located at the bottom. Each panel features a stylized bird from the Big Buck Bunny animated short. A yellow beak and large eyes, sitting on a branch. A blue-to-pink gradient sky and green leaves in the background.

The three panels:

Yliluoma's 2nd dithering algorithm: Dithered result with the most noise.
EMA-Constant: Significantly smoother result.
Knoll (10% strength): Smooth result similar to EMA-Constant.

Text at the bottom:
Summary: This article discusses Yliiluoma's 2011 ordered dither algorithm (left), explains its internals in greater detail than other treatments, presents new simplified variants (middle), and compares the results to a state-of-the-art algorithm (right). Source code is included at the end.
A 2x2 grid comparing four versions of the same pixel-art scene from the 1999 Chrono Cross video game: three characters in a detailed interior room with curtains and windows. Each version uses a different dithering technique.

The images are labeled (top-left to bottom-right): "EMA-Exact", "EMA-Constant", "Knoll (20% strength)", and "Knoll (10% strength)".
22612
Pekka Väänänen @pekkavaa.bsky.social · 26/06/2026
Trying to finish a new blog post on ordered dithering. It's been _so_ much work but I think it's turning out really interesting.
A six-pane comparison of a Chrono Cross screenshot reduced to a 16-color fixed palette. Each pane shows a different result and they all are somewhat noisy. The panes are labeled EMA-Sweep, EMA-Exact, EMA-Constant, Yliluoma-2, Yliluoma-2 (no luma weighting), Knoll (30% strength).
0130
Reposted by Pekka Väänänen
Ruben S. @atomicagegames.co.uk · 22/06/2026
Tried my hand at a 90s pre-rendered CGI aesthetic in Unity for a game jam #GameDev #IndieDev
88416
Pekka Väänänen @pekkavaa.bsky.social · 18/06/2026
An incredible blog post on ordered dithering in color. Perhaps I shared this before but it deserves to be seen: matejlou.blog/2023/12/06/o...
matejlou.blog
Ordered Dithering with Arbitrary or Irregular Colour Palettes
Introduction Hello. I have recently been spending a lot of time dithering. In image processing, dithering is the act of applying intentional perturbations or noise in order to compensate for the in…
0110
Pekka Väänänen @pekkavaa.bsky.social · 12/06/2026
My new hobby: Taking holiday photos with multiple exposures to create fake GDC slides afterwards.
A PowerPoint slide titled "Cathedral Church of Saint Vitus". There is a three-pane comparison images labeled "Direct and emissive", "Indirect diffuse (baked)" and "Final render (Xbox One)". The background color is black and at the bottom there's a grey text "Rendering Secrets of 'Pekka's Two Nights in Prague'"
0548
Reposted by Pekka Väänänen
Mike Turitzin @miketuritzin.bsky.social · 10/06/2026
Really love this project and article - a handmade early-mid-90s-ish FPS. The procedural pixel art "gibbing" and particle effects are pretty cool, among other stuff staniks.github.io/articles/cat...
staniks.github.io
Catlantean 3D - Making Graphics Like It's 1993
A deep dive into the asset creation pipeline for Catlantean 3D.
1345
Pekka Väänänen @pekkavaa.bsky.social · 10/06/2026
A computer history question: Why didn't VGA support a 256-color 640x400 mode even though a frame would fit in 256 KB memory? I can see a business reason (the IBM 8514/A high-end card introduced at the same time) but is there a known technical justification for not having such a mode?
391
Pekka Väänänen @pekkavaa.bsky.social · 01/06/2026
A new article on my website about a small thing that has bothered me for a long time: Should you normalize RGB values by 255 or 256? 30fps.net/pages/255-vs...
A number line where 3-bit integers in the range [0..7] being mapped to real numbers with two approaches: the standard (top) and alternative (bottom).

On the X-axis we’ve got a number line and the locations of brown circles on it represent the decoded floating-point values. The numbers inside are the integer inputs. Each integer has arrows pointing to it; these show a range of floating-point values that round to it. I’ll call these ranges “bins” in the rest of this article.
47118
Pekka Väänänen @pekkavaa.bsky.social · 28/05/2026
I uploaded a few historical text files related to color quantization and dithering. It's easy to forget how difficult it was to read up on the latest research without the internet. Well, it still is but for different reasons😅 paletteprogramming.com/archive/
paletteprogramming.com
Palette Programming: Invent Your Own Color Reduction Algorithm
A book and website about color reduction algorithms for retro computer graphics.
181
Pekka Väänänen @pekkavaa.bsky.social · 26/05/2026
Now this is a bold typeface for a paper title. "Quantization" by Allen Gersho. IEEE Communications Society Magazine, September 1977.
Abstract—Quantization is the process of replacing analog samples with approximate values taken from a finite set of allowed values. The approximate values corresponding to a sequence of analog samples can then be specified by a digital signal for transmission, storage, or other digital processing. In this expository paper, the basic ideas of uniform quantization, companding, robustness to input power level, and optimal quantization are reviewed and explained. The performance of various schemes is compared using the ratio of signal power to

QUANTIZATION

mean-square quantizing noise as a criterion. Entropy coding and the ultimate theoretical bound on block quantizer performance are also compared with the simpler zero-memory quantizer.

ALLEN GERSHO, MEMBER, IEEE
0102
Pekka Väänänen @pekkavaa.bsky.social · 12/05/2026
Applying Floyd-Steinberg dithering (with serpentine scan order) to constant grey images creates interesting artifacts. Inspired by a 2001 paper by Victor Ostromoukhov who brute forced optimal weights for each level based in a similar experiment. The paper: perso.liris.cnrs.fr/victor.ostro...
1150
Pekka Väänänen @pekkavaa.bsky.social · 23/04/2026
New article: Single-pass palette refinement and ordered dithering 30fps.net/pages/bayer-... It's a method that modifies the online k-means algorithm to produce an ordered dithering pattern as a side effect. The result is closer to pleasant glitch aesthetics than a proper dithering replacement.
A comparison table with two rows and two columns. The top row header is "Order" and "Intermediate OKM result (32 colors)".

The first row contains "Bayer 2x2 (unshuffled)" on the left and a pixelated image of two macaws on a branch on the right, with a 2x2 Bayer dithering effect.

The second row contains "Bayer 4x4 (unshuffled)" on the left and another version of the same macaws on the right, with a finer 4x4 Bayer dithering effect.A grid of four versions of the same scene featuring a lighthouse on a rocky hill with a house nearby.

The top row shows the original image (left) and online k-means "refined" palette in random pixel traversal order. It has sepia tones.

The bottom row shows the results with 2x2 Bayer order traversal. The shuffled (left) result is almost identical to the random order one, only the sky is a bi more blue. The unshuffled (right) image is practically greyscale with very visible 2x2 dither pattern.
0266
Pekka Väänänen @pekkavaa.bsky.social · 21/04/2026
The "popularity" color quantizer is one of the simplest: find the K most used unique colors in the image. That's the palette. I was surprised how well it works when K=256 and the image is reduced to 4-bits per channel before. Even transparencies look kinda OK.
Two images of Swedish red houses with transparent sky shown as a grey checkerboard pattern. The top one is the original, below it is the color reduced version that looks almost identical.

Here the 4-bit quantized 256-color result does look decent. The only 
visible issues are the white fringes on the treeline on the left half of the image, 
and small overall color distortion caused by the 4-bit colors. For example the 
red paint of the facades is reproduced with a subtle greyish noise not present 
in the original.
1160
Reposted by Pekka Väänänen
David Aerne @meodai.bsky.social · 20/04/2026
I will not continue to work on this so I might just share the URL. I just wanted to pack everything about a palette onto one view: palettarium.color.pizza
08911
Pekka Väänänen @pekkavaa.bsky.social · 15/04/2026
Came across this handy matrix of accurate but integer-only bit depth conversion formulas: threadlocalmutex.com?p=48
threadlocalmutex.com
Fast and Accurate Color Depth Conversion – threadlocalmutex.com
When converting between low-bit color color formats (R4G4B4A4, R5G6B6, R8G8B8A8, and so on), the conversion routines used often don't map to the nearest color channel values in the target bit depth.…
0101
Pekka Väänänen @pekkavaa.bsky.social · 14/04/2026
A cool plot from "A Simple and Efficient Error-Diffusion Algorithm" by Victor Ostromoukhov, 2001. They show how the classic Floyd-Steinberg error diffusion can't always produce a pattern with the blue noise (no clumping) property. Paper: perso.liris.cnrs.fr/victor.ostro...
080
Pekka Väänänen @pekkavaa.bsky.social · 31/03/2026
Always a delight to see science being done on a 2 MHz 6502. From "On the Statistical Analysis of Dirty Pictures" by Julian Besag, 1986.
"Our experience of ICM on such scenes is limited to a couple of artificial examples and several lung and liver scans produced by a gamma camera, with Poisson distributed records. The processing was carried out on the BBC Model B, with 8 or 16 levels and second-order neighbourhood. Although the results were satisfactory, it would be desirable generally to adopt a larger neighbourhood to allow for curvature effects, through coefficients which depend"
060
Pekka Väänänen @pekkavaa.bsky.social · 23/03/2026
Why not blend gradually between ordered dither (top) and error diffusion (bottom)? Such a cool idea. Example image by Viktor Massalogin github.com/bntre/dither...
0110
Pekka Väänänen @pekkavaa.bsky.social · 20/03/2026
I researched Google's squoosh.app image compression tool's palette code and it's a vendored copy of libimagequant, but what's more interesting is that the code also supports ZX Spectrum color limits! github.com/GoogleChrome...
A screenshot of code that says:

/**
 * The ZX has one bit per pixel, but can assign two colours to an 8x8 block. The
 * two colours must both be 'regular' or 'bright'. Black exists as both regular
 * and bright.
 */
val zx_quantize(std::string rawimage, int image_width, int image_height, float dithering) {
  auto image_buffer = (const liq_color*)rawimage.c_str();
  int size = image_width * image_height;
  liq_color block[8 * 8];
  uint8_t image8bit[8 * 8];
  std::vector<liq_color> result(size);

  // For each 8x8 grid
  for (int block_start_y = 0; block_start_y < image_height; block_start_y += 8) {
    for (int block_start_x = 0; block_start_x < image_width; block_start_x += 8) {
      int color_popularity[15] = {0};
030
Reposted by Pekka Väänänen
Eric Lengyel @ericlengyel.bsky.social · 17/03/2026
New blog post: A Decade of Slug This talks about the evolution of the Slug font rendering algorithm, and it includes an exciting announcement: The patent has been dedicated to the public domain. terathon.com/blog/decade-...
1127386
Pekka Väänänen @pekkavaa.bsky.social · 17/03/2026
I tried using an octree for color quantization with the help of a 1996 Dr Dobbs article: jacobfilipp.com/DrDobbs/arti... It was quite a hassle, doesn't work that well (at least my version), and isn't as fast as I expected. On the right: sklearn's bottom-up clustering for comparison.
1142
Reposted by Pekka Väänänen
cammy @cammydev.bsky.social · 07/03/2026
more early counter strike vibes
78213
Reposted by Pekka Väänänen
N64brew @n64brew.bsky.social · 25/02/2026
The N64brew Game Jam #6 has come to an end, and we've had a total of 28 submissions this year! Our judge team is currently deliberating over each entry to decide which team will pick the charity to send the $3300+ donations we've received. While we wait, let's look through every entry! 🧵
1159
Reposted by Pekka Väänänen
Edd B @kiwi-tiki.bsky.social · 16/02/2026
The topic of raycasting on MSX has come up. A while ago I made a renderer for MSX 1 which looks like a raycaster (so-called backwards projection) but in fact it's a rasterizer (forward projection). This reduces the per-column cost significantly. Doom worked this way. www.youtube.com/watch?v=8l52...
youtube.com
MSX1 3D Interactive Demo
YouTube video by cave_fish
3146
Pekka Väänänen @pekkavaa.bsky.social · 18/02/2026
All the work put in by the N64 homebrew and ROM hacking communities are starting to bear fruit :) In the past years there has been a steady improvement in hardware documentation, SDKs, emulators, Blender addons, and 3D graphics libraries (OpenGL and Tiny3D for libdragon, F3DEX3 for libultra).
0100
Pekka Väänänen @pekkavaa.bsky.social · 12/02/2026
A cool trick to clean up error diffusion dithering: if the per-pixel accumulated error goes over a certain threshold, dampen it by a factor of 0.8. This suppresses bright, sparse pixels. Found this in libimagequant's code.
Left: Original Floyd-Steinberg dithering. Sky gradients are reproduced with very distracting pixel patterns.
Right: With large error dampening added, only moderate noise appears.
1111
Pekka Väänänen @pekkavaa.bsky.social · 04/02/2026
I tried imitating GIMP's "reduced color bleed" addition to Floyd-Steinberg dithering and it works! Maximum error offset allowed for a pixel gets clamped with a tanh-like function, hiding individual high-contrast pixels in solid regions. The clamping function is shown in green in the attached plot.
Left: Floyd-Steinberg dithering.
Right: Floyd-Steinberg with reduced color bleeding aka clamped error offset.Error clamping function. Green: used by my code (tanh). Orange: GIMP's reduced color bleed, blue: GIMP's normal Floyd-Steinberg.
1181
Pekka Väänänen @pekkavaa.bsky.social · 02/02/2026
My first Floyd-Steinberg dithering routine! Still looks a bit broken. I've understood dithering should be done in the linear color space of the display device but with big pixels maybe that's not the best idea. At least the first bird Really Pops. The third was the first 2x2 matrix test.
Gamma birdLinear birdSomewhat broken 2x2 gamma bird
040
Reposted by Pekka Väänänen
Edd B @kiwi-tiki.bsky.social · 01/02/2026
I discovered something interesting in the MSX2 version of Pac-Mania. The game has an isometric perspective just like the arcade original, but it first loads the background with an orthogonal perspective and then applies a shear effect to it in VRAM. The original simply loads pre-sheared tiles. #msx
1224
Reposted by Pekka Väänänen
Jacco Bikker 🎦 @jbikker.bsky.social · 29/01/2026
Awesome writeup on the power of pre-splitting for producing high-quality BVHs: github.com/BoyBaykiller... One interesting quote: "You may notice PreSplitting improves performance but increases the SAH cost. SAH is not a perfect predictor of performance."
github.com
GitHub - BoyBaykiller/IDKEngine: OpenGL Toy-"Engine" with Wavefront Path Tracer and Rasterizer with Voxel Cone Tracing/Global Illumination
OpenGL Toy-"Engine" with Wavefront Path Tracer and Rasterizer with Voxel Cone Tracing/Global Illumination - BoyBaykiller/IDKEngine
0214
Pekka Väänänen @pekkavaa.bsky.social · 22/01/2026
I'm starting to get attached to these fast & loose diagram sketches I'm doing for my book. But redrawing them as clean vector art allows easier editing so it's worth the trouble.
The diagram shows a matrix X with labeled points (1–6) representing an original image, and a matrix Y with corresponding labeled points with the name c (sub index 1–2) showing a reconstruction. Below each matrix is a point diagram: the left one for the original image with scattered colored dots divided in two classes, and the right one for the reconstruction with connected dots showing how the two clusters are represented by their mean positions. The formula between them indicates a difference squared, representing reconstruction error.
070
Pekka Väänänen @pekkavaa.bsky.social · 18/01/2026
Here's a little project I've been working on: Liero64 - a port of the 1998 MS-DOS game for the Nintendo 64. Gameplay video: youtu.be/aemLngAjEi8 ROM: codeberg.org/pekkavaa/Lie...
youtu.be
[N64] Liero64 Gameplay
The classic 1998 MS-DOS game Liero is now on the Nintendo 64! This video shows me (left) playing against the CPU opponent (right). Download the ROM at…
23911
Reposted by Pekka Väänänen
Makkon @makkon.bsky.social · 11/01/2026
screenshots of the startmap I built for qbj3 or Quake Brutalist Jam III some fancy bits in here will be in the coming update, like the paintings and plaques
a dreary sky, a lonely grey concrete tower in the center. there are chambers on the base that a person can enter with stairs leading up to thema brutalist concrete lobby, with nice carpet, furniture, and plants hanging from suspended planters. the lighting is warm and welcominga cavernous gallery room, with a high ceiling and artificial sun panels pouring warm diffused light into the room. there is a statue of a floating octahedron in the center. plants and vines hang down from the walls. the room branches off into other chambers.view of a tall, oddly shaped hallway. carpet down the whole length. on the left there is seating, a cabinet, and a large painting of some abstract rock formation.
291007254