NgtsRenderTexture
import { ChangeDetectionStrategy, Component } from '@angular/core';import { SobaWrapper } from '@soba/wrapper.ts';import { NgtCanvas, provideNgtRenderer } from 'angular-three/dom';import { SceneGraph } from './scene-graph';
@Component({ selector: 'app-render-texture', template: ` <ngt-canvas [camera]="{ position: [0, 0, 5], fov: 50 }"> <app-soba-wrapper *canvasContent [grid]="false"> <app-scene-graph /> </app-soba-wrapper> </ngt-canvas> `, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'render-texture-demo relative block h-full' }, imports: [NgtCanvas, SobaWrapper, SceneGraph],})export default class RenderTexture { static clientProviders = [provideNgtRenderer()];}import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, viewChild } from '@angular/core';import { beforeRender, NgtArgs } from 'angular-three';import { NgtsPerspectiveCamera } from 'angular-three-soba/cameras';import { NgtsRenderTexture } from 'angular-three-soba/staging';import * as THREE from 'three';
@Component({ selector: 'app-scene-graph', template: ` <!-- Main cube with render texture --> <ngt-mesh #cube [rotation]="[0, Math.PI / 6, 0]"> <ngt-box-geometry *args="[2, 2, 2]" /> <ngt-mesh-standard-material> <ngts-render-texture [options]="{ anisotropy: 16 }"> <ng-template renderTextureContent> <!-- Inner scene camera --> <ngts-perspective-camera [options]="{ manual: true, makeDefault: true, aspect: 1, position: [0, 0, 5] }" />
<!-- Inner scene background --> <ngt-color attach="background" *args="['#1a1a2e']" />
<!-- Inner scene lighting --> <ngt-ambient-light [intensity]="0.5 * Math.PI" /> <ngt-point-light [position]="[5, 5, 5]" [intensity]="Math.PI" />
<!-- Inner scene content - spinning torus knot --> <ngt-mesh #innerMesh> <ngt-torus-knot-geometry *args="[0.8, 0.3, 128, 32]" /> <ngt-mesh-standard-material color="#ff6b6b" [metalness]="0.5" [roughness]="0.3" /> </ngt-mesh> </ng-template> </ngts-render-texture> </ngt-mesh-standard-material> </ngt-mesh> `, schemas: [CUSTOM_ELEMENTS_SCHEMA], changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgtArgs, NgtsRenderTexture, NgtsPerspectiveCamera],})export class SceneGraph { protected readonly Math = Math;
private cubeRef = viewChild<ElementRef<THREE.Mesh>>('cube'); private innerMeshRef = viewChild<ElementRef<THREE.Mesh>>('innerMesh');
constructor() { beforeRender(({ delta }) => { const cube = this.cubeRef()?.nativeElement; const innerMesh = this.innerMeshRef()?.nativeElement;
if (cube) { cube.rotation.y += delta * 0.3; }
if (innerMesh) { innerMesh.rotation.x += delta; innerMesh.rotation.y += delta * 0.5; } }); }}NgtsRenderTexture is a port of Drei’s RenderTexture which allows you to render a live scene into a texture that can be applied to a material.
The contents run inside a portal and are separate from the rest of the canvas, allowing independent events, environment maps, cameras, and more.
Usage
Basic Render Texture
Use the *renderTextureContent structural directive to define the content rendered to the texture:
<ngt-mesh> <ngt-box-geometry /> <ngt-mesh-basic-material> <ngts-render-texture> <app-texture-scene *renderTextureContent /> </ngts-render-texture> </ngt-mesh-basic-material></ngt-mesh>With Custom Dimensions
<ngt-mesh> <ngt-plane-geometry *args="[4, 4]" /> <ngt-mesh-basic-material> <ngts-render-texture [options]="{ width: 512, height: 512 }"> <app-mini-scene *renderTextureContent /> </ngts-render-texture> </ngt-mesh-basic-material></ngt-mesh>High-Quality with Samples
<ngt-mesh> <ngt-sphere-geometry *args="[1, 32, 32]" /> <ngt-mesh-standard-material> <ngts-render-texture [options]="{ samples: 16, generateMipmaps: true }"> <app-reflection-scene *renderTextureContent /> </ngts-render-texture> </ngt-mesh-standard-material></ngt-mesh>Static Render (Single Frame)
For static content, render only once for better performance:
<ngt-mesh> <ngt-plane-geometry *args="[2, 2]" /> <ngt-mesh-basic-material> <ngts-render-texture [options]="{ frames: 1 }"> <app-static-scene *renderTextureContent /> </ngts-render-texture> </ngt-mesh-basic-material></ngt-mesh>Portal Content Example
The texture scene component runs in its own portal with independent state:
@Component({ selector: 'app-texture-scene', template: ` <ngt-color *args="['#202020']" attach="background" /> <ngt-ambient-light [intensity]="0.5" /> <ngt-point-light [position]="[5, 5, 5]" />
<ngt-mesh (click)="onClick()"> <ngt-dodecahedron-geometry /> <ngt-mesh-standard-material color="hotpink" /> </ngt-mesh>
<ngts-orbit-controls /> `,})export class TextureScene { onClick() { console.log('Clicked inside render texture!'); }}Notes
- The render texture creates an off-screen framebuffer that’s updated each frame (or specified number of frames)
- Events are independent from the main scene and work within the portal
- Higher samples value improves anti-aliasing but impacts performance
- Use
frames: 1for static content to avoid unnecessary re-renders - The texture automatically attaches to the parent material’s
mapproperty
Options
Properties
| name | type | description |
|---|---|---|
| width | number | Width of the texture. Default: viewport width |
| height | number | Height of the texture. Default: viewport height |
| samples | number | The number of samples for the texture (anti-aliasing). Default: 8 |
| stencilBuffer | boolean | Whether to use a stencil buffer. Default: false |
| depthBuffer | boolean | Whether to use a depth buffer. Default: true |
| generateMipmaps | boolean | Whether to generate mipmaps. Default: false |
| renderPriority | number | The render priority of the texture. Default: 0 |
| eventPriority | number | The event priority of the texture. Default: 0 |
| frames | number | The number of frames to render. Use Infinity for continuous rendering. Default: Infinity |
| compute | function | A custom function to compute events in the render texture portal. |