Skip to content

Commit aa3d022

Browse files
committed
fix(soba): adjust contact shadows
1 parent 2eec63b commit aa3d022

File tree

3 files changed

+107
-85
lines changed

3 files changed

+107
-85
lines changed

apps/kitchen-sink/src/app/soba/lowpoly-earth/experience.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
import { NgTemplateOutlet } from '@angular/common';
12
import {
23
ChangeDetectionStrategy,
34
Component,
45
computed,
6+
contentChild,
57
CUSTOM_ELEMENTS_SCHEMA,
68
ElementRef,
79
input,
810
signal,
911
Signal,
12+
TemplateRef,
1013
viewChild,
1114
} from '@angular/core';
1215
import { injectBeforeRender, NgtArgs, NgtEuler, NgtHTML, NgtVector3 } from 'angular-three';
@@ -103,16 +106,19 @@ export class MarkerIcon extends NgtHTML {
103106
</ngt-group>
104107
</ngt-mesh>
105108
</ngt-group>
109+
110+
<ng-container [ngTemplateOutlet]="content()" />
106111
}
107112
`,
108113
schemas: [CUSTOM_ELEMENTS_SCHEMA],
109114
changeDetection: ChangeDetectionStrategy.OnPush,
110-
imports: [Marker, MarkerIcon],
115+
imports: [Marker, MarkerIcon, NgTemplateOutlet],
111116
})
112117
export class Model {
113118
protected readonly Math = Math;
114119

115120
position = input<NgtVector3>([0, 0, 0]);
121+
content = contentChild.required(TemplateRef);
116122

117123
gltf = injectGLTF(() => './earth.gltf') as Signal<any>;
118124
}
@@ -122,13 +128,14 @@ export class Model {
122128
template: `
123129
<ngt-color *args="['#ececec']" attach="background" />
124130
<ngt-ambient-light [intensity]="0.5" />
125-
<app-model [position]="[0, 0.25, 0]" />
131+
<app-model [position]="[0, 0.25, 0]">
132+
<ng-template>
133+
<ngts-contact-shadows
134+
[options]="{ frames: 1, scale: 5, position: [0, -1, 0], far: 1, blur: 5, color: '#204080' }"
135+
/>
136+
</ng-template>
137+
</app-model>
126138
<ngts-environment [options]="{ preset: 'city' }" />
127-
<!-- NOTE: frames is set to 6 because we have a racing condition where the shadows are not rendered. -->
128-
<!-- Ideally, we only want to render the shadows for the first frame -->
129-
<ngts-contact-shadows
130-
[options]="{ frames: 6, scale: 5, position: [0, -1, 0], far: 1, blur: 5, color: '#204080' }"
131-
/>
132139
<ngts-orbit-controls [options]="{ autoRotate: true }" />
133140
`,
134141
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -138,3 +145,5 @@ export class Model {
138145
export class Experience {
139146
protected readonly Math = Math;
140147
}
148+
149+
injectGLTF.preload(() => './earth.gltf');

libs/soba/loaders/src/lib/gltf-loader.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ function _extensions(useDraco: boolean | string, useMeshOpt: boolean, extensions
2929
};
3030
}
3131

32-
export type NgtsGLTF<T extends Partial<NgtObjectMap>> = GLTF & NgtObjectMap & T;
33-
3432
function _injectGLTF<TUrl extends string | string[] | Record<string, string>>(
3533
path: () => TUrl,
3634
{

libs/soba/staging/src/lib/contact-shadows.ts

Lines changed: 91 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@ import {
1313
Color,
1414
ColorRepresentation,
1515
Group,
16-
Material,
1716
Mesh,
1817
MeshBasicMaterial,
1918
MeshDepthMaterial,
2019
OrthographicCamera,
2120
PlaneGeometry,
2221
ShaderMaterial,
23-
Texture,
2422
WebGLRenderTarget,
2523
} from 'three';
2624
import { HorizontalBlurShader, VerticalBlurShader } from 'three-stdlib';
@@ -119,24 +117,20 @@ export class NgtsContactShadows {
119117
private color = pick(this.options, 'color');
120118
private near = pick(this.options, 'near');
121119
private far = pick(this.options, 'far');
122-
123-
private shadowsOptions = computed(() => {
124-
const [width, height, resolution, color] = [
125-
this.scaledWidth(),
126-
this.scaledHeight(),
127-
this.resolution(),
128-
this.color(),
129-
];
130-
const renderTarget = new WebGLRenderTarget(resolution, resolution);
131-
const renderTargetBlur = new WebGLRenderTarget(resolution, resolution);
132-
renderTargetBlur.texture.generateMipmaps = renderTarget.texture.generateMipmaps = false;
133-
134-
const planeGeometry = new PlaneGeometry(width, height).rotateX(Math.PI / 2);
135-
const blurPlane = new Mesh(planeGeometry);
136-
const depthMaterial = new MeshDepthMaterial();
137-
depthMaterial.depthTest = depthMaterial.depthWrite = false;
138-
depthMaterial.onBeforeCompile = (shader) => {
139-
shader.uniforms = { ...shader.uniforms, ucolor: { value: new Color(color) } };
120+
private smooth = pick(this.options, 'smooth');
121+
private frames = pick(this.options, 'frames');
122+
private blur = pick(this.options, 'blur');
123+
124+
private renderTarget = computed(() => this.createRenderTarget(this.resolution()));
125+
private renderTargetBlur = computed(() => this.createRenderTarget(this.resolution()));
126+
planeGeometry = computed(() => new PlaneGeometry(this.scaledWidth(), this.scaledHeight()).rotateX(Math.PI / 2));
127+
private blurPlane = computed(() => new Mesh(this.planeGeometry()));
128+
private depthMaterial = computed(() => {
129+
const color = new Color(this.color());
130+
const material = new MeshDepthMaterial();
131+
material.depthTest = material.depthWrite = false;
132+
material.onBeforeCompile = (shader) => {
133+
shader.uniforms = { ...shader.uniforms, ucolor: { value: color } };
140134
shader.fragmentShader = shader.fragmentShader.replace(
141135
`void main() {`, //
142136
`uniform vec3 ucolor;
@@ -149,86 +143,107 @@ export class NgtsContactShadows {
149143
'vec4( ucolor * fragCoordZ * 2.0, ( 1.0 - fragCoordZ ) * 1.0 );',
150144
);
151145
};
152-
153-
const horizontalBlurMaterial = new ShaderMaterial(HorizontalBlurShader);
154-
const verticalBlurMaterial = new ShaderMaterial(VerticalBlurShader);
155-
verticalBlurMaterial.depthTest = horizontalBlurMaterial.depthTest = false;
156-
157-
return {
158-
renderTarget,
159-
planeGeometry,
160-
depthMaterial,
161-
blurPlane,
162-
horizontalBlurMaterial,
163-
verticalBlurMaterial,
164-
renderTargetBlur,
165-
};
146+
return material;
166147
});
167148

149+
private horizontalBlurMaterial = new ShaderMaterial({ ...HorizontalBlurShader, depthTest: false });
150+
private verticalBlurMaterial = new ShaderMaterial({ ...VerticalBlurShader, depthTest: false });
151+
168152
renderOrder = pick(this.options, 'renderOrder');
169153
opacity = pick(this.options, 'opacity');
170154
depthWrite = pick(this.options, 'depthWrite');
171-
planeGeometry = computed(() => this.shadowsOptions().planeGeometry);
172-
texture = computed(() => this.shadowsOptions().renderTarget.texture);
155+
texture = pick(this.renderTarget, 'texture');
173156
cameraArgs = computed(() => {
174157
const [width, height, near, far] = [this.scaledWidth(), this.scaledHeight(), this.near(), this.far()];
175158
return [-width / 2, width / 2, height / 2, -height / 2, near, far];
176159
});
177160

161+
constructor() {
162+
extend({ Group, Mesh, MeshBasicMaterial, OrthographicCamera });
163+
164+
let count = 0;
165+
injectBeforeRender(() => {
166+
const shadowsCamera = this.shadowsCameraRef()?.nativeElement;
167+
if (!shadowsCamera) return;
168+
169+
const frames = this.frames();
170+
if (frames === Infinity || count < frames) {
171+
this.renderShadows();
172+
count++;
173+
}
174+
});
175+
}
176+
177+
private renderShadows() {
178+
const shadowsCamera = this.shadowsCameraRef()?.nativeElement;
179+
if (!shadowsCamera) return;
180+
181+
const [blur, smooth, gl, scene, contactShadows, depthMaterial, renderTarget] = [
182+
this.blur(),
183+
this.smooth(),
184+
this.gl(),
185+
this.scene(),
186+
this.contactShadowsRef().nativeElement,
187+
this.depthMaterial(),
188+
this.renderTarget(),
189+
];
190+
191+
const initialBackground = scene.background;
192+
const initialOverrideMaterial = scene.overrideMaterial;
193+
const initialClearAlpha = gl.getClearAlpha();
194+
195+
contactShadows.visible = false;
196+
scene.background = null;
197+
scene.overrideMaterial = depthMaterial;
198+
gl.setClearAlpha(0);
199+
200+
// render to the render target to get the depths
201+
gl.setRenderTarget(renderTarget);
202+
gl.render(scene, shadowsCamera);
203+
204+
this.blurShadows(blur);
205+
if (smooth) this.blurShadows(blur * 0.4);
206+
207+
// reset
208+
gl.setRenderTarget(null);
209+
210+
contactShadows.visible = true;
211+
scene.overrideMaterial = initialOverrideMaterial;
212+
scene.background = initialBackground;
213+
gl.setClearAlpha(initialClearAlpha);
214+
}
215+
178216
private blurShadows(blur: number) {
179-
const { renderTarget, renderTargetBlur, blurPlane, horizontalBlurMaterial, verticalBlurMaterial } =
180-
this.shadowsOptions();
181217
const shadowsCamera = this.shadowsCameraRef()?.nativeElement;
182218
if (!shadowsCamera) return;
183219

220+
const [blurPlane, horizontalBlurMaterial, verticalBlurMaterial, renderTargetBlur, renderTarget, gl] = [
221+
this.blurPlane(),
222+
this.horizontalBlurMaterial,
223+
this.verticalBlurMaterial,
224+
this.renderTargetBlur(),
225+
this.renderTarget(),
226+
this.gl(),
227+
];
228+
184229
blurPlane.visible = true;
185230
blurPlane.material = horizontalBlurMaterial;
186231
horizontalBlurMaterial.uniforms['tDiffuse'].value = renderTarget.texture;
187232
horizontalBlurMaterial.uniforms['h'].value = blur / 256;
188-
this.gl().setRenderTarget(renderTargetBlur);
189-
this.gl().render(blurPlane, shadowsCamera);
233+
gl.setRenderTarget(renderTargetBlur);
234+
gl.render(blurPlane, shadowsCamera);
190235

191236
blurPlane.material = verticalBlurMaterial;
192237
verticalBlurMaterial.uniforms['tDiffuse'].value = renderTargetBlur.texture;
193238
verticalBlurMaterial.uniforms['v'].value = blur / 256;
194-
this.gl().setRenderTarget(renderTarget);
195-
this.gl().render(blurPlane, shadowsCamera);
239+
gl.setRenderTarget(renderTarget);
240+
gl.render(blurPlane, shadowsCamera);
196241
blurPlane.visible = false;
197242
}
198243

199-
constructor() {
200-
extend({ Group, Mesh, MeshBasicMaterial, OrthographicCamera });
201-
202-
let count = 0;
203-
let initialBackground: Color | Texture | null;
204-
let initialOverrideMaterial: Material | null;
205-
206-
injectBeforeRender(() => {
207-
const shadowsCamera = this.shadowsCameraRef()?.nativeElement;
208-
if (!shadowsCamera) return;
209-
const [{ frames, blur, smooth }, gl, scene, contactShadows, { depthMaterial, renderTarget }] = [
210-
this.options(),
211-
this.gl(),
212-
this.scene(),
213-
this.contactShadowsRef().nativeElement,
214-
this.shadowsOptions(),
215-
];
216-
if (frames === Infinity || count < frames * frames) {
217-
count++;
218-
initialBackground = scene.background;
219-
initialOverrideMaterial = scene.overrideMaterial;
220-
contactShadows.visible = false;
221-
scene.background = null;
222-
scene.overrideMaterial = depthMaterial;
223-
gl.setRenderTarget(renderTarget);
224-
gl.render(scene, shadowsCamera);
225-
this.blurShadows(blur);
226-
if (smooth) this.blurShadows(blur * 0.4);
227-
gl.setRenderTarget(null);
228-
contactShadows.visible = true;
229-
scene.overrideMaterial = initialOverrideMaterial;
230-
scene.background = initialBackground;
231-
}
232-
});
244+
private createRenderTarget(resolution: number) {
245+
const renderTarget = new WebGLRenderTarget(resolution, resolution);
246+
renderTarget.texture.generateMipmaps = false;
247+
return renderTarget;
233248
}
234249
}

0 commit comments

Comments
 (0)