@eigong/three-effekseer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @eigong/three-effekseer
2
+
3
+ `@eigong/three-effekseer` is a WebGPU add-on that integrates Effekseer into a patched Three.js WebGPU renderer.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install three@npm:@eigong/three@0.183.1-external-render-pass-hook.1 @eigong/three-effekseer
9
+ ```
10
+
11
+ ## Version Baseline
12
+
13
+ This add-on is currently based on Effekseer `1.80 b2`.
14
+ The required Three.js baseline is `@eigong/three@0.183.1-external-render-pass-hook.1`.
15
+
16
+ ## Requirements
17
+
18
+ - The local Three.js fork must expose the external render pass hook implemented in:
19
+ - [WebGPURenderer.js](../../node_modules/three/src/renderers/webgpu/WebGPURenderer.js)
20
+ - [WebGPUBackend.js](../../node_modules/three/src/renderers/webgpu/WebGPUBackend.js)
21
+ - The Effekseer WebGPU runtime must already be loaded in the page.
22
+
23
+ ## Canonical Example
24
+
25
+ The canonical consumer of the add-on is the vanilla example:
26
+
27
+ - HTML entry: [vanilla/index.html](../../react-vite-ts/vanilla/index.html)
28
+ - Integration: [vanilla-main.ts](../../react-vite-ts/src/vanilla-main.ts)
29
+
30
+ The React example in [ThreeEffekseerCanvas.tsx](../../react-vite-ts/src/ThreeEffekseerCanvas.tsx) consumes the same add-on, but it is not the reference integration.
31
+
32
+ ## Public API
33
+
34
+ ```ts
35
+ import { EffekseerRenderPass } from '@eigong/three-effekseer'
36
+
37
+ const pass = new EffekseerRenderPass(
38
+ renderer,
39
+ scene,
40
+ camera,
41
+ ctx,
42
+ { mode: 'composite' }
43
+ )
44
+
45
+ const capabilities = pass.getCapabilities()
46
+ ```
47
+
48
+ This mirrors the WebGL-side integration format by exposing a dedicated `EffekseerRenderPass` on the Three side while keeping the Effekseer context external.
49
+
50
+ `mode` defaults to `'basic'`.
51
+
52
+ ## Modes
53
+
54
+ | Mode | Distortion | Depth Occlusion | Soft Particles | LOD | Collisions | Notes |
55
+ | --- | --- | --- | --- | --- | --- | --- |
56
+ | `basic` | No | Yes | No | No | No | Effekseer is injected into the primary scene render pass. Lowest cost path. |
57
+ | `composite` | Yes | Yes | No | No | No | Uses scene capture for background refraction, renders the scene again into a composite target, then presents through `PostProcessing`. |
58
+
59
+ ## Unsupported Features
60
+
61
+ - `Soft particles`: not supported yet. The add-on does not expose a sampleable scene depth texture to Effekseer.
62
+ - `LOD`: not supported. The add-on does not currently provide a Three-side feature contract for effect level-of-detail selection.
63
+ - `Collisions`: not supported. The add-on does not currently bridge Three scene collision data or collision callbacks into Effekseer.
@@ -0,0 +1,307 @@
1
+ import * as THREE4 from 'three/webgpu';
2
+
3
+ // common.ts
4
+ function updateCameraProjection(camera, width, height) {
5
+ const cameraWithProjection = camera;
6
+ if (cameraWithProjection.isPerspectiveCamera === true && typeof cameraWithProjection.aspect === "number") {
7
+ cameraWithProjection.aspect = width / height;
8
+ }
9
+ cameraWithProjection.updateProjectionMatrix?.();
10
+ }
11
+ function syncEffekseerCamera(camera, effekseer) {
12
+ camera.updateMatrixWorld();
13
+ effekseer.setProjectionMatrix(camera.projectionMatrix.elements);
14
+ effekseer.setCameraMatrix(camera.matrixWorldInverse.elements);
15
+ }
16
+
17
+ // createBasicPass.ts
18
+ function isPrimaryScenePass(camera, info) {
19
+ return info.camera === camera;
20
+ }
21
+ function createBasicPass(init) {
22
+ const { renderer, scene, camera, effekseer } = init;
23
+ const capabilities = {
24
+ mode: "basic",
25
+ supportsDistortion: false,
26
+ supportsDepthOcclusion: true,
27
+ supportsSoftParticles: false,
28
+ supportsLOD: false,
29
+ supportsCollisions: false
30
+ };
31
+ return {
32
+ getCapabilities() {
33
+ return capabilities;
34
+ },
35
+ resize(width, height, pixelRatio) {
36
+ renderer.setPixelRatio(pixelRatio);
37
+ renderer.setSize(width, height, false);
38
+ updateCameraProjection(camera, width, height);
39
+ },
40
+ render(deltaFrames) {
41
+ const previousHook = renderer.getExternalRenderPassHook();
42
+ renderer.setExternalRenderPassHook((info) => {
43
+ previousHook?.(info);
44
+ if (!isPrimaryScenePass(camera, info)) {
45
+ return;
46
+ }
47
+ syncEffekseerCamera(camera, effekseer);
48
+ effekseer.drawExternal(info.renderPassEncoder, {
49
+ colorFormat: info.colorFormat,
50
+ depthFormat: info.depthStencilFormat,
51
+ sampleCount: info.sampleCount
52
+ });
53
+ });
54
+ try {
55
+ syncEffekseerCamera(camera, effekseer);
56
+ effekseer.update(deltaFrames);
57
+ renderer.render(scene, camera);
58
+ } finally {
59
+ renderer.setExternalRenderPassHook(previousHook);
60
+ }
61
+ },
62
+ dispose() {
63
+ }
64
+ };
65
+ }
66
+ function isCompositePass(info, renderTarget, camera) {
67
+ return info.renderTarget === renderTarget && info.camera === camera;
68
+ }
69
+ function createCompositePassPresenter(init) {
70
+ const { renderer, scene, camera, renderTarget, resolveCompositePassState } = init;
71
+ return {
72
+ render(input) {
73
+ const previousHook = renderer.getExternalRenderPassHook();
74
+ const previousRenderTarget = renderer.getRenderTarget();
75
+ const previousActiveCubeFace = renderer.getActiveCubeFace();
76
+ const previousActiveMipmapLevel = renderer.getActiveMipmapLevel();
77
+ renderer.setRenderTarget(renderTarget);
78
+ renderer.setExternalRenderPassHook((info) => {
79
+ previousHook?.(info);
80
+ if (!isCompositePass(info, renderTarget, camera)) {
81
+ return;
82
+ }
83
+ const compositePassState = resolveCompositePassState(input, info);
84
+ if (!compositePassState) {
85
+ return;
86
+ }
87
+ input.effekseer.drawExternal(info.renderPassEncoder, compositePassState.effekseerPassState);
88
+ });
89
+ try {
90
+ renderer.render(scene, camera);
91
+ } finally {
92
+ renderer.setExternalRenderPassHook(previousHook);
93
+ renderer.setRenderTarget(
94
+ previousRenderTarget,
95
+ previousActiveCubeFace,
96
+ previousActiveMipmapLevel
97
+ );
98
+ }
99
+ },
100
+ resize() {
101
+ },
102
+ dispose() {
103
+ }
104
+ };
105
+ }
106
+ function createFinalPassPresenter(init) {
107
+ const { renderer } = init;
108
+ const postProcessing = new THREE4.PostProcessing(renderer);
109
+ let sourceTexture = null;
110
+ const updateOutputNode = (texture) => {
111
+ if (sourceTexture === texture) {
112
+ return;
113
+ }
114
+ sourceTexture = texture;
115
+ postProcessing.outputNode = THREE4.TSL.texture(texture);
116
+ postProcessing.needsUpdate = true;
117
+ };
118
+ return {
119
+ render(texture) {
120
+ updateOutputNode(texture);
121
+ postProcessing.render();
122
+ },
123
+ resize() {
124
+ postProcessing.needsUpdate = true;
125
+ },
126
+ dispose() {
127
+ postProcessing.dispose();
128
+ }
129
+ };
130
+ }
131
+ function getNativeGPUTexture(renderer, texture) {
132
+ const nativeTexture = renderer.backend.get(texture).texture;
133
+ return nativeTexture ?? null;
134
+ }
135
+
136
+ // createCompositePass.ts
137
+ function createSceneCaptureTrigger(renderer, scenePass, samples) {
138
+ const postProcessing = new THREE4.PostProcessing(renderer);
139
+ const triggerTarget = new THREE4.RenderTarget(1, 1, {
140
+ type: renderer.getOutputBufferType(),
141
+ colorSpace: THREE4.LinearSRGBColorSpace,
142
+ depthBuffer: false,
143
+ samples
144
+ });
145
+ triggerTarget.texture.name = "ThreeEffekseer.captureTrigger";
146
+ postProcessing.outputColorTransform = false;
147
+ postProcessing.outputNode = scenePass.getTextureNode("output");
148
+ return {
149
+ render() {
150
+ const previousRenderTarget = renderer.getRenderTarget();
151
+ const previousActiveCubeFace = renderer.getActiveCubeFace();
152
+ const previousActiveMipmapLevel = renderer.getActiveMipmapLevel();
153
+ renderer.setRenderTarget(triggerTarget);
154
+ try {
155
+ postProcessing.render();
156
+ } finally {
157
+ renderer.setRenderTarget(
158
+ previousRenderTarget,
159
+ previousActiveCubeFace,
160
+ previousActiveMipmapLevel
161
+ );
162
+ }
163
+ },
164
+ resize() {
165
+ },
166
+ dispose() {
167
+ postProcessing.dispose();
168
+ triggerTarget.dispose();
169
+ }
170
+ };
171
+ }
172
+ function syncScenePassResources(renderer, scenePassResources) {
173
+ const renderTarget = scenePassResources.scenePass?.renderTarget ?? null;
174
+ const colorTexture = renderTarget?.texture ?? null;
175
+ const depthTexture = renderTarget?.depthTexture ?? null;
176
+ scenePassResources.renderTarget = renderTarget;
177
+ scenePassResources.colorTexture = colorTexture;
178
+ scenePassResources.depthTexture = depthTexture;
179
+ scenePassResources.nativeColorTexture = colorTexture ? getNativeGPUTexture(renderer, colorTexture) : null;
180
+ scenePassResources.nativeColorTextureView = scenePassResources.nativeColorTexture?.createView() ?? null;
181
+ scenePassResources.nativeDepthTexture = depthTexture ? getNativeGPUTexture(renderer, depthTexture) : null;
182
+ scenePassResources.nativeDepthTextureView = null;
183
+ }
184
+ function createFinalPassState(renderer, scenePassResources, info) {
185
+ syncScenePassResources(renderer, scenePassResources);
186
+ const backgroundTextureView = scenePassResources.nativeColorTextureView;
187
+ return {
188
+ backgroundTextureView,
189
+ depthTextureView: null,
190
+ effekseerPassState: {
191
+ colorFormat: info.colorFormat,
192
+ depthFormat: info.depthStencilFormat,
193
+ sampleCount: info.sampleCount,
194
+ ...backgroundTextureView ? { backgroundTextureView } : {}
195
+ }
196
+ };
197
+ }
198
+ function createCompositePass(init) {
199
+ const { renderer, scene, camera, effekseer } = init;
200
+ const samples = Math.max(0, renderer.samples || 0);
201
+ const capabilities = {
202
+ mode: "composite",
203
+ supportsDistortion: true,
204
+ supportsDepthOcclusion: true,
205
+ supportsSoftParticles: false,
206
+ supportsLOD: false,
207
+ supportsCollisions: false
208
+ };
209
+ const scenePass = THREE4.TSL.pass(scene, camera);
210
+ const sceneCaptureTrigger = createSceneCaptureTrigger(renderer, scenePass, samples);
211
+ const compositionTarget = new THREE4.RenderTarget(1, 1, {
212
+ type: renderer.getOutputBufferType(),
213
+ colorSpace: THREE4.LinearSRGBColorSpace,
214
+ depthBuffer: true,
215
+ samples
216
+ });
217
+ compositionTarget.texture.name = "ThreeEffekseer.composition";
218
+ const drawingBufferSize = new THREE4.Vector2();
219
+ const scenePassResources = {
220
+ scenePass,
221
+ renderTarget: scenePass.renderTarget,
222
+ colorTexture: scenePass.renderTarget.texture,
223
+ depthTexture: scenePass.renderTarget.depthTexture,
224
+ nativeColorTexture: null,
225
+ nativeColorTextureView: null,
226
+ nativeDepthTexture: null,
227
+ nativeDepthTextureView: null
228
+ };
229
+ const compositePresenter = createCompositePassPresenter({
230
+ renderer,
231
+ scene,
232
+ camera,
233
+ renderTarget: compositionTarget,
234
+ resolveCompositePassState: (_input, info) => createFinalPassState(renderer, scenePassResources, info)
235
+ });
236
+ const presenter = createFinalPassPresenter({ renderer });
237
+ return {
238
+ getCapabilities() {
239
+ return capabilities;
240
+ },
241
+ resize(width, height, pixelRatio) {
242
+ renderer.setPixelRatio(pixelRatio);
243
+ renderer.setSize(width, height, false);
244
+ renderer.getDrawingBufferSize(drawingBufferSize);
245
+ compositionTarget.setSize(drawingBufferSize.width, drawingBufferSize.height);
246
+ updateCameraProjection(camera, width, height);
247
+ presenter.resize(width, height, pixelRatio);
248
+ },
249
+ render(deltaFrames) {
250
+ syncEffekseerCamera(camera, effekseer);
251
+ effekseer.update(deltaFrames);
252
+ sceneCaptureTrigger.render();
253
+ scenePassResources.renderTarget = scenePass.renderTarget;
254
+ scenePassResources.colorTexture = scenePass.renderTarget.texture;
255
+ scenePassResources.depthTexture = scenePass.renderTarget.depthTexture;
256
+ compositePresenter.render({
257
+ scenePassResources,
258
+ effekseer
259
+ });
260
+ presenter.render(compositionTarget.texture);
261
+ },
262
+ dispose() {
263
+ sceneCaptureTrigger.dispose();
264
+ presenter.dispose();
265
+ compositionTarget.dispose();
266
+ }
267
+ };
268
+ }
269
+
270
+ // ThreeEffekseerPass.ts
271
+ function createThreeEffekseerPass(init, options = {}) {
272
+ if (options.mode === "composite") {
273
+ return createCompositePass(init);
274
+ }
275
+ return createBasicPass(init);
276
+ }
277
+
278
+ // EffekseerRenderPass.ts
279
+ var EffekseerRenderPass = class {
280
+ pass;
281
+ constructor(renderer, scene, camera, effekseer, options = {}) {
282
+ this.pass = createThreeEffekseerPass(
283
+ {
284
+ renderer,
285
+ scene,
286
+ camera,
287
+ effekseer
288
+ },
289
+ options
290
+ );
291
+ }
292
+ getCapabilities() {
293
+ return this.pass.getCapabilities();
294
+ }
295
+ setSize(width, height, pixelRatio = 1) {
296
+ this.pass.resize(width, height, pixelRatio);
297
+ }
298
+ render(deltaFrames = 1) {
299
+ this.pass.render(deltaFrames);
300
+ }
301
+ dispose() {
302
+ this.pass.dispose();
303
+ }
304
+ };
305
+ var EffekseerRenderPass_default = EffekseerRenderPass;
306
+
307
+ export { EffekseerRenderPass_default as EffekseerRenderPass };
package/dist/index.js ADDED
@@ -0,0 +1,321 @@
1
+ // common.ts
2
+ function updateCameraProjection(camera, width, height) {
3
+ const cameraWithProjection = camera;
4
+ if (cameraWithProjection.isPerspectiveCamera === true && typeof cameraWithProjection.aspect === "number") {
5
+ cameraWithProjection.aspect = width / height;
6
+ }
7
+ cameraWithProjection.updateProjectionMatrix?.();
8
+ }
9
+ function syncEffekseerCamera(camera, effekseer) {
10
+ camera.updateMatrixWorld();
11
+ effekseer.setProjectionMatrix(camera.projectionMatrix.elements);
12
+ effekseer.setCameraMatrix(camera.matrixWorldInverse.elements);
13
+ }
14
+
15
+ // createBasicPass.ts
16
+ function isPrimaryScenePass(camera, info) {
17
+ return info.camera === camera;
18
+ }
19
+ function createBasicPass(init) {
20
+ const { renderer, scene, camera, effekseer } = init;
21
+ const capabilities = {
22
+ mode: "basic",
23
+ supportsDistortion: false,
24
+ supportsDepthOcclusion: true,
25
+ supportsSoftParticles: false,
26
+ supportsLOD: false,
27
+ supportsCollisions: false
28
+ };
29
+ return {
30
+ getCapabilities() {
31
+ return capabilities;
32
+ },
33
+ resize(width, height, pixelRatio) {
34
+ renderer.setPixelRatio(pixelRatio);
35
+ renderer.setSize(width, height, false);
36
+ updateCameraProjection(camera, width, height);
37
+ },
38
+ render(deltaFrames) {
39
+ const previousHook = renderer.getExternalRenderPassHook();
40
+ renderer.setExternalRenderPassHook((info) => {
41
+ previousHook?.(info);
42
+ if (!isPrimaryScenePass(camera, info)) {
43
+ return;
44
+ }
45
+ syncEffekseerCamera(camera, effekseer);
46
+ effekseer.drawExternal(info.renderPassEncoder, {
47
+ colorFormat: info.colorFormat,
48
+ depthFormat: info.depthStencilFormat,
49
+ sampleCount: info.sampleCount
50
+ });
51
+ });
52
+ try {
53
+ syncEffekseerCamera(camera, effekseer);
54
+ effekseer.update(deltaFrames);
55
+ renderer.render(scene, camera);
56
+ } finally {
57
+ renderer.setExternalRenderPassHook(previousHook);
58
+ }
59
+ },
60
+ dispose() {
61
+ }
62
+ };
63
+ }
64
+
65
+ // createCompositePass.ts
66
+ import * as THREE4 from "three/webgpu";
67
+
68
+ // createCompositePassPresenter.ts
69
+ import "three/webgpu";
70
+ function isCompositePass(info, renderTarget, camera) {
71
+ return info.renderTarget === renderTarget && info.camera === camera;
72
+ }
73
+ function createCompositePassPresenter(init) {
74
+ const { renderer, scene, camera, renderTarget, resolveCompositePassState } = init;
75
+ return {
76
+ render(input) {
77
+ const previousHook = renderer.getExternalRenderPassHook();
78
+ const previousRenderTarget = renderer.getRenderTarget();
79
+ const previousActiveCubeFace = renderer.getActiveCubeFace();
80
+ const previousActiveMipmapLevel = renderer.getActiveMipmapLevel();
81
+ renderer.setRenderTarget(renderTarget);
82
+ renderer.setExternalRenderPassHook((info) => {
83
+ previousHook?.(info);
84
+ if (!isCompositePass(info, renderTarget, camera)) {
85
+ return;
86
+ }
87
+ const compositePassState = resolveCompositePassState(input, info);
88
+ if (!compositePassState) {
89
+ return;
90
+ }
91
+ input.effekseer.drawExternal(info.renderPassEncoder, compositePassState.effekseerPassState);
92
+ });
93
+ try {
94
+ renderer.render(scene, camera);
95
+ } finally {
96
+ renderer.setExternalRenderPassHook(previousHook);
97
+ renderer.setRenderTarget(
98
+ previousRenderTarget,
99
+ previousActiveCubeFace,
100
+ previousActiveMipmapLevel
101
+ );
102
+ }
103
+ },
104
+ resize() {
105
+ },
106
+ dispose() {
107
+ }
108
+ };
109
+ }
110
+
111
+ // createFinalPassPresenter.ts
112
+ import * as THREE2 from "three/webgpu";
113
+ function createFinalPassPresenter(init) {
114
+ const { renderer } = init;
115
+ const postProcessing = new THREE2.PostProcessing(renderer);
116
+ let sourceTexture = null;
117
+ const updateOutputNode = (texture) => {
118
+ if (sourceTexture === texture) {
119
+ return;
120
+ }
121
+ sourceTexture = texture;
122
+ postProcessing.outputNode = THREE2.TSL.texture(texture);
123
+ postProcessing.needsUpdate = true;
124
+ };
125
+ return {
126
+ render(texture) {
127
+ updateOutputNode(texture);
128
+ postProcessing.render();
129
+ },
130
+ resize() {
131
+ postProcessing.needsUpdate = true;
132
+ },
133
+ dispose() {
134
+ postProcessing.dispose();
135
+ }
136
+ };
137
+ }
138
+
139
+ // webgpuInternals.ts
140
+ import "three/webgpu";
141
+ function getNativeGPUTexture(renderer, texture) {
142
+ const nativeTexture = renderer.backend.get(texture).texture;
143
+ return nativeTexture ?? null;
144
+ }
145
+
146
+ // createCompositePass.ts
147
+ function createSceneCaptureTrigger(renderer, scenePass, samples) {
148
+ const postProcessing = new THREE4.PostProcessing(renderer);
149
+ const triggerTarget = new THREE4.RenderTarget(1, 1, {
150
+ type: renderer.getOutputBufferType(),
151
+ colorSpace: THREE4.LinearSRGBColorSpace,
152
+ depthBuffer: false,
153
+ samples
154
+ });
155
+ triggerTarget.texture.name = "ThreeEffekseer.captureTrigger";
156
+ postProcessing.outputColorTransform = false;
157
+ postProcessing.outputNode = scenePass.getTextureNode("output");
158
+ return {
159
+ render() {
160
+ const previousRenderTarget = renderer.getRenderTarget();
161
+ const previousActiveCubeFace = renderer.getActiveCubeFace();
162
+ const previousActiveMipmapLevel = renderer.getActiveMipmapLevel();
163
+ renderer.setRenderTarget(triggerTarget);
164
+ try {
165
+ postProcessing.render();
166
+ } finally {
167
+ renderer.setRenderTarget(
168
+ previousRenderTarget,
169
+ previousActiveCubeFace,
170
+ previousActiveMipmapLevel
171
+ );
172
+ }
173
+ },
174
+ resize() {
175
+ },
176
+ dispose() {
177
+ postProcessing.dispose();
178
+ triggerTarget.dispose();
179
+ }
180
+ };
181
+ }
182
+ function syncScenePassResources(renderer, scenePassResources) {
183
+ const renderTarget = scenePassResources.scenePass?.renderTarget ?? null;
184
+ const colorTexture = renderTarget?.texture ?? null;
185
+ const depthTexture = renderTarget?.depthTexture ?? null;
186
+ scenePassResources.renderTarget = renderTarget;
187
+ scenePassResources.colorTexture = colorTexture;
188
+ scenePassResources.depthTexture = depthTexture;
189
+ scenePassResources.nativeColorTexture = colorTexture ? getNativeGPUTexture(renderer, colorTexture) : null;
190
+ scenePassResources.nativeColorTextureView = scenePassResources.nativeColorTexture?.createView() ?? null;
191
+ scenePassResources.nativeDepthTexture = depthTexture ? getNativeGPUTexture(renderer, depthTexture) : null;
192
+ scenePassResources.nativeDepthTextureView = null;
193
+ }
194
+ function createFinalPassState(renderer, scenePassResources, info) {
195
+ syncScenePassResources(renderer, scenePassResources);
196
+ const backgroundTextureView = scenePassResources.nativeColorTextureView;
197
+ return {
198
+ backgroundTextureView,
199
+ depthTextureView: null,
200
+ effekseerPassState: {
201
+ colorFormat: info.colorFormat,
202
+ depthFormat: info.depthStencilFormat,
203
+ sampleCount: info.sampleCount,
204
+ ...backgroundTextureView ? { backgroundTextureView } : {}
205
+ }
206
+ };
207
+ }
208
+ function createCompositePass(init) {
209
+ const { renderer, scene, camera, effekseer } = init;
210
+ const samples = Math.max(0, renderer.samples || 0);
211
+ const capabilities = {
212
+ mode: "composite",
213
+ supportsDistortion: true,
214
+ supportsDepthOcclusion: true,
215
+ supportsSoftParticles: false,
216
+ supportsLOD: false,
217
+ supportsCollisions: false
218
+ };
219
+ const scenePass = THREE4.TSL.pass(scene, camera);
220
+ const sceneCaptureTrigger = createSceneCaptureTrigger(renderer, scenePass, samples);
221
+ const compositionTarget = new THREE4.RenderTarget(1, 1, {
222
+ type: renderer.getOutputBufferType(),
223
+ colorSpace: THREE4.LinearSRGBColorSpace,
224
+ depthBuffer: true,
225
+ samples
226
+ });
227
+ compositionTarget.texture.name = "ThreeEffekseer.composition";
228
+ const drawingBufferSize = new THREE4.Vector2();
229
+ const scenePassResources = {
230
+ scenePass,
231
+ renderTarget: scenePass.renderTarget,
232
+ colorTexture: scenePass.renderTarget.texture,
233
+ depthTexture: scenePass.renderTarget.depthTexture,
234
+ nativeColorTexture: null,
235
+ nativeColorTextureView: null,
236
+ nativeDepthTexture: null,
237
+ nativeDepthTextureView: null
238
+ };
239
+ const compositePresenter = createCompositePassPresenter({
240
+ renderer,
241
+ scene,
242
+ camera,
243
+ renderTarget: compositionTarget,
244
+ resolveCompositePassState: (_input, info) => createFinalPassState(renderer, scenePassResources, info)
245
+ });
246
+ const presenter = createFinalPassPresenter({ renderer });
247
+ return {
248
+ getCapabilities() {
249
+ return capabilities;
250
+ },
251
+ resize(width, height, pixelRatio) {
252
+ renderer.setPixelRatio(pixelRatio);
253
+ renderer.setSize(width, height, false);
254
+ renderer.getDrawingBufferSize(drawingBufferSize);
255
+ compositionTarget.setSize(drawingBufferSize.width, drawingBufferSize.height);
256
+ updateCameraProjection(camera, width, height);
257
+ sceneCaptureTrigger.resize(width, height, pixelRatio);
258
+ compositePresenter.resize(width, height, pixelRatio);
259
+ presenter.resize(width, height, pixelRatio);
260
+ },
261
+ render(deltaFrames) {
262
+ syncEffekseerCamera(camera, effekseer);
263
+ effekseer.update(deltaFrames);
264
+ sceneCaptureTrigger.render();
265
+ scenePassResources.renderTarget = scenePass.renderTarget;
266
+ scenePassResources.colorTexture = scenePass.renderTarget.texture;
267
+ scenePassResources.depthTexture = scenePass.renderTarget.depthTexture;
268
+ compositePresenter.render({
269
+ scenePassResources,
270
+ effekseer
271
+ });
272
+ presenter.render(compositionTarget.texture);
273
+ },
274
+ dispose() {
275
+ sceneCaptureTrigger.dispose();
276
+ compositePresenter.dispose();
277
+ presenter.dispose();
278
+ compositionTarget.dispose();
279
+ }
280
+ };
281
+ }
282
+
283
+ // ThreeEffekseerPass.ts
284
+ function createThreeEffekseerPass(init, options = {}) {
285
+ if (options.mode === "composite") {
286
+ return createCompositePass(init);
287
+ }
288
+ return createBasicPass(init);
289
+ }
290
+
291
+ // EffekseerRenderPass.ts
292
+ var EffekseerRenderPass = class {
293
+ pass;
294
+ constructor(renderer, scene, camera, effekseer, options = {}) {
295
+ this.pass = createThreeEffekseerPass(
296
+ {
297
+ renderer,
298
+ scene,
299
+ camera,
300
+ effekseer
301
+ },
302
+ options
303
+ );
304
+ }
305
+ getCapabilities() {
306
+ return this.pass.getCapabilities();
307
+ }
308
+ setSize(width, height, pixelRatio = 1) {
309
+ this.pass.resize(width, height, pixelRatio);
310
+ }
311
+ render(deltaFrames = 1) {
312
+ this.pass.render(deltaFrames);
313
+ }
314
+ dispose() {
315
+ this.pass.dispose();
316
+ }
317
+ };
318
+ var EffekseerRenderPass_default = EffekseerRenderPass;
319
+ export {
320
+ EffekseerRenderPass_default as EffekseerRenderPass
321
+ };
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ /// <reference path="./three-webgpu-hook.d.ts" />
2
+
3
+ export * from './dist/index'
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@eigong/three-effekseer",
3
+ "version": "0.1.0",
4
+ "description": "WebGPU add-on that integrates Effekseer into a patched Three.js renderer",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "index.d.ts",
18
+ "three-webgpu-hook.d.ts",
19
+ "README.md"
20
+ ],
21
+ "sideEffects": false,
22
+ "scripts": {
23
+ "build": "tsup index.ts --format esm --dts --clean",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "effekseer",
28
+ "three",
29
+ "threejs",
30
+ "webgpu"
31
+ ],
32
+ "peerDependencies": {
33
+ "three": ">=0.183.1 <0.184.0"
34
+ },
35
+ "devDependencies": {
36
+ "tsup": "^8.5.0",
37
+ "typescript": "^5.9.3"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ }
42
+ }
@@ -0,0 +1,28 @@
1
+ import 'three/webgpu'
2
+ import type { Camera, RenderTarget } from 'three'
3
+
4
+ declare module 'three/webgpu' {
5
+ export interface ExternalRenderPassHookInfo {
6
+ renderer: WebGPURenderer
7
+ renderPassEncoder: GPURenderPassEncoder
8
+ commandEncoder: GPUCommandEncoder
9
+ colorFormat: GPUTextureFormat
10
+ depthStencilFormat: GPUTextureFormat | null
11
+ sampleCount: number
12
+ isDefaultCanvasTarget: boolean
13
+ renderTarget: RenderTarget | null
14
+ camera: Camera | null
15
+ colorAttachmentView: GPUTextureView
16
+ resolveTargetView: GPUTextureView | null
17
+ depthStencilAttachmentView: GPUTextureView | null
18
+ sampleableColorTextureView: GPUTextureView | null
19
+ sampleableDepthTextureView: GPUTextureView | null
20
+ }
21
+
22
+ export type ExternalRenderPassHook = (info: ExternalRenderPassHookInfo) => void
23
+
24
+ export interface WebGPURenderer {
25
+ setExternalRenderPassHook(callback: ExternalRenderPassHook | null): this
26
+ getExternalRenderPassHook(): ExternalRenderPassHook | null
27
+ }
28
+ }