NgtsMeshTransmissionMaterial
import { ChangeDetectionStrategy, Component, computed, ElementRef, inject, signal } from '@angular/core';import { SobaWrapper } from '@soba/wrapper';import { TweakpaneCheckbox, TweakpaneColor, TweakpaneFolder, TweakpaneNumber, TweakpanePane } from 'angular-three-tweakpane';import { NgtCanvas, provideNgtRenderer } from 'angular-three/dom';import { SceneGraph } from './scene-graph';
@Component({ selector: 'app-soba-mesh-transmission-material', template: ` <ngt-canvas shadows [camera]="{ position: [15, 0, 15], fov: 25 }"> <app-soba-wrapper *canvasContent [grid]="false" [controls]="null"> <app-scene-graph [blur]="blur()" [options]="materialOptions()" /> </app-soba-wrapper> </ngt-canvas>
<tweakpane-pane title="MeshTransmissionMaterial" [container]="host"> <tweakpane-number [(value)]="blur" label="blur" [params]="{ min: 0, max: 1, step: 0.1 }" /> <tweakpane-folder title="material"> <tweakpane-color [(value)]="background" label="background" /> <tweakpane-checkbox [(value)]="backside" label="backside" /> <tweakpane-number [(value)]="samples" label="samples" [params]="{ min: 1, max: 100, step: 1 }" /> <tweakpane-number [(value)]="resolution" label="resolution" [params]="{ min: 1, max: 1000, step: 1 }" /> <tweakpane-number [(value)]="transmission" label="transmission" [params]="{ min: 0, max: 10, step: 0.1 }" /> <tweakpane-number [(value)]="roughness" label="roughness" [params]="{ min: 0, max: 1, step: 0.1 }" /> <tweakpane-number [(value)]="thickness" label="thickness" [params]="{ min: 0, max: 10, step: 0.1 }" /> <tweakpane-number [(value)]="ior" label="ior" [params]="{ min: 0, max: 10, step: 0.1 }" /> <tweakpane-number [(value)]="chromaticAberration" label="chromaticAberration" [params]="{ min: 0, max: 10, step: 0.1 }" /> <tweakpane-number [(value)]="anisotropy" label="anisotropy" [params]="{ min: 0, max: 10, step: 0.1 }" /> <tweakpane-number [(value)]="distortion" label="distortion" [params]="{ min: 0, max: 10, step: 0.1 }" /> <tweakpane-number [(value)]="distortionScale" label="distortionScale" [params]="{ min: 0, max: 10, step: 0.1 }" /> <tweakpane-number [(value)]="temporalDistortion" label="temporalDistortion" [params]="{ min: 0, max: 10, step: 0.1 }" /> <tweakpane-number [(value)]="clearcoat" label="clearcoat" [params]="{ min: 0, max: 10, step: 0.1 }" /> <tweakpane-number [(value)]="attenuationDistance" label="attenuationDistance" [params]="{ min: 0, max: 10, step: 0.1 }" /> <tweakpane-color [(value)]="attenuationColor" label="attenuationColor" /> </tweakpane-folder> </tweakpane-pane> `, host: { class: 'mesh-transmission-material-demo relative block h-full' }, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ NgtCanvas, SceneGraph, SobaWrapper, TweakpanePane, TweakpaneNumber, TweakpaneFolder, TweakpaneCheckbox, TweakpaneColor, ],})export default class MeshTransmissionMaterial { static clientProviders = [provideNgtRenderer()];
protected host = inject(ElementRef); protected blur = signal(0.1); protected background = signal('#839681'); protected backside = signal(false); protected samples = signal(10); protected resolution = signal(2048); protected transmission = signal(1); protected roughness = signal(0); protected thickness = signal(3.5); protected ior = signal(1.5); protected chromaticAberration = signal(0.06); protected anisotropy = signal(0.1); protected distortion = signal(0.0); protected distortionScale = signal(0.3); protected temporalDistortion = signal(0.5); protected clearcoat = signal(1); protected attenuationDistance = signal(0.5); protected attenuationColor = signal('#ffffff'); protected color = signal('#c9ffa1');
protected materialOptions = computed(() => ({ background: this.background(), backside: this.backside(), samples: this.samples(), resolution: this.resolution(), transmission: this.transmission(), roughness: this.roughness(), thickness: this.thickness(), ior: this.ior(), chromaticAberration: this.chromaticAberration(), anisotropy: this.anisotropy(), distortion: this.distortion(), distortionScale: this.distortionScale(), temporalDistortion: this.temporalDistortion(), clearcoat: this.clearcoat(), attenuationDistance: this.attenuationDistance(), attenuationColor: this.attenuationColor(), color: this.color(), }));}
import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, input } from '@angular/core';import { NgtsOrbitControls } from 'angular-three-soba/controls';import { gltfResource } from 'angular-three-soba/loaders';import { NgtsMeshTransmissionMaterial, type NgtsMeshTransmissionMaterialOptions } from 'angular-three-soba/materials';import { NgtsAccumulativeShadows, NgtsCenter, NgtsEnvironment, NgtsRandomizedLights } from 'angular-three-soba/staging';import * as THREE from 'three';
import gelatinousCubeGLB from '@common-assets/gelatinous_cube.glb' with { loader: 'file' };
@Component({ selector: 'app-gelatinous-cube', template: ` <ngt-group [dispose]="null"> @if (gltf.value(); as gltf) { <ngt-mesh [geometry]="gltf.meshes['cube1'].geometry" [position]="[-0.56, 0.38, -0.11]"> <ngts-mesh-transmission-material [options]="options()" /> </ngt-mesh>
<ngt-mesh castShadow [renderOrder]="100" [geometry]="gltf.meshes['cube2'].geometry" [material]="gltf.materials['cube_mat']" [position]="[-0.56, 0.38, -0.11]" > <ngt-value [rawValue]="FrontSide" attach="material.side" /> </ngt-mesh>
<ngt-mesh [geometry]="gltf.meshes['bubbles'].geometry" [material]="gltf.materials['cube_bubbles_mat']" [position]="[-0.56, 0.38, -0.11]" />
<ngt-group [position]="[-0.56, 0.38, -0.41]"> <ngt-mesh [geometry]="gltf.meshes['arrows'].geometry" [material]="gltf.materials['weapons_mat']" /> <ngt-mesh [geometry]="gltf.meshes['skeleton_1'].geometry" [material]="gltf.materials['skele_mat']" /> <ngt-mesh [geometry]="gltf.meshes['skeleton_2'].geometry" [material]="gltf.materials['weapons_mat']" > <ngt-value [rawValue]="FrontSide" attach="material.side" /> </ngt-mesh> </ngt-group> } </ngt-group> `,
imports: [NgtsMeshTransmissionMaterial], schemas: [CUSTOM_ELEMENTS_SCHEMA], changeDetection: ChangeDetectionStrategy.OnPush,})export class GelatinousCube { protected readonly FrontSide = THREE.FrontSide;
options = input({} as NgtsMeshTransmissionMaterialOptions);
protected gltf = gltfResource(() => gelatinousCubeGLB);}
@Component({ selector: 'app-scene-graph', template: ` <ngt-ambient-light />
<ngt-group [position]="[0, -2.5, 0]"> <ngts-center [options]="{ top: true }"> <app-gelatinous-cube [options]="options()" /> </ngts-center> <ngts-accumulative-shadows [options]="{ temporal: true, frames: 100, alphaTest: 0.9, color: '#3ead5d', colorBlend: 1, opacity: 0.8, scale: 20, }" > <ngts-randomized-lights [options]="{ radius: 10, ambient: 0.5, intensity: Math.PI, position: [2.5, 8, -2.5], bias: 0.001 }" /> </ngts-accumulative-shadows> </ngt-group>
<ngts-orbit-controls [options]="{ minPolarAngle: 0, maxPolarAngle: Math.PI / 2, makeDefault: true }" />
<ngts-environment [options]="{ files: 'https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/dancing_hall_1k.hdr', background: true, backgroundBlurriness: blur(), }" /> `, imports: [ GelatinousCube, NgtsCenter, NgtsEnvironment, NgtsOrbitControls, NgtsAccumulativeShadows, NgtsRandomizedLights, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], changeDetection: ChangeDetectionStrategy.OnPush,})export class SceneGraph { protected readonly Math = Math;
blur = input(0.1); options = input({} as NgtsMeshTransmissionMaterialOptions);}
NgtsMeshTransmissionMaterial
, port from Drei’s MeshTransmissionMaterial, is an enhanced version of THREE.MeshPhysicalMaterial
with advanced features for realistic transmission effects. It maintains all standard THREE.MeshPhysicalMaterial
properties (transmission
, thickness
, IOR
, roughness
etc…) while adding: chromatic aberration, noise-based roughness blur, primitive anisotropic blur support, and ability to see other transmissive/transparent objects.
Usage
import { NgtsMeshTransmissionMaterial } from 'angular-three-soba/materials';
<ngt-mesh> <ngt-mesh-transmission-material /></ngt-mesh>
Performance Considerations
NgtsMeshTransmissionMaterial
causes an additional render pass of the scene. You can use low samples
and resolution
for better performance.
Roughness
With roughness
, you might need to consider a small resolutions (i.e: 32x32
) for good performance with good visuals.
Opt-out of transmission
For performance and visual reasons, the parent ngt-mesh
is temporarily removed from the render stack. If you have objects that you do not want to see reflected in the material, you can render those objects as ngt-mesh
content.
Sharing buffer textures
For improved performance when using multiple NgtsMeshTransmissionMaterial
, you can share buffer textures in different ways:
Using transmissionSampler
property
This property enables internal THREE.js buffer used by THREE.MeshPhysicalMaterial
<ngt-mesh> <ngts-mesh-transmission-material [options]="{ transmissionSampler: true }" /></ngt-mesh><ngt-mesh> <ngts-mesh-transmission-material [options]="{ transmissionSampler: true }" /></ngt-mesh>
With fbo
You can create an FBO with fbo
and pass the texture to buffer
option
@Component({ template: ` <ngt-mesh> <ngts-mesh-transmission-material [options]="{ buffer: fbo.texture }" /> </ngt-mesh> <ngt-mesh> <ngts-mesh-transmission-material [options]="{ buffer: fbo.texture }" /> </ngt-mesh> `})export class MyCmp { protected fbo = fbo();
constructor() { beforeRender(({ gl, scene, camera }) => { gl.setRenderTarget(this.fbo); gl.render(scene, camera); gl.setRenderTarget(null); }); }}
Using NgtsPerspectiveCamera
You can also use a NgtsPerspectiveCamera
to create a shared buffer
<ngts-perspective-camera [options]="{ makeDefault: true, fov: 75, position: [10, 0, 15], resolution: 1024 }"> <ng-template cameraContent let-texture> <ngt-mesh> <ngts-mesh-transmission-material [options]="{ buffer: texture }" /> </ngt-mesh> <ngt-mesh> <ngts-mesh-transmission-material [options]="{ buffer: texture }" /> </ngt-mesh> </ng-template></ngts-perspective-camera>
Options
options
input accepts any properties from THREE.MeshPhysicalMaterial
and MeshTransmissionMaterial
in addition to the following:
Properties
name | type | description |
---|---|---|
anisotropicBlur | number | The amount of anisotropic blurring used to reduce artifacts. |
buffer | THREE.Texture | The buffer texture that is used to store the transmission data. |
transmissionSampler | boolean | transmissionSampler, you can use the threejs transmission sampler texture that is generated once for all transmissive materials. The upside is that it can be faster if you use multiple MeshPhysical and Transmission materials, the downside is that transmissive materials using this can't see other transparent or transmissive objects, default: false |
backside | boolean | Render the backside of the material (more cost, better results), default: false |
backsideThickness | number | Backside thickness (when backside is true), default: 0 |
backsideEnvMapIntensity | number | Backside env map intensity (when backside is true), default: 1 |
resolution | number | Resolution of the local buffer, default: undefined (fullscreen) |
backsideResolution | number | Resolution of the local buffer for backfaces, default: undefined (fullscreen) |
samples | number | Refraction samples, default: 6 |
background | THREE.Texture | THREE.Color | null | Buffer scene background (can be a texture, a cubetexture or a color), default: null |