useLoop
The useLoop
composable allows you to register callbacks that run before and after each render cycle, or take complete control of the rendering process within TresCanvas
components.
Usage
useLoop
can only be used in child components of a TresCanvas
component, as its data is provided by TresCanvas
.import { useLoop } from '@tresjs/core'
const { onBeforeRender, onRender } = useLoop()
onBeforeRender(() => {
console.log('before render')
})
onRender(() => {
console.log('after render')
})
Priority
The onBeforeRender
and onRender
callbacks can be registered with a priority. The priority is a number that determines the order in which the callbacks are executed. The default priority is 0.
onBeforeRender(() => {
console.log('earlier before render')
}, -10)
onBeforeRender(() => {
console.log('just before render')
})
onBeforeRender(() => {
console.log('even closer before render')
}, 10)
onRender(() => {
console.log('even closer after render')
}, -10)
onRender(() => {
console.log('just after render')
})
onRender(() => {
console.log('later after render')
}, 10)
Register update callbacks
The most common use of onBeforeRender
is to register update callbacks for animations, such as rotating or moving objects in the scene.
<script setup lang="ts">
import { useLoop } from '@tresjs/core'
const { onBeforeRender } = useLoop()
const cube = ref<Mesh>()
// Frame-rate independent animation using delta time
onBeforeRender(({ delta, elapsed }) => {
if (cube.value) {
// Smooth rotation based on frame time
cube.value.rotation.y += delta * 2 // 2 radians per second
// Oscillating movement based on elapsed time
cube.value.position.y = Math.sin(elapsed * 3) * 0.5
}
})
</script>
<template>
<TresMesh ref="cube">
<TresBoxGeometry :args="[1, 1, 1]" />
<TresMeshNormalMaterial />
</TresMesh>
</template>
<script setup lang="ts">
import { TresCanvas } from '@tresjs/core'
import AnimatedCube from './AnimatedCube.vue'
</script>
<template>
<TresCanvas>
<AnimatedCube />
</TresCanvas>
</template>
Take Over the Render Loop
You can take complete control of the rendering process by using the render
method from useLoop
. This allows you to implement custom rendering logic, post-processing effects, or conditional rendering.
import { useLoop, useTresContext } from '@tresjs/core'
const { render } = useLoop()
const { renderer, scene, camera } = useTresContext()
// Take over the render loop with custom logic
render((notifySuccess) => {
// Your custom rendering logic here
if (camera.activeCamera.value) {
renderer.instance.render(scene.value, camera.activeCamera.value)
// IMPORTANT: Call notifySuccess() to indicate the frame was rendered successfully
notifySuccess()
}
})
notifySuccess()
in the example above) to properly notify the render loop that the frame was completed. This is essential for the render modes (always
, on-demand
, manual
) to function correctly.Custom Rendering Examples
Here are examples showing different custom rendering scenarios:
<script setup lang="ts">
import { useLoop, useTresContext } from '@tresjs/core'
const { render } = useLoop()
const { renderer, scene, camera } = useTresContext()
const shouldRender = ref(true)
render((notifySuccess) => {
// Only render when condition is met
if (shouldRender.value && camera.activeCamera.value) {
renderer.instance.render(scene.value, camera.activeCamera.value)
}
// Always call notifySuccess, even if we didn't render
notifySuccess()
})
</script>
<script setup lang="ts">
import { useLoop, useTresContext } from '@tresjs/core'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
const { render } = useLoop()
const { renderer, scene, camera } = useTresContext()
// Setup post-processing composer
const composer = new EffectComposer(renderer.instance)
render((notifySuccess) => {
// Initialize composer on first render
if (composer.passes.length === 0 && camera.activeCamera.value) {
const renderPass = new RenderPass(scene.value, camera.activeCamera.value)
composer.addPass(renderPass)
}
// Render with post-processing
composer.render()
// Notify that the frame is complete
notifySuccess()
})
</script>
<script setup lang="ts">
import { useLoop, useTresContext } from '@tresjs/core'
import { WebGLRenderTarget } from 'three'
const { render } = useLoop()
const { renderer, scene, camera } = useTresContext()
const renderTarget = new WebGLRenderTarget(1024, 1024)
render((notifySuccess) => {
if (camera.activeCamera.value) {
// First pass: render to texture
renderer.instance.setRenderTarget(renderTarget)
renderer.instance.render(scene.value, camera.activeCamera.value)
// Second pass: render to screen
renderer.instance.setRenderTarget(null)
renderer.instance.render(scene.value, camera.activeCamera.value)
}
// Notify completion
notifySuccess()
})
</script>
- Manually triggering a render
- Always calling
notifySuccess()
at the end of your render function - Handling conditional rendering logic yourself
- Managing any post-processing effects
- Ensuring proper frame timing and performance
always
, on-demand
, manual
) will be bypassed when using custom rendering.Callback Parameters
Both onBeforeRender
and onRender
callbacks receive a context object containing timing information and access to the TresJS context:
onBeforeRender(({ delta, elapsed, renderer, camera, scene, sizes, invalidate, advance }) => {
// Timing information
console.log('Time since last frame:', delta) // in seconds
console.log('Total elapsed time:', elapsed) // in seconds
// TresJS context access
console.log('Current camera:', camera.value)
console.log('Scene:', scene.value)
console.log('Canvas size:', sizes.width.value, sizes.height.value)
// Control methods
invalidate() // Mark scene for re-render (useful in on-demand mode)
advance() // Manually advance one frame (useful in manual mode)
})
onBeforeRender
and onRender
Parameters
render
Method Parameters The
The render
method takes a function that receives a single notifySuccess
callback parameter:
render
method does NOT receive a context object like onBeforeRender
and onRender
. Instead, use useTres()
to access the renderer, scene, and camera within your render function.Type
function useLoop(): UseLoopReturn
interface UseLoopReturn {
/** Stops the render loop */
stop: () => void
/** Starts the render loop */
start: () => void
/** Reactive reference indicating if the loop is currently active */
isActive: Ref<boolean>
/** Register a callback to run before each render */
onBeforeRender: (fn: LoopCallback, priority?: number) => { off: () => void }
/** Register a callback to run after each render */
onRender: (fn: LoopCallback, priority?: number) => { off: () => void }
/** Take complete control over the rendering process */
render: (fn: RenderFunction) => void
}
type LoopCallback = (context: LoopContext) => void | Promise<void>
type RenderFunction = (notifySuccess: () => void) => void
interface LoopContext {
/** Time in seconds since the last frame */
delta: number
/** Total elapsed time in seconds since render loop started */
elapsed: number
/** The Three.js WebGL renderer instance */
renderer: TresRenderer
/** The currently active camera */
camera: ComputedRef<Camera | undefined>
/** The Three.js scene object */
scene: ShallowRef<TresScene>
/** Reactive size information for the canvas */
sizes: SizesType
/** Reference to current camera controls */
controls: Ref<TresControl | null>
/** TresJS extension function */
extend: (objects: any) => void
/** Event manager for pointer interactions */
events: EventManager
/** Mark scene for re-render in on-demand mode */
invalidate: () => void
/** Manually advance one frame in manual mode */
advance: () => void
}
interface SizesType {
/** Canvas width in pixels */
width: Ref<number>
/** Canvas height in pixels */
height: Ref<number>
/** Canvas aspect ratio (width / height) */
aspectRatio: Ref<number>
/** Device pixel ratio */
pixelRatio: Ref<number>
}
type TresRenderer = WebGLRenderer | Renderer