Skip to main content

Your first rendering pipeline

info

Now it's time to set up your first rendering pipeline! In this guide, we'll walk you through the process of creating a simple rendering pipeline using the p5.asciify library. We'll cover the following steps:

  • Setting up and configuring a simple rendering pipeline through the setupAsciify() function.
  • Updating properties of the rendering pipeline during the draw loop.
  • Applying a filter to the ASCII representation drawn to the main canvas using the drawAsciify() function.

Setting up the brightness-based ASCII conversion renderer

Looking back at the first snippet given in the Introduction section, we saw that the p5.asciify library applied ASCII conversion to the main canvas by default without using any functions provided by p5.asciify, just plain p5.js code.

Now, let's start introducing the setupAsciify() function to this sketch to set up and customize the already pre-enabled brightness-based ASCII conversion renderer to our liking.

let asciifier;
let brightnessRenderer;

function setup() {
  setAttributes('antialias', false);
  createCanvas(windowWidth, windowHeight, WEBGL);
}

// Called automatically after p5.js `setup()`
// to set up the rendering pipeline(s)
function setupAsciify() {
  // Fetch relevant objects from the library
  asciifier = p5asciify.asciifier();
  brightnessRenderer = asciifier
    .renderers() // get the renderer manager
    .get("brightness"); // get the "brightness" renderer

  // Update the font size of the rendering pipeline
  asciifier.fontSize(16);

  // Update properties of the brightness renderer
  brightnessRenderer.update({
    enabled: true, // redundant, but for clarity
    characters: " .:-=+*%@#",
    characterColor: "#ffffff",
    characterColorMode: "sampled", // or "fixed"
    backgroundColor: "#000000",
    backgroundColorMode: "fixed", // or "sampled"
    invertMode: true, // swap char and bg colors
    rotationAngle: 0, // rotation angle in degrees
    flipVertically: false, // flip chars vertically
    flipHorizontally: false, // flip chars horizontally
  });
}

function draw() {
  background(32);
  fill(255);
  rotateX(radians(frameCount * 3));
  rotateZ(radians(frameCount));
  directionalLight(255, 255, 255, 0, 0, -1);
  box(400, 100, 100);
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

In comparison to the previous snippet, we added two new variables and a new function setupAsciify() to the sketch:

  • asciifier

    • This variable will hold the reference to the P5Asciifier object that is provided by default through the p5asciify object, allowing us to manage the rendering pipeline and its renderers.
  • brightnessRenderer

    • This variable will hold the reference to a brightness-based P5AsciifyBrightnessRenderer, which is part of the asciifiers rendering pipeline.

In setupAsciify(), which is called automatically once at the beginning of the sketch after the p5.js setup() function, we fetch the relevant objects we want to use throughout the sketch and store them in the variables we created earlier.

Using asciifier.fontSize(number);, we can set the font size for all renderers in the pipeline.

tip

During setupAsciify(), make sure to store all relevant references to P5Asciifier and it's P5AsciifyRenderer objects in variables so you can access them later in the draw() loop or other functions without having to search for them in potentially larger lists of renderers every time you need them.

tip

The asciifier object also provides a lot of other functions and properties to manage the rendering pipeline, such as asciifier.font(p5.Font); to set the font for all renderers in the pipeline. Make sure to check the P5Asciifier API documentation for more information on the available functions and properties.

Adding the edge detection-based ASCII conversion renderer on top

Right now, the sketch only uses the brightness-based ASCII conversion renderer. Let's add an edge detection-based ASCII conversion renderer on top of it to create a more complex rendering pipeline.

We can do this by enabling the pre-defined "edge" renderer in the asciifier object and setting its properties to our liking.

let asciifier;
let brightnessRenderer;
let edgeRenderer;

function setup() {
  setAttributes('antialias', false);
  createCanvas(windowWidth, windowHeight, WEBGL);
}

// Called automatically after p5.js `setup()`
// to set up the rendering pipeline(s)
function setupAsciify() {
  // Fetch relevant objects from the library
  asciifier = p5asciify.asciifier();
  brightnessRenderer = asciifier
    .renderers() // get the renderer manager
    .get("brightness"); // get the "brightness" renderer

  edgeRenderer = asciifier
    .renderers() // get the renderer manager
    .get("edge"); // get the "edge" renderer

  // Update the font size of the rendering pipeline
  asciifier.fontSize(16);

  // Update properties of the brightness renderer
  brightnessRenderer.update({
    enabled: true, // redundant, but for clarity
    characters: " .:-=+*%@#",
    characterColor: "#ffffff",
    characterColorMode: "sampled", // or "fixed"
    backgroundColor: "#000000",
    backgroundColorMode: "fixed", // or "sampled"
    invertMode: true, // swap char and bg colors
    rotationAngle: 0, // rotation angle in degrees
    flipVertically: false, // flip chars vertically
    flipHorizontally: false, // flip chars horizontally
  });

  // Update properties of the edge renderer
  edgeRenderer.update({
    enabled: true, // redundant, but for clarity
    characters: "-/|\\-/|\\", // should be 8 characters long
    characterColor: "#ffffff",
    characterColorMode: "fixed", // or "sampled"
    backgroundColor: "#000000",
    backgroundColorMode: "fixed", // or "sampled"
    invertMode: false, // swap char and bg colors
    rotationAngle: 0, // rotation angle in degrees
    flipVertically: false, // flip chars vertically
    flipHorizontally: false, // flip chars horizontally
    sampleThreshhold: 16, // sample threshold for edge detection
    sobelThreshold: 0.5, // sobel threshold for edge detection
  });
}

function draw() {
  background(32);
  fill(255);
  rotateX(radians(frameCount * 3));
  rotateZ(radians(frameCount));
  directionalLight(255, 255, 255, 0, 0, -1);
  box(400, 100, 100);
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

The set up is similar to the previous one, but now we have two renderers in the pipeline: the brightness-based and the edge detection-based renderer. Since the edge detection-based renderer is executed after the brightness-based one, it will be drawn on top of the ASCII representation of the texture generated by the brightness-based renderer. If the order was reversed, the edges wouldn't be visible, as they would be drawn behind the all covering brightness-based renderer.

Drawing on top of the ASCII representation

Since p5.asciify encapsulates everything that happens in the draw() loop, we need to use the drawAsciify() function to draw on top of the ASCII representation that is being rendered to the main canvas. This function is called automatically once per frame after the ASCII representation is drawn to the main canvas.

let asciifier;
let brightnessRenderer;
let edgeRenderer;

function setup() {
  setAttributes('antialias', false);
  createCanvas(windowWidth, windowHeight, WEBGL);
}

// Called automatically after p5.js `setup()`
// to set up the rendering pipeline(s)
function setupAsciify() {
  // Fetch relevant objects from the library
  asciifier = p5asciify.asciifier();
  brightnessRenderer = asciifier
    .renderers() // get the renderer manager
    .get("brightness"); // get the "brightness" renderer

  edgeRenderer = asciifier
    .renderers() // get the renderer manager
    .get("edge"); // get the "edge" renderer

  // Update the font size of the rendering pipeline
  asciifier.fontSize(16);

  // Update properties of the brightness renderer
  brightnessRenderer.update({
    enabled: true, // redundant, but for clarity
    characters: " .:-=+*%@#",
    characterColor: "#ffffff",
    characterColorMode: "sampled", // or "fixed"
    backgroundColor: "#000000",
    backgroundColorMode: "fixed", // or "sampled"
    invertMode: true, // swap char and bg colors
    rotationAngle: 0, // rotation angle in degrees
    flipVertically: false, // flip chars vertically
    flipHorizontally: false, // flip chars horizontally
  });

  // Update properties of the edge renderer
  edgeRenderer.update({
    enabled: true, // redundant, but for clarity
    characters: "-/|\\-/|\\", // should be 8 characters long
    characterColor: "#ffffff",
    characterColorMode: "fixed", // or "sampled"
    backgroundColor: "#000000",
    backgroundColorMode: "fixed", // or "sampled"
    invertMode: false, // swap char and bg colors
    rotationAngle: 0, // rotation angle in degrees
    flipVertically: false, // flip chars vertically
    flipHorizontally: false, // flip chars horizontally
    sampleThreshhold: 16, // sample threshold for edge detection
    sobelThreshold: 0.5, // sobel threshold for edge detection
  });
}

function draw() {
  background(32);
  fill(255);
  rotateX(radians(frameCount * 3));
  rotateZ(radians(frameCount));
  directionalLight(255, 255, 255, 0, 0, -1);
  box(400, 100, 100);
}

// called automatically after p5.js `draw()`
function drawAsciify() {
  filter(INVERT); // invert the colors of the canvas
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

In this sketch, we use the drawAsciify() function to apply an inversion filter to the main canvas, which is done using the p5.js function filter(INVERT);. This will invert the colors of the ASCII representation, making it look like a negative image.

You can use drawAsciify() similarly to the draw() function in p5.js, so there are no limitations on what you can do with it. Since we are in a WebGL context, we could also apply the asciified texture to a 3D shape using texture(asciifier.texture); and box();, for example. This way, you can create complex 3D scenes with ASCII representations of textures applied to them.

Updating properties during runtime

Since we just learned that p5.asciify encapsulates everything that happens in the draw() loop, with the ASCII representation being generated and drawn to the main canvas afterwards, it makes sense to update the properties of the rendering pipeline during the draw() loop to see the changes in the frame that's rendered next. Changes to the properties being done in the drawAsciify() function will be applied to the frame after the one being rendered at the moment.

Besides updating the properties by calling a renderers update() function, where we can pass any number of properties to update, we can also call individual functions like .enabled(boolean) or .flipVertically(boolean) to update the properties of the renderer.

let asciifier;
let brightnessRenderer;
let edgeRenderer;

let brightnessInvertModeState = false;

function setup() {
  setAttributes('antialias', false);
  createCanvas(windowWidth, windowHeight, WEBGL);
}

// Called automatically after p5.js `setup()`
// to set up the rendering pipeline(s)
function setupAsciify() {
  // Fetch relevant objects from the library
  asciifier = p5asciify.asciifier();
  brightnessRenderer = asciifier
    .renderers() // get the renderer manager
    .get("brightness"); // get the "brightness" renderer

  edgeRenderer = asciifier
    .renderers() // get the renderer manager
    .get("edge"); // get the "edge" renderer

  // Update the font size of the rendering pipeline
  asciifier.fontSize(16);

  // Update properties of the brightness renderer
  brightnessRenderer.update({
    enabled: true, // redundant, but for clarity
    characters: " .:-=+*%@#",
    characterColor: "#ffffff",
    characterColorMode: "sampled", // or "fixed"
    backgroundColor: "#000000",
    backgroundColorMode: "fixed", // or "sampled"
    invertMode: brightnessInvertModeState, // swap char and bg colors
    rotationAngle: 0, // rotation angle in degrees
    flipVertically: false, // flip chars vertically
    flipHorizontally: false, // flip chars horizontally
  });

  // Update properties of the edge renderer
  edgeRenderer.update({
    enabled: true, // redundant, but for clarity
    characters: "-/|\\-/|\\", // should be 8 characters long
    characterColor: "#ffffff",
    characterColorMode: "fixed", // or "sampled"
    backgroundColor: "#000000",
    backgroundColorMode: "fixed", // or "sampled"
    invertMode: false, // swap char and bg colors
    rotationAngle: 0, // rotation angle in degrees
    flipVertically: false, // flip chars vertically
    flipHorizontally: false, // flip chars horizontally
    sampleThreshhold: 16, // sample threshold for edge detection
    sobelThreshold: 0.5, // sobel threshold for edge detection
  });
}

function draw() {
  // Cycle background color between 0 and 255 using sin
  let bgColor = map(sin(frameCount * 0.05), -1, 1, 0, 255);
  background(bgColor);
  
  fill(255);
  rotateX(radians(frameCount * 3));
  rotateZ(radians(frameCount));
  directionalLight(255, 255, 255, 0, 0, -1);
  box(400, 100, 100);

  if(frameCount % 60 === 0) {
    // toggle invert mode every 60 frames
    brightnessInvertModeState = !brightnessInvertModeState;
    brightnessRenderer.invert(brightnessInvertModeState); 
  }
}

// called automatically after p5.js `draw()`
function drawAsciify() {
  filter(INVERT); // invert the colors of the canvas
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}


info

That's it for this guide! You now know how to set up a simple rendering pipeline using the p5.asciify library and some of it's pre-defined renderers. You also learned how to draw on top of the ASCII representation and update properties of the rendering pipeline during runtime. We've only scratched the surface here with those examples, so feel free to experiment with the code and try out different combinations of renderers and properties to create your own unique ASCII art!