Blog post hero image

What's hot in TresJS v4 πŸ”₯

Alvaro Saburido
Alvaro Saburido @alvarosabu

TresJS v4 is the biggest version of TresJS yet! πŸŽ‰ We are super excited to show you all the new features we have stored for you in this new major version

The truth is, after 1 year of life, TresJS has grown a lot and we have learned a lot from the community and the users.

We recollected feedback, A LOT of it:

  • Performance issues πŸ˜…
  • On-demand rendering πŸ‘€
  • Type issues after ThreeJS v156 πŸ₯²
  • Memory Leak on route change 🫠
  • Events propagation issues 🀯
  • Event handlers on primitives 🫣
  • Please add a take-over render loop mode
  • Lot of issues with current loop approach.

And we have been working hard to solve all of them.

What’s hot in TresJS v4

  • On-demand Rendering
  • Refactored Event Store and Propagation logic
  • Added event handlers to primitives
  • object prop on primitives is now reactive.
  • Better disposal of resources for memory management
  • New approach for the render and update loops
  • A LOT of bug fixes and improvements

On-demand Rendering

Until now, the render loops always render.

The renderMode prop has been added:

  • on-demand will automatically invalidate the frame on:
    • Instances prop changes
    • Context state change (resize, clearColor etc)
    • remove nodes (v-if)
<TresCanvas
  render-mode="on-demand"
  clear-color="#82DBC5"
  @render="onRender"
>

Since it is not really possible to observe all the possible changes in your application, you can also manually invalidate the frame by calling the invalidate() method from the useTres composable:

import { useTres } from '@tresjs/core'

const boxRef = ref()
const { invalidate } = useTres()

watch(boxRef.value, () => {
  boxRef.value.position.x = 1
  invalidate()
})

Mode manual

If you want to have full control of when the scene is rendered, you can set the render-mode prop to manual:

<TresCanvas render-mode="manual">
  <!-- Your scene goes here -->
</TresCanvas>

In this mode, Tres will not render the scene automatically. You will need to call the advance() method from the useTres composable to render the scene:

<script setup>
import { useTres } from '@tresjs/core'

const { advance } = useTres()

onMounted(() => {
  advance()
})
</script>

Refactored Event Store and Propagation logic

We have refactored the event store and propagation logic to make it more efficient and reliable.The following pointer events are available on v3 and previous:

  • click
  • pointer-move
  • pointer-enter
  • pointer-leave

From v4.x on, the following pointer events are been added to the list:

  • context-menu (right click)
  • double-click
  • pointer-down
  • pointer-up
  • wheel
  • pointer-missed

Event bubbling 🫧

The event bubbling is now available in TresJS. You can stop the event propagation by calling the stopPropagation method from the event object:

<TresMesh
  @click="(event) => {
    console.log('click')
    event.stopPropagation()
  }"
/>

Primitives enhancements

You can now add event handlers to primitives, specially usefull to add pointer events to models:

<script setup>
import { useGLTF } from '@tresjs/cientos'

const { scene: model } = await useGLTF(
  'models/ugly-bunny.gltf',
)
function handleClick(ev) {
  console.log('clicked', ev)
}
</script>

<template>
  <primitive 
    :object="model"
    @click="handleClick"
  />
</template>

Reactive object prop

<script setup>
const isCube = ref(false)
</script>

<template>
  <primitive 
    :object="isCube ? CubeModel : AkuAkuModel" 
  />
</template>

Conditionally render primitives

You can now conditionally render primitives using vue v-if directive.

<script setup>
const isVisible = ref(false)
</script>

<template>
  <primitive 
    v-if="isVisible" 
    :object="CubeModel" 
  />
</template>

Memory management

  • Automatic disposal of resources when the component is unmounted.
  • Manual dispose utility method to dispose of resources manually (primitives)
  • Frees CPU and GPU memory.

Render and Update loops

Until v4 the render and update loops were managed by the useRenderLoop composable. This approach covered most of the basic usecases but it was not enough for more complex scenarios:

  • Since is a global composable, multiple canvas would share the same loop.
  • The loop was not bounded to the context of the canvas.
  • Pause and resume was availablle but not working as expected.
  • No way to control the loop from the outside.
  • No way to have multiple loops in the same canvas. No posibility to prioritize order of execution.
  • No take-over mode for the render loop.

In v4 we have introduce a new composable called useLoop that will allow you to have full control of the loop:

<script setup>
import { useLoop } from '@tresjs/core'

const boxRef = ref()

const { onBeforeRender } = useLoop()

onBeforeRender(({ delta }) => {
  boxRef.value.rotation.y += delta
})
</script>

<template>
  <TresMesh ref="boxRef">
    <TresBoxGeometry />
    <TresMeshBasicMaterial color="teal" />
  </TresMesh>
</template>

The callback function will be triggered just before a frame is rendered and it will be deregistered automatically when the component is destroyed.

Take over the render loop

You can take over the render call by using the render method.

const { render } = useLoop()

render(({ renderer, scene, camera }) => {
  renderer.render(scene, camera)
})
Consider that if you take over the render loop, you will need to manually render the scene and take care of features like the conditional rendering yourself.

Register after render callbacks (ex physics calculations)

You can also register callbacks which are invoked after rendring by using the onAfterRender method.

const { onAfterRender } = useLoop()

onAfterRender(({ renderer }) => {
  // Calculations
})

Render priority

Both useBeforeRender and useAfteRender provide an optional priority number. This number could be anything from Number.NEGATIVE_INFINITY to Number.POSITIVE_INFINITY being the 0 by default. The lower the number, the earlier the callback will be executed.

onBeforeRender(() => {
  console.count('triggered first')
}, -1)

onBeforeRender(() => {
  console.count('triggered second')
}, 1)

Params of the callback

All callbacks receive an object with the following properties:

  • delta: The delta time between the current and the last frame. This is the time in miliseconds since the last frame.
  • elapsed: The elapsed time since the start of the render loop.
  • clock: The THREE clock instance.
  • renderer: The WebGLRenderer of your scene.
  • scene: The scene of your scene.
  • camera: The currently active camera.
  • raycaster: The global raycaster used for pointer events.
  • controls: The controls of your scene.
  • invalidate: A method to invalidate the render loop. This is only required if you set the render-mode prop to on-demand.
  • advance: A method to advance the render loop. This is only required if you set the render-mode prop to manual.

Pausing and resuming the update loop

You can use pause and resume methods:

const { onBeforeRender, pause, resume } = useLoop()

onBeforeRender(({ elapsed }) => {
  sphereRef.value.position.y += Math.sin(elapsed) * 0.01
})

pause() // This will pause the loop
resume() // This will resume the loop

Pausing and resuming the render

You can use pauseRender and resumeRender methods:

const { pauseRender, resumeRender } = useLoop()

onBeforeRender(({ elapse }) => {
  sphereRef.value.position.y += Math.sin(elapsed) * 0.01
})

pauseRender() // This will pause the renderer
resumeRender() // This will resume the renderer

Unregistering callbacks

You can unregister a callback by calling the method off returned by the onBeforeRender or onAfterRender method.

const { onBeforeRender } = useLoop()

const { off } = onBeforeRender(({ elapsed }) => {
  sphereRef.value.position.y += Math.sin(elapsed) * 0.01
})

Devtools improvements

  • Renderer info available when inspecting the scene
  • All the shaders programs (materials) are available to inspect
  • Search scene three objects by name

Conclusion

We are super excited about this new version of TresJS. Please give it a try and let us know what you think on the Discord channel. We are always looking for feedback to improve the library.

Give some love to the contributors that make it happen. πŸ™Œ πŸ’š