OpenGL_JS-iblCubemap

OpenGL_JS-iblCubemap

  • hdr

  • cubemap
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); }`
  • cubemap ( )
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); }`
  • cubeface
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); }`
  • cubeface
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); }