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 |