Category: Productions

Pyrotranquil 3K

This is a small intro (less than the usual 4K) using particles and compute shaders. It took part in the R-sync 2024 demoparty in Belgium, where it took first place in the combined demoparty. I was trying to make an infinitely-running demo like Timeless/Tran, but due to numerical precision the effect starts to look a bit weird after 7-8 minutes.

You can watch a HD recording on Youtube , feedback welcome on the Pouet page

Or you can download an run the intro yourself: Pyrotranquil by Fulcrum (HD)

Creative Carving 1K

This done-in-a-hurry 1K was made for the Demosplash 2023 combined intro competition, where it placed 1st. It’s based on a recursive torus idea that I had for quite a while, with some added near-Halloween styling.

You can watch it on Youtube , feedback welcome on Pouet

Or you can download the executable and run it yourself: Creative Carving 1K by Fulcrum

(Standard disclaimer about antivirus not liking 1K intros applies)

Truck Is Jarig 4K

Here’s a crazy idea I have had for a few years: remake the entire Rob Is Jarig/Aardbei demo from 2000 in a 4K. That demo has been submitted at quite a few demoparties (see Pouet ). At some point people started to dance on stage if that demo was shown. Unfortunately the Fins at Assembly’22 didn’t know that custom (or they were scared of the security guards. Yes Assembly has those). Still, they at least recognized my artistic impression of Truck ;-)

Download: http://www.fulcrum-demo.org/wp-content/uploads/2022/08/Truck-is-Jarig-4K-by-Fulcrum.zip

Youtube: https://www.youtube.com/watch?v=KuYktqWhznI

Pouet page: https://www.pouet.net/prod.php?which=91905

Dissecting the Scene Syndrome executable graphics

Another 4K executable graphics (more like 2.3 KB, actually) , which placed 2nd at the Outline 2022 executable graphics compo. It was also nominated for a Meteorik award in the “Best executable graphics” category.

Pouet page: https://www.pouet.net/prod.php?which=91576

Download: https://www.fulcrum-demo.org/wp-content/uploads/2022/05/Dissecting-the-Scene-Syndrome-by-Fulcrum.zip

DC Offset 8K

This image has an empty alt attribute; its file name is DCOffset-1024x582.png

This was our contribution to the 8K intro competition at Revision 2021, where it placed 2nd. It is the first production using our experimental GPU-synth. I wanted to see if a synth in the same language as the visuals would compress better and ultimately be smaller than the usual softsynths (Clinkster/4Klang). Unfortunately, you can’t really do an apples to apples comparison by implementing half a synth, or even 80% of one. And I’m not that interested in synths, VSTi’s etc etc, so this took a long time. This intro gave the final motivational push to finish it. We ran in the usual last-days bugs, sleepless nights, asking the organizers to extend the deadline etc… so it’s not as polished as we had hoped. Still, we learned a lot, and had fun making this!

Pouet page: https://www.pouet.net/prod.php?which=88658

Download the intro: https://files.scene.org/view/parties/2021/revision21/pc-8k-intro/dc_offset_by_fulcrum_%28synth_fix%29.zip

Youtube: https://www.youtube.com/watch?v=XwRqysVSAwA

Jumpgate 10.24 Shader code

This is the exhaustively documented shader code of the Jumpgate 10.24 1 kilobyte intro.




/*
Jumpgate 10.24 by Seven/Fulcrum
-------------------------------
This is the documented sourcecode for the shader of my 1 kilobyte intro for Assembly
2020. Apart from the whitespace for readability, it's identical to the shader used
in the final 720p version.

I'm sharing this so can you learn from it (especially from my mistakes), so don't copy
it wholesale, don't use it for work purposes :D and of course I have no
responsibility if anything bad happens. It's a 1K, not a shining example of
safety and clarity!

*/

// m is the time. A simple frame counter is passed via gl_color, I rely on automatic
// truncation to get the x component. There is a scaling factor to speed up or slow down the entire
// intro. The offset .65 ensures the ship reaches the jumpgate at time 0. We're going to see this number
// a lot. f is used for various things, d is the random seed for the ship generation routine.
float m=10.5*gl_Color-.65,f,d;

// standard 2D rotation routine, rotates a vec2 m radians. I think H4rdy/Lemon shared this variant first.
// Named s for StandardRotationFunction
vec2 s(vec2 y,float m)
{
  return y*cos(m)+vec2(y.y,-y.x)*sin(m);
}

// NSA-approved random function. We're just trying to get some variation and be very compressible,
// not to be cryptographically secure :) The seed is increased locally, and some simple math is done.
// Note the use of frac() instead of fract(), which is an HLSL function that the nvidia drivers accept
// with a warning, but AMD errors on it. I normally don't stoop to exploiting brand-dependent differences,
// but since I already made a mistake that made the intro nvidia-only, I might as wel save some bytes...
// The .65 could have been any number, but I reused a number for maximum compression.
// Named s for SomewhatRandom
float s()
{
  return d++,frac(d*.65*frac(d*.65));
}

// The heart of the intro: the Signed Distance Function (SDF) for the procedurally generated ships/jumpgate.
// y is the 3D point for which to evaluate the SDF, m is the random seed for this specific spaceship.
// Named s for ShipSDF
float s(vec3 y,float m)
{
  // Initialize the distance as "far away". Any big number is OK so try to use one that's needed elsewhere.
  float f = 154; 
  // initialize the random seed before calling s() with abandon.
  d=m;

  // The basic shape of the ship is a randomly-oriented plane, shifted from the origin, and then mirrored
  // around 2 axis. This typically gives you an octahedron (a pyramid on top of an upside-down pyramid).
  // Then I uses IQ's elongation function, which add bevels to the octahedron. But it's simplified which
  // causes the elongation to only happen on the positive side of each axis, so the shape is not centered
  // on the origin anymore.
  // We create 4 of those, but each time, they're 4 times as many (due to mirroring), a bit smaller and
  // shifted more to the rear. So we get one big octahedron (body/cockpit), with 4 smaller ones a bit more
  // to the back (wings?), with 16 even smaller(engines?), and 64 smallest (random parts) at the end.
  // Depending on the random function, some of these might not be visible. We also rotate the octahedra to
  // generate wings etc.  
  
  // 4 levels of parts
  for(float d=0;d<4;d++)
  {
	// elongate a random amount.
    y-=min(max(y,0),s());
	// mirror X and Y axis.
    y.xy=abs(y.xy);

	// Combine the SDFs of each part with the min operator
    f=min(f,
		  pow(.7,d) // Scale the result up. This SHOULD be the same as 1/scalefactor (which is 1.6), but you
					// can abuse this as a kind of safety factor (to hide artifacts of other bugs) or as an 
					// overstepping factor (to speed up marching, if other functions are too conservative).
					// So you can cheat a bit and re-use some number (like .65), and then suffer from it when
					// your intro contains artifacts and you can't fix it without breaking the filesize...
		  *(dot(abs(y),	// use the dot function as a SDF for a plane oriented by it's normal.
			    normalize(vec3(s(),s(),s()*.2) // random normal, tweaked to get octahedra stretched along Z-axis
											   // ALSO UNDEFINED BEHAVIOR THAT BREAKS ON AMD! 
				+.01) // to prevent too many very thin needle shapes, add a constant before normalizing.
			-s())); // this is the thickness of the plane, randomly picked.

	// So, did you figure out the undefined behavior? The order of evaluation of paramaters is not defined in GLSL.
	// So if your random function generates 0.3, 1, and 0.5, nvidia will give you vec3(.3, 1, .5), but AMD might
	// give you vec3(.5,1, .3)... In the safe versions, this is fixed with extra variables: a=s(), b=s(), c=s(),
	// ... vec3(a,b,c); , But I didn't had room for that in the compo version :( Sorry, AMD fans.

	// Scale the octahedra down, and shift them a random amount, but mostly to the back of the plane.
	// Also undefined behavior again.
	y=y*1.6-vec3(s(),s(),s()*-3);
	//Finally, rotate the next part a random amount around the z-axis 
    y.xy=s(y.xy,s());
  }
  return f;
}

// This is the SDF for the entire fleet and the jumpgate.
// Named s for SceneSDF
float s(vec3 y)
{
  // The jumpgate is just the back end of a carefully-chosen ship, scaled up, and combined with a sphere.
  // Remember that the elongation operation was not symmetrical anymore? That means we have to shift the
  // gate back a bit (.15) so it matches the sphere.
  float f=min(length(y)-14, // the sphere
			  s(y/9+.15,8.8)*9); // jumpgate, seed 8.8 (I tried hundreds of combinations), scaled by 9.
	
  // This generates the fleet. For maximum compression (have you heard that before?), I use the same
  // loop variable and iteration count as the ship SDF. So we have 4 different ship types. Of course, the
  // higher the amount of ship types, the longer you have to look for a seed that generates decent ones
  // for ALL types. So this is another giant timesink, and every time you tweak a constant in the ShipSDF,
  // you have to start over :(
  // The fleet is build up the same as each individual ship: 4 layers, each layer scaled down and mirrored.
  // So that should give us 1 big ship, 4 medium, 16 small, 64 tiny. There is no rotation, because then it
  // looked like a traffic accident instead of an organized fleet. But the resulting pyramid (big ship at
  // top, smaller ones below) looked far too regular, with perfectly-aligned layers of ships, so I 
  // added another trick.
  for(float d=0;d<4;d++)
  {
	  // Offset the ships from each other. This would make a line from big ship to small ship. 
    y-=vec3(18,9,50);

	// mirror the X and Y direction, causes more smaller ships.
    y.xy=abs(y.xy);

	// Here's the extra trick: shift mirrored space back! This cause another 4 extra ships
	// to appear in front of each original. Depending on the camera angle, it also wreaks havoc with your
	// SDF accuracy, so you better like futzing around with parameters to hide marching artifacts :(
    if(y.z>13)
      y-=vec3(8,6,26);
    
	// Combine the SDFs of each ship with the min operator
	f=min(f,
		pow(m+1.1,d) // scaling correction. Giant hack ahoy: the smallest ships had bad overstepping artifacts
				// which were fixed by decreasing this, but that caused the jumpgate to disappear behind the 
				// big ships. Since they are at different times in the intro, I use the time m (which starts at
				// -.65) to adjust the scaling differently during the intro.
		*s(y,d+.31)); // Ship SDF, with seed .31 + the layer index.

		// Scale the fleet (same factor as ship parts, FOR GREAT COMPRESSION!!). Shifting the ships around
		// is kind of useless since each layer is shifted the same amount, but it improved compression at some point.
		y=y*1.6-vec3(s(),s(),s() -3);
  }
  return f;
}

void main()
{

  vec3 v,		// the current point we're evaluation on the camera ray from this pixel.
	   z=normalize(-vec3(1,.55,2)+.0015*gl_FragCoord); // the raymarch direction, for a 720p screen.
													   // Note abuse of truncation of gl_FragCoord again.

  // When the camera reaches the gate, we want it to enter straight ahead. So get a time value that becomes 0 near the gate.
  f=min(m+.1,0);
  // rotate the camera left/right (one sweep) and up/down (sine wave).
  // The f*f*(2-f) is IQ's near-identity function to smoothly halt the camera motion.
  z.yz=s(z.yz,f*f*(f-2)*sin(m*-3.5));
  z.xz=s(z.xz,f*f*(f-2)*4);

  // The actual raymarching. No early exit at all. The camera position is defined in the parameter.
  // f is the total distance traveled. It starts as the time value instead of 0,but that's OK as
  // we're not doing close-ups
  // z is the direction, v is the current position.
  for(float d=0;d<104;d++)
    f+=s(v=vec3(0,0,-m*274)+z*f);
  
  
  vec3 y=-vec3(1,0,.2), // the sun direction, slightly higher than 1 for dramatic lighting and better compression
	   x=normalize(vec3(s(v+vec3(.01,0,0)),	// the normal, calculated by the usual 3 evaluation method
		                s(v+vec3(0,.01,0)), // plus the assumption we are perfectly on the surface.
		                s(v+vec3(0,0,.01)))), 
       r=pow(max(0,dot(z,reflect(y,x))),104)  // specular lighting
	     +vec3(.31,.4,.5)*(max(0,dot(y,x))	  // diffuse lighting, on blue-ish material
									  +.5);   // ambient lighting
  
  z=s(v)<.01?reflect(z,x):z; // if we've hit something, reflect the ray direction using the normal.

  // The first of 3 very similar fractals. This is based on Knighty's Cosmos fractal (on Shadertoy.com)
  // but simplified because I just need a static background. The main concern is to get stars that are
  // not too big, but not tiny either because those flicker like hell in motion.
  for(float d=0;d<15;d++) // 15 layers 
  {
    y=vec3(.65,.2,d*.004)-z*d*.004; // startpoint depends on ray direction and carefully recycled constants.
    for(float d=0;d<15;d++) // 15 iterations
      f=dot(y,y),y=abs(y)/f-.65; // get square of distance (dot), mirror (abs) and shift (-.65)
    x-=f*y; // color the distance f with the endposition y, which gives pretty coherent colors.
  }

  // The ships red coloring. Only show it if we didn't hit the gate sphere.
  if(length(v)>14.1)
  {
    y=v-84; // scaling factor to get somewhat interesting patterns on most ships (not full red/blank)
    for(float d=0;d<15;d++) // 15 iterations
      f=length(y),y=abs(y)/f-.65; // get distance (length), mirror (abs) and shift (-.65)

	// blank the green and blue components. The threshold makes the red or blank parts dominate.
    if(f<.3)
      r.yz=0; // add this to the material color, not the background
  }
  // The jumpgate sphere fractal. Only show if we hit the sphere.
  if(length(v)<14.1)
    for(float d=0;d<15;d++) // 15 layers
    {
      y=vec3(0,0,-m*30-1)-z; // the startpoint is chosen so the inversion happens at teh right time.
      for(float d=0;d<15;d++) // 15 iterations
        f=length(y),y=abs(y)/f-.65; // This fractal really, really depends on the .65 value. 
      x+=f*154; // add the fractal to the background, with appropriate brightness
    }

  // depending on whether we hit something, show the background, or the material with reflected background.
  gl_FragColor.xyz=s(v)<.01?r+x*.0004:x*.004;
  
}

Jumpgate 10.24 1Kb intro

Jumpgate 10.24 is our contribution to the Assembly 2020 1 kilobyte intro competition. It was an online-only event, for obvious reasons, but we’re glad it wasn’t cancelled.

You can watch the intro on Youtube: https://youtu.be/Cv4DYmxgYlQ

Or you can download the executables (1K versions and safe versions) here: zip archive. Note that some virusscanners may confuse 1K intros with malware due to the unconventional coding techniques. The almost-compo version is nvidia only and rather slow, I recommend running one of the safe versions.

The Last Dance 8K

Originally conceived as an 4K intro for Assembly 2018, we released this 8K at Revision 2019, in cooperation with Horology. It needs a very powerful graphics card. You can either watch in on Youtube: https://youtu.be/x_MalHjxcEs

Or if your hardware is fast enough (AMD RX 480, Nvidia 1080 GTX), you can download the executable here:

MAGI

It took us 7 years to release a new demo, and one month later we have another ready :) Made in approximately 1 week time to support the MAGFest 2019 demoparty, it took 1st place in the combined intro/demo competition. It’s a bit short, but we’re happy we made it.

Get the executable here: MAGI by Fulcrum

If your videocard is too slow, watch it on Youtube: MAGI by Fulcrum

Party At Spencer’s

So, after almost 7 years, we made a demo again! Slightly hurried, but we wanted to support the American demoscene at Demosplah 2018, where we placed 2nd in the Modern democompo.

Get the demo here: Party At Spencers by Fulcrum final