export var vs_cubemap =
`#version 300 es
precision mediump float;
layout (location = 0) in vec3 aPos;
out vec3 WorldPos;
uniform mat4 projection;
uniform mat4 view;
void main()
{
WorldPos = aPos;
gl_Position = projection * view * vec4(WorldPos, 1.0);
}`
export var fs_equirectangularToCubemap =
`#version 300 es
precision mediump float;
out vec4 FragColor;
//out uvec4 uFragColor;
in vec3 WorldPos;
uniform sampler2D equirectangularMap;
//atan(n.z, n.x) UV U
//asin(n.y) UV V
//atan [ , ] asin [/2,/2]
//[0,1]
//conversion from (-pi, pi)=>(-1/2, 1/2) and (-pi/2, pi/2)=>(-1/2, 1/2)
const vec2 invAtan = vec2(0.1591, 0.3183);
vec2 SampleSphericalMap(vec3 v)
{
vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
uv *= invAtan;
uv += 0.5;
return uv;
}
void main()
{
vec2 uv = SampleSphericalMap(normalize(WorldPos));
vec3 color = texture(equirectangularMap, uv).rgb;
FragColor = vec4(color, 1.0);
//uFragColor = uvec4(100, 0, 0, 1);
}`
export var vs_cubeface =
`#version 300 es
precision mediump float;
layout (location = 0) in vec3 aPos;
//uniform mat4 projection;
//uniform mat4 view;
uniform float index;
uniform float aspectRatio;
out vec3 WorldPos;
void main()
{
WorldPos = aPos;
float f = 0.24;
vec3 clipPos = f*WorldPos;
clipPos.x=-(-1.0+f+clipPos.x+index*(2.0*f+0.01));
clipPos.y=clipPos.y*aspectRatio;
gl_Position = vec4(clipPos, 1.0);
}`
export var fs_cubeface =
`#version 300 es
precision mediump float;
out vec4 FragColor;
in vec3 WorldPos;
uniform samplerCube environmentMap;
uniform bool showIntensity;
uniform int id;
void main()
{
//I will use the openGL id's for the different sides of the cube
//Lefthanded, positive Z is backside cube!
//0 GL_TEXTURE_CUBE_MAP_POSITIVE_X =right
//1 GL_TEXTURE_CUBE_MAP_NEGATIVE_X =left
//2 GL_TEXTURE_CUBE_MAP_POSITIVE_Y =top
//3 GL_TEXTURE_CUBE_MAP_NEGATIVE_Y =bottom
//4 GL_TEXTURE_CUBE_MAP_POSITIVE_Z =back!
//5 GL_TEXTURE_CUBE_MAP_NEGATIVE_Z =front!
//if (showLeftRight) [1, 5, 0, 4] else [2, 4, 3]
vec3 cubePos = WorldPos;//if id = 5 (front, negatiev_z) no changes.
if (id==1) {cubePos.x = -WorldPos.z; cubePos.z = WorldPos.x; }//left
if (id==4) {cubePos.x = -WorldPos.x; cubePos.z =-WorldPos.z;}//back
if (id==0) {cubePos.x = WorldPos.z; cubePos.z =-WorldPos.x;}//right
if (id==5) {cubePos.x = WorldPos.x; cubePos.z =WorldPos.z;}//front
if (id==2) {cubePos.z = WorldPos.y; cubePos.y=WorldPos.z;} //bottom
if (id==3) {cubePos.z = -WorldPos.y; cubePos.y=-WorldPos.z;}//top
vec3 envColor = texture(environmentMap, cubePos).rgb;
if (showIntensity) {
if (envColor.r>1.0 || envColor.g>1.0 || envColor.b>1.0) {
FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
else {
FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
return;
}
//HDR tonemap and gamma correct
envColor = envColor/(envColor + vec3(1.0));
envColor = pow(envColor, vec3(1.0/2.2));
FragColor = vec4(envColor, 1.0);
}`
let captureViews = [
mat4.lookAt(mat4.create(), vec3.fromValues(0.0, 0.0, 0.0), vec3.fromValues(1.0, 0.0, 0.0), vec3.fromValues(0.0, -1.0, 0.0)),
mat4.lookAt(mat4.create(), vec3.fromValues(0.0, 0.0, 0.0), vec3.fromValues(-1.0, 0.0, 0.0), vec3.fromValues(0.0, -1.0, 0.0)),
mat4.lookAt(mat4.create(), vec3.fromValues(0.0, 0.0, 0.0), vec3.fromValues(0.0, 1.0, 0.0), vec3.fromValues(0.0, 0.0, 1.0)),
mat4.lookAt(mat4.create(), vec3.fromValues(0.0, 0.0, 0.0), vec3.fromValues(0.0, -1.0, 0.0), vec3.fromValues(0.0, 0.0, -1.0)),
mat4.lookAt(mat4.create(), vec3.fromValues(0.0, 0.0, 0.0), vec3.fromValues(0.0, 0.0, 1.0), vec3.fromValues(0.0, -1.0, 0.0)),
mat4.lookAt(mat4.create(), vec3.fromValues(0.0, 0.0, 0.0), vec3.fromValues(0.0, 0.0, -1.0), vec3.fromValues(0.0, -1.0, 0.0))
];
let hdrTexture;
if (data) {
let floats = rgbeToFloat(data);
hdrTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, hdrTexture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB16F, width, height, 0, gl.RGB, gl.FLOAT, floats);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
}
else {
console.log("Failed to load HDR image.");
}
function rgbeToFloat(buffer) {
var s, l = buffer.byteLength >> 2, res = res || new Float32Array(l * 3);
for (var i = 0; i < l; i++) {
s = Math.pow(2, buffer[i * 4 + 3] - (128 + 8));
res[i * 3] = buffer[i * 4] * s;
res[i * 3 + 1] = buffer[i * 4 + 1] * s;
res[i * 3 + 2] = buffer[i * 4 + 2] * s;
}
return res;
}
function m(a, b) { for (var i in b)
a[i] = b[i]; return a; }
;
function loadHDR(url, completion) {
var req = m(new XMLHttpRequest(), { responseType: "arraybuffer" });
req.onerror = completion.bind(req, false);
req.onload = function () {
if (this.status >= 400)
return this.onerror();
var header = '', pos = 0, d8 = new Uint8Array(this.response), format;
while (!header.match(/\n\n[^\n]+\n/g))
header += String.fromCharCode(d8[pos++]);
format = header.match(/FORMAT=(.*)$/m)[1];
if (format != '32-bit_rle_rgbe')
return console.warn('unknown format : ' + format), this.onerror();
var rez = header.split(/\n/).reverse()[1].split(' '), width = +rez[3] * 1, height = +rez[1] * 1;
var img = new Uint8Array(width * height * 4), ipos = 0;
for (var j = 0; j < height; j++) {
var rgbe = d8.slice(pos, pos += 4), scanline = [];
if (rgbe[0] != 2 || (rgbe[1] != 2) || (rgbe[2] & 0x80)) {
var len = width, rs = 0;
pos -= 4;
while (len > 0) {
img.set(d8.slice(pos, pos += 4), ipos);
if (img[ipos] == 1 && img[ipos + 1] == 1 && img[ipos + 2] == 1) {
for (img[ipos + 3] << rs; i > 0; i--) {
img.set(img.slice(ipos - 4, ipos), ipos);
ipos += 4;
len--;
}
rs += 8;
}
else {
len--;
ipos += 4;
rs = 0;
}
}
}
else {
if ((rgbe[2] << 8) + rgbe[3] != width)
return console.warn('HDR line mismatch ..'), this.onerror();
for (var i = 0; i < 4; i++) {
var ptr = i * width, ptr_end = (i + 1) * width, buf, count;
while (ptr < ptr_end) {
buf = d8.slice(pos, pos += 2);
if (buf[0] > 128) {
count = buf[0] - 128;
while (count-- > 0)
scanline[ptr++] = buf[1];
}
else {
count = buf[0] - 1;
scanline[ptr++] = buf[1];
while (count-- > 0)
scanline[ptr++] = d8[pos++];
}
}
}
for (var i = 0; i < width; i++) {
img[ipos++] = scanline[i];
img[ipos++] = scanline[i + width];
img[ipos++] = scanline[i + 2 * width];
img[ipos++] = scanline[i + 3 * width];
}
}
}
completion && completion(img, width, height);
};
req.open("GET", url, true);
req.send(null);
return req;
}
function toCubemap(data, width, height) {
let captureFBO = gl.createFramebuffer();
let captureRBO = gl.createRenderbuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, captureFBO);
gl.bindRenderbuffer(gl.RENDERBUFFER, captureRBO);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT24, whCube, whCube);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, captureRBO);
envCubemap = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, envCubemap);
for (let i = 0; i < 6; ++i) {
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA16F, whCube, whCube, 0, gl.RGBA, gl.FLOAT, new Float32Array(whCube * whCube * 4));
}
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
let captureProjection = mat4.create();
mat4.perspective(captureProjection, (90.0) * Math.PI/180, 1.0, 0.1, 10.0);
equirectangularToCubemapShader.use(gl);
equirectangularToCubemapShader.setInt(gl, "equirectangularMap", 0);
gl.uniformMatrix4fv(gl.getUniformLocation(equirectangularToCubemapShader.programId, "projection"), false, captureProjection);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, hdrTexture);
gl.viewport(0, 0, whCube, whCube);
gl.bindFramebuffer(gl.FRAMEBUFFER, captureFBO);
let error = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (error != gl.FRAMEBUFFER_COMPLETE)
console.log("framebuf(1) status error= " + error);
for (let i = 0; i < 6; ++i) {
gl.uniformMatrix4fv(gl.getUniformLocation(equirectangularToCubemapShader.programId, "view"), false, captureViews[i]);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, envCubemap, 0);
let error = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (error != gl.FRAMEBUFFER_COMPLETE)
console.log("framebuf(2) status error= " + error);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
renderCube();
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, canvas.width, canvas.height);
cubefaceShader.use(gl);
let projection = mat4.create();
mat4.perspective(projection, 0.5 * Math.PI, canvas.width/canvas.height, 0.1, 10.0);
gl.uniformMatrix4fv(gl.getUniformLocation(cubefaceShader.programId, "projection"), false, projection);
animate();
}
function renderCube() {
if (!cubeVAO) {
let vertices = new Float32Array([
-1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0,
1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 0.0,
1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0,
-1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
-1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 1.0,
-1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0,
1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0,
-1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0,
-1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
-1.0, 1.0, 1.0, -1.0, 0.0, 0.0, 1.0, 0.0,
-1.0, 1.0, -1.0, -1.0, 0.0, 0.0, 1.0, 1.0,
-1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 1.0,
-1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 1.0,
-1.0, -1.0, 1.0, -1.0, 0.0, 0.0, 0.0, 0.0,
-1.0, 1.0, 1.0, -1.0, 0.0, 0.0, 1.0, 0.0,
1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 0.0, 1.0,
1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0,
1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 0.0, 1.0,
1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0,
-1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 0.0, 1.0,
1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 1.0, 1.0,
1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0,
1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0,
-1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0,
-1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 0.0, 1.0,
-1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0,
1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0,
1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 1.0,
1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0,
-1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0,
-1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0
]);
cubeVAO = gl.createVertexArray();
let cubeVBO = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVBO);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.bindVertexArray(cubeVAO);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 8 * sizeFloat, 0);
gl.enableVertexAttribArray(1);
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 8 * sizeFloat, (3 * sizeFloat));
gl.enableVertexAttribArray(2);
gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 8 * sizeFloat, (6 * sizeFloat));
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindVertexArray(null);
}
gl.bindVertexArray(cubeVAO);
gl.drawArrays(gl.TRIANGLES, 0, 36);
gl.bindVertexArray(null);
}
const sizeFloat = 4;
const whCube = 512;
const ext = gl.getExtension("EXT_color_buffer_float");
if (!ext) {
console.log("EXT_color_buffer_float needed");
}
let showIntensity = true;
let showLeftRight = true;
let spacePressed = false;
let equirectangularToCubemapShader = null;
let cubefaceShader = null;
let cubeVAO = null;
let quadVAO = null;
let envCubemap = null;
let camera = new Camera(vec3.fromValues(0.0, 0.0, 3.0), vec3.fromValues(0.0, 1.0, 0.0));
let deltaTime = 0.0;
let main = function () {
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
equirectangularToCubemapShader = new Shader(gl, vs_cubemap, fs_equirectangularToCubemap);
cubefaceShader = new Shader(gl, vs_cubeface, fs_cubeface);
cubefaceShader.use(gl);
cubefaceShader.setInt(gl, "environmentMap", 0);
loadHDR("../../textures/hdr/newport_loft.hdr", toCubemap);
}();
function animate() {
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
showCubefaces();
processInput();
requestAnimationFrame(animate);
}
function showCubefaces() {
cubefaceShader.setBoolean(gl, "showIntensity", showIntensity);
if (showLeftRight) {
[1, 5, 0, 4].forEach((id, index) => showCubeface(id, index));
}
else
[2, 5, 3].forEach((id, index) => showCubeface(id, index));
}
function showCubeface(id, index) {
cubefaceShader.setFloat(gl, "index", index);
cubefaceShader.setInt(gl, "id", id);
cubefaceShader.setFloat(gl, "aspectRatio", canvas.width/canvas.height);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, envCubemap);
renderQuad();
}
function renderQuad() {
if (!quadVAO) {
let vertices = new Float32Array([
-1.0, -1.0, 1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
1.0, 1.0, 1.0, 0.0, 0.0, -1.0, 1.0, 1.0,
1.0, -1.0, 1.0, 0.0, 0.0, -1.0, 1.0, 0.0,
1.0, 1.0, 1.0, 0.0, 0.0, -1.0, 1.0, 1.0,
-1.0, -1.0, 1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
-1.0, 1.0, 1.0, 0.0, 0.0, -1.0, 0.0, 1.0,
]);
quadVAO = gl.createVertexArray();
let quadVBO = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, quadVBO);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.bindVertexArray(quadVAO);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 8 * sizeFloat, 0);
gl.enableVertexAttribArray(1);
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 8 * sizeFloat, (3 * sizeFloat));
gl.enableVertexAttribArray(2);
gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 8 * sizeFloat, (6 * sizeFloat));
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindVertexArray(null);
}
gl.bindVertexArray(quadVAO);
gl.drawArrays(gl.TRIANGLES, 0, 6);
gl.bindVertexArray(null);
}