# Render Targets

A [render target](https://api.playcanvas.com/engine/classes/RenderTarget.html) is a rectangular rendering surface you can render into instead of the screen. It wraps one or more renderable color textures, along with an optional depth (and stencil) buffer. Once a camera has rendered into it, the color texture holds the result and can be used anywhere a normal texture can - most commonly applied to a material to display it in the scene, or fed into further processing.

This underpins effects such as in-world screens, security monitors, mirrors and portals, reflection and refraction, and custom multi-pass pipelines. A render target can also render into a single face of a cubemap, a single layer of a texture array - see [Cubemap faces and texture array layers](https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/render-targets.md#cubemap-faces-and-texture-array-layers) - or a single depth slice of a volume texture - see [Volume texture slices](https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/render-targets.md#volume-texture-slices).

## Creating a render target

First create the color [texture](https://api.playcanvas.com/engine/classes/Texture.html) to render into - see [Textures](https://developer.playcanvas.com/user-manual/graphics/textures.md) for the texture types and their options. It must use a renderable, uncompressed format (see [Choosing a format](https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/render-targets.md#choosing-a-format) below):

```javascript
const texture = new Texture(app.graphicsDevice, {
    name: 'RT-color',
    width: 512,
    height: 256,
    format: PIXELFORMAT_SRGBA8,
    mipmaps: true,
    minFilter: FILTER_LINEAR,
    magFilter: FILTER_LINEAR,
    addressU: ADDRESS_CLAMP_TO_EDGE,
    addressV: ADDRESS_CLAMP_TO_EDGE
});
```

Then wrap it in a render target. Request a depth buffer if the scene you render needs depth testing, and set `samples` for hardware anti-aliasing (see [Anti-aliasing](https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/render-targets.md#anti-aliasing)):

```javascript
const renderTarget = new RenderTarget({
    name: 'RT',
    colorBuffer: texture,
    depth: true,
    origin: RENDERTARGET_ORIGIN_TOP
});
```

The [`origin`](https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/render-targets.md#orientation) option is explained below.

## Rendering the scene into it

Assign the render target to a camera's [`renderTarget`](https://api.playcanvas.com/engine/classes/CameraComponent.html#rendertarget) property. That camera then renders into the texture instead of the screen. Give it a negative `priority` so it renders before the main camera each frame, ensuring the texture is up to date when the main camera uses it:

```javascript
const textureCamera = new Entity('TextureCamera');
textureCamera.addComponent('camera', {
    // rendered before the main camera (default priority 0)
    priority: -1,
    renderTarget
});
app.root.addChild(textureCamera);
```

A render target can also be filled by means other than a camera - for example a fullscreen shader pass or a compute shader - but rendering a scene with a camera is the most common case.

## Excluding the display surface with layers

When the render target's texture is displayed on an object within the same scene, that object must **not** be rendered into the render target itself - otherwise the surface would try to render the texture it is currently producing, feeding back on itself.

The clean way to arrange this is with [layers](https://developer.playcanvas.com/user-manual/graphics/layers.md). A camera only renders the layers listed in its `layers` array, so placing the display object in a layer the texture camera does not list excludes it. The [render-to-texture example](https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/render-targets.md#example) below uses three layers and two cameras:

- **World** - the scene content. Listed by both cameras, so it renders into the texture and to the screen.
- **Excluded** - the object that displays the texture (and anything else that should appear on screen only). Listed by the main camera only.
- **Skybox** - listed by both cameras.

```javascript
// a layer for objects that must not render into the texture
const excludedLayer = new Layer({ name: 'Excluded' });
app.scene.layers.insert(excludedLayer, 1);

const worldLayer = app.scene.layers.getLayerByName('World');
const skyboxLayer = app.scene.layers.getLayerByName('Skybox');

// texture camera renders the scene, but NOT the Excluded layer
textureCamera.camera.layers = [worldLayer.id, skyboxLayer.id];

// main camera renders everything, including the display surface in the Excluded layer
mainCamera.camera.layers = [worldLayer.id, excludedLayer.id, skyboxLayer.id];
```

## Using the result

The render target's color texture is available as [`renderTarget.colorBuffer`](https://api.playcanvas.com/engine/classes/RenderTarget.html#colorbuffer) (it is the same texture you created). Apply it to a material like any other texture - for instance as the emissive map of the plane that acts as the display surface:

```javascript
const material = new StandardMaterial();
material.emissiveMap = renderTarget.colorBuffer;
material.emissive = Color.WHITE;
material.update();
```

## Cubemap faces and texture array layers

Instead of a whole texture, a render target can render into a single face of a cubemap, or a single layer of a 2D texture array (a texture created with `arrayLength`). Select the cubemap face with the [`face`](https://api.playcanvas.com/engine/classes/RenderTarget.html#face) option, or the array layer with the [`layer`](https://api.playcanvas.com/engine/classes/RenderTarget.html#layer) option.

To render to several faces or layers, create a render target for each of them, all sharing the same texture. Render targets are lightweight - when the faces or layers are only rendered once, for example to bake a set of views, the render targets can be destroyed afterwards, and the texture keeps the result:

```javascript
const layerCount = 16;

const textureArray = new Texture(app.graphicsDevice, {
    name: 'RT-array',
    width: 256,
    height: 256,
    arrayLength: layerCount,
    format: PIXELFORMAT_SRGBA8,
    mipmaps: false
});

// a render target for each layer, all sharing the texture array
const renderTargets = [];
for (let layer = 0; layer < layerCount; layer++) {
    renderTargets.push(new RenderTarget({
        colorBuffer: textureArray,
        layer,
        depth: true,
        origin: RENDERTARGET_ORIGIN_TOP
    }));
}
```

With `depth: true`, each render target allocates its own depth buffer. A depth buffer you provide with the `depthBuffer` option can be a texture array (or a cubemap) as well, in which case the same layer of it is rendered to, or a 2D depth texture shared by the render targets of all layers.

`StandardMaterial` does not sample texture arrays. To display a layer, use a custom shader that declares the texture array and selects the layer to sample - see [Sampling in shaders](https://developer.playcanvas.com/user-manual/graphics/textures.md#sampling-in-shaders).

### Sampling a texture while rendering into it

A texture cannot be sampled in the same render pass that renders into it - even into one of its other layers - as the shader has access to the whole texture:

- **WebGL2** - not possible. A sampler cannot be restricted to some of the layers of a texture array.
- **WebGPU** - possible, by binding a [`TextureView`](https://api.playcanvas.com/engine/classes/TextureView.html) that excludes the layer being rendered to, created with [`Texture#getView`](https://api.playcanvas.com/engine/classes/Texture.html#getview). The shader indexes the layers of the view relative to its first layer:

```javascript
// sample only layer 1, while a render target renders into layer 0 - the shader samples it as its layer 0
material.setParameter('uLayers', textureArray.getView(0, 1, 1, 1));
```

On WebGL, a `TextureView` binds the whole texture instead. To read and write the same data on all platforms, alternate between two textures - render into one while sampling the other.

The following example renders a statue from 64 directions into the layers of a texture array, using a render target for each layer, and draws thousands of impostors - camera facing quads showing the view closest to the direction they are seen from:

[Live example: Texture Array Impostors](https://playcanvas.com/examples/#/render-targets/texture-array-impostors) ([source](https://github.com/playcanvas/engine/blob/main/examples/src/examples/render-targets/texture-array-impostors/example.mjs))

## Volume texture slices

A render target can render into a single depth slice of a [volume texture](https://developer.playcanvas.com/user-manual/graphics/textures.md#texture-types) (a texture created with `volume: true`), selected with the [`slice`](https://api.playcanvas.com/engine/classes/RenderTarget.html#slice) option. To render to several slices, create a render target for each of them, all sharing the same texture:

```javascript
const sliceCount = 32;

const volume = new Texture(app.graphicsDevice, {
    name: 'RT-volume',
    width: 128,
    height: 128,
    depth: sliceCount,
    volume: true,
    format: PIXELFORMAT_RGBA8,
    mipmaps: false
});

// a render target for each depth slice, all sharing the volume texture
const sliceTargets = [];
for (let slice = 0; slice < sliceCount; slice++) {
    sliceTargets.push(new RenderTarget({
        colorBuffer: volume,
        slice,
        depth: false
    }));
}
```

Rendering to a volume texture has these differences from rendering to the faces of a cubemap or the layers of a texture array:

- **No multisampling** - a multisampled rendering cannot be resolved into a depth slice, so the `samples` option is ignored.
- **2D depth buffer** - a volume texture cannot be a depth buffer. With `depth: true`, each render target allocates its own 2D depth buffer, or a 2D depth texture can be shared by the render targets of all slices.
- **No sampling while rendering** - the volume texture cannot be sampled in a render pass that renders into one of its slices, on both WebGL2 and WebGPU, and a `TextureView` cannot exclude a slice. To read and write the same data, alternate between two volume textures.

A volume texture is sampled with 3D coordinates in a custom shader, see [Sampling in shaders](https://developer.playcanvas.com/user-manual/graphics/textures.md#sampling-in-shaders).

When the slices change over time, rendering only some of them each frame, in turn, spreads the cost of the render passes. The following example fills a volume texture with colorful fog data - either on the CPU, or rendered on the GPU into its slices, updating a few slices each frame - and raymarches it over the scene, optionally using its mipmaps:

[Live example: Volume Texture](https://playcanvas.com/examples/#/render-targets/volume-texture) ([source](https://github.com/playcanvas/engine/blob/main/examples/src/examples/render-targets/volume-texture/example.mjs))

## Mipmaps

When the color texture has mipmaps, the render target regenerates them after each render pass, so the result can be sampled with mipmap filtering.

The mipmaps are generated with a box filter, which averages each block of 2x2 texels of the previous level. When the small mip levels are sampled to blur the result, such as for the reflection of a rough surface, their blocks show and flicker as the content moves. The `mipmapFilter` option of the texture selects a smooth Gaussian filter instead, at a higher cost of generating the mipmaps:

```javascript
const texture = new Texture(app.graphicsDevice, {
    name: 'RT-color',
    width: 512,
    height: 256,
    format: PIXELFORMAT_SRGBA8,
    mipmaps: true,
    mipmapFilter: MIPMAPFILTER_GAUSSIAN,
    minFilter: FILTER_LINEAR_MIPMAP_LINEAR,
    magFilter: FILTER_LINEAR
});
```

The Gaussian filter applies to 2D textures of formats which can be filtered. Cubemaps, texture arrays and volume textures use the box filter, as do the mipmaps generated for data uploaded to a texture.

To render into a specific mip level instead, use the `mipLevel` option. Specifying it - even as 0 - also disables the automatic mipmap generation. Rendering to a mip level other than 0 is not supported together with a depth buffer.

When a render target renders into a cubemap face or an array layer, WebGPU regenerates the mipmaps of the rendered face or layer only. WebGL2 regenerates the mipmaps of all faces or layers of the texture, which is costly when rendering into many layers. To generate them only once, disable the mipmap generation on all render targets except the one rendered last:

```javascript
for (let layer = 0; layer < layerCount; layer++) {
    const lastLayer = layer === layerCount - 1;
    renderTargets.push(new RenderTarget({
        colorBuffer: textureArray,
        layer,
        depth: true,

        // on WebGL2, generate the mipmaps of the whole texture array once, after the last layer
        // is rendered (render the layers in order, for example using the camera priority)
        mipLevel: app.graphicsDevice.isWebGL2 && !lastLayer ? 0 : undefined
    }));
}
```

For a volume texture, the mipmaps of the whole volume are regenerated after rendering into a slice, on both WebGL2 and WebGPU, as each mip level is filtered from several depth slices. So when rendering into many slices, generate them once on both, by passing `mipLevel: 0` to all render targets except the one rendered last.

## Orientation

WebGL2 and WebGPU natively store a rendered image with the opposite vertical row order. If you leave the orientation unspecified and then sample the render target as a regular texture (with mesh UVs), the result appears vertically mirrored between the two APIs. The `origin` option pins the stored orientation so the render target looks identical everywhere. It can be:

- [`RENDERTARGET_ORIGIN_TOP`](https://api.playcanvas.com/engine/variables/RENDERTARGET_ORIGIN_TOP.html) - row 0 is the top of the rendered image, on all graphics APIs, matching how image textures are stored. **Use this for any render target you sample as a regular texture** (a material map, or a cube map face). Recommended for most content - write the sampling code as if the texture were a loaded image.
- [`RENDERTARGET_ORIGIN_BOTTOM`](https://api.playcanvas.com/engine/variables/RENDERTARGET_ORIGIN_BOTTOM.html) - row 0 is the bottom of the rendered image, on all graphics APIs, replicating WebGL2's native layout. Use this to keep consuming code written against WebGL conventions working unchanged - shaders deriving UVs from projected (NDC) coordinates, or texture atlases addressing cells by viewport rectangles.
- [`RENDERTARGET_ORIGIN_NATIVE`](https://api.playcanvas.com/engine/variables/RENDERTARGET_ORIGIN_NATIVE.html) - the image is stored in the graphics API's native orientation, so the row order differs between WebGL2 and WebGPU. This is the default. It is only appropriate for orientation-agnostic consumers, such as screen-space sampling using coordinates derived from the fragment position.

In short: if you display a render target on a surface in your scene, use `RENDERTARGET_ORIGIN_TOP`.

## Choosing a format

The color texture must use a renderable, uncompressed format - see [Pixel formats](https://developer.playcanvas.com/user-manual/graphics/textures.md#pixel-formats) for the groups of formats:

- **`PIXELFORMAT_RGBA8`** is the standard choice, renderable everywhere.
- **sRGB formats** such as `PIXELFORMAT_SRGBA8` are renderable as well. The rendered linear colors are converted to sRGB when written, and blending happens in linear space. See [sRGB texture handling](https://developer.playcanvas.com/user-manual/graphics/linear-workflow/textures.md).
- **`PIXELFORMAT_RGB10A2`** offers 10 bits per RGB channel with 2-bit alpha - higher precision than `RGBA8` at the same memory cost, renderable on both WebGL2 and WebGPU. `PIXELFORMAT_RGB10A2U` is its unsigned integer variant.
- **HDR formats** - float `PIXELFORMAT_RGBA32F`, half-float `PIXELFORMAT_RGBA16F` and small-float `PIXELFORMAT_RG11B10F` - are renderable subject to device support:
  - on WebGPU, rendering to float and half-float formats is always supported, and to the small-float format when [`GraphicsDevice.textureRG11B10Renderable`](https://api.playcanvas.com/engine/classes/GraphicsDevice.html#texturerg11b10renderable) is true.
  - on WebGL2, rendering to all three is supported when [`GraphicsDevice.textureFloatRenderable`](https://api.playcanvas.com/engine/classes/GraphicsDevice.html#texturefloatrenderable) is true. Otherwise, when [`GraphicsDevice.textureHalfFloatRenderable`](https://api.playcanvas.com/engine/classes/GraphicsDevice.html#texturehalffloatrenderable) is true, rendering to half-float formats only is supported - the case of many mobile iOS devices.

  Rather than picking one directly, query [`GraphicsDevice.getRenderableHdrFormat`](https://api.playcanvas.com/engine/classes/GraphicsDevice.html#getrenderablehdrformat), which returns the first supported option. See [HDR Rendering](https://developer.playcanvas.com/user-manual/graphics/linear-workflow/hdr-rendering.md) for rendering the scene in HDR.
- **Integer formats**, such as `PIXELFORMAT_R8U`, are renderable too. They store the values a shader outputs as they are, with no filtering or blending, and a multisampled integer render target cannot be hardware-resolved.
- **`PIXELFORMAT_RGB9E5`** is a compact HDR format that can be sampled but **cannot** be used as a render target color buffer, and neither can compressed formats.

See the [`Texture`](https://api.playcanvas.com/engine/classes/Texture.html) API reference for the full list of formats and the detailed support rules.

For depth testing during rendering, request a depth buffer with `depth: true` when creating the render target (as shown above). Use `stencil: true` as well if you need a stencil buffer.

## Resizing

To change a render target's resolution - for example to keep it matched to the output size when the window resizes - call [`renderTarget.resize(width, height)`](https://api.playcanvas.com/engine/classes/RenderTarget.html#resize). This resizes the underlying color and depth buffers; their previous contents are not preserved.

## Anti-aliasing

Set `samples` greater than 1 to render the target with hardware multi-sample anti-aliasing (MSAA). The multi-sampled result is automatically resolved into the single-sampled color texture you created, which is the one you sample from:

```javascript
const renderTarget = new RenderTarget({
    colorBuffer: texture,
    depth: true,
    origin: RENDERTARGET_ORIGIN_TOP,
    samples: 4
});
```

For control over how the samples are resolved - or to read them individually in a shader - see [Explicit multisampled render targets](https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/render-targets.md#explicit-multisampled-render-targets-and-custom-resolves) below.

## Explicit multisampled render targets and custom resolves

:::note

The features in this section require Engine 2.22 or later, and are WebGPU only.

:::

The automatic resolve above averages the samples with a fixed hardware "box" filter. Techniques that need a different resolve - a tonemapped color resolve, a min/max depth resolve, or reading the individual samples in a shader - create the multisampled textures explicitly (a [`Texture`](https://api.playcanvas.com/engine/classes/Texture.html) with `samples` greater than 1) and use them directly as the render target's buffers:

```javascript
// multisampled textures - the render target renders directly into their samples
const msColor = new Texture(app.graphicsDevice, { width, height, format: PIXELFORMAT_RGBA16F, samples: 4 });
const msDepth = new Texture(app.graphicsDevice, { width, height, format: PIXELFORMAT_DEPTH, samples: 4 });

// optional single-sampled resolve targets
const resolvedColor = new Texture(app.graphicsDevice, { width, height, format: PIXELFORMAT_RGBA16F, mipmaps: false });
const resolvedDepth = new Texture(app.graphicsDevice, { width, height, format: PIXELFORMAT_R32F, mipmaps: false });

const renderTarget = new RenderTarget({
    colorBuffer: msColor,
    resolveBuffer: resolvedColor,       // hardware resolve at the end of a render pass
    depthBuffer: msDepth,
    depthResolveBuffer: resolvedDepth   // shader-based resolve, controlled by depthResolveMode
});
```

- **Color**: when a `resolveBuffer` is provided, the color samples are hardware-resolved into it at the end of a render pass, like the automatic path. When it is omitted, the samples are stored instead, and a later pass reads them individually with `textureLoad` on a `texture_multisampled_2d` - a custom resolve. This is also the only way to use MSAA with formats the hardware cannot resolve, such as integer formats. With multiple render targets, each attachment has its own optional resolve texture (`resolveBuffers`).
- **Depth**: depth has no hardware resolve on WebGPU. A multisampled `depthBuffer` can be read per sample as a `texture_depth_multisampled_2d`, or resolved into the `depthResolveBuffer` by an engine-provided shader whose operation is selected with [`depthResolveMode`](https://api.playcanvas.com/engine/classes/RenderTarget.html#depthresolvemode) - `DEPTHRESOLVE_MIN` (the default, selecting the nearest surface), `DEPTHRESOLVE_MAX` or `DEPTHRESOLVE_SAMPLE0`. The same mode also controls the depth resolve used by the scene depth map and by depth copies.

Two examples demonstrate these techniques - a custom tonemapped color resolve compared side by side against the hardware resolve, and per-sample depth fog compared against fog computed from a resolved depth:

[Live example: Custom MSAA Resolve](https://playcanvas.com/examples/#/render-targets/custom-msaa-resolve) ([source](https://github.com/playcanvas/engine/blob/main/examples/src/examples/render-targets/custom-msaa-resolve/example.mjs))

[Live example: MSAA Depth Fog](https://playcanvas.com/examples/#/render-targets/msaa-depth-fog) ([source](https://github.com/playcanvas/engine/blob/main/examples/src/examples/render-targets/msaa-depth-fog/example.mjs))

## Cleaning up

A render target does not own its textures, so destroy them separately when you are done. Destroy the color texture (and depth buffer texture, if you created one explicitly), then the render target:

```javascript
renderTarget.colorBuffer.destroy();
renderTarget.destroy();
```

When the render targets of several cubemap faces, array layers or volume slices share a texture, destroy each render target, and the shared texture once.

## Example

The following example renders a scene into a texture from a second camera and displays it on a plane in the world. It uses the three-layer setup described above to keep the display plane out of the render target, and switches the texture camera between perspective and orthographic projection every few seconds.

[Live example: Render to Texture](https://playcanvas.com/examples/#/render-targets/render-to-texture) ([source](https://github.com/playcanvas/engine/blob/main/examples/src/examples/render-targets/render-to-texture/example.mjs))

## Related pages

- [Textures](https://developer.playcanvas.com/user-manual/graphics/textures.md) - the texture types, pixel formats, uploading data, sampling, and reading back and copying textures.
- [Multiple Render Targets](https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/multiple-render-targets.md) - render to several color buffers at once from a single pass.
- [Multiple Cameras](https://developer.playcanvas.com/user-manual/graphics/cameras/multiple-cameras.md) - composing views and assigning render targets to cameras.
- [Layers](https://developer.playcanvas.com/user-manual/graphics/layers.md) - controlling which objects each camera renders.
- [Post Effects](https://developer.playcanvas.com/user-manual/graphics/posteffects.md) - built-in and custom post-processing built on render targets.
- [Device Loss and Recovery](https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/device-loss.md) - restoring GPU-generated contents after context or device loss.
- [TextureRenderer](https://developer.playcanvas.com/user-manual/graphics/debug-drawing.md#texturerenderer) - inspect render-target color and individual channels on screen.
