forked from brianzinn/create-react-app-babylonjs
-
Notifications
You must be signed in to change notification settings - Fork 3
/
WebglCubeMaps.js
445 lines (373 loc) · 13.4 KB
/
WebglCubeMaps.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, TouchableOpacity, View} from 'react-native';
import {GCanvasView} from '@flyskywhy/react-native-gcanvas';
import * as webglUtils from 'webgl-utils.js';
import m4 from 'm4.js';
var isLooping = false;
var gl;
// ref to https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-cube-maps.html
var vertexShaderSource = `
attribute vec4 a_position;
attribute vec2 a_texcoord;
uniform mat4 u_matrix;
varying vec3 v_normal;
void main() {
// Multiply the position by the matrix.
gl_Position = u_matrix * a_position;
// Pass a normal. Since the positions
// centered around the origin we can just
// pass the position
v_normal = normalize(a_position.xyz);
}
`;
var fragmentShaderSource = `
precision mediump float;
// Passed in from the vertex shader.
varying vec3 v_normal;
// The texture.
uniform samplerCube u_texture;
void main() {
gl_FragColor = textureCube(u_texture, normalize(v_normal));
}
`;
function main() {
// Get A WebGL context
/** @type {HTMLCanvasElement} */
// var canvas = document.querySelector("#canvas");
// var gl = canvas.getContext("webgl");
// if (!gl) {
// return;
// }
// setup GLSL program
var program = webglUtils.createProgramFromSources(gl, [vertexShaderSource, fragmentShaderSource]);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
// lookup uniforms
var matrixLocation = gl.getUniformLocation(program, "u_matrix");
var textureLocation = gl.getUniformLocation(program, "u_texture");
// Create a buffer for positions
var positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Put the positions in the buffer
setGeometry(gl);
// Create a texture.
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
// Get A 2D context
/** @type {Canvas2DRenderingContext} */
const ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 128;
ctx.canvas.height = 128;
const faceInfos = [
{ target: gl.TEXTURE_CUBE_MAP_POSITIVE_X, faceColor: '#F00', textColor: '#0FF', text: '+X' },
{ target: gl.TEXTURE_CUBE_MAP_NEGATIVE_X, faceColor: '#FF0', textColor: '#00F', text: '-X' },
{ target: gl.TEXTURE_CUBE_MAP_POSITIVE_Y, faceColor: '#0F0', textColor: '#F0F', text: '+Y' },
{ target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, faceColor: '#0FF', textColor: '#F00', text: '-Y' },
{ target: gl.TEXTURE_CUBE_MAP_POSITIVE_Z, faceColor: '#00F', textColor: '#FF0', text: '+Z' },
{ target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, faceColor: '#F0F', textColor: '#0F0', text: '-Z' },
];
faceInfos.forEach((faceInfo) => {
const {target, faceColor, textColor, text} = faceInfo;
generateFace(ctx, faceColor, textColor, text);
// Upload the canvas to the cubemap face.
const level = 0;
const internalFormat = gl.RGBA;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
gl.texImage2D(target, level, internalFormat, format, type, ctx.canvas);
});
gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
function radToDeg(r) {
return r * 180 / Math.PI;
}
function degToRad(d) {
return d * Math.PI / 180;
}
var fieldOfViewRadians = degToRad(60);
var modelXRotationRadians = degToRad(0);
var modelYRotationRadians = degToRad(0);
// Get the starting time.
var then = 0;
requestAnimationFrame(drawScene);
// Draw the scene.
function drawScene(time) {
// convert to seconds
time *= 0.001;
// Subtract the previous time from the current time
var deltaTime = time - then;
// Remember the current time for the next frame.
then = time;
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
// Animate the rotation
modelYRotationRadians += -0.7 * deltaTime;
modelXRotationRadians += -0.4 * deltaTime;
// Clear the canvas AND the depth buffer.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the position attribute
gl.enableVertexAttribArray(positionLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 3; // 3 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionLocation, size, type, normalize, stride, offset);
// Compute the projection matrix
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var projectionMatrix =
m4.perspective(fieldOfViewRadians, aspect, 1, 2000);
var cameraPosition = [0, 0, 2];
var up = [0, 1, 0];
var target = [0, 0, 0];
// Compute the camera's matrix using look at.
var cameraMatrix = m4.lookAt(cameraPosition, target, up);
// Make a view matrix from the camera matrix.
var viewMatrix = m4.inverse(cameraMatrix);
var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
var matrix = m4.xRotate(viewProjectionMatrix, modelXRotationRadians);
matrix = m4.yRotate(matrix, modelYRotationRadians);
// Set the matrix.
gl.uniformMatrix4fv(matrixLocation, false, matrix);
// Tell the shader to use texture unit 0 for u_texture
gl.uniform1i(textureLocation, 0);
// Draw the geometry.
gl.drawArrays(gl.TRIANGLES, 0, 6 * 6);
if (!isLooping) {
return;
}
requestAnimationFrame(drawScene);
}
}
function generateFace(ctx, faceColor, textColor, text) {
const {width, height} = ctx.canvas;
ctx.fillStyle = faceColor;
ctx.fillRect(0, 0, width, height);
ctx.font = `${width * 0.7}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = textColor;
ctx.fillText(text, width / 2, height / 2);
}
// Fill the buffer with the values that define a cube.
function setGeometry(gl) {
var positions = new Float32Array(
[
-0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
-0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
-0.5, 0.5, -0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, -0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
0.5, 0.5, -0.5,
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, -0.5, 0.5,
-0.5, -0.5, 0.5,
0.5, -0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, -0.5,
-0.5, -0.5, 0.5,
-0.5, 0.5, -0.5,
-0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
-0.5, 0.5, -0.5,
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
0.5, 0.5, -0.5,
0.5, 0.5, 0.5,
]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
}
export default class WebglCubeMaps extends Component {
constructor(props) {
super(props);
this.canvas = null;
this.state = {
debugInfo: 'Click me to start or stop drawing',
hasOc1: false,
};
// only useful on Android, because it's always true on iOS
this.isGReactTextureViewReady = true;
}
componentDidMount() {
if (Platform.OS === 'web') {
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
if (entry.target.id === 'canvasExample') {
let {width, height} = entry.contentRect;
this.onCanvasResize({width, height, canvas: entry.target});
}
}
});
resizeObserver.observe(document.getElementById('canvasExample'));
}
}
componentWillUnmount() {
isLooping = false;
}
initCanvas = (canvas) => {
if (this.canvas) {
return;
}
this.canvas = canvas;
if (Platform.OS === 'web') {
// canvas.width not equal canvas.clientWidth but "Defaults to 300" ref
// to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas,
// so have to assign again, unless <canvas width=SOME_NUMBER/> in render()
this.canvas.width = this.canvas.clientWidth;
this.canvas.height = this.canvas.clientHeight;
}
gl = this.canvas.getContext('webgl');
};
onCanvasResize = ({width, height, canvas}) => {
canvas.width = width;
canvas.height = height;
// if isResetGlViewportAfterSetWidthOrHeight is true, you can use below
// this.drawSome();
// if (!isLooping) {
// setTimeout(this.drawSome, 32);
// }
};
drawSome = async () => {
if (isLooping) {
isLooping = false;
return;
}
isLooping = true;
// On Android, sometimes this.isGReactTextureViewReady is false e.g.
// navigate from a canvas page into a drawer item page with
// react-navigation on Android, the canvas page will be maintain
// mounted by react-navigation, then if you continually call
// this drawSome() in some loop, it's wasting CPU and GPU,
// if you don't care about such wasting, you can delete
// this.isGReactTextureViewReady and related onIsReady.
if (gl && this.isGReactTextureViewReady) {
main();
}
};
takePicture = () => {
if (this.canvas) {
const data = this.canvas.toDataURL();
console.warn(data);
}
};
render() {
return (
<View style={styles.container}>
{Platform.OS !== 'web' && (
<GCanvasView
style={{
width: 1000, // 1000 should enough for offscreen canvas usage
height: 1000,
position: 'absolute',
left: 1000, // 1000 should enough to not display on screen means offscreen canvas :P
top: 0,
zIndex: -100, // -100 should enough to not bother onscreen canvas
}}
offscreenCanvas={true}
onCanvasCreate={(canvas) => {
this.setState({hasOc1: true});
}}
devicePixelRatio={1} // should not 1 < devicePixelRatio < 2 as float to avoid pixel offset flaw when GetImageData with PixelsSampler in @flyskywhy/react-native-gcanvas/core/src/support/GLUtil.cpp
isGestureResponsible={false}
/>
)}
<TouchableOpacity onPress={this.drawSome}>
<Text style={styles.welcome}>{this.state.debugInfo}</Text>
</TouchableOpacity>
{Platform.OS === 'web' ? (
<canvas
id={'canvasExample'}
ref={this.initCanvas}
style={
{
flex: 1,
width: '100%',
//
// width: 200,
// height: 300,
} /* canvas with react-native-web can't use width and height in styles.gcanvas */
}
/>
) : (this.state.hasOc1 && (
<GCanvasView
onCanvasResize={this.onCanvasResize}
onCanvasCreate={this.initCanvas}
onIsReady={(value) => (this.isGReactTextureViewReady = value)}
isGestureResponsible={
true /* Here is just for example, you can remove this line because default is true */
}
isAutoClearRectBeforePutImageData={
false /* default is false, only for canvas 2d, if you want to be exactly compatible with Web, you can set it to true */
}
isResetGlViewportAfterSetWidthOrHeight={
false /* default is true, generally true for canvas 2d and false for webgl 3d */
}
isEnableFboMsaa={
false /* default is false, only for Android, only for canvas 2d, if you want anti-aliasing present same behavior on Android and Web, you can set it to true */
}
devicePixelRatio={
undefined /* Here is just for example, you can remove this line because default is undefined and means default is PixelRatio.get(), ref to "About devicePixelRatio" below */
}
style={styles.gcanvas}
/>
))}
<TouchableOpacity onPress={this.takePicture}>
<Text style={styles.welcome}>Click me toDataURL()</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
gcanvas: {
flex: 1,
width: '100%',
// above maybe will cause
// ERROR *** Error compiling shader '[object Object]':
// ERROR *** Error compiling shader '[object Object]':
// WARN Possible Unhandled Promise Rejection (id: 0):
// TypeError: null is not an object (evaluating 'shader.id')
// if let this component as a children of another component,
// you can use below
// width: 200,
// height: 300,
// backgroundColor: '#FF000030', // TextureView doesn't support displaying a background drawable since Android API 24
},
welcome: {
color: 'black',
fontSize: 20,
textAlign: 'center',
marginVertical: 20,
},
});