@volter/editor-threejs 0.5.190 → 0.5.191
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/editor-threejs",
|
|
3
3
|
"author": "Volter AI, Inc.",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.191",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -135,12 +135,12 @@
|
|
|
135
135
|
"serving": "./dist-node/serving.mjs"
|
|
136
136
|
},
|
|
137
137
|
"dependencies": {
|
|
138
|
-
"@volter/editor-project": "0.5.
|
|
138
|
+
"@volter/editor-project": "0.5.191",
|
|
139
139
|
"@sparkjsdev/spark": "^2.1.0",
|
|
140
140
|
"three.quarks": "^0.16.0",
|
|
141
141
|
"zod": "^4.3.6",
|
|
142
142
|
"typescript": "5.9.3",
|
|
143
|
-
"@volter/editor-sdk": "0.5.
|
|
143
|
+
"@volter/editor-sdk": "0.5.191",
|
|
144
144
|
"postprocessing": "6.38.3",
|
|
145
145
|
"quarks.core": "0.16.0",
|
|
146
146
|
"@fortawesome/free-solid-svg-icons": "^7.2.0"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import {FullScreenQuad} from 'three/addons/postprocessing/Pass.js';
|
|
3
|
+
|
|
4
|
+
let quad: FullScreenQuad | null = null;
|
|
5
|
+
let material: THREE.RawShaderMaterial | null = null;
|
|
6
|
+
|
|
7
|
+
/** The capture renders exactly 2x in each axis. Linear filtering at a pixel's
|
|
8
|
+
* center averages its four scene-linear samples BEFORE the nonlinear display
|
|
9
|
+
* transform. Downsampling display-encoded canvas pixels darkens color edges.
|
|
10
|
+
* The caller owns the returned target; input and associated alpha stay intact. */
|
|
11
|
+
export function resolveSceneLinearSize(
|
|
12
|
+
renderer: THREE.WebGLRenderer,
|
|
13
|
+
input: THREE.WebGLRenderTarget,
|
|
14
|
+
width: number,
|
|
15
|
+
height: number,
|
|
16
|
+
): THREE.WebGLRenderTarget {
|
|
17
|
+
if (input.width !== width * 2 || input.height !== height * 2)
|
|
18
|
+
throw new Error('Linear capture resolve requires a 2x scene target');
|
|
19
|
+
material ??= new THREE.RawShaderMaterial({
|
|
20
|
+
uniforms: {frame: {value:null}, texel: {value:new THREE.Vector2()}},
|
|
21
|
+
depthTest:false, depthWrite:false, blending:THREE.NoBlending,
|
|
22
|
+
vertexShader:`precision highp float; attribute vec3 position; attribute vec2 uv;
|
|
23
|
+
varying vec2 vUv; void main(){vUv=uv;gl_Position=vec4(position,1.0);}`,
|
|
24
|
+
fragmentShader:`precision highp float; uniform sampler2D frame; uniform vec2 texel; varying vec2 vUv;
|
|
25
|
+
void main(){
|
|
26
|
+
gl_FragColor=(texture2D(frame,vUv+texel*vec2(-.5,-.5))+
|
|
27
|
+
texture2D(frame,vUv+texel*vec2(.5,-.5))+
|
|
28
|
+
texture2D(frame,vUv+texel*vec2(-.5,.5))+
|
|
29
|
+
texture2D(frame,vUv+texel*vec2(.5,.5)))*.25;
|
|
30
|
+
}`,
|
|
31
|
+
});
|
|
32
|
+
quad ??= new FullScreenQuad(material);
|
|
33
|
+
material.uniforms['frame']!.value=input.texture;
|
|
34
|
+
material.uniforms['texel']!.value.set(1/input.width,1/input.height);
|
|
35
|
+
const output=new THREE.WebGLRenderTarget(width,height,{type:THREE.HalfFloatType});
|
|
36
|
+
const previous=renderer.getRenderTarget();
|
|
37
|
+
try { renderer.setRenderTarget(output); quad.render(renderer); return output; }
|
|
38
|
+
catch(error) { output.dispose(); throw error; }
|
|
39
|
+
finally { renderer.setRenderTarget(previous); }
|
|
40
|
+
}
|
package/src/capture/scene.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { acquireInspectorPreviewRenderer } from '../viewport/preview-renderer';
|
|
5
5
|
import { viewportCaptureOutputPass } from './output-pass';
|
|
6
|
+
import type { DocumentDisplayTransform } from '../render/document-display-transform';
|
|
7
|
+
import {resolveSceneLinearSize} from './linear-resolve';
|
|
6
8
|
|
|
7
9
|
export interface SceneCaptureOptions {
|
|
8
10
|
width: number;
|
|
@@ -10,6 +12,8 @@ export interface SceneCaptureOptions {
|
|
|
10
12
|
transparent?: boolean;
|
|
11
13
|
toneMapping?: THREE.ToneMapping;
|
|
12
14
|
exposure?: number;
|
|
15
|
+
/** Integration-owned GPU display resolve, shared with its live viewport. */
|
|
16
|
+
displayTransform?: DocumentDisplayTransform;
|
|
13
17
|
/** Caller-owned scene-linear effect. The input carries a depth texture;
|
|
14
18
|
* the returned target remains caller-owned and must contain half floats. */
|
|
15
19
|
effect?: {
|
|
@@ -69,6 +73,7 @@ function capture(
|
|
|
69
73
|
};
|
|
70
74
|
let hdr: THREE.WebGLRenderTarget | undefined;
|
|
71
75
|
let display: THREE.WebGLRenderTarget | undefined;
|
|
76
|
+
let displayInput: THREE.WebGLRenderTarget | undefined;
|
|
72
77
|
let completed = false;
|
|
73
78
|
try {
|
|
74
79
|
hdr = new THREE.WebGLRenderTarget(renderWidth, renderHeight, { type: THREE.HalfFloatType });
|
|
@@ -92,26 +97,24 @@ function capture(
|
|
|
92
97
|
completed = true;
|
|
93
98
|
return { pixels, width, height };
|
|
94
99
|
}
|
|
95
|
-
display = new THREE.WebGLRenderTarget(renderWidth, renderHeight);
|
|
96
|
-
|
|
97
|
-
renderer,
|
|
98
|
-
display,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
103
|
-
const pixels = new Uint8Array(renderWidth * renderHeight * 4);
|
|
104
|
-
renderer.readRenderTargetPixels(display, 0, 0, renderWidth, renderHeight, pixels);
|
|
100
|
+
display = new THREE.WebGLRenderTarget(options.displayTransform ? width : renderWidth, options.displayTransform ? height : renderHeight);
|
|
101
|
+
if (options.displayTransform) {
|
|
102
|
+
displayInput=resolveSceneLinearSize(renderer,resolved,width,height);
|
|
103
|
+
options.displayTransform.render(renderer, displayInput, display, options.transparent === true);
|
|
104
|
+
}
|
|
105
|
+
else viewportCaptureOutputPass(options.transparent === true).render(renderer, display, resolved, 0, false);
|
|
106
|
+
const pixels = new Uint8Array(display.width * display.height * 4);
|
|
107
|
+
renderer.readRenderTargetPixels(display, 0, 0, display.width, display.height, pixels);
|
|
105
108
|
const full = document.createElement('canvas');
|
|
106
|
-
full.width =
|
|
107
|
-
full.height =
|
|
109
|
+
full.width = display.width;
|
|
110
|
+
full.height = display.height;
|
|
108
111
|
const context = full.getContext('2d');
|
|
109
112
|
if (!context) throw new Error('Scene capture could not create an image canvas');
|
|
110
|
-
const image = context.createImageData(
|
|
111
|
-
const stride =
|
|
112
|
-
for (let y = 0; y <
|
|
113
|
+
const image = context.createImageData(display.width, display.height);
|
|
114
|
+
const stride = display.width * 4;
|
|
115
|
+
for (let y = 0; y < display.height; y++)
|
|
113
116
|
image.data.set(
|
|
114
|
-
pixels.subarray((
|
|
117
|
+
pixels.subarray((display.height - 1 - y) * stride, (display.height - y) * stride),
|
|
115
118
|
y * stride,
|
|
116
119
|
);
|
|
117
120
|
context.putImageData(image, 0, 0);
|
|
@@ -134,6 +137,7 @@ function capture(
|
|
|
134
137
|
renderer.shadowMap.type = previous.shadowType;
|
|
135
138
|
renderer.setClearColor(previous.clearColor, previous.clearAlpha);
|
|
136
139
|
display?.dispose();
|
|
140
|
+
displayInput?.dispose();
|
|
137
141
|
hdr?.dispose();
|
|
138
142
|
lease.release({ discard: !completed });
|
|
139
143
|
}
|
|
@@ -154,3 +158,30 @@ export function captureSceneImage(
|
|
|
154
158
|
): string {
|
|
155
159
|
return capture(scene, camera, options, false);
|
|
156
160
|
}
|
|
161
|
+
|
|
162
|
+
/** Resolve a compositor/EXR frame through the SAME GPU display pipeline as
|
|
163
|
+
* the document. The input stays scene-linear; only the returned bytes are
|
|
164
|
+
* display encoded. Pixels retain GL's bottom-up ordering. */
|
|
165
|
+
export function resolveSceneLinearDisplay(
|
|
166
|
+
frame: LinearCaptureFrame,
|
|
167
|
+
transform: DocumentDisplayTransform,
|
|
168
|
+
): Uint8Array {
|
|
169
|
+
const lease = acquireInspectorPreviewRenderer();
|
|
170
|
+
const renderer = lease.renderer;
|
|
171
|
+
const previous = renderer.getRenderTarget();
|
|
172
|
+
const input = new THREE.DataTexture(frame.pixels, frame.width, frame.height, THREE.RGBAFormat, THREE.HalfFloatType);
|
|
173
|
+
input.needsUpdate = true;
|
|
174
|
+
const output = new THREE.WebGLRenderTarget(frame.width, frame.height);
|
|
175
|
+
let completed = false;
|
|
176
|
+
try {
|
|
177
|
+
transform.render(renderer, {texture: input}, output, true);
|
|
178
|
+
const pixels = new Uint8Array(frame.width * frame.height * 4);
|
|
179
|
+
renderer.readRenderTargetPixels(output, 0, 0, frame.width, frame.height, pixels);
|
|
180
|
+
completed = true;
|
|
181
|
+
return pixels;
|
|
182
|
+
} finally {
|
|
183
|
+
renderer.setRenderTarget(previous);
|
|
184
|
+
input.dispose(); output.dispose();
|
|
185
|
+
lease.release({discard: !completed});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -3,6 +3,7 @@ import { resetViewPresentation, setViewPresentation, type ViewportXray } from '@
|
|
|
3
3
|
import { invalidateStages } from '@volter/editor-sdk/kit/stage-invalidation';
|
|
4
4
|
import type { AuthoringAdapter } from '@volter/editor-project/adapter';
|
|
5
5
|
import { viewportCaptureOutputPass } from '@volter/editor-threejs/capture/output-pass';
|
|
6
|
+
import { resolveSceneLinearSize } from '@volter/editor-threejs/capture/linear-resolve';
|
|
6
7
|
import { contentWorldBounds } from '@volter/editor-threejs/viewport/content-bounds';
|
|
7
8
|
import { setUserData } from '@volter/editor-threejs/ecs/user-data';
|
|
8
9
|
import {
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
import type { EffectComposer, EffectPass, RenderPass } from 'postprocessing';
|
|
14
15
|
import * as THREE from 'three';
|
|
15
16
|
import { DepthDrawMaterials } from '../../render/depth-draw-materials';
|
|
17
|
+
import type { DocumentDisplayTransform } from '../../render/document-display-transform';
|
|
16
18
|
import { threeObject } from '../../adapter/three-contract';
|
|
17
19
|
import { axisViewName, cameraPresetDirection, type ModelCameraPreset } from '../asset-workflow/model-inspection';
|
|
18
20
|
import { activeKeymapNavigation } from '@volter/editor-sdk/kit/keymap-presets';
|
|
@@ -127,6 +129,25 @@ export class Object3DDocumentSession {
|
|
|
127
129
|
private boneSelectionHighlight: BoneSelectionHighlight | null = null;
|
|
128
130
|
private boneSelectionSignature = '';
|
|
129
131
|
private composer: EffectComposer | null = null;
|
|
132
|
+
private displayTransform: DocumentDisplayTransform | null = null;
|
|
133
|
+
private displayTarget: THREE.WebGLRenderTarget | null = null;
|
|
134
|
+
|
|
135
|
+
/** Authoring diagnostics retain their studio display; authored materials
|
|
136
|
+
* and scene-lit views use the integration's color pipeline. */
|
|
137
|
+
setDisplayTransform(transform: DocumentDisplayTransform | null): void {
|
|
138
|
+
if (this.displayTransform === transform) return;
|
|
139
|
+
this.displayTransform = transform;
|
|
140
|
+
if (!transform) {
|
|
141
|
+
this.displayTarget?.dispose();
|
|
142
|
+
this.displayTarget = null;
|
|
143
|
+
}
|
|
144
|
+
invalidateStages();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private materialDisplayTransform(): DocumentDisplayTransform | null {
|
|
148
|
+
return this.state.mode === 'rendered' || this.state.mode === 'preview'
|
|
149
|
+
? this.displayTransform : null;
|
|
150
|
+
}
|
|
130
151
|
/** One in-flight `import('postprocessing')` at a time — {@link ensureComposer}
|
|
131
152
|
* is called from every frame. */
|
|
132
153
|
private composerLoading = false;
|
|
@@ -1406,6 +1427,24 @@ export class Object3DDocumentSession {
|
|
|
1406
1427
|
renderViewport(deltaSeconds = 0, interactive = false): void {
|
|
1407
1428
|
this.advanceLook(deltaSeconds);
|
|
1408
1429
|
this.ensureComposer();
|
|
1430
|
+
const transform = this.materialDisplayTransform();
|
|
1431
|
+
if (transform) {
|
|
1432
|
+
const size = this.renderer.getDrawingBufferSize(new THREE.Vector2());
|
|
1433
|
+
const target = this.displayTarget ??= new THREE.WebGLRenderTarget(size.x, size.y, {
|
|
1434
|
+
type: THREE.HalfFloatType,
|
|
1435
|
+
samples: 4,
|
|
1436
|
+
});
|
|
1437
|
+
target.setSize(size.x, size.y);
|
|
1438
|
+
const previous = this.renderer.getRenderTarget();
|
|
1439
|
+
try {
|
|
1440
|
+
this.renderer.setRenderTarget(target);
|
|
1441
|
+
this.render(camera => this.renderLinearScene(camera, target), interactive);
|
|
1442
|
+
transform.render(this.renderer, target, previous, false);
|
|
1443
|
+
} finally {
|
|
1444
|
+
this.renderer.setRenderTarget(previous);
|
|
1445
|
+
}
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1409
1448
|
this.render((camera) => {
|
|
1410
1449
|
const composer = this.composer;
|
|
1411
1450
|
if (!composer) {
|
|
@@ -1491,7 +1530,9 @@ export class Object3DDocumentSession {
|
|
|
1491
1530
|
const sceneTarget = new THREE.WebGLRenderTarget(renderWidth, renderHeight, {
|
|
1492
1531
|
type: THREE.HalfFloatType,
|
|
1493
1532
|
});
|
|
1494
|
-
const
|
|
1533
|
+
const transform = this.materialDisplayTransform();
|
|
1534
|
+
const target = new THREE.WebGLRenderTarget(transform ? outputWidth : renderWidth, transform ? outputHeight : renderHeight);
|
|
1535
|
+
let resolved: THREE.WebGLRenderTarget | null = null;
|
|
1495
1536
|
const previousTarget = this.renderer.getRenderTarget();
|
|
1496
1537
|
const previousPixelRatio = this.renderer.getPixelRatio();
|
|
1497
1538
|
const perspective = this.viewport.camera;
|
|
@@ -1510,25 +1551,24 @@ export class Object3DDocumentSession {
|
|
|
1510
1551
|
(camera) => this.renderSolidForCapture(camera, sceneTarget, renderWidth, renderHeight),
|
|
1511
1552
|
typeof size !== 'number' && size.transparent === true,
|
|
1512
1553
|
);
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
);
|
|
1554
|
+
const straightAlpha = typeof size !== 'number' && size.transparent === true;
|
|
1555
|
+
if (transform) {
|
|
1556
|
+
resolved = resolveSceneLinearSize(this.renderer, sceneTarget, outputWidth, outputHeight);
|
|
1557
|
+
transform.render(this.renderer, resolved, target, straightAlpha);
|
|
1558
|
+
}
|
|
1559
|
+
else viewportCaptureOutputPass(straightAlpha).render(this.renderer, target, sceneTarget, 0, false);
|
|
1520
1560
|
|
|
1521
|
-
const rowBytes =
|
|
1522
|
-
const pixels = new Uint8Array(rowBytes *
|
|
1523
|
-
this.renderer.readRenderTargetPixels(target, 0, 0,
|
|
1561
|
+
const rowBytes = target.width * 4;
|
|
1562
|
+
const pixels = new Uint8Array(rowBytes * target.height);
|
|
1563
|
+
this.renderer.readRenderTargetPixels(target, 0, 0, target.width, target.height, pixels);
|
|
1524
1564
|
const fullCanvas = document.createElement('canvas');
|
|
1525
|
-
fullCanvas.width =
|
|
1526
|
-
fullCanvas.height =
|
|
1565
|
+
fullCanvas.width = target.width;
|
|
1566
|
+
fullCanvas.height = target.height;
|
|
1527
1567
|
const fullContext = fullCanvas.getContext('2d');
|
|
1528
1568
|
if (!fullContext) return null;
|
|
1529
|
-
const imageData = fullContext.createImageData(
|
|
1530
|
-
for (let y = 0; y <
|
|
1531
|
-
const sourceRow = (
|
|
1569
|
+
const imageData = fullContext.createImageData(target.width, target.height);
|
|
1570
|
+
for (let y = 0; y < target.height; y++) {
|
|
1571
|
+
const sourceRow = (target.height - 1 - y) * rowBytes;
|
|
1532
1572
|
const destinationRow = y * rowBytes;
|
|
1533
1573
|
imageData.data.set(pixels.subarray(sourceRow, sourceRow + rowBytes), destinationRow);
|
|
1534
1574
|
}
|
|
@@ -1555,6 +1595,7 @@ export class Object3DDocumentSession {
|
|
|
1555
1595
|
// selection and 4 after a repaint that resized.
|
|
1556
1596
|
this.composer?.setSize(this.renderWidth, this.renderHeight, false);
|
|
1557
1597
|
target.dispose();
|
|
1598
|
+
resolved?.dispose();
|
|
1558
1599
|
sceneTarget.dispose();
|
|
1559
1600
|
// AND THE VIEW IS DRAWN AGAIN. Sizing the composer clears what the view showed, and a stage
|
|
1560
1601
|
// that draws on demand had nothing asking it to: on the browser-substrate page every photograph
|
|
@@ -1583,6 +1624,9 @@ export class Object3DDocumentSession {
|
|
|
1583
1624
|
if (this.selectionOutline)
|
|
1584
1625
|
releaseThreeSelectionOutline(this.renderer, this.selectionOutline, this.selectionOutlinePass);
|
|
1585
1626
|
this.composer?.dispose();
|
|
1627
|
+
this.displayTarget?.dispose();
|
|
1628
|
+
this.displayTarget = null;
|
|
1629
|
+
this.displayTransform = null;
|
|
1586
1630
|
this.sceneRenderPass = null;
|
|
1587
1631
|
this.selectionOutline = null;
|
|
1588
1632
|
this.selectionOutlinePass = null;
|
|
@@ -1673,7 +1717,9 @@ export class Object3DDocumentSession {
|
|
|
1673
1717
|
this.composerLoading = false;
|
|
1674
1718
|
if (this.disposed || this.composer) return;
|
|
1675
1719
|
const camera = this.camera();
|
|
1676
|
-
const composer = new postprocessing.EffectComposer(this.renderer
|
|
1720
|
+
const composer = new postprocessing.EffectComposer(this.renderer, {
|
|
1721
|
+
frameBufferType: THREE.HalfFloatType,
|
|
1722
|
+
});
|
|
1677
1723
|
this.sceneRenderPass = new postprocessing.RenderPass(this.scene, camera);
|
|
1678
1724
|
composer.addPass(this.sceneRenderPass);
|
|
1679
1725
|
this.selectionOutline = acquireThreeSelectionOutline(
|
|
@@ -1739,14 +1785,32 @@ export class Object3DDocumentSession {
|
|
|
1739
1785
|
}
|
|
1740
1786
|
const previousWidth = this.renderWidth;
|
|
1741
1787
|
const previousHeight = this.renderHeight;
|
|
1788
|
+
try {
|
|
1789
|
+
composer.setSize(width, height, false);
|
|
1790
|
+
this.renderLinearScene(camera, target);
|
|
1791
|
+
} finally {
|
|
1792
|
+
composer.setSize(previousWidth, previousHeight, false);
|
|
1793
|
+
this.syncComposerOutput();
|
|
1794
|
+
this.renderer.setRenderTarget(target);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
/** Live HDR draw at the renderer's CURRENT size. Resizing the composer with
|
|
1799
|
+
* device pixels here would resize the canvas and multiply HiDPI twice on
|
|
1800
|
+
* every frame. Only an offscreen photograph changes its sizing. */
|
|
1801
|
+
private renderLinearScene(camera: THREE.Camera, target: THREE.WebGLRenderTarget): void {
|
|
1802
|
+
const composer = this.composer;
|
|
1803
|
+
const outlined = (this.selectionOutline?.selection.size ?? 0) > 0;
|
|
1804
|
+
if (!composer || !outlined || !this.sceneRenderPass || !this.selectionOutlinePass) {
|
|
1805
|
+
this.renderer.render(this.scene, camera);
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1742
1808
|
try {
|
|
1743
1809
|
this.sceneRenderPass.renderToScreen = false;
|
|
1744
1810
|
this.selectionOutlinePass.renderToScreen = false;
|
|
1745
|
-
composer.setSize(width, height, false);
|
|
1746
1811
|
this.sceneRenderPass.render(this.renderer, composer.inputBuffer, composer.outputBuffer, 0);
|
|
1747
1812
|
this.selectionOutlinePass.render(this.renderer, composer.inputBuffer, target, 0);
|
|
1748
1813
|
} finally {
|
|
1749
|
-
composer.setSize(previousWidth, previousHeight, false);
|
|
1750
1814
|
this.syncComposerOutput();
|
|
1751
1815
|
this.renderer.setRenderTarget(target);
|
|
1752
1816
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
/** An integration's scene-linear to display resolve. The stage owns its HDR
|
|
4
|
+
* input; the integration owns the transform and its GPU resources. Output is
|
|
5
|
+
* already display encoded, with associated alpha for the canvas and straight
|
|
6
|
+
* alpha for a transparent photograph. No readback belongs on this path. */
|
|
7
|
+
export interface DocumentDisplayTransform {
|
|
8
|
+
render(
|
|
9
|
+
renderer: THREE.WebGLRenderer,
|
|
10
|
+
input: Pick<THREE.WebGLRenderTarget, 'texture'>,
|
|
11
|
+
output: THREE.WebGLRenderTarget | null,
|
|
12
|
+
straightAlpha: boolean,
|
|
13
|
+
): void;
|
|
14
|
+
}
|