反射方程

简化方程

漫反射部分方程

- p 位于环境贴图的中心的话,只依赖于 wi 的积分
- 结果:将环境立方体贴图及其生成的辐照度图
- 辐照度图:使用任何一个向量对立方体贴图进行采样,就可以获取该方向上的场景辐照度。

HDR转为浮点数数组
- learnOpengl里的stb_image.h文件,直接获取HDR数组
- 从等距柱状投影图直接来获取HDR数组
- 性能很差
- 代码中的loadHDR,rgbeToFloat
- 每个通道32位,每个颜色 3 个通道。
通过立方体,然后获取六个面的贴图

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;
//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);
}`
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 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))
];
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);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
renderCube();
}
展示立方体包围盒

export var vs_background =
`#version 300 es
precision mediump float;
layout (location = 0) in vec3 aPos;
uniform mat4 projection;
uniform mat4 view;
out vec3 WorldPos;
void main()
{
WorldPos = aPos;
mat4 rotView = mat4(mat3(view));
vec4 clipPos = projection * rotView * vec4(WorldPos, 1.0);
//注意这里的小技巧 xyww 可以确保渲染的立方体片段的深度值总是 1.0,即最大深度,如立方体贴图教程中所述。注意我们需要将深度比较函数更改为 GL_LEQUAL:
//glDepthFunc(GL_LEQUAL);
gl_Position = clipPos.xyww;
}`
export var fs_background =
`#version 300 es
precision mediump float;
out vec4 FragColor;
in vec3 WorldPos;
uniform samplerCube environmentMap;
void main()
{
vec3 envColor = texture(environmentMap, WorldPos).rgb;
// HDR tonemap and gamma correct
envColor = envColor / (envColor + vec3(1.0));
envColor = pow(envColor, vec3(1.0/2.2));
FragColor = vec4(envColor, 1.0);
}`
backgroundShader.use(gl);
gl.uniformMatrix4fv(gl.getUniformLocation(backgroundShader.programId, "view"), false, view);
gl.activeTexture(gl.TEXTURE0);
if (showCubeMap)
gl.bindTexture(gl.TEXTURE_CUBE_MAP, envCubemap);
else
gl.bindTexture(gl.TEXTURE_CUBE_MAP, irradianceMap);
renderCube();
立方体贴图的卷积后的贴图

- 对立方体贴图进行卷积等于计算朝向 N 的半球 Ω 中每个方向 wi 的总平均辐射率。
- 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);
}`
- irradianceConvolution片元着色器
export var fs_irradianceConvolution =
`#version 300 es
precision mediump float;
out vec4 FragColor;
in vec3 WorldPos;
uniform samplerCube environmentMap;
const float PI = 3.14159265359;
void main()
{
// The world vector acts as the normal of a tangent surface
// from the origin, aligned to WorldPos. Given this normal, calculate all
// incoming radiance of the environment. The result of this radiance
// is the radiance of light coming from -Normal direction, which is what
// we use in the PBR shader to sample irradiance.
vec3 N = normalize(WorldPos);
vec3 irradiance = vec3(0.0);
// tangent space calculation from origin point
vec3 up = vec3(0.0, 1.0, 0.0);
vec3 right = cross(up, N);
up = cross(N, right);
float sampleDelta = 0.025; //我们以一个固定的 sampleDelta 增量值遍历半球,减小(或增加)这个增量将会增加(或减少)精确度。
float nrSamples = 0.0;
for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta)
{
for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta)
{
// spherical to cartesian (in tangent space)
vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
// tangent space to world
vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;
irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta);
nrSamples++;
}
}
irradiance = PI * irradiance * (1.0 / float(nrSamples));
FragColor = vec4(irradiance, 1.0);
}`
- FBO得到立方体贴图的卷积后的贴图irradianceMap
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
const whIrradiance = 32;
irradianceMap = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, irradianceMap);
for (let i = 0; i < 6; ++i) {
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA16F, whIrradiance, whIrradiance, 0, gl.RGBA, gl.FLOAT, new Float32Array(whIrradiance * whIrradiance * 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);
gl.bindFramebuffer(gl.FRAMEBUFFER, captureFBO);
gl.bindRenderbuffer(gl.RENDERBUFFER, captureRBO);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT24, whIrradiance, whIrradiance);
irradianceShader.use(gl);
irradianceShader.setInt(gl, "environmentMap", 0);
gl.uniformMatrix4fv(gl.getUniformLocation(irradianceShader.programId, "projection"), false, captureProjection);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, envCubemap);
gl.viewport(0, 0, whIrradiance, whIrradiance);
gl.bindFramebuffer(gl.FRAMEBUFFER, captureFBO);
for (let i = 0; i < 6; ++i) {
gl.uniformMatrix4fv(gl.getUniformLocation(irradianceShader.programId, "view"), false, captureViews[i]);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, irradianceMap, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
renderCube();
}
PBR 和间接辐照度光照

export var vs_pbr =
`#version 300 es
precision mediump float;
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;
layout (location = 2) in vec3 aNormal;
out vec2 TexCoords;
out vec3 WorldPos;
out vec3 Normal;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
void main()
{
TexCoords = aTexCoords;
WorldPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(model) * aNormal;
gl_Position = projection * view * vec4(WorldPos, 1.0);
}`
export var fs_pbr =
`#version 300 es
precision mediump float;
out vec4 FragColor;
in vec2 TexCoords;
in vec3 WorldPos;
in vec3 Normal;
// material parameters
uniform vec3 albedo;
uniform float metallic;
uniform float roughness;
uniform float ao;
// IBL
uniform samplerCube irradianceMap; //更新了的部分
// lights
uniform vec3 lightPositions[4];
uniform vec3 lightColors[4];
uniform vec3 camPos;
const float PI = 3.14159265359;
// ----------------------------------------------------------------------------
float DistributionGGX(vec3 N, vec3 H, float roughness)
{
float a = roughness*roughness;
float a2 = a*a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH*NdotH;
float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
// ----------------------------------------------------------------------------
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
{
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}
// ----------------------------------------------------------------------------
void main()
{
vec3 N = Normal;
vec3 V = normalize(camPos - WorldPos);
vec3 R = reflect(-V, N);
// calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
// of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
// reflectance equation
vec3 Lo = vec3(0.0);
for(int i = 0; i < 4; ++i)
{
// calculate per-light radiance
vec3 L = normalize(lightPositions[i] - WorldPos);
vec3 H = normalize(V + L);
float distance = length(lightPositions[i] - WorldPos);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = lightColors[i] * attenuation;
// Cook-Torrance BRDF
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 nominator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001; // 0.001 to prevent divide by zero.
vec3 specular = nominator / denominator;
// kS is equal to Fresnel
vec3 kS = F;
// for energy conservation, the diffuse and specular light can't
// be above 1.0 (unless the surface emits light); to preserve this
// relationship the diffuse component (kD) should equal 1.0 - kS.
vec3 kD = vec3(1.0) - kS;
// multiply kD by the inverse metalness such that only non-metals
// have diffuse lighting, or a linear blend if partly metal (pure metals
// have no diffuse light).
kD *= 1.0 - metallic;
// scale light by NdotL
float NdotL = max(dot(N, L), 0.0);
// add to outgoing radiance Lo
Lo += (kD * albedo / PI + specular) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again
}
// ambient lighting (we now use IBL as the ambient term)
vec3 kS = fresnelSchlick(max(dot(N, V), 0.0), F0);
vec3 kD = 1.0 - kS;
kD *= 1.0 - metallic;
vec3 irradiance = texture(irradianceMap, N).rgb;
vec3 diffuse = irradiance * albedo; //相乘
vec3 ambient = (kD * diffuse) * ao; //相乘
// vec3 ambient = vec3(0.002);
vec3 color = ambient + Lo;
// HDR tonemapping
color = color / (color + vec3(1.0));
// gamma correct
color = pow(color, vec3(1.0/2.2));
FragColor = vec4(color , 1.0);
}`
let currentFrame = performance.now();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
processInput();
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
pbrShader.use(gl);
view = camera.GetViewMatrix();
gl.uniformMatrix4fv(gl.getUniformLocation(pbrShader.programId, "view"), false, view);
gl.uniform3fv(gl.getUniformLocation(pbrShader.programId, "camPos"), new Float32Array(camera.Position));
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, irradianceMap);
mat4.identity(model);
for (let row = 0; row < nrRows; ++row) {
pbrShader.setFloat(gl, "metallic", row / nrRows);
for (let col = 0; col < nrColumns; ++col) {
pbrShader.setFloat(gl, "roughness", Math.min(Math.max(col / nrColumns, 0.025), 1.0));
mat4.identity(model);
mat4.translate(model, model, vec3.fromValues((col - (nrColumns / 2)) * spacing, (row - (nrRows / 2)) * spacing, 0.0));
gl.uniformMatrix4fv(gl.getUniformLocation(pbrShader.programId, "model"), false, model);
renderSphere2();
}
}
for (let i = 0; i < lightPositions.length / 3; ++i) {
let newPos = vec3.fromValues(lightPositions[3 * i], lightPositions[3 * i + 1], lightPositions[3 * i + 2]);
pbrShader.setFloat(gl, "metallic", 1.0);
pbrShader.setFloat(gl, "roughness", 1.0);
mat4.identity(model);
mat4.translate(model, model, newPos);
mat4.scale(model, model, vec3.fromValues(0.5, 0.5, 0.5));
gl.uniformMatrix4fv(gl.getUniformLocation(pbrShader.programId, "model"), false, model);
renderSphere2();
requestAnimationFrame(render);
}
function renderSphere2() {
if (sphereVAO == null) {
sphere = new Sphere2(1.0, 14, 14, 0, 2 * Math.PI, 0, Math.PI);
sphereVAO = gl.createVertexArray();
let vbo = gl.createBuffer();
let ebo = gl.createBuffer();
gl.bindVertexArray(sphereVAO);
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sphere.vertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(sphere.indices), gl.STATIC_DRAW);
let stride = (3 + 2 + 3) * sizeFloat;
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, stride, 0);
gl.enableVertexAttribArray(1);
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, stride, (3 * sizeFloat));
gl.enableVertexAttribArray(2);
gl.vertexAttribPointer(2, 3, gl.FLOAT, false, stride, (5 * sizeFloat));
}
gl.bindVertexArray(sphereVAO);
gl.drawElements(gl.TRIANGLE_STRIP, sphere.indexCount, gl.UNSIGNED_SHORT, 0);
}
代码

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;
//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_background =
`#version 300 es
precision mediump float;
layout (location = 0) in vec3 aPos;
uniform mat4 projection;
uniform mat4 view;
out vec3 WorldPos;
void main()
{
WorldPos = aPos;
mat4 rotView = mat4(mat3(view));
vec4 clipPos = projection * rotView * vec4(WorldPos, 1.0);
gl_Position = clipPos.xyww;
}`
export var fs_background =
`#version 300 es
precision mediump float;
out vec4 FragColor;
in vec3 WorldPos;
uniform samplerCube environmentMap;
void main()
{
vec3 envColor = texture(environmentMap, WorldPos).rgb;
// HDR tonemap and gamma correct
envColor = envColor / (envColor + vec3(1.0));
envColor = pow(envColor, vec3(1.0/2.2));
FragColor = vec4(envColor, 1.0);
}`
export var fs_irradianceConvolution =
`#version 300 es
precision mediump float;
out vec4 FragColor;
in vec3 WorldPos;
uniform samplerCube environmentMap;
const float PI = 3.14159265359;
void main()
{
// The world vector acts as the normal of a tangent surface
// from the origin, aligned to WorldPos. Given this normal, calculate all
// incoming radiance of the environment. The result of this radiance
// is the radiance of light coming from -Normal direction, which is what
// we use in the PBR shader to sample irradiance.
vec3 N = normalize(WorldPos);
vec3 irradiance = vec3(0.0);
// tangent space calculation from origin point
vec3 up = vec3(0.0, 1.0, 0.0);
vec3 right = cross(up, N);
up = cross(N, right);
float sampleDelta = 0.025;
float nrSamples = 0.0;
for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta)
{
for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta)
{
// spherical to cartesian (in tangent space)
vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
// tangent space to world
vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;
irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta);
nrSamples++;
}
}
irradiance = PI * irradiance * (1.0 / float(nrSamples));
FragColor = vec4(irradiance, 1.0);
}`
export var vs_pbr =
`#version 300 es
precision mediump float;
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;
layout (location = 2) in vec3 aNormal;
out vec2 TexCoords;
out vec3 WorldPos;
out vec3 Normal;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
void main()
{
TexCoords = aTexCoords;
WorldPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(model) * aNormal;
gl_Position = projection * view * vec4(WorldPos, 1.0);
}`
export var fs_pbr =
`#version 300 es
precision mediump float;
out vec4 FragColor;
in vec2 TexCoords;
in vec3 WorldPos;
in vec3 Normal;
// material parameters
uniform vec3 albedo;
uniform float metallic;
uniform float roughness;
uniform float ao;
// IBL
uniform samplerCube irradianceMap;
// lights
uniform vec3 lightPositions[4];
uniform vec3 lightColors[4];
uniform vec3 camPos;
const float PI = 3.14159265359;
// ----------------------------------------------------------------------------
float DistributionGGX(vec3 N, vec3 H, float roughness)
{
float a = roughness*roughness;
float a2 = a*a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH*NdotH;
float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
// ----------------------------------------------------------------------------
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
// ----------------------------------------------------------------------------
void main()
{
vec3 N = Normal;
vec3 V = normalize(camPos - WorldPos);
vec3 R = reflect(-V, N);
// calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
// of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
// reflectance equation
vec3 Lo = vec3(0.0);
for(int i = 0; i < 4; ++i)
{
// calculate per-light radiance
vec3 L = normalize(lightPositions[i] - WorldPos);
vec3 H = normalize(V + L);
float distance = length(lightPositions[i] - WorldPos);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = lightColors[i] * attenuation;
// Cook-Torrance BRDF
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 nominator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001; // 0.001 to prevent divide by zero.
vec3 specular = nominator / denominator;
// kS is equal to Fresnel
vec3 kS = F;
// for energy conservation, the diffuse and specular light can't
// be above 1.0 (unless the surface emits light); to preserve this
// relationship the diffuse component (kD) should equal 1.0 - kS.
vec3 kD = vec3(1.0) - kS;
// multiply kD by the inverse metalness such that only non-metals
// have diffuse lighting, or a linear blend if partly metal (pure metals
// have no diffuse light).
kD *= 1.0 - metallic;
// scale light by NdotL
float NdotL = max(dot(N, L), 0.0);
// add to outgoing radiance Lo
Lo += (kD * albedo / PI + specular) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again
}
// ambient lighting (we now use IBL as the ambient term)
vec3 kS = fresnelSchlick(max(dot(N, V), 0.0), F0);
vec3 kD = 1.0 - kS;
kD *= 1.0 - metallic;
vec3 irradiance = texture(irradianceMap, N).rgb;
vec3 diffuse = irradiance * albedo;
vec3 ambient = (kD * diffuse) * ao;
// vec3 ambient = vec3(0.002);
vec3 color = ambient + Lo;
// HDR tonemapping
color = color / (color + vec3(1.0));
// gamma correct
color = pow(color, vec3(1.0/2.2));
FragColor = vec4(color , 1.0);
}`
const sizeFloat = 4;
const whCube = 512;
const ext = gl.getExtension("EXT_color_buffer_float");
let showCubeMap = true;
let spacePressed = false;
let pbrShader = null;
let equirectangularToCubemapShader = null;
let irradianceShader = null;
let backgroundShader = null;
let projection = mat4.create(), view = mat4.create();
let model = mat4.create();
let lightPositions = null;
let lightColors = null;
const nrRows = 7;
const nrColumns = 7;
const spacing = 2.5;
let cubeVAO = null;
let quadVAO = null;
let envCubemap = null;
let sphereVAO = null;
let sphere;
let captureFBO = null;
let irradianceMap = null;
let camera = new Camera(vec3.fromValues(0.0, 0.0, 5.0), vec3.fromValues(0.0, 1.0, 0.0));
let main = function () {
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
pbrShader = new Shader(gl, vs_pbr, fs_pbr);
equirectangularToCubemapShader = new Shader(gl, vs_cubemap, fs_equirectangularToCubemap);
irradianceShader = new Shader(gl, vs_cubemap, fs_irradianceConvolution);
backgroundShader = new Shader(gl, vs_background, fs_background);
pbrShader.use(gl);
pbrShader.setInt(gl, "irradianceMap", 0);
gl.uniform3f(gl.getUniformLocation(pbrShader.programId, "albedo"), 0.5, 0.0, 0.0);
pbrShader.setFloat(gl, "ao", 1.0);
backgroundShader.use(gl);
backgroundShader.setInt(gl, "environmentMap", 0);
lightPositions = new Float32Array([
-10.0, 10.0, 10.0,
10.0, 10.0, 10.0,
-10.0, -10.0, 10.0,
10.0, -10.0, 10.0
]);
lightColors = new Float32Array([
300.0, 300.0, 300.0,
300.0, 300.0, 300.0,
300.0, 300.0, 300.0,
300.0, 300.0, 300.0
]);
loadHDR("../../textures/hdr/newport_loft.hdr", toCubemap);
}();
function render() {
let currentFrame = performance.now();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
processInput();
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
pbrShader.use(gl);
view = camera.GetViewMatrix();
gl.uniformMatrix4fv(gl.getUniformLocation(pbrShader.programId, "view"), false, view);
gl.uniform3fv(gl.getUniformLocation(pbrShader.programId, "camPos"), new Float32Array(camera.Position));
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, irradianceMap);
mat4.identity(model);
for (let row = 0; row < nrRows; ++row) {
pbrShader.setFloat(gl, "metallic", row / nrRows);
for (let col = 0; col < nrColumns; ++col) {
pbrShader.setFloat(gl, "roughness", Math.min(Math.max(col / nrColumns, 0.025), 1.0));
mat4.identity(model);
mat4.translate(model, model, vec3.fromValues((col - (nrColumns / 2)) * spacing, (row - (nrRows / 2)) * spacing, 0.0));
gl.uniformMatrix4fv(gl.getUniformLocation(pbrShader.programId, "model"), false, model);
renderSphere2();
}
}
for (let i = 0; i < lightPositions.length / 3; ++i) {
let newPos = vec3.fromValues(lightPositions[3 * i], lightPositions[3 * i + 1], lightPositions[3 * i + 2]);
pbrShader.setFloat(gl, "metallic", 1.0);
pbrShader.setFloat(gl, "roughness", 1.0);
mat4.identity(model);
mat4.translate(model, model, newPos);
mat4.scale(model, model, vec3.fromValues(0.5, 0.5, 0.5));
gl.uniformMatrix4fv(gl.getUniformLocation(pbrShader.programId, "model"), false, model);
renderSphere2();
requestAnimationFrame(render);
}
backgroundShader.use(gl);
gl.uniformMatrix4fv(gl.getUniformLocation(backgroundShader.programId, "view"), false, view);
gl.activeTexture(gl.TEXTURE0);
if (showCubeMap)
gl.bindTexture(gl.TEXTURE_CUBE_MAP, envCubemap);
else
gl.bindTexture(gl.TEXTURE_CUBE_MAP, irradianceMap);
renderCube();
}
function renderSphere2() {
if (sphereVAO == null) {
sphere = new Sphere2(1.0, 14, 14, 0, 2 * Math.PI, 0, Math.PI);
sphereVAO = gl.createVertexArray();
let vbo = gl.createBuffer();
let ebo = gl.createBuffer();
gl.bindVertexArray(sphereVAO);
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sphere.vertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(sphere.indices), gl.STATIC_DRAW);
let stride = (3 + 2 + 3) * sizeFloat;
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, stride, 0);
gl.enableVertexAttribArray(1);
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, stride, (3 * sizeFloat));
gl.enableVertexAttribArray(2);
gl.vertexAttribPointer(2, 3, gl.FLOAT, false, stride, (5 * sizeFloat));
}
gl.bindVertexArray(sphereVAO);
gl.drawElements(gl.TRIANGLE_STRIP, sphere.indexCount, gl.UNSIGNED_SHORT, 0);
}
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) {
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 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))
];
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);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
renderCube();
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
const whIrradiance = 32;
irradianceMap = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, irradianceMap);
for (let i = 0; i < 6; ++i) {
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA16F, whIrradiance, whIrradiance, 0, gl.RGBA, gl.FLOAT, new Float32Array(whIrradiance * whIrradiance * 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);
gl.bindFramebuffer(gl.FRAMEBUFFER, captureFBO);
gl.bindRenderbuffer(gl.RENDERBUFFER, captureRBO);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT24, whIrradiance, whIrradiance);
irradianceShader.use(gl);
irradianceShader.setInt(gl, "environmentMap", 0);
gl.uniformMatrix4fv(gl.getUniformLocation(irradianceShader.programId, "projection"), false, captureProjection);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, envCubemap);
gl.viewport(0, 0, whIrradiance, whIrradiance);
gl.bindFramebuffer(gl.FRAMEBUFFER, captureFBO);
for (let i = 0; i < 6; ++i) {
gl.uniformMatrix4fv(gl.getUniformLocation(irradianceShader.programId, "view"), false, captureViews[i]);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, irradianceMap, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
renderCube();
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
mat4.perspective(projection, camera.Zoom, canvas.width / canvas.height, 0.1, 100.0);
pbrShader.use(gl);
gl.uniformMatrix4fv(gl.getUniformLocation(pbrShader.programId, "projection"), false, projection);
gl.uniform3fv(gl.getUniformLocation(pbrShader.programId, "lightPositions"), lightPositions);
gl.uniform3fv(gl.getUniformLocation(pbrShader.programId, "lightColors"), lightColors);
backgroundShader.use(gl);
gl.uniformMatrix4fv(gl.getUniformLocation(backgroundShader.programId, "projection"), false, projection);
gl.viewport(0, 0, canvas.width, canvas.height);
requestAnimationFrame(render);
}
- 这个是drawcall 方便framebufferTexture2D生成贴图
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);
}
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; }
;