threlte logo
@threlte/extras

<AsciiRenderer>

A wrapper around Three’s AsciiEffect addon. It replaces the main render function with a function that renders the scene to an HTML table and overlays it on top of the canvas. Areas with a higher “brightness” are mapped to characters that appear “fuller”.

<script lang="ts">
  import Scene from './Scene.svelte'
  import type { AsciiEffectOptions } from 'three/examples/jsm/Addons.js'
  import { AsciiRenderer } from '@threlte/extras'
  import { Button, Checkbox, Color, Folder, Pane, Slider, Text } from 'svelte-tweakpane-ui'
  import { Canvas } from '@threlte/core'

  let fgColor = $state('#ff2400')
  let bgColor = $state('#000000')

  const defaultCharacters = ' .:-+*=%@#'
  let characters = $state(defaultCharacters)

  let alpha = $state(1)
  let block = $state(false)
  let color = $state(false)
  let invert = $state(true)
  let resolution = $state(0.1)
  let scale = $state(1)

  const options: AsciiEffectOptions = $derived({
    alpha,
    block,
    color,
    invert,
    resolution,
    scale
  })

  let autoRotate = $state(true)
</script>

<div>
  <Pane
    position="fixed"
    title="AsciiRenderer"
  >
    <Folder title="scene">
      <Checkbox
        bind:value={autoRotate}
        label="auto rotate"
      />
    </Folder>
    <Folder title="options">
      <Slider
        bind:value={scale}
        label="scale"
        min={1}
        max={3}
        step={1}
      />
      <Slider
        bind:value={resolution}
        label="resolution"
        min={0.05}
        max={0.2}
        step={0.05}
      />
      <Checkbox
        bind:value={invert}
        label="invert"
      />
      <Checkbox
        bind:value={color}
        label="color"
      />
      {#if color}
        <Checkbox
          bind:value={block}
          label="block"
        />
      {/if}
    </Folder>
    <Folder title="props">
      <Text
        bind:value={characters}
        label="characters"
      />
      <Button
        on:click={() => {
          characters = defaultCharacters
        }}
        title="reset characters"
      />
      {#if !color}
        <Color
          bind:value={fgColor}
          label="text color"
        />
        <Color
          bind:value={bgColor}
          label="background color"
        />
      {/if}
    </Folder>
  </Pane>
  <Canvas>
    <AsciiRenderer
      {bgColor}
      {characters}
      {fgColor}
      {options}
    />
    <Scene {autoRotate} />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import { OrbitControls } from '@threlte/extras'
  import { T } from '@threlte/core'

  type SceneProps = { autoRotate: boolean }

  let { autoRotate = true }: SceneProps = $props()
</script>

<T.DirectionalLight position={5} />

<T.PerspectiveCamera
  makeDefault
  position.z={5}
>
  <OrbitControls {autoRotate} />
</T.PerspectiveCamera>

<T.Mesh>
  <T.MeshNormalMaterial />
  <T.TorusKnotGeometry />
</T.Mesh>

Usage

Typically you’d use <AsciiRenderer> alongside your main Scene component. <AsciiRenderer> creates an absolutely postioned table element that is appended to the dom element of the <Canvas>. You may need to set the wrapping element’s position to relative

<div>
  <Canvas>
    <AsciiRenderer />
    <Scene />
  </Canvas>
</div>

<style>
  div {
    position: relative;
  }
</style>

Characters

The characters prop should be sorted by ascending “opaqueness”.

<AsciiRenderer characters=" #" />

The example above uses a character set with two characters - and #.

Colors

By default the renderer sets options.color to false and will only use the colors given by the fgColor and bgColor props. fgColor and bgColor can be any acceptable CSS color string. If your colors contain an alpha component, make sure to set options.alpha to true.

If options.color is set to true, fgColor and bgColor will be ignored and the corresponding color of the scene will be used for each character.

<AsciiRenderer options={{ color: true }} />

Setting options.color to true slows down performance. Using a static scene or manually rendering can help.

Other Options Props

Because the effect doesn’t support dynamically updating options, any time an options property changes, a new AsciiEffect is created inside <AsciiRenderer. For this reason, options is passed as an object to <AsciiRenderer>

  • options.block makes the characters into color blocks. It is only applied if options.color is true.
  • options.invert inverts the fgColor and bgColor colors.
  • options.resolution controls how detailed the render is.
  • options.scale controls the scale of the characters. Note that zooming the camera does not control the size of the characters.

A new AsciiEffect instance must be created anytime options changes because an effect can not have it options changed after it has been created. This is a limitation of the AsciiEffect addon from ThreeJS.

Disabling the Render Task

If at some point your scene doesn’t need to be rendered because it will no longer update or nothing will change between frames, you can turn off the rendering task by setting the autoRender prop to false. This will stop the render task from running which can improve performance. This is especially useful when using options.color which is known to slow down the renderer.

AsciiRenderer passes its AsciiEffect instance to its children snippet. This allows you to opt out of AsciiEffect’s renderering task but still use the effect it creates. It can also be used for on-demand rendering.

<script lang="ts">
  import Scene from './Scene.svelte'
  import type { AsciiEffect } from 'three/examples/jsm/Addons.js'
  import type { AsciiEffectOptions } from 'three/examples/jsm/Addons.js'
  import { AsciiRenderer } from '@threlte/extras'
  import { Canvas } from '@threlte/core'
  import { Checkbox, Pane } from 'svelte-tweakpane-ui'

  let asciiRenderer: Component | undefined = $state()

  let color = $state(false)

  const options: AsciiEffectOptions = $derived({ color })
</script>

<Pane
  title="render on update"
  position="fixed"
>
  <Checkbox
    bind:value={color}
    label="color"
  />
</Pane>

<div>
  <Canvas>
    <AsciiRenderer
      autoRender={false}
      bind:this={asciiRenderer}
      {options}
    >
      {#snippet children({ asciiEffect })}
        <Scene {asciiEffect} />
      {/snippet}
    </AsciiRenderer>
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import { T, useThrelte } from '@threlte/core'
  import type { AsciiEffect } from 'three/examples/jsm/Addons.js'

  let { asciiEffect }: { asciiEffect: AsciiEffect } = $props()

  const { autoRender, camera, scene } = useThrelte()

  $effect(() => {
    const lastAutoRender = autoRender.current
    autoRender.set(false)
    return () => {
      autoRender.set(lastAutoRender)
    }
  })

  // render once
  $effect(() => {
    asciiEffect.render(scene, camera.current)
  })
</script>

<T.DirectionalLight position={5} />

<T.PerspectiveCamera
  makeDefault
  position.z={5}
/>

<T.Mesh>
  <T.MeshNormalMaterial />
  <T.TorusKnotGeometry />
</T.Mesh>

The example above demonstrates how to render on demand. The scene is only rendered when the color checkbox changes. The change causes the options object to update which triggers a new AsciiEffect to be created. The effect is passed into the <Scene> component and an $effect is used to rerender the scene.

Component Signature

Props

name
type
required
default
description

autoRender
boolean
no
true
whether to automatically run the render task

bgColor
string
no
'#000000'
background color used when `options.color` is set to `false`

camera
THREE.Camera
no
useThrelte().camera.current
the camera to use when renderering

characters
string
no
' .:-+*=%@#'
character set used by the renderer. Sorted left-to-right from least to most opaque

fgColor
string
no
'#ffffff'
text color used when `options.color` is set to `false`

onstart
() => void
no
function to call anytime the renderer has been started

onstop
() => void
no
function to call anytime the renderer has been stopped

options
Three.AsciiEffectOptions
no
{}
object passed to the effect's constructor. because the effect doesn't support adjusting individual option props, whenever an option prop changes, the effect must be recreated

scene
THREE.Scene
no
useThrelte().scene
the scene to use when renderering

Exports

name
type
description

start
() => void
Manually start the render task

stop
() => void
Manually stop the render task

getEffect
() => THREE.AsciiEffect
Getter for the underlying AsciiEffect instance