BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Jump Into the Demoscene; It Is Where Logic, Creativity, and Artistic Expression Merge

Jump Into the Demoscene; It Is Where Logic, Creativity, and Artistic Expression Merge

Key Takeaways

  • The demoscene blends creativity and coding, offering a space where anyone can express themselves through technology.
  • It challenges you to create real-time digital art using code, often within strict constraints.
  • My experience in the scene has shaped my ability to think outside the box and solve complex problems.
  • The demoscene is a community where collaboration and learning are key, welcoming people from all skill levels.
  • It is the perfect playground if you’re passionate about coding and creativity.

The demoscene is a vibrant, creative subculture where you can use mathematics, algorithms, creativity, and a wee bit of chaos to express yourself. In this article, I want to inspire you to grab the opportunity to get your voice and vision on the table and show you that the demoscene is not only for mathematical wizards.

The demoscene, in essence

The demoscene is a subculture of computer art enthusiasts spanning the globe. These creative individuals come together at gatherings where they create and show off their demos. A demo is self-executing software that displays graphics and music and is sort of a cerebral art piece. It began in the software piracy culture in the home computer revolution in the 1980s. It started with people who cracked games and wanted to flaunt their accomplishments by adding a small intro before the games or software loaded. This evolved into a culture that celebrates creativity, mathematics, programming, music, and freedom of artistic expression.

The anatomy of a demo

A demo is a multimedia digital art showcasing your abilities as a creative programmer and musician and the capabilities of your chosen platform. It is a piece built within certain constraints and has to be a piece of executable software at its core.

There are some great examples of great demos. "State of the Art" and "9 Fingers" by the group Spaceballs were two groundbreaking demos on the Amiga platform. The demo "Second Reality" by the Future Crew was a mind-blowing, impressive demo that put the IBM PC platform on the demoscene map. "Mojo" by Bonzai & Pretzel Logic is an utterly mind-blowing contemporary demo on the Commodore 64. "Elevated" by Rgba & TBC is truly impressive because it marked a point where traditional CPU-based demos were left in the dust for the new age of GPUs, and the fact that the entire thing is done in only 4096 bytes is almost unbelievable. Countless others could be mentioned.

One point I must emphasize is that compared to 3D animations made in 3D applications such as Blender or Maya, the graphics might not seem so impressive. However, you must remember that everything is done with code and runs in real-time - not pre-rendered.

Why demos? The motivation behind digital algorithmic art

The motivations could be many. I can speak from my experience of being on the scene as part of a crew. It is an incredible experience being part of a team and using your brain and creativity for fun but competitively.

It is like a team sport; we only use our minds rather than our bodies. You assemble a group with different skills in a team playing a game. Then, you train the team to work together, play off each other’s strengths, and work toward a common goal. In soccer, you have a keeper, the defense line, the midfielders, and the strikers. Each position has responsibilities, and through training and tactics, you work together to win a game.

This is the same in a demo crew. Some are great at graphics, some are great at music, some have a deep mathematical understanding, some are awesome at programming and optimization, and maybe one has a creative vision. You play off everyone’s strength and come together as a team to create a stunning art piece that will resonate with an audience.

I was part of a couple of crews in my formative years, and the sense of comradery, working together, learning together, and being serious about the next production to showcase at the next big demo party. It is very much the same. The primary motivation is to learn, improve, hone your skills, and go toe to toe against some of the world's greatest minds and most peculiar characters. The culture is also great, inclusive, and open.

The demo party concept is where all these enthusiasts come together, travel from every corner of the world with their gear, set up their stations, and celebrate this craft over intense, sleepless, fun, and engaging days and nights. It is like a music festival, a sports tournament, and a 24/7 art exhibition and workshop melted together into one.

There is nothing like having your art piece displayed on a concert-sized screen, with your music roaring on a gigantic sound system in front of a cheering crowd of 3000 enthusiasts who are there for the same reason. When your piece is well received, you get to tell yourself, "I did that with my brain!" It is a true rush beyond anything else I have experienced and can compare it to.

Overcoming challenges in demo creation: Vision, math, and constraints

There are three main challenges when making a demo. The first one is the idea or the vision. What do I want to show on screen? What story am I telling? How do I want the audience to feel? That is the creative aspect.

Then, once you have that, you must figure out how to implement it. How on earth can I make a black hole floating through the galaxy? What is the math behind how it works? And how can I approximate the same results when the ideal math is too complicated to compute in real time?

The third is once you have conquered the first two challenges: Oh rats, I have to make this fit into 64k or whatever constraint your discipline requires.

You deal with these challenges in a lot of ways. Sometimes, you get inspired by the work of others: "Wow, that spinning dodecahedron was cool; I wonder how they did that?" Then, you figure it out and start getting ideas for doing it differently. "What if I combined that with some awesome ray marching terrain and glowing infinity stone shading?" Then, you evolve that into a story or a theme.

I often remember some imagery from a dream. Then, I start to play around with ideas to visualize them. And then I build from that. But a lot of times, I am just doodling with some weird algorithms, and suddenly, something cool is on the screen, and the creative juices start to flow.

The second challenge is what I find the most fun. More often than not, I have to go on a journey of discovery to figure out what techniques, mathematics, algorithms, and tools I need to use. Sometimes, I see how others have managed to do similar concepts and borrow from them, or I ask fellow demosceners how they would approach it. It is quite an open community, and people usually love to share. One must remember that much of this is based on mathematical concepts, often borrowed from fields other than graphics. Still, these concepts are openly documented in various books and publications. It is not usually protected intellectual property. But you may stumble upon some algorithms protected by copyright and subject to licensing, such as Simplex Noise, invented by Ken Perlin. However, copying techniques from other productions are usually very common in the scene.

Remember that the culture evolved from software piracy, so "stealing" is common. Like Picasso said: "Good artists borrow, great artists steal". It is not okay to take someone else’s work and turn it in as your own; you must make it your own or use the technique to tell your story. This is why there are cliches in many demos, like plasma effects, pseudo-3D shapes, interference patterns, etc.

The last challenge is the most challenging to take on. Because at that point, you have reached your goal. Everything looks perfect; you have made your vision; the only problem is that it needs to be reduced to fit within the constraints of the competition you are entering. So here, you must figure out how to shave off instructions, compress data, and commit programming crimes against the best practice coding police. This is when methodologies like design patterns, clean code, and responsible craftsmanship go out the window, and things like the game Quake’s "Fast Inverse Square Root Algorithm" come into play. There are so many aspects of this; everything from rolling out loops, self-modifying code, evil floating point hacks, algorithmic approximations, and such comes into play.

Here is an example. A standard approach to compute the inverse of a square root in C might be something like this:

#include <math.h> float InverseSqrt(float number) { return 1.0F / sqrtf(number); }

Easy to read, easy to understand. But you are including the math.h library. This will impact the size of your binary. Also, the sqrt function might be too slow for your demo to run efficiently.

The fast method found in the Quake III engine looks like this:

float Q_rsqrt(float number)
{
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y  = number;
  i  = * ( long * ) &y;                       // evil floating point bit level hacking
  i  = 0x5f3759df - ( i >> 1 );               // wtf?
  y  = * ( float * ) &i;
  y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
  // y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

  return y;
}

This is quite hard to understand at first glance. You take a float, cast it into a long, do some weird bit smashing with a strange constant, then stuff it back into a float as if it is perfectly ok to do so. However, this does an undefined operation twice, and then it does a Newton iteration to approximate a result. Originally, it was done twice, but the second iteration was commented out because one approximation was sufficient.

This algorithm is a testament to the sort of wizardry you learn in the demoscene. You learn to cheat and abuse your platforms because you get so familiar with them that even undefined operations can be manipulated into doing your bidding. You would never program anything like this on a commercial software product or check in code like this on a typical project unless you have no other option to reach your goal. But imagine an enterprise software team doing a code review on this after a pull request.

Let’s create a demo effect together; let’s get our hands dirty

I will show you one of my favorite effects, known as "fire". What I do is, through a straightforward algorithm, create something that looks like fire burning violently on screen. It is quite impressive in my view.

There are three prerequisites. I will use JavaScript because it is easy, everyone has a browser, and the tooling is minimal. Next, I am using my own graphics library, which I have built. It is called DrCiRCUiTs Canvas Library; it is free, open-source, and has no license. You can find it on NPM here.

It is not the most elegant library, but it hides a lot of the necessary boilerplate and gives you some nice, simple concepts to get started.

Like most of these effects, I use the canvas API but do not draw boxes. Instead, I use bitmap data because bitmap operations like this are more performant than individual drawing operations.

The steps are simple to this effect:

You must build a palette of colors, from white to yellow to orange to black. This represents the fire transitioning from burning hot to smoke. Let’s go old school and use 256 colors in this palette. You can either hard code it - this is what I would do back in the day - but these days, with modern RGB displays, you might just as quickly generate it. Also, my library has a nice little object for handling colors.

Next, you need to create a drawing buffer on the screen, loop over the pixels, and update the effect for each iteration.

You will also load a logo as a mask buffer, where you will randomize the pixels of the logo's edges. This will give a burning silhouette in the middle of the screen.

Step 1: Generate the palette.

function generatePalette() {
    for (let i = 0; i < 255; i++) {
        let lightness = i / 255;
        let hue = i.map(0, 255, 0, 85); // Map color from red (0 degrees) to yellow (85 degrees) in HSL
        palette.push(HSLtoRGB(hue, 1, lightness)); // Convert HSL to RGB and add to palette
    }
}

// Converts HSL color format to RGB (helper function for palette generation)
  function HSLtoRGB(h, s, l) {
    let r = 0,
      g = 0,
      b = 0;
    if (s === 0) {
      r = g = b = l; // Achromatic case
    } else {
      let hp = h / 60;
      let c = (1 - Math.abs(2 * l - 1)) * s;
      let x = c * (1 - Math.abs((hp % 2) - 1));

      if (hp >= 0 && hp <= 1) {
        r = c;
        g = x;
      } else if (hp > 1 && hp <= 2) {
        r = x;
        g = c;
      } else if (hp > 2 && hp <= 3) {
        g = c;
        b = x;
      } else if (hp > 3 && hp <= 4) {
        g = x;
        b = c;
      } else if (hp > 4 && hp <= 5) {
        r = x;
        b = c;
      } else if (hp > 5 && hp <= 6) {
        r = c;
        b = x;
      }

      let m = l - c / 2;
      r += m;
      g += m;
      b += m;
    }
    return dcl.color(
      Math.round(r * 255),
      Math.round(g * 255),
      Math.round(b * 255)
    ); // Convert to RGB
  }

Step 2: Boilerplate and setting up the buffers.

function initializeFlameArray() {
    for (let y = 0; y < scr.height; y++) {
        let row = [];
        for (let x = 0; x < scr.width; x++) {
            row.push(y === scr.height - 1 ? dcl.randomi(0, 255) : 0); // Randomize the last row
        }
        flame.push(row);
    }
}

function setup() {
    scr = dcl.setupScreen(640, 480); // Set up a screen with 640x480 pixels
    scr.setBgColor("black"); // Set background color to black
    document.body.style.backgroundColor = "black"; // Set page background color

    generatePalette(); // Generates a color palette for the flame
    initializeFlameArray(); // Set up the flame array
    id = new ImageData(scr.width, scr.height); // Initialize screen buffer
}

Step 3: Setting up the draw loop.

function draw(t) {
    randomizeLastRow(); // Randomize the bottom row for dynamic flame effect
    propagateFlame(); // Compute flame propagation using neighboring pixels
    renderFlame(); // Render flame pixels to the screen
    scr.ctx.putImageData(id, 0, 0); // Draw the image data onto the screen
    requestAnimationFrame(draw); // Continuously redraw the scene
}

 // Randomizes the values in the last row to simulate the flame source
  function randomizeLastRow() {
    for (let x = 0; x < flame[flame.length - 1].length; x++) {
      flame[flame.length - 1][x] = dcl.randomi(0, 255); // Random values for flame source
    }
  }

Step 4: Calculate the fire up the screen by playing "minesweeper" with the pixels and averaging them.

function propagateFlame() {
    for (let y = 0; y < flame.length - 1; y++) {
        for (let x = 0; x < flame[y].length; x++) {
            let y1 = (y + 1) % flame.length;
            let y2 = (y + 2) % flame.length;
            let x1 = (x - 1 + flame[y].length) % flame[y].length;
            let x2 = x % flame[y].length;
            let x3 = (x + 1 + flame[y].length) % flame[y].length;

            // Sum the surrounding pixels and average them for flame propagation
            let sum = (flame[y1][x1] + flame[y1][x2] + flame[y1][x3] + flame[y2][x2]) / 4.02;
            flame[y][x] = sum; // Adjust flame height
        }
    }
}

Step 5: Look up the mask buffer, and randomize edge pixels, then render the flame effect

function renderFlame() {
    for (let y = 0; y < scr.height; y++) {
        for (let x = 0; x < scr.width; x++) {
            let fy = y % flame.length;
            let fx = x % flame[fy].length;
            let i = Math.floor(flame[fy][fx]);
            let color = pallette[i]; // Fetch the color from the palette

            if (!color) continue; // Skip if no color found

            // Compute the pixel index in the image data buffer (4 values per pixel: r, g, b, a)
            let idx = 4 * (y * scr.width + x);
            id.data[idx] = color.r; // Set red value
            id.data[idx + 1] = color.g; // Set green value
            id.data[idx + 2] = color.b; // Set blue value
            id.data[idx + 3] = 255; // Set alpha value to fully opaque

            // Check for the logo mask and adjust the flame accordingly
            let pr = pd.data[idx];
            let pa = pd.data[idx + 3];
            if (pr < 64 && pa > 0) {
                flame[fy][fx] = dcl.randomi(0, 255); // Intensify flame in the logo region
            }
            if (pr === 255 && pa === 255) {
                id.data[idx] = id.data[idx + 1] = id.data[idx + 2] = 0; // Render logo as black
                id.data[idx + 3] = 255; // Full opacity
            }
        }
    }
}

Step 6: Load the image mask and trigger the effect.

let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
let img = new Image();
img.src = "path_to_your_logo_image.png"; // Replace with your logo image path or URL
img.addEventListener("load", function () {
    canvas.width = img.width;
    canvas.height = img.height;
    ctx.drawImage(img, 0, 0);
    pd = ctx.getImageData(0, 0, img.width, img.height); // Extract image data for mask
    setup(); // Initialize the flame simulation
    draw();  // Start the drawing loop
});

Step 7: Iterate and view your creation.

You can view the code for my implementation at this codepen.

Now, this is not coded to be super-optimized. I did it on purpose to keep it readable and easy to understand for newcomers. I leave it as an exercise to those of you who want to take it on as a little exercise in code golf - the least amount of instructions to achieve the same effect.

I would love to see your versions of this effect. I encourage you to experiment with the code and observe what happens when you change things. For instance, changing which pixels are included in the calculation and using other calculations besides a simple average may drastically change the effect!

How being a demoscener has shaped my career as a programmer

Creating demos has, for me, been a catalyst for acquiring the skill to learn at a rapid pace. It has also taught me the value of knowing your programming platform deeply and intimately. I am not saying that every programmer needs to do that, but it has proven highly valuable to me in my mainstream career to have the ability to learn new things fast and to have the drive to know what is going on under the hood.

It has also taught me that creativity is an underrated skill amongst developers, and it should be treasured more, especially when working with innovation. Software development is often viewed as people who just have to type in the solution for the feature described for the next iteration. It might seem simple. "We are implementing a solution that will take a file of invoices from our client, then distribute those invoices to their clients".

Seems simple enough; it should be able to go into the sprint backlog. Then you find out once you pull that task from the backlog and think: "I’ll parse the XML, separate the documents, split them into files, and ship them out through email". But once you get started, you find that the XML file is 12 GB, your XML parser needs 12GB of sequential memory to be allocated for the parsing to work, then writing 2 million single files to disk takes hours, and you have a 30-minute processing window.

Well, what do you do? You have to think way outside the box to achieve the task. Of course, you could try to go back and say: "This can’t be done within the parameters of the task; we need to renegotiate our client's expectations". But the demoscener and creative aspects of me will rarely let such a challenge go.

This is a true story, and I solved it by reducing the task into the two essential parts of the specification and reinventing the specification in between. The 12GB invoice file and the processing window were the essential bits, and I could get creative with the others. My solution achieved the outcome in less than 5 minutes. I would never have been able to think that way if I never had done any creative coding.

We sometimes forget that software development is all about creating something that doesn’t exist, and more often than not, we are asked to deliver something we do not know how to build or solve within a scope that we have to define at the highest point of ignorance - creativity will help you do that.

The future of the demoscene: Inclusivity, growth, and opportunities

My hope for the future of the demoscene is that it grows even more glorious and with even more great programmers. I yearn for a future when the diversity of the demoscene is like a mirror image of humanity.

I dream of each innovation that can catapult this art form into a new realm of wonder and creativity, and I want to get as many people involved as possible.

I hope a lot of programmers, young and old, take the leap into this wonderful scene and culture and quickly see that it is not this esoteric mystical world reserved only for a handful of brilliant people. It is truly a place for everyone, where you can challenge yourself to create and show who you are on the screen through algorithmic expression.

Lessons from the demoscene: Creativity, perseverance, and technical mastery

When I ask myself the question: So what have I learned? Today? Through my life? That is a big question. In the context of what I have learned from the demoscene, it is this: I, too, can make demos; what seemed impossible and beyond my intellectual capacity at first evolved into second nature through perseverance and not letting my inner impostor syndrome get the better of me. It helped me gain the confidence and drive to make technology my career; so far, it has turned out to be a good choice.

About the Author

Rate this Article

Adoption
Style

BT