22import { registerComponent } from '../../core/component.js' ;
33import * as THREE from 'three' ;
44
5+ // WebGLRenderer uses WebGLCubeRenderTarget, WebGPURenderer uses CubeRenderTarget.
6+ // Written so that Webpack can't statically determine the export used;
7+ // only one of the two exists depending on the three.js build.
8+ var cubeRenderTargetImpl = [ 'WebGLCubeRenderTarget' , 'CubeRenderTarget' ] . find ( function ( x ) { return THREE [ x ] ; } ) ;
9+
510var VERTEX_SHADER = [
611 'attribute vec3 position;' ,
712 'attribute vec2 uv;' ,
@@ -57,13 +62,18 @@ export var Component = registerComponent('screenshot', {
5762 if ( this . canvas ) { return ; }
5863 var gl = el . renderer . getContext ( ) ;
5964 if ( ! gl ) { return ; }
60- this . cubeMapSize = gl . getParameter ( gl . MAX_CUBE_MAP_TEXTURE_SIZE ) ;
61- this . material = new THREE . RawShaderMaterial ( {
62- uniforms : { map : { type : 't' , value : null } } ,
63- vertexShader : VERTEX_SHADER ,
64- fragmentShader : FRAGMENT_SHADER ,
65- side : THREE . DoubleSide
66- } ) ;
65+ this . cubeMapSize = gl . getParameter ? gl . getParameter ( gl . MAX_CUBE_MAP_TEXTURE_SIZE ) : 2048 ;
66+ // WebGPURenderer does not support RawShaderMaterial, use a node material with TSL.
67+ if ( THREE . TSL ) {
68+ this . material = this . createNodeMaterial ( ) ;
69+ } else {
70+ this . material = new THREE . RawShaderMaterial ( {
71+ uniforms : { map : { type : 't' , value : null } } ,
72+ vertexShader : VERTEX_SHADER ,
73+ fragmentShader : FRAGMENT_SHADER ,
74+ side : THREE . DoubleSide
75+ } ) ;
76+ }
6777 this . quad = new THREE . Mesh (
6878 new THREE . PlaneGeometry ( 1 , 1 ) ,
6979 this . material
@@ -76,6 +86,26 @@ export var Component = registerComponent('screenshot', {
7686 this . onKeyDown = this . onKeyDown . bind ( this ) ;
7787 } ,
7888
89+ /**
90+ * TSL (node material) equivalent of the raw GLSL shaders above, used with
91+ * WebGPURenderer which does not support RawShaderMaterial.
92+ */
93+ createNodeMaterial : function ( ) {
94+ var TSL = THREE . TSL ;
95+ var material = new THREE . MeshBasicNodeMaterial ( { side : THREE . DoubleSide } ) ;
96+ var uv = TSL . uv ( ) ;
97+ var longitude = TSL . float ( 1 ) . sub ( uv . x ) . mul ( 2 * Math . PI ) . sub ( Math . PI ) . add ( Math . PI / 2 ) ;
98+ var latitude = uv . y . mul ( Math . PI ) ;
99+ var dir = TSL . vec3 (
100+ TSL . sin ( longitude ) . mul ( TSL . sin ( latitude ) ) . negate ( ) ,
101+ TSL . cos ( latitude ) ,
102+ TSL . cos ( longitude ) . mul ( TSL . sin ( latitude ) ) . negate ( )
103+ ) ;
104+ this . cubeTextureNode = TSL . cubeTexture ( new THREE . CubeTexture ( ) , dir ) ;
105+ material . colorNode = TSL . vec4 ( this . cubeTextureNode . rgb , 1 ) ;
106+ return material ;
107+ } ,
108+
79109 getRenderTarget : function ( width , height ) {
80110 return new THREE . WebGLRenderTarget ( width , height , {
81111 colorSpace : this . el . sceneEl . renderer . outputColorSpace ,
@@ -140,10 +170,10 @@ export var Component = registerComponent('screenshot', {
140170 } else {
141171 // Use ortho camera.
142172 camera = this . camera ;
143- cubeRenderTarget = new THREE . WebGLCubeRenderTarget (
173+ cubeRenderTarget = new THREE [ cubeRenderTargetImpl ] (
144174 Math . min ( this . cubeMapSize , 2048 ) ,
145175 {
146- format : THREE . RGBFormat ,
176+ format : THREE . RGBAFormat ,
147177 generateMipmaps : true ,
148178 minFilter : THREE . LinearMipmapLinearFilter ,
149179 colorSpace : THREE . SRGBColorSpace
@@ -155,7 +185,11 @@ export var Component = registerComponent('screenshot', {
155185 el . camera . getWorldQuaternion ( cubeCamera . quaternion ) ;
156186 // Render scene with cube camera.
157187 cubeCamera . update ( el . renderer , el . object3D ) ;
158- this . quad . material . uniforms . map . value = cubeCamera . renderTarget . texture ;
188+ if ( this . cubeTextureNode ) {
189+ this . cubeTextureNode . value = cubeCamera . renderTarget . texture ;
190+ } else {
191+ this . quad . material . uniforms . map . value = cubeCamera . renderTarget . texture ;
192+ }
159193 size = { width : this . data . width , height : this . data . height } ;
160194 // Use quad to project image taken by the cube camera.
161195 this . quad . visible = true ;
@@ -174,19 +208,25 @@ export var Component = registerComponent('screenshot', {
174208 var isVREnabled = this . el . renderer . xr . enabled ;
175209 var renderer = this . el . renderer ;
176210 var params ;
211+ var self = this ;
177212 this . setup ( ) ;
178213 // Disable VR.
179214 renderer . xr . enabled = false ;
180215 params = this . setCapture ( projection ) ;
181- this . renderCapture ( params . camera , params . size , params . projection ) ;
182- // Trigger file download.
183- this . saveCapture ( ) ;
216+ this . renderCapture ( params . camera , params . size , params . projection )
217+ . then ( function ( ) {
218+ // Trigger file download.
219+ self . saveCapture ( ) ;
220+ } ) ;
184221 // Restore VR.
185222 renderer . xr . enabled = isVREnabled ;
186223 } ,
187224
188225 /**
189226 * Return canvas instead of triggering download (e.g., for uploading blob to server).
227+ * With WebGLRenderer the canvas is returned synchronously.
228+ * With WebGPURenderer pixels are read back asynchronously, so a Promise
229+ * resolving to the canvas is returned instead.
190230 */
191231 getCanvas : function ( projection ) {
192232 var isVREnabled = this . el . renderer . xr . enabled ;
@@ -195,22 +235,22 @@ export var Component = registerComponent('screenshot', {
195235 // Disable VR.
196236 var params = this . setCapture ( projection ) ;
197237 renderer . xr . enabled = false ;
198- this . renderCapture ( params . camera , params . size , params . projection ) ;
238+ var promise = this . renderCapture ( params . camera , params . size , params . projection ) ;
199239 // Restore VR.
200240 renderer . xr . enabled = isVREnabled ;
201- return this . canvas ;
241+ if ( renderer . readRenderTargetPixels ) { return this . canvas ; }
242+ return promise ;
202243 } ,
203244
204245 renderCapture : function ( camera , size , projection ) {
205246 var autoClear = this . el . renderer . autoClear ;
206247 var el = this . el ;
207- var imageData ;
208248 var output ;
209249 var pixels ;
210250 var renderer = el . renderer ;
251+ var self = this ;
211252 // Create rendering target and buffer to store the read pixels.
212253 output = this . getRenderTarget ( size . width , size . height ) ;
213- pixels = new Uint8Array ( 4 * size . width * size . height ) ;
214254 // Resize quad, camera, and canvas.
215255 this . resize ( size . width , size . height ) ;
216256 // Render scene to render target.
@@ -220,8 +260,24 @@ export var Component = registerComponent('screenshot', {
220260 renderer . render ( el . object3D , camera ) ;
221261 renderer . autoClear = autoClear ;
222262 // Read image pixels back.
223- renderer . readRenderTargetPixels ( output , 0 , 0 , size . width , size . height , pixels ) ;
224- renderer . setRenderTarget ( null ) ;
263+ if ( renderer . readRenderTargetPixels ) {
264+ pixels = new Uint8Array ( 4 * size . width * size . height ) ;
265+ renderer . readRenderTargetPixels ( output , 0 , 0 , size . width , size . height , pixels ) ;
266+ renderer . setRenderTarget ( null ) ;
267+ this . copyCapture ( pixels , size , projection ) ;
268+ return Promise . resolve ( this . canvas ) ;
269+ }
270+ // WebGPURenderer only supports reading pixels back asynchronously.
271+ return renderer . readRenderTargetPixelsAsync ( output , 0 , 0 , size . width , size . height )
272+ . then ( function ( pixels ) {
273+ renderer . setRenderTarget ( null ) ;
274+ self . copyCapture ( pixels , size , projection ) ;
275+ return self . canvas ;
276+ } ) ;
277+ } ,
278+
279+ copyCapture : function ( pixels , size , projection ) {
280+ var imageData ;
225281 if ( projection === 'perspective' ) {
226282 pixels = this . flipPixelsVertically ( pixels , size . width , size . height ) ;
227283 }
0 commit comments