-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathrender.js
More file actions
115 lines (102 loc) · 4.88 KB
/
Copy pathrender.js
File metadata and controls
115 lines (102 loc) · 4.88 KB
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
// © 2026 Epic Games, Inc. RealityScan® is a trademark of Epic Games, Inc.
// This tool is provided under the RealityScan End User License Agreement:
// https://www.realityscan.com/eula
// WebGL2 renderer: loads an equirect image to a texture once, then renders
// each face by changing yaw/pitch/fov uniforms.
class FaceRenderer {
constructor() {
this.canvas = document.createElement("canvas");
const gl = this.canvas.getContext("webgl2", { preserveDrawingBuffer: false });
if (!gl) throw new Error("WebGL2 is not supported in this browser. Try Chrome, Edge, Firefox, or Safari (latest).");
this.gl = gl;
this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
this.program = this._buildProgram(VERT_SHADER, FRAG_SHADER);
this.uYaw = gl.getUniformLocation(this.program, "u_yaw");
this.uPitch = gl.getUniformLocation(this.program, "u_pitch");
this.uTanHalf = gl.getUniformLocation(this.program, "u_tanHalfFov");
this.uEquirect = gl.getUniformLocation(this.program, "u_equirect");
// Fullscreen quad
this.vao = gl.createVertexArray();
gl.bindVertexArray(this.vao);
const quad = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]);
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
const aPos = gl.getAttribLocation(this.program, "a_pos");
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
gl.bindVertexArray(null);
this.texture = gl.createTexture();
}
_buildProgram(vertSrc, fragSrc) {
const gl = this.gl;
const compile = (type, src) => {
const sh = gl.createShader(type);
gl.shaderSource(sh, src);
gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(sh);
throw new Error("Shader compile failed:\n" + log);
}
return sh;
};
const vs = compile(gl.VERTEX_SHADER, vertSrc);
const fs = compile(gl.FRAGMENT_SHADER, fragSrc);
const prog = gl.createProgram();
gl.attachShader(prog, vs);
gl.attachShader(prog, fs);
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
throw new Error("Program link failed:\n" + gl.getProgramInfoLog(prog));
}
return prog;
}
// Upload an equirect source to the GPU. Accepts anything texImage2D takes:
// ImageBitmap, HTMLImageElement, HTMLCanvasElement, HTMLVideoElement.
// For video, the currently presented frame is captured.
setEquirect(source) {
const gl = this.gl;
const w = source.videoWidth ?? source.naturalWidth ?? source.width;
const h = source.videoHeight ?? source.naturalHeight ?? source.height;
if (w > this.maxTextureSize || h > this.maxTextureSize) {
throw new Error(
`Source (${w}x${h}) exceeds this device's max texture size (${this.maxTextureSize}). ` +
`Try a smaller image/video or use a desktop browser.`
);
}
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // longitude wraps
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); // poles clamp
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
this.equirectWidth = w;
this.equirectHeight = h;
}
// Render one face. Returns a Blob (JPEG) of the rendered face.
async renderFace(face, fovDeg, outSize, jpegQuality = 0.92) {
const gl = this.gl;
this.canvas.width = outSize;
this.canvas.height = outSize;
gl.viewport(0, 0, outSize, outSize);
gl.useProgram(this.program);
gl.bindVertexArray(this.vao);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.uniform1i(this.uEquirect, 0);
gl.uniform1f(this.uYaw, face.yaw * Math.PI / 180);
gl.uniform1f(this.uPitch, face.pitch * Math.PI / 180);
gl.uniform1f(this.uTanHalf, Math.tan(fovDeg * Math.PI / 360)); // tan(fov/2 in rad)
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// Read back as JPEG via canvas.toBlob
return new Promise((resolve, reject) => {
this.canvas.toBlob(
blob => blob ? resolve(blob) : reject(new Error("canvas.toBlob returned null")),
"image/jpeg",
jpegQuality
);
});
}
}