Skip to content

Commit 1bc4763

Browse files
committed
environment and spotlight
1 parent ef706a3 commit 1bc4763

20 files changed

+1493
-3
lines changed

libs/soba/shaders/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './grid-material/grid-material';
22
export * from './shader-material/shader-material';
33
export * from './sparkles-material/sparkles-material';
4+
export * from './spot-light-material/spot-light-material';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import type { NgtShaderMaterial } from 'angular-three';
2+
import * as THREE from 'three';
3+
4+
export class SpotLightMaterial extends THREE.ShaderMaterial {
5+
constructor() {
6+
super({
7+
uniforms: {
8+
depth: { value: null },
9+
opacity: { value: 1 },
10+
attenuation: { value: 2.5 },
11+
anglePower: { value: 12 },
12+
spotPosition: { value: new THREE.Vector3(0, 0, 0) },
13+
lightColor: { value: new THREE.Color('white') },
14+
cameraNear: { value: 0 },
15+
cameraFar: { value: 1 },
16+
resolution: { value: new THREE.Vector2(0, 0) },
17+
},
18+
transparent: true,
19+
depthWrite: false,
20+
vertexShader: /* glsl */ `
21+
varying vec3 vNormal;
22+
varying vec3 vWorldPosition;
23+
varying float vViewZ;
24+
varying float vIntensity;
25+
uniform vec3 spotPosition;
26+
uniform float attenuation;
27+
28+
void main() {
29+
// compute intensity
30+
vNormal = normalize( normalMatrix * normal );
31+
vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
32+
vWorldPosition = worldPosition.xyz;
33+
vec4 viewPosition = viewMatrix * worldPosition;
34+
vViewZ = viewPosition.z;
35+
float intensity = distance(worldPosition.xyz, spotPosition) / attenuation;
36+
intensity = 1.0 - clamp(intensity, 0.0, 1.0);
37+
vIntensity = intensity;
38+
// set gl_Position
39+
gl_Position = projectionMatrix * viewPosition;
40+
41+
}`,
42+
fragmentShader: /* glsl */ `
43+
#include <packing>
44+
45+
varying vec3 vNormal;
46+
varying vec3 vWorldPosition;
47+
uniform vec3 lightColor;
48+
uniform vec3 spotPosition;
49+
uniform float attenuation;
50+
uniform float anglePower;
51+
uniform sampler2D depth;
52+
uniform vec2 resolution;
53+
uniform float cameraNear;
54+
uniform float cameraFar;
55+
varying float vViewZ;
56+
varying float vIntensity;
57+
uniform float opacity;
58+
59+
float readDepth( sampler2D depthSampler, vec2 coord ) {
60+
float fragCoordZ = texture2D( depthSampler, coord ).x;
61+
float viewZ = perspectiveDepthToViewZ(fragCoordZ, cameraNear, cameraFar);
62+
return viewZ;
63+
}
64+
65+
void main() {
66+
float d = 1.0;
67+
bool isSoft = resolution[0] > 0.0 && resolution[1] > 0.0;
68+
if (isSoft) {
69+
vec2 sUv = gl_FragCoord.xy / resolution;
70+
d = readDepth(depth, sUv);
71+
}
72+
float intensity = vIntensity;
73+
vec3 normal = vec3(vNormal.x, vNormal.y, abs(vNormal.z));
74+
float angleIntensity = pow( dot(normal, vec3(0.0, 0.0, 1.0)), anglePower );
75+
intensity *= angleIntensity;
76+
// fades when z is close to sampled depth, meaning the cone is intersecting existing geometry
77+
if (isSoft) {
78+
intensity *= smoothstep(0., 1., vViewZ - d);
79+
}
80+
gl_FragColor = vec4(lightColor, intensity * opacity);
81+
82+
#include <tonemapping_fragment>
83+
#include <${parseInt(THREE.REVISION.replace(/\D+/g, '')) >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
84+
}`,
85+
});
86+
}
87+
}
88+
89+
declare global {
90+
interface HTMLElementTagNameMap {
91+
/**
92+
* @extends ngt-shader-material
93+
*/
94+
'ngt-spot-light-material': NgtShaderMaterial;
95+
}
96+
}

libs/soba/src/setup-canvas.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ interface CanvasOptions {
4040
makeDefault?: boolean;
4141
};
4242
lights?: boolean;
43+
useLegacyLights?: boolean;
4344
compoundPrefixes?: string[];
4445
loader?: boolean;
4546
}
@@ -56,6 +57,7 @@ const defaultCanvasOptions: CanvasOptions = {
5657
debounce: 200,
5758
},
5859
whiteBackground: false,
60+
useLegacyLights: false,
5961
controls: true,
6062
lights: true,
6163
loader: false,
@@ -76,7 +78,7 @@ const STORY_INPUTS = new InjectionToken<Signal<Record<string, unknown>>>('story
7678
</ng-container>
7779
7880
<ng-container *ngIf="canvasOptions.lights">
79-
<ngt-ambient-light [intensity]="0.8" />
81+
<ngt-ambient-light [intensity]="0.8 * Math.PI" />
8082
<ngt-point-light [intensity]="1" [position]="[0, 6, 0]" />
8183
</ng-container>
8284
@@ -90,6 +92,7 @@ const STORY_INPUTS = new InjectionToken<Signal<Record<string, unknown>>>('story
9092
schemas: [CUSTOM_ELEMENTS_SCHEMA],
9193
})
9294
class StorybookScene implements OnInit {
95+
Math = Math;
9396
canvasOptions = inject(CANVAS_OPTIONS);
9497

9598
private story = inject(STORY_COMPONENT);
@@ -175,7 +178,9 @@ export class StorybookSetup implements OnInit {
175178
this.ref.setInput('shadows', true);
176179
this.ref.setInput('performance', this.options.performance);
177180
this.ref.setInput('camera', this.options.camera);
178-
// this.ref.setInput('gl', { useLegacyLights: true });
181+
// NOTE: r155 has made useLegacyLights "false" by default.
182+
console.log('legacyLights', this.options.useLegacyLights);
183+
this.ref.setInput('gl', { useLegacyLights: this.options.useLegacyLights ?? false });
179184
this.ref.setInput('compoundPrefixes', this.options.compoundPrefixes || []);
180185
this.ref.setInput('sceneGraph', StorybookScene);
181186
safeDetectChanges(this.ref.changeDetectorRef);
@@ -199,6 +204,7 @@ type DeepPartialObject<T> = {
199204
export function makeCanvasOptions(options: DeepPartial<CanvasOptions> = {}) {
200205
const mergedOptions = {
201206
...defaultCanvasOptions,
207+
useLegacyLights: options.useLegacyLights ?? defaultCanvasOptions.useLegacyLights,
202208
camera: { ...defaultCanvasOptions.camera, ...(options.camera || {}) },
203209
performance: { ...defaultCanvasOptions.performance, ...(options.performance || {}) },
204210
whiteBackground: options.whiteBackground ?? defaultCanvasOptions.whiteBackground,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { NgIf } from '@angular/common';
2+
import { Component, CUSTOM_ELEMENTS_SCHEMA, Input } from '@angular/core';
3+
import { Meta } from '@storybook/angular';
4+
import { NgtArgs } from 'angular-three';
5+
import { NgtsPerspectiveCamera } from 'angular-three-soba/cameras';
6+
import { NgtsOrbitControls } from 'angular-three-soba/controls';
7+
import { ENVIRONMENT_PRESETS, NgtsContactShadows, NgtsEnvironment } from 'angular-three-soba/staging';
8+
import { makeDecorators, makeStoryObject, number, select } from '../setup-canvas';
9+
10+
const presets = Object.keys(ENVIRONMENT_PRESETS);
11+
12+
const preset = select('park', { options: presets });
13+
const height = number(15, { range: true, min: 0, max: 50, step: 0.1 });
14+
const radius = number(60, { range: true, min: 0, max: 200, step: 1 });
15+
const blur = number(0, { range: true, min: 0, max: 1, step: 0.01 });
16+
const defaultPreset = select(presets[0], { options: presets });
17+
18+
@Component({
19+
standalone: true,
20+
template: `
21+
<ngts-environment [ground]="{ height, radius }" [preset]="preset" />
22+
<ngt-mesh [position]="[0, 5, 0]">
23+
<ngt-box-geometry *args="[10, 10, 10]" />
24+
<ngt-mesh-standard-material [metalness]="1" [roughness]="0" />
25+
</ngt-mesh>
26+
<ngts-contact-shadows
27+
[resolution]="1024"
28+
[position]="[0, 0, 0]"
29+
[scale]="100"
30+
[blur]="2"
31+
[opacity]="1"
32+
[far]="10"
33+
/>
34+
<ngts-orbit-controls [autoRotate]="true" />
35+
<ngts-perspective-camera [position]="[40, 40, 40]" [makeDefault]="true" />
36+
`,
37+
imports: [NgtsEnvironment, NgtsOrbitControls, NgtsPerspectiveCamera, NgtsContactShadows, NgtArgs],
38+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
39+
})
40+
class GroundEnvironmentStory {
41+
@Input() preset = preset.defaultValue;
42+
@Input() height = height.defaultValue;
43+
@Input() radius = radius.defaultValue;
44+
}
45+
46+
@Component({
47+
standalone: true,
48+
template: `
49+
<ngts-environment
50+
[background]="background"
51+
[path]="'soba/cube/'"
52+
[files]="['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png']"
53+
/>
54+
<ngt-mesh>
55+
<ngt-torus-knot-geometry *args="[1, 0.5, 128, 32]" />
56+
<ngt-mesh-standard-material [metalness]="1" [roughness]="0" />
57+
</ngt-mesh>
58+
<ngts-orbit-controls [autoRotate]="true" />
59+
`,
60+
imports: [NgtsEnvironment, NgtsOrbitControls, NgtArgs],
61+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
62+
})
63+
class FilesEnvironmentStory {
64+
@Input() background = true;
65+
}
66+
67+
@Component({
68+
standalone: true,
69+
template: `
70+
<ngts-environment [background]="background" [preset]="preset" [blur]="blur" />
71+
<ngt-mesh>
72+
<ngt-torus-knot-geometry *args="[1, 0.5, 128, 32]" />
73+
<ngt-mesh-standard-material [metalness]="1" [roughness]="0" />
74+
</ngt-mesh>
75+
<ngts-orbit-controls [autoRotate]="true" />
76+
`,
77+
imports: [NgtsEnvironment, NgtsOrbitControls, NgtArgs, NgIf],
78+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
79+
})
80+
class DefaultEnvironmentStory {
81+
@Input() background = true;
82+
@Input() preset = defaultPreset.defaultValue;
83+
@Input() blur = blur.defaultValue;
84+
}
85+
86+
export default {
87+
title: 'Staging/Environment',
88+
decorators: makeDecorators(),
89+
} as Meta;
90+
91+
export const Default = makeStoryObject(DefaultEnvironmentStory, {
92+
canvasOptions: { controls: false, camera: { position: [0, 0, 10] } },
93+
argsOptions: { background: true, blur, preset: defaultPreset },
94+
});
95+
96+
export const Files = makeStoryObject(FilesEnvironmentStory, {
97+
canvasOptions: { controls: false, camera: { position: [0, 0, 10] } },
98+
argsOptions: { background: true },
99+
});
100+
101+
export const Ground = makeStoryObject(GroundEnvironmentStory, {
102+
canvasOptions: { controls: false, camera: { position: [0, 0, 10] } },
103+
argsOptions: { preset, height, radius },
104+
});

0 commit comments

Comments
 (0)