Skip to content

Commit 0081147

Browse files
authored
feat(engine): implement composer rendering in GameRenderer (#69)
1 parent fa6c17f commit 0081147

File tree

10 files changed

+217
-62
lines changed

10 files changed

+217
-62
lines changed

packages/engine/src/components/Camera.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ export class Camera extends ActorComponent {
3232
} = options;
3333

3434
this.threeCamera = new THREE.PerspectiveCamera(fov, aspect, near, far);
35-
}
36-
37-
start() {
3835
this.threeCamera.add(this.actor.gameInstance.audio.listener);
3936
this.actor.gameInstance.renderer.addRenderComponent(this.threeCamera);
4037
}

packages/engine/src/components/Camera3DControls.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,9 @@ export class Camera3DControls extends Behavior {
5454
this.maxRollDown = options.maxRollDown ?? -Math.PI / 2;
5555
this.#rotationSpeed = options.rotationSpeed ?? 2;
5656
this.#movementSpeed = options.speed ?? 20;
57-
}
5857

59-
start() {
60-
this.camera.add(this.actor.gameInstance.audio.listener);
6158
this.actor.gameInstance.renderer.addRenderComponent(this.camera);
59+
this.camera.add(this.actor.gameInstance.audio.listener);
6260
}
6361

6462
set speed(

packages/engine/src/systems/GameInstance.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import * as THREE from "three";
44
// Import Internal Dependencies
55
import {
66
type GameRenderer
7-
} from "./Renderers/index.js";
7+
} from "./renderers/index.js";
88
import {
9-
SceneEngine,
109
type Scene
1110
} from "./Scene.js";
1211
import {
@@ -28,7 +27,7 @@ export interface GameInstanceOptions {
2827
enableOnExit?: boolean;
2928

3029
loadingManager?: THREE.LoadingManager;
31-
scene?: Scene;
30+
scene: Scene;
3231
input?: Input;
3332
scheduler?: FixedTimeStep;
3433
audio?: GlobalAudio;
@@ -53,11 +52,11 @@ export class GameInstance<T = THREE.WebGLRenderer> {
5352

5453
constructor(
5554
renderer: GameRenderer<T>,
56-
options: GameInstanceOptions = {}
55+
options: GameInstanceOptions
5756
) {
5857
const {
5958
loadingManager = new THREE.LoadingManager(),
60-
scene = new SceneEngine(),
59+
scene,
6160
input = new Input(renderer.canvas, { enableOnExit: options.enableOnExit ?? false }),
6261
scheduler = new FixedTimeStep(options.clock),
6362
audio = new GlobalAudio(),
@@ -108,9 +107,7 @@ export class GameInstance<T = THREE.WebGLRenderer> {
108107
return true;
109108
}
110109

111-
if (updates > 0) {
112-
this.renderer.draw(this.scene);
113-
}
110+
updates > 0 && this.renderer.draw();
114111

115112
return false;
116113
}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
// Import Third-party Dependencies
22
import * as THREE from "three";
3+
import type { Pass } from "three/addons/postprocessing/EffectComposer.js";
34

45
// Import Internal Dependencies
5-
import type { Scene } from "../Scene.js";
6+
import type { RenderMode } from "./RenderStrategy.js";
67

78
export type RenderComponent = THREE.PerspectiveCamera | THREE.OrthographicCamera;
89

910
export interface GameRenderer<T = any> {
1011
readonly canvas: HTMLCanvasElement;
1112

1213
getSource(): T;
14+
setRenderMode(mode: RenderMode): this;
1315
setRatio(ratio: number | null): this;
16+
setEffects(...effects: Pass[]): this;
1417

1518
addRenderComponent(component: RenderComponent): void;
1619
removeRenderComponent(component: RenderComponent): void;
1720

1821
resize(): void;
19-
draw(scene: Scene): void;
22+
draw(): void;
2023
onDraw(callback: (source: T) => void): void;
2124
clear(): void;
2225
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Import Third-party Dependencies
2+
import * as THREE from "three";
3+
import type {
4+
EffectComposer,
5+
Pass
6+
} from "three/addons/postprocessing/EffectComposer.js";
7+
8+
// Import Internal Dependencies
9+
import type { RenderComponent } from "./GameRenderer.js";
10+
11+
export type RenderMode = "direct" | "composer";
12+
13+
export interface RenderStrategy {
14+
render(scene: THREE.Scene, renderComponents: RenderComponent[]): void;
15+
resize(width: number, height: number): void;
16+
}
17+
18+
export class DirectRenderStrategy implements RenderStrategy {
19+
#renderer: THREE.WebGLRenderer;
20+
21+
constructor(
22+
renderer: THREE.WebGLRenderer
23+
) {
24+
this.#renderer = renderer;
25+
}
26+
27+
render(
28+
scene: THREE.Scene,
29+
renderComponents: RenderComponent[]
30+
): void {
31+
for (const renderComponent of renderComponents) {
32+
this.#renderer.render(scene, renderComponent);
33+
}
34+
}
35+
36+
resize(width: number, height: number): void {
37+
this.#renderer.setSize(width, height, false);
38+
}
39+
}
40+
41+
export class ComposerRenderStrategy implements RenderStrategy {
42+
#composer: EffectComposer;
43+
44+
constructor(
45+
composer: EffectComposer
46+
) {
47+
this.#composer = composer;
48+
}
49+
50+
render(
51+
_scene: THREE.Scene,
52+
_renderComponents: RenderComponent[]
53+
): void {
54+
this.#composer.render();
55+
}
56+
57+
resize(width: number, height: number): void {
58+
this.#composer.setSize(width, height);
59+
}
60+
61+
addEffect(pass: Pass): void {
62+
this.#composer.addPass(pass);
63+
}
64+
65+
removeEffect(pass: Pass): void {
66+
this.#composer.removePass(pass);
67+
}
68+
69+
getComposer(): EffectComposer {
70+
return this.#composer;
71+
}
72+
}

packages/engine/src/systems/Renderers/ThreeRenderer.ts

Lines changed: 111 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Import Third-party Dependencies
22
import * as THREE from "three";
3+
import { EffectComposer, type Pass } from "three/addons/postprocessing/EffectComposer.js";
4+
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
35
import { EventEmitter } from "@posva/event-emitter";
46

57
// Import Internal Dependencies
@@ -8,6 +10,12 @@ import type {
810
RenderComponent
911
} from "./GameRenderer.js";
1012
import type { Scene } from "../Scene.js";
13+
import {
14+
type RenderMode,
15+
type RenderStrategy,
16+
DirectRenderStrategy,
17+
ComposerRenderStrategy
18+
} from "./RenderStrategy.js";
1119

1220
export type ThreeRendererEvents = {
1321
resize: [
@@ -18,28 +26,33 @@ export type ThreeRendererEvents = {
1826
];
1927
};
2028

29+
export interface ThreeRendererOptions {
30+
/**
31+
* @default "direct"
32+
*/
33+
renderMode: RenderMode;
34+
scene: Scene;
35+
}
36+
2137
export class ThreeRenderer extends EventEmitter<
2238
ThreeRendererEvents
2339
> implements GameRenderer {
2440
webGLRenderer: THREE.WebGLRenderer;
2541
renderComponents: RenderComponent[] = [];
42+
renderStrategy: RenderStrategy;
2643
ratio: number | null = null;
44+
scene: Scene;
2745

2846
constructor(
29-
canvas: HTMLCanvasElement
47+
canvas: HTMLCanvasElement,
48+
options: ThreeRendererOptions
3049
) {
3150
super();
32-
this.webGLRenderer = new THREE.WebGLRenderer({
33-
canvas,
34-
antialias: true,
35-
alpha: true
36-
});
37-
38-
this.webGLRenderer.setPixelRatio(window.devicePixelRatio);
39-
this.webGLRenderer.shadowMap.enabled = true;
40-
this.webGLRenderer.shadowMap.type = THREE.BasicShadowMap;
41-
this.webGLRenderer.setSize(0, 0, false);
42-
this.webGLRenderer.autoClear = false;
51+
const { scene, renderMode = "direct" } = options;
52+
53+
this.scene = scene;
54+
this.webGLRenderer = createWebGLRenderer(canvas);
55+
this.setRenderMode(renderMode);
4356
}
4457

4558
get canvas() {
@@ -50,29 +63,77 @@ export class ThreeRenderer extends EventEmitter<
5063
return this.webGLRenderer;
5164
}
5265

53-
addRenderComponent(component: RenderComponent): void {
66+
addRenderComponent(
67+
component: RenderComponent
68+
): void {
5469
this.renderComponents.push(component);
70+
if (this.renderStrategy instanceof ComposerRenderStrategy) {
71+
const renderPass = new RenderPass(this.scene.getSource(), component);
72+
this.renderStrategy.addEffect(renderPass);
73+
}
5574
}
5675

57-
removeRenderComponent(component: RenderComponent): void {
76+
removeRenderComponent(
77+
component: RenderComponent
78+
): void {
5879
const index = this.renderComponents.indexOf(component);
5980
if (index !== -1) {
6081
this.renderComponents.splice(index, 1);
6182
}
83+
84+
if (this.renderStrategy instanceof ComposerRenderStrategy) {
85+
const composer = this.renderStrategy.getComposer();
86+
const renderPass = composer.passes.find(
87+
(pass) => (pass as RenderPass).camera === component
88+
)!;
89+
this.renderStrategy.removeEffect(renderPass);
90+
}
91+
}
92+
93+
setRenderMode(
94+
mode: RenderMode
95+
): this {
96+
if (mode === "direct") {
97+
this.renderStrategy = new DirectRenderStrategy(this.webGLRenderer);
98+
}
99+
else {
100+
const composer = new EffectComposer(this.webGLRenderer);
101+
102+
const scene = this.scene.getSource();
103+
for (const renderComponent of this.renderComponents) {
104+
const renderPass = new RenderPass(scene, renderComponent);
105+
composer.addPass(renderPass);
106+
}
107+
108+
this.renderStrategy = new ComposerRenderStrategy(
109+
composer
110+
);
111+
}
112+
this.resize();
113+
this.clear();
114+
115+
return this;
116+
}
117+
118+
setEffects(...effects: Pass[]): this {
119+
if (this.renderStrategy instanceof ComposerRenderStrategy) {
120+
for (const pass of effects) {
121+
this.renderStrategy.addEffect(pass);
122+
}
123+
}
124+
125+
return this;
62126
}
63127

64128
setRatio(
65129
ratio: number | null = null
66130
) {
67131
this.ratio = ratio;
68-
if (this.ratio) {
69-
this.webGLRenderer.domElement.style.margin = "0";
70-
this.webGLRenderer.domElement.style.flex = "1";
71-
}
72-
else {
73-
this.webGLRenderer.domElement.style.margin = "auto";
74-
this.webGLRenderer.domElement.style.flex = "none";
75-
}
132+
133+
const styles = this.ratio ?
134+
{ margin: "0", flex: "1" } :
135+
{ margin: "auto", flex: "none" };
136+
Object.assign(this.webGLRenderer.domElement.style, styles);
76137
this.resize();
77138

78139
return this;
@@ -107,7 +168,7 @@ export class ThreeRenderer extends EventEmitter<
107168
this.webGLRenderer.domElement.width !== width ||
108169
this.webGLRenderer.domElement.height !== height
109170
) {
110-
this.webGLRenderer.setSize(width, height, false);
171+
this.renderStrategy.resize(width, height);
111172
for (const renderComponent of this.renderComponents) {
112173
if (renderComponent instanceof THREE.PerspectiveCamera) {
113174
renderComponent.aspect = width / height;
@@ -118,24 +179,14 @@ export class ThreeRenderer extends EventEmitter<
118179
}
119180
};
120181

121-
draw(
122-
scene: Scene
123-
) {
182+
draw() {
124183
this.resize();
125184
this.clear();
126-
// this.renderComponents.sort((a, b) => {
127-
// let order = (a.depth - b.depth);
128-
// if (order === 0) {
129-
// order = this.cachedActors.indexOf(a.actor) - this.cachedActors.indexOf(b.actor);
130-
// }
131-
132-
// return order;
133-
// });
134-
135-
const source = scene.getSource();
136-
for (const renderComponent of this.renderComponents) {
137-
this.webGLRenderer.render(source, renderComponent);
138-
}
185+
186+
this.renderStrategy.render(
187+
this.scene.getSource(),
188+
this.renderComponents
189+
);
139190
this.emit("draw", { source: this.webGLRenderer });
140191
}
141192

@@ -149,3 +200,24 @@ export class ThreeRenderer extends EventEmitter<
149200
this.webGLRenderer.clear();
150201
}
151202
}
203+
204+
function createWebGLRenderer(
205+
canvas: HTMLCanvasElement
206+
): THREE.WebGLRenderer {
207+
const renderer = new THREE.WebGLRenderer({
208+
canvas,
209+
antialias: true,
210+
alpha: true
211+
});
212+
213+
renderer.setPixelRatio(window.devicePixelRatio);
214+
renderer.shadowMap.enabled = true;
215+
renderer.shadowMap.type = THREE.BasicShadowMap;
216+
renderer.setSize(0, 0, false);
217+
renderer.autoClear = false;
218+
renderer.outputColorSpace = THREE.SRGBColorSpace;
219+
renderer.toneMapping = THREE.NeutralToneMapping;
220+
renderer.toneMappingExposure = 1.25;
221+
222+
return renderer;
223+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./GameRenderer.js";
22
export * from "./ThreeRenderer.js";
3+
export type { RenderMode } from "./RenderStrategy.js";

0 commit comments

Comments
 (0)