From 749b440dd39a8db818b22986ed62027916be9f67 Mon Sep 17 00:00:00 2001 From: 4eb0da Date: Tue, 20 Aug 2024 03:59:02 +0300 Subject: [PATCH 1/2] ibl --- docs/preview/preview.html | 1 + docs/preview/preview.ts | 25 +- renderer/modelRenderer.ts | 1026 +++++++++++++++++++++++++++++++++++-- renderer/rendererData.ts | 4 + 4 files changed, 995 insertions(+), 61 deletions(-) diff --git a/docs/preview/preview.html b/docs/preview/preview.html index c2e85ab..4a23d7e 100644 --- a/docs/preview/preview.html +++ b/docs/preview/preview.html @@ -28,6 +28,7 @@ + diff --git a/docs/preview/preview.ts b/docs/preview/preview.ts index 1958f1e..0e231fe 100755 --- a/docs/preview/preview.ts +++ b/docs/preview/preview.ts @@ -4,7 +4,7 @@ import { decodeDds, parseHeaders } from 'dds-parser'; import { parse as parseMDL } from '../../mdl/parse'; import { parse as parseMDX } from '../../mdx/parse'; import { Model, TextureFlags } from '../../model'; -import { ModelRenderer } from '../../renderer/modelRenderer'; +import { DDS_FORMAT, ModelRenderer } from '../../renderer/modelRenderer'; import { vec3RotateZ } from '../../renderer/util'; import { decode, getImageData } from '../../blp/decode'; import '../common/shim'; @@ -37,6 +37,7 @@ let wireframe = false; let showSkeleton = false; let skeletonNodes: string[] | null = null; let shadow = true; +let ibl = true; const cameraBasePos: vec3 = vec3.create(); const cameraPos: vec3 = vec3.create(); @@ -151,7 +152,7 @@ function calcCameraQuat(cameraPos: vec3, cameraTarget: vec3): quat { function drawScene() { gl.depthMask(true); - mat4.perspective(pMatrix, Math.PI / 4, canvas.width / canvas.height, 0.1, 2000.0); + mat4.perspective(pMatrix, Math.PI / 4, canvas.width / canvas.height, 0.1, 3000.0); vec3.set( cameraBasePos, @@ -173,7 +174,7 @@ function drawScene() { const cameraQuat: quat = calcCameraQuat(cameraPos, cameraTarget); const lightQuat: quat = calcCameraQuat(lightPosition, lightTarget); - + modelRenderer.setLightPosition(lightPosition); modelRenderer.setLightColor(lightColor); @@ -184,7 +185,8 @@ function drawScene() { modelRenderer.setCamera(lightPosition, lightQuat); modelRenderer.render(lightMVMatrix, pMatrix, { - wireframe: false + wireframe: false, + env: false }); gl.bindFramebuffer(gl.FRAMEBUFFER, null); @@ -196,6 +198,7 @@ function drawScene() { modelRenderer.setCamera(cameraPos, cameraQuat); modelRenderer.render(mvMatrix, pMatrix, { wireframe, + env: ibl, shadowMapTexture: shadow ? framebufferDepthTexture : undefined, shadowMapMatrix: shadow ? shadowMapMatrix : undefined, shadowBias: 1e-6, @@ -213,7 +216,7 @@ function tick(timestamp: number) { drawScene(); } -function loadTexture(src: string, textureName: string, flags: TextureFlags) { +function loadTexture(src: string, textureName: string, flags: TextureFlags | 0) { const img = new Image(); img.onload = () => { @@ -337,6 +340,12 @@ function initControls() { shadow = shadowCheck.checked; }); + const iblCheck = document.getElementById('ibl') as HTMLInputElement; + ibl = iblCheck.checked; + iblCheck.addEventListener('input', () => { + ibl = iblCheck.checked; + }); + const readSkeletonNodes = (value: string) => { const val = value.trim(); @@ -420,8 +429,8 @@ function initCameraMove() { if (cameraTheta > Math.PI / 2 * 0.98) { cameraTheta = Math.PI / 2 * 0.98; } - if (cameraTheta < 0) { - cameraTheta = 0; + if (cameraTheta < -Math.PI / 2 * 0.98) { + cameraTheta = -Math.PI / 2 * 0.98; } downX = x; @@ -601,7 +610,7 @@ function initDragDrop() { if (format) { modelRenderer.setTextureCompressedImage( textureName, - format, + format as DDS_FORMAT, reader.result as ArrayBuffer, dds, textureFlags diff --git a/renderer/modelRenderer.ts b/renderer/modelRenderer.ts index a642823..b1792b3 100644 --- a/renderer/modelRenderer.ts +++ b/renderer/modelRenderer.ts @@ -11,13 +11,19 @@ import {ParticlesController} from './particles'; import {RibbonsController} from './ribbons'; // actually, all is number -type DDS_FORMAT = WEBGL_compressed_texture_s3tc['COMPRESSED_RGBA_S3TC_DXT1_EXT'] | - WEBGL_compressed_texture_s3tc['COMPRESSED_RGBA_S3TC_DXT3_EXT'] | - WEBGL_compressed_texture_s3tc['COMPRESSED_RGBA_S3TC_DXT5_EXT'] | +export type DDS_FORMAT = WEBGL_compressed_texture_s3tc['COMPRESSED_RGBA_S3TC_DXT1_EXT'] | + WEBGL_compressed_texture_s3tc['COMPRESSED_RGBA_S3TC_DXT3_EXT'] | + WEBGL_compressed_texture_s3tc['COMPRESSED_RGBA_S3TC_DXT5_EXT'] | WEBGL_compressed_texture_s3tc['COMPRESSED_RGB_S3TC_DXT1_EXT']; const MAX_NODES = 256; +const ENV_MAP_SIZE = 2048; +const ENV_CONVOLUTE_DIFFUSE_SIZE = 32; +const ENV_PREFILTER_SIZE = 128; +const MAX_ENV_MIP_LEVELS = 8; +const BRDF_LUT_SIZE = 512; + const vertexShaderHardwareSkinning = ` attribute vec3 aVertexPosition; attribute vec3 aNormal; @@ -120,24 +126,24 @@ const fragmentShader = ` } `; -const vertexShaderHDHardwareSkinning = ` - attribute vec3 aVertexPosition; - attribute vec3 aNormal; - attribute vec2 aTextureCoord; - attribute vec4 aSkin; - attribute vec4 aBoneWeight; - attribute vec4 aTangent; +const vertexShaderHDHardwareSkinning = `#version 300 es + in vec3 aVertexPosition; + in vec3 aNormal; + in vec2 aTextureCoord; + in vec4 aSkin; + in vec4 aBoneWeight; + in vec4 aTangent; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uNodesMatrices[${MAX_NODES}]; - varying vec3 vNormal; - varying vec3 vTangent; - varying vec3 vBinormal; - varying vec2 vTextureCoord; - varying mat3 vTBN; - varying vec3 vFragPos; + out vec3 vNormal; + out vec3 vTangent; + out vec3 vBinormal; + out vec2 vTextureCoord; + out mat3 vTBN; + out vec3 vFragPos; void main(void) { vec4 position = vec4(aVertexPosition, 1.0); @@ -179,15 +185,17 @@ const vertexShaderHDHardwareSkinning = ` } `; -const fragmentShaderHD = ` +const fragmentShaderHD = `#version 300 es precision mediump float; - varying vec2 vTextureCoord; - varying vec3 vNormal; - varying vec3 vTangent; - varying vec3 vBinormal; - varying mat3 vTBN; - varying vec3 vFragPos; + in vec2 vTextureCoord; + in vec3 vNormal; + in vec3 vTangent; + in vec3 vBinormal; + in mat3 vTBN; + in vec3 vFragPos; + + out vec4 FragColor; uniform sampler2D uSampler; uniform sampler2D uNormalSampler; @@ -203,9 +211,14 @@ const fragmentShaderHD = ` uniform mat4 uShadowMapLightMatrix; uniform float uShadowBias; uniform float uShadowSmoothingStep; + uniform bool uHasEnv; + uniform samplerCube uIrradianceMap; + uniform samplerCube uPrefilteredEnv; + uniform sampler2D uBRDFLUT; const float PI = 3.14159265359; const float gamma = 2.2; + const float MAX_REFLECTION_LOD = ${MAX_ENV_MIP_LEVELS.toFixed(1)}; float distributionGGX(vec3 normal, vec3 halfWay, float roughness) { float a = roughness * roughness; @@ -227,7 +240,7 @@ const fragmentShaderHD = ` float num = nDotV; float denom = nDotV * (1. - k) + k; - + return num / denom; } @@ -236,38 +249,43 @@ const fragmentShaderHD = ` float nDotL = max(dot(normal, lightDir), .0); float ggx2 = geometrySchlickGGX(nDotV, roughness); float ggx1 = geometrySchlickGGX(nDotL, roughness); - + return ggx1 * ggx2; } vec3 fresnelSchlick(float lightFactor, vec3 f0) { return f0 + (1. - f0) * pow(clamp(1. - lightFactor, 0., 1.), 5.); - } + } + + vec3 fresnelSchlickRoughness(float lightFactor, vec3 f0, float roughness) { + return f0 + (max(vec3(1.0 - roughness), f0) - f0) * pow(clamp(1.0 - lightFactor, 0.0, 1.0), 5.0); + } void main(void) { vec2 texCoord = (uTVextexAnim * vec3(vTextureCoord.s, vTextureCoord.t, 1.)).st; - vec4 resultColor = texture2D(uSampler, texCoord); + vec4 resultColor = texture(uSampler, texCoord); - vec4 orm = texture2D(uOrmSampler, texCoord); + vec4 orm = texture(uOrmSampler, texCoord); float occlusion = orm.r; float roughness = orm.g; float metallic = orm.b; float teamColorFactor = orm.a; - vec4 baseColor = texture2D(uSampler, texCoord); + vec4 baseColor = texture(uSampler, texCoord); vec3 teamColor = baseColor.rgb * uReplaceableColor; baseColor.rgb = mix(baseColor.rgb, teamColor, teamColorFactor); baseColor.rgb = pow(baseColor.rgb, vec3(gamma)); resultColor.rgb = mix(resultColor.rgb, resultColor.rgb * uReplaceableColor, teamColorFactor); - vec3 normal = texture2D(uNormalSampler, texCoord).rgb; + vec3 normal = texture(uNormalSampler, texCoord).rgb; normal = normal * 2.0 - 1.0; normal.x = -normal.x; normal.y = -normal.y; normal = normalize(vTBN * -normal); vec3 viewDir = normalize(uCameraPos - vFragPos); + vec3 reflected = reflect(-viewDir, normal); vec3 lightDir = normalize(uLightPos - vFragPos); float lightFactor = max(dot(normal, lightDir), .0); @@ -280,17 +298,27 @@ const fragmentShaderHD = ` vec3 halfWay = normalize(viewDir + lightDir); float ndf = distributionGGX(normal, halfWay, roughness); float g = geometrySmith(normal, viewDir, lightDir, roughness); - vec3 f = fresnelSchlick(max(dot(halfWay, viewDir), 0.), f0); + vec3 f = fresnelSchlick(max(dot(halfWay, viewDir), 0.), f0); vec3 kS = f; - // vec3 kD = vec3(1.) - kS; vec3 kD = vec3(1.); - // kD *= 1.0 - metallic; + if (uHasEnv) { + kD *= 1.0 - metallic; + } else { + kD = vec3(1.) - kS; + } vec3 num = ndf * g * f; float denom = 4. * max(dot(normal, viewDir), 0.) * max(dot(normal, lightDir), 0.) + .0001; vec3 specular = num / denom; - totalLight += (kD * baseColor.rgb / PI + specular) * radiance * lightFactor; + totalLight = (kD * baseColor.rgb / PI + specular) * radiance * lightFactor; + + if (uHasEnv) { + vec3 prefilteredColor = textureLod(uPrefilteredEnv, reflected, roughness * MAX_REFLECTION_LOD).rgb; + vec3 f = fresnelSchlickRoughness(max(dot(normal, viewDir), 0.0), f0, roughness); + vec2 envBRDF = texture(uBRDFLUT, vec2(max(dot(normal, viewDir), 0.0), roughness)).rg; + specular = prefilteredColor * (f * envBRDF.x + envBRDF.y); + } if (uHasShadowMap) { vec4 fragInLightPos = uShadowMapLightMatrix * vec4(vFragPos, 1.); @@ -299,11 +327,11 @@ const fragmentShaderHD = ` int passes = 5; float step = 1. / float(passes); - float lightDepth = texture2D(uShadowMapSampler, shadowMapCoord.xy).r; - float lightDepth0 = texture2D(uShadowMapSampler, vec2(shadowMapCoord.x + uShadowSmoothingStep, shadowMapCoord.y)).r; - float lightDepth1 = texture2D(uShadowMapSampler, vec2(shadowMapCoord.x, shadowMapCoord.y + uShadowSmoothingStep)).r; - float lightDepth2 = texture2D(uShadowMapSampler, vec2(shadowMapCoord.x, shadowMapCoord.y - uShadowSmoothingStep)).r; - float lightDepth3 = texture2D(uShadowMapSampler, vec2(shadowMapCoord.x - uShadowSmoothingStep, shadowMapCoord.y)).r; + float lightDepth = texture(uShadowMapSampler, shadowMapCoord.xy).r; + float lightDepth0 = texture(uShadowMapSampler, vec2(shadowMapCoord.x + uShadowSmoothingStep, shadowMapCoord.y)).r; + float lightDepth1 = texture(uShadowMapSampler, vec2(shadowMapCoord.x, shadowMapCoord.y + uShadowSmoothingStep)).r; + float lightDepth2 = texture(uShadowMapSampler, vec2(shadowMapCoord.x, shadowMapCoord.y - uShadowSmoothingStep)).r; + float lightDepth3 = texture(uShadowMapSampler, vec2(shadowMapCoord.x - uShadowSmoothingStep, shadowMapCoord.y)).r; float currentDepth = shadowMapCoord.z; float visibility = 0.; @@ -326,15 +354,28 @@ const fragmentShaderHD = ` totalLight *= visibility; } - vec3 ambient = vec3(.03) * baseColor.rgb * occlusion; - vec3 color = ambient + totalLight; - + vec3 color; + + if (uHasEnv) { + vec3 diffuse = texture(uIrradianceMap, normal).rgb * baseColor.rgb; + vec3 prefilteredColor = textureLod(uPrefilteredEnv, reflected, roughness * MAX_REFLECTION_LOD).rgb; + vec2 envBRDF = texture(uBRDFLUT, vec2(max(dot(normal, viewDir), 0.0), roughness)).rg; + specular = prefilteredColor * (f * envBRDF.x + envBRDF.y); + + vec3 ambient = (kD * diffuse + specular) * occlusion; + color = ambient + totalLight; + } else { + vec3 ambient = vec3(.03); + ambient *= baseColor.rgb * occlusion; + color = ambient + totalLight; + } + color = pow(color, vec3(1. / gamma)); - - gl_FragColor = vec4(color, 1.); + + FragColor = vec4(color, 1.); // hand-made alpha-test - if (gl_FragColor[3] < uDiscardAlphaLevel) { + if (FragColor[3] < uDiscardAlphaLevel) { discard; } } @@ -366,6 +407,341 @@ const skeletonFragmentShader = ` } `; +const envToCubemapVertexShader = ` + attribute vec3 aPos; + + uniform mat4 uMVMatrix; + uniform mat4 uPMatrix; + + varying vec3 vLocalPos; + + void main(void) { + vLocalPos = aPos; + gl_Position = uPMatrix * uMVMatrix * vec4(aPos, 1.0); + } +`; + +const envToCubemapFragmentShader = ` + precision mediump float; + + varying vec3 vLocalPos; + + uniform sampler2D uEquirectangularMap; + + const vec2 invAtan = vec2(0.1591, 0.3183); + + vec2 SampleSphericalMap(vec3 v) { + // vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); + vec2 uv = vec2(atan(v.x, v.y), asin(-v.z)); + uv *= invAtan; + uv += 0.5; + return uv; + } + + void main(void) { + vec2 uv = SampleSphericalMap(normalize(vLocalPos)); // make sure to normalize localPos + vec3 color = texture2D(uEquirectangularMap, uv).rgb; + + gl_FragColor = vec4(color, 1.0); + // gl_FragColor = vec4(vLocalPos, 1.0); + } +`; + +const envVertexShader = `#version 300 es + in vec3 aPos; + out vec3 vLocalPos; + + uniform mat4 uMVMatrix; + uniform mat4 uPMatrix; + + void main(void) { + vLocalPos = aPos; + mat4 rotView = mat4(mat3(uMVMatrix)); // remove translation from the view matrix + vec4 clipPos = uPMatrix * rotView * 1000. * vec4(aPos, 1.0); + + gl_Position = clipPos.xyww; + // gl_Position = uPMatrix * uMVMatrix * vec4(aPos, 1.0); + } +`; + +const envFragmentShader = `#version 300 es + precision mediump float; + + in vec3 vLocalPos; + + out vec4 FragColor; + + uniform samplerCube uEnvironmentMap; + + void main(void) { + // vec3 envColor = textureLod(uEnvironmentMap, vLocalPos, 0.0).rgb; + vec3 envColor = texture(uEnvironmentMap, vLocalPos).rgb; + + FragColor = vec4(envColor, 1.0); + } +`; + +const convoluteEnvDiffuseVertexShader = ` + attribute vec3 aPos; + + uniform mat4 uMVMatrix; + uniform mat4 uPMatrix; + + varying vec3 vLocalPos; + + void main(void) { + vLocalPos = aPos; + gl_Position = uPMatrix * uMVMatrix * vec4(aPos, 1.0); + } +`; + +const convoluteEnvDiffuseFragmentShader = ` + precision mediump float; + + varying vec3 vLocalPos; + + uniform samplerCube uEnvironmentMap; + + const float PI = 3.14159265359; + + void main(void) { + vec3 irradiance = vec3(0.0); + + // the sample direction equals the hemisphere's orientation + vec3 normal = normalize(vLocalPos); + + vec3 up = vec3(0.0, 1.0, 0.0); + vec3 right = normalize(cross(up, normal)); + up = normalize(cross(normal, right)); + + const 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 * normal; + + irradiance += textureCube(uEnvironmentMap, sampleVec).rgb * cos(theta) * sin(theta); + nrSamples++; + } + } + irradiance = PI * irradiance * (1.0 / float(nrSamples)); + + gl_FragColor = vec4(irradiance, 1.0); + // gl_FragColor = vec4(textureCube(uEnvironmentMap, vLocalPos).rgb, 1.0); + // gl_FragColor = vec4(1.0); + } +`; + +const prefilterEnvVertexShader = `#version 300 es + in vec3 aPos; + + out vec3 vLocalPos; + + uniform mat4 uMVMatrix; + uniform mat4 uPMatrix; + + void main(void) { + vLocalPos = aPos; + gl_Position = uPMatrix * uMVMatrix * vec4(aPos, 1.0); + } +`; + +const prefilterEnvFragmentShader = `#version 300 es + precision mediump float; + + out vec4 FragColor; + + in vec3 vLocalPos; + + uniform samplerCube uEnvironmentMap; + uniform float uRoughness; + + const float PI = 3.14159265359; + + float RadicalInverse_VdC(uint bits) { + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return float(bits) * 2.3283064365386963e-10; // / 0x100000000 + } + + vec2 Hammersley(uint i, uint N) { + return vec2(float(i)/float(N), RadicalInverse_VdC(i)); + } + + vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) { + float a = roughness * roughness; + + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + + // from spherical coordinates to cartesian coordinates + vec3 H; + H.x = cos(phi) * sinTheta; + H.y = sin(phi) * sinTheta; + H.z = cosTheta; + + // from tangent-space vector to world-space sample vector + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + + return normalize(sampleVec); + } + + void main() { + vec3 N = normalize(vLocalPos); + vec3 R = N; + vec3 V = R; + + const uint SAMPLE_COUNT = 1024u; + float totalWeight = 0.0; + vec3 prefilteredColor = vec3(0.0); + for(uint i = 0u; i < SAMPLE_COUNT; ++i) + { + vec2 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, N, uRoughness); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + + float NdotL = max(dot(N, L), 0.0); + if(NdotL > 0.0) { + prefilteredColor += texture(uEnvironmentMap, L).rgb * NdotL; + totalWeight += NdotL; + } + } + prefilteredColor = prefilteredColor / totalWeight; + + FragColor = vec4(prefilteredColor, 1.0); + } +`; + +const integrateBRDFVertexShader = `#version 300 es + in vec3 aPos; + + out vec2 vLocalPos; + + void main(void) { + vLocalPos = aPos.xy; + gl_Position = vec4(aPos, 1.0); + } +`; + +const integrateBRDFFragmentShader = `#version 300 es + precision mediump float; + + in vec2 vLocalPos; + + out vec4 FragColor; + + const float PI = 3.14159265359; + + float RadicalInverse_VdC(uint bits) { + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return float(bits) * 2.3283064365386963e-10; // / 0x100000000 + } + + vec2 Hammersley(uint i, uint N) { + return vec2(float(i)/float(N), RadicalInverse_VdC(i)); + } + + vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) { + float a = roughness * roughness; + + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + + // from spherical coordinates to cartesian coordinates + vec3 H; + H.x = cos(phi) * sinTheta; + H.y = sin(phi) * sinTheta; + H.z = cosTheta; + + // from tangent-space vector to world-space sample vector + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + + return normalize(sampleVec); + } + + float geometrySchlickGGX(float nDotV, float roughness) { + float r = roughness; + float k = r * r / 2.; + + float num = nDotV; + float denom = nDotV * (1. - k) + k; + + return num / denom; + } + + float geometrySmith(vec3 normal, vec3 viewDir, vec3 lightDir, float roughness) { + float nDotV = max(dot(normal, viewDir), .0); + float nDotL = max(dot(normal, lightDir), .0); + float ggx2 = geometrySchlickGGX(nDotV, roughness); + float ggx1 = geometrySchlickGGX(nDotL, roughness); + + return ggx1 * ggx2; + } + + vec2 IntegrateBRDF(float NdotV, float roughness) { + vec3 V; + V.x = sqrt(1.0 - NdotV*NdotV); + V.y = 0.0; + V.z = NdotV; + + float A = 0.0; + float B = 0.0; + + vec3 N = vec3(0.0, 0.0, 1.0); + + const uint SAMPLE_COUNT = 1024u; + for(uint i = 0u; i < SAMPLE_COUNT; ++i) + { + vec2 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, N, roughness); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + + float NdotL = max(L.z, 0.0); + float NdotH = max(H.z, 0.0); + float VdotH = max(dot(V, H), 0.0); + + if(NdotL > 0.0) + { + float G = geometrySmith(N, V, L, roughness); + float G_Vis = (G * VdotH) / (NdotH * NdotV); + float Fc = pow(1.0 - VdotH, 5.0); + + A += (1.0 - Fc) * G_Vis; + B += Fc * G_Vis; + } + } + A /= float(SAMPLE_COUNT); + B /= float(SAMPLE_COUNT); + return vec2(A, B); + } + + void main() { + FragColor = vec4(IntegrateBRDF((vLocalPos.x + 1.0) * .5, (vLocalPos.y + 1.0) * .5), 0., 1.); + } +`; + const translation = vec3.create(); const rotation = quat.create(); const scaling = vec3.create(); @@ -428,6 +804,10 @@ export class ModelRenderer { shadowMapLightMatrixUniform: WebGLUniformLocation | null; shadowBiasUniform: WebGLUniformLocation | null; shadowSmoothingStepUniform: WebGLUniformLocation | null; + hasEnvUniform: WebGLUniformLocation | null; + irradianceMapUniform: WebGLUniformLocation | null; + prefilteredEnvUniform: WebGLUniformLocation | null; + brdfLUTUniform: WebGLUniformLocation | null; }; private skeletonShaderProgram: WebGLProgram | null; private skeletonVertexShader: WebGLShader | null; @@ -458,6 +838,58 @@ export class ModelRenderer { private skinWeightBuffer: WebGLBuffer[] = []; private tangentBuffer: WebGLBuffer[] = []; + private cubeVertexBuffer: WebGLBuffer; + private squareVertexBuffer: WebGLBuffer; + private brdfLUT: WebGLTexture; + + private envToCubemapShaderProgramLocations: { + vertexPositionAttribute: number | null; + pMatrixUniform: WebGLUniformLocation | null; + mvMatrixUniform: WebGLUniformLocation | null; + envMapSamplerUniform: WebGLUniformLocation | null; + }; + private envToCubemapVertexShader: WebGLShader | null; + private envToCubemapFragmentShader: WebGLShader | null; + private envToCubemapShaderProgram: WebGLProgram | null; + + private envShaderProgramLocations: { + vertexPositionAttribute: number | null; + pMatrixUniform: WebGLUniformLocation | null; + mvMatrixUniform: WebGLUniformLocation | null; + envMapSamplerUniform: WebGLUniformLocation | null; + }; + private envVertexShader: WebGLShader | null; + private envFragmentShader: WebGLShader | null; + private envShaderProgram: WebGLProgram | null; + + private convoluteDiffuseEnvShaderProgramLocations: { + vertexPositionAttribute: number | null; + pMatrixUniform: WebGLUniformLocation | null; + mvMatrixUniform: WebGLUniformLocation | null; + envMapSamplerUniform: WebGLUniformLocation | null; + }; + private convoluteDiffuseEnvVertexShader: WebGLShader | null; + private convoluteDiffuseEnvFragmentShader: WebGLShader | null; + private convoluteDiffuseEnvShaderProgram: WebGLProgram | null; + + private prefilterEnvShaderProgramLocations: { + vertexPositionAttribute: number | null; + pMatrixUniform: WebGLUniformLocation | null; + mvMatrixUniform: WebGLUniformLocation | null; + envMapSamplerUniform: WebGLUniformLocation | null; + roughnessUniform: WebGLUniformLocation | null; + }; + private prefilterEnvVertexShader: WebGLShader | null; + private prefilterEnvFragmentShader: WebGLShader | null; + private prefilterEnvShaderProgram: WebGLProgram | null; + + private integrateBRDFShaderProgramLocations: { + vertexPositionAttribute: number | null; + }; + private integrateBRDFVertexShader: WebGLShader | null; + private integrateBRDFFragmentShader: WebGLShader | null; + private integrateBRDFShaderProgram: WebGLProgram | null; + constructor(model: Model) { this.isHD = model.Geosets?.some(it => it.SkinWeights?.length > 0); @@ -486,7 +918,11 @@ export class ModelRenderer { shadowMapSamplerUniform: null, shadowMapLightMatrixUniform: null, shadowBiasUniform: null, - shadowSmoothingStepUniform: null + shadowSmoothingStepUniform: null, + hasEnvUniform: null, + irradianceMapUniform: null, + prefilteredEnvUniform: null, + brdfLUTUniform: null }; this.skeletonShaderProgramLocations = { vertexPositionAttribute: null, @@ -494,6 +930,34 @@ export class ModelRenderer { mvMatrixUniform: null, pMatrixUniform: null }; + this.envToCubemapShaderProgramLocations = { + vertexPositionAttribute: null, + pMatrixUniform: null, + mvMatrixUniform: null, + envMapSamplerUniform: null + }; + this.envShaderProgramLocations = { + vertexPositionAttribute: null, + mvMatrixUniform: null, + pMatrixUniform: null, + envMapSamplerUniform: null + }; + this.convoluteDiffuseEnvShaderProgramLocations = { + vertexPositionAttribute: null, + mvMatrixUniform: null, + pMatrixUniform: null, + envMapSamplerUniform: null + }; + this.prefilterEnvShaderProgramLocations = { + envMapSamplerUniform: null, + mvMatrixUniform: null, + pMatrixUniform: null, + roughnessUniform: null, + vertexPositionAttribute: null + }; + this.integrateBRDFShaderProgramLocations = { + vertexPositionAttribute: null + }; this.model = model; @@ -515,7 +979,11 @@ export class ModelRenderer { lightColor: null, shadowBias: 0, shadowSmoothingStep: 0, - textures: {} + textures: {}, + envTextures: {}, + requiredEnvMaps: {}, + irradianceMap: {}, + prefilteredEnvMap: {} }; this.rendererData.teamColor = vec3.fromValues(1., 0., 0.); @@ -620,13 +1088,25 @@ export class ModelRenderer { this.gl.getExtension('MOZ_EXT_texture_filter_anisotropic') || this.gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic') ); + + if (this.model.Version >= 1000 && isWebGL2(this.gl)) { + this.model.Materials.forEach(material => { + if (material.Shader === 'Shader_HD_DefaultUnit' && material.Layers.length === 6 && typeof material.Layers[5].TextureID === 'number') { + this.rendererData.requiredEnvMaps[this.model.Textures[material.Layers[5].TextureID].Image] = true; + } + }); + } + this.initShaders(); this.initBuffers(); + this.initCube(); + this.initSquare(); + this.initBRDFLUT(); this.particlesController.initGL(glContext); this.ribbonsController.initGL(glContext); } - public setTextureImage (path: string, img: HTMLImageElement, flags: TextureFlags): void { + public setTextureImage (path: string, img: HTMLImageElement, flags: TextureFlags | 0): void { this.rendererData.textures[path] = this.gl.createTexture(); this.gl.bindTexture(this.gl.TEXTURE_2D, this.rendererData.textures[path]); // this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, true); @@ -634,6 +1114,9 @@ export class ModelRenderer { this.setTextureParameters(flags, true); this.gl.generateMipmap(this.gl.TEXTURE_2D); + + this.processEnvMaps(path); + this.gl.bindTexture(this.gl.TEXTURE_2D, null); } @@ -645,6 +1128,7 @@ export class ModelRenderer { this.gl.texImage2D(this.gl.TEXTURE_2D, i, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, imageData[i]); } this.setTextureParameters(flags, false); + this.processEnvMaps(path); this.gl.bindTexture(this.gl.TEXTURE_2D, null); } @@ -665,7 +1149,7 @@ export class ModelRenderer { if (isWebGL2(this.gl)) { this.gl.texStorage2D(this.gl.TEXTURE_2D, count, format, ddsInfo.images[0].shape.width, ddsInfo.images[0].shape.height); - + for (let i = 0; i < count; ++i) { const image = ddsInfo.images[i]; this.gl.compressedTexSubImage2D(this.gl.TEXTURE_2D, i, 0, 0, image.shape.width, image.shape.height, format, view.subarray(image.offset, image.offset + image.length)); @@ -678,6 +1162,7 @@ export class ModelRenderer { } this.setTextureParameters(flags, isWebGL2(this.gl)); + this.processEnvMaps(path); this.gl.bindTexture(this.gl.TEXTURE_2D, null); } @@ -730,17 +1215,23 @@ export class ModelRenderer { public render (mvMatrix: mat4, pMatrix: mat4, { wireframe, + env = true, shadowMapTexture, shadowMapMatrix, shadowBias, shadowSmoothingStep } : { wireframe: boolean; + env?: boolean; shadowMapTexture?: WebGLTexture; shadowMapMatrix?: mat4; shadowBias?: number; shadowSmoothingStep?: number; }): void { + if (env) { + this.renderEnv(mvMatrix, pMatrix); + } + this.gl.useProgram(this.shaderProgram); this.gl.uniformMatrix4fv(this.shaderProgramLocations.pMatrixUniform, false, pMatrix); @@ -807,6 +1298,27 @@ export class ModelRenderer { this.gl.uniform1i(this.shaderProgramLocations.hasShadowMapUniform, 0); } + const envTexture = this.model.Textures[material.Layers[5]?.TextureID as number].Image; + const irradianceMap = this.rendererData.irradianceMap[envTexture]; + const prefilteredEnv = this.rendererData.prefilteredEnvMap[envTexture]; + if (env && irradianceMap) { + this.gl.uniform1i(this.shaderProgramLocations.hasEnvUniform, 1); + this.gl.activeTexture(this.gl.TEXTURE4); + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, irradianceMap); + this.gl.uniform1i(this.shaderProgramLocations.irradianceMapUniform, 4); + this.gl.activeTexture(this.gl.TEXTURE5); + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, prefilteredEnv); + this.gl.uniform1i(this.shaderProgramLocations.prefilteredEnvUniform, 5); + this.gl.activeTexture(this.gl.TEXTURE6); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.brdfLUT); + this.gl.uniform1i(this.shaderProgramLocations.brdfLUTUniform, 6); + } else { + this.gl.uniform1i(this.shaderProgramLocations.hasEnvUniform, 0); + this.gl.uniform1i(this.shaderProgramLocations.irradianceMapUniform, 4); + this.gl.uniform1i(this.shaderProgramLocations.prefilteredEnvUniform, 5); + this.gl.uniform1i(this.shaderProgramLocations.brdfLUTUniform, 6); + } + this.setLayerPropsHD(materialID, material.Layers); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer[i]); @@ -891,9 +1403,37 @@ export class ModelRenderer { this.ribbonsController.render(mvMatrix, pMatrix); } + private renderEnv (mvMatrix: mat4, pMatrix: mat4): void { + if (!isWebGL2(this.gl)) { + return; + } + + this.gl.disable(this.gl.BLEND); + this.gl.disable(this.gl.DEPTH_TEST); + this.gl.disable(this.gl.CULL_FACE); + + for (const path in this.rendererData.envTextures) { + this.gl.useProgram(this.envShaderProgram); + + this.gl.uniformMatrix4fv(this.envShaderProgramLocations.pMatrixUniform, false, pMatrix); + this.gl.uniformMatrix4fv(this.envShaderProgramLocations.mvMatrixUniform, false, mvMatrix); + + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, this.rendererData.envTextures[path]); + this.gl.uniform1i(this.envShaderProgramLocations.envMapSamplerUniform, 0); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexBuffer); + this.gl.enableVertexAttribArray(this.envShaderProgramLocations.vertexPositionAttribute); + this.gl.vertexAttribPointer(this.envShaderProgramLocations.vertexPositionAttribute, 3, this.gl.FLOAT, false, 0, 0); + this.gl.drawArrays(this.gl.TRIANGLES, 0, 6 * 6); + this.gl.disableVertexAttribArray(this.envShaderProgramLocations.vertexPositionAttribute); + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, null); + } + } + /** - * @param mvMatrix - * @param pMatrix + * @param mvMatrix + * @param pMatrix * @param nodes Nodes to highlight. null means draw all */ public renderSkeleton (mvMatrix: mat4, pMatrix: mat4, nodes: string[] | null): void { @@ -1017,7 +1557,7 @@ export class ModelRenderer { this.gl.bufferData(this.gl.ARRAY_BUFFER, buffer, this.gl.DYNAMIC_DRAW); } - private setTextureParameters (flags: TextureFlags, hasMipmaps: boolean) { + private setTextureParameters (flags: TextureFlags | 0, hasMipmaps: boolean) { if (flags & TextureFlags.WrapWidth) { this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.REPEAT); } else { @@ -1037,6 +1577,195 @@ export class ModelRenderer { } } + private processEnvMaps (path: string): void { + if (!this.rendererData.requiredEnvMaps[path] || !this.rendererData.textures[path] || !isWebGL2(this.gl)) { + return; + } + + this.gl.disable(this.gl.BLEND); + this.gl.disable(this.gl.DEPTH_TEST); + this.gl.disable(this.gl.CULL_FACE); + + const pMatrix = mat4.create(); + const mvMatrix = mat4.create(); + const eye = vec3.fromValues(0, 0, 0); + const center = [ + vec3.fromValues(1, 0, 0), + vec3.fromValues(-1, 0, 0), + vec3.fromValues(0, 1, 0), + vec3.fromValues(0, -1, 0), + vec3.fromValues(0, 0, 1), + vec3.fromValues(0, 0, -1) + ]; + const up = [ + vec3.fromValues(0, -1, 0), + vec3.fromValues(0, -1, 0), + vec3.fromValues(0, 0, 1), + vec3.fromValues(0, 0, -1), + vec3.fromValues(0, -1, 0), + vec3.fromValues(0, -1, 0) + ]; + + const framebuffer = this.gl.createFramebuffer(); + + this.gl.useProgram(this.envToCubemapShaderProgram); + + const cubemap = this.rendererData.envTextures[path] = this.gl.createTexture(); + this.gl.activeTexture(this.gl.TEXTURE1); + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, cubemap); + for (let i = 0; i < 6; ++i) { + // this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGB16F, ENV_MAP_SIZE, ENV_MAP_SIZE, 0, this.gl.RGB, this.gl.FLOAT, null); + this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGB, ENV_MAP_SIZE, ENV_MAP_SIZE, 0, this.gl.RGB, this.gl.UNSIGNED_BYTE, null); + } + + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_WRAP_R, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexBuffer); + this.gl.enableVertexAttribArray(this.envToCubemapShaderProgramLocations.vertexPositionAttribute); + this.gl.vertexAttribPointer(this.envToCubemapShaderProgramLocations.vertexPositionAttribute, 3, this.gl.FLOAT, false, 0, 0); + + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer); + + mat4.perspective(pMatrix, Math.PI / 2, 1, .1, 10); + this.gl.uniformMatrix4fv(this.envToCubemapShaderProgramLocations.pMatrixUniform, false, pMatrix); + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.rendererData.textures[path]); + this.gl.uniform1i(this.envToCubemapShaderProgramLocations.envMapSamplerUniform, 0); + this.gl.viewport(0, 0, ENV_MAP_SIZE, ENV_MAP_SIZE); + for (let i = 0; i < 6; ++i) { + this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, cubemap, 0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); + + mat4.lookAt(mvMatrix, eye, center[i], up[i]); + this.gl.uniformMatrix4fv(this.envToCubemapShaderProgramLocations.mvMatrixUniform, false, mvMatrix); + + this.gl.drawArrays(this.gl.TRIANGLES, 0, 6 * 6); + } + + this.gl.disableVertexAttribArray(this.envToCubemapShaderProgramLocations.vertexPositionAttribute); + + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); + + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, cubemap); + this.gl.generateMipmap(this.gl.TEXTURE_CUBE_MAP); + + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, null); + + // Diffuse env convolution + + this.gl.useProgram(this.convoluteDiffuseEnvShaderProgram); + + const diffuseCubemap = this.rendererData.irradianceMap[path] = this.gl.createTexture(); + this.gl.activeTexture(this.gl.TEXTURE1); + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, diffuseCubemap); + for (let i = 0; i < 6; ++i) { + // this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGB16F, ENV_MAP_SIZE, ENV_MAP_SIZE, 0, this.gl.RGB, this.gl.FLOAT, null); + this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGB, ENV_CONVOLUTE_DIFFUSE_SIZE, ENV_CONVOLUTE_DIFFUSE_SIZE, 0, this.gl.RGB, this.gl.UNSIGNED_BYTE, null); + } + + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_WRAP_R, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexBuffer); + this.gl.enableVertexAttribArray(this.convoluteDiffuseEnvShaderProgramLocations.vertexPositionAttribute); + this.gl.vertexAttribPointer(this.convoluteDiffuseEnvShaderProgramLocations.vertexPositionAttribute, 3, this.gl.FLOAT, false, 0, 0); + + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer); + + mat4.perspective(pMatrix, Math.PI / 2, 1, .1, 10); + this.gl.uniformMatrix4fv(this.convoluteDiffuseEnvShaderProgramLocations.pMatrixUniform, false, pMatrix); + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, this.rendererData.envTextures[path]); + this.gl.uniform1i(this.convoluteDiffuseEnvShaderProgramLocations.envMapSamplerUniform, 0); + this.gl.viewport(0, 0, ENV_CONVOLUTE_DIFFUSE_SIZE, ENV_CONVOLUTE_DIFFUSE_SIZE); + for (let i = 0; i < 6; ++i) { + this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, diffuseCubemap, 0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); + + mat4.lookAt(mvMatrix, eye, center[i], up[i]); + this.gl.uniformMatrix4fv(this.convoluteDiffuseEnvShaderProgramLocations.mvMatrixUniform, false, mvMatrix); + + this.gl.drawArrays(this.gl.TRIANGLES, 0, 6 * 6); + } + + this.gl.disableVertexAttribArray(this.convoluteDiffuseEnvShaderProgramLocations.vertexPositionAttribute); + + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); + + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, diffuseCubemap); + this.gl.generateMipmap(this.gl.TEXTURE_CUBE_MAP); + + // Prefilter env map with different roughness + + this.gl.useProgram(this.prefilterEnvShaderProgram); + + const prefilterCubemap = this.rendererData.prefilteredEnvMap[path] = this.gl.createTexture(); + this.gl.activeTexture(this.gl.TEXTURE1); + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, prefilterCubemap); + this.gl.texStorage2D(this.gl.TEXTURE_CUBE_MAP, MAX_ENV_MIP_LEVELS, this.gl.RGBA8, ENV_PREFILTER_SIZE, ENV_PREFILTER_SIZE); + // for (let i = 0; i < 6; ++i) { + // this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGB, ENV_PREFILTER_SIZE, ENV_PREFILTER_SIZE, 0, this.gl.RGB, this.gl.UNSIGNED_BYTE, null); + // } + for (let mip = 0; mip < MAX_ENV_MIP_LEVELS; ++mip) { + for (let i = 0; i < 6; ++i) { + const size = ENV_PREFILTER_SIZE * .5 ** mip; + const data = new Uint8Array(size * size * 4); + this.gl.texSubImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, mip, 0, 0, size, size, this.gl.RGBA, this.gl.UNSIGNED_BYTE, data); + } + } + + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_WRAP_R, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR_MIPMAP_LINEAR); + this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexBuffer); + this.gl.enableVertexAttribArray(this.prefilterEnvShaderProgramLocations.vertexPositionAttribute); + this.gl.vertexAttribPointer(this.prefilterEnvShaderProgramLocations.vertexPositionAttribute, 3, this.gl.FLOAT, false, 0, 0); + + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer); + + mat4.perspective(pMatrix, Math.PI / 2, 1, .1, 10); + this.gl.uniformMatrix4fv(this.prefilterEnvShaderProgramLocations.pMatrixUniform, false, pMatrix); + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, this.rendererData.envTextures[path]); + this.gl.uniform1i(this.prefilterEnvShaderProgramLocations.envMapSamplerUniform, 0); + + for (let mip = 0; mip < MAX_ENV_MIP_LEVELS; ++mip) { + const mipWidth = ENV_PREFILTER_SIZE *.5 ** mip; + const mipHeight = ENV_PREFILTER_SIZE *.5 ** mip; + this.gl.viewport(0, 0, mipWidth, mipHeight); + + const roughness = mip / (MAX_ENV_MIP_LEVELS - 1); + + this.gl.uniform1f(this.prefilterEnvShaderProgramLocations.roughnessUniform, roughness); + + for (let i = 0; i < 6; ++i) { + this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, prefilterCubemap, mip); + this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); + + mat4.lookAt(mvMatrix, eye, center[i], up[i]); + this.gl.uniformMatrix4fv(this.prefilterEnvShaderProgramLocations.mvMatrixUniform, false, mvMatrix); + + this.gl.drawArrays(this.gl.TRIANGLES, 0, 6 * 6); + } + } + + // cleanup + + this.gl.activeTexture(this.gl.TEXTURE1); + this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, null); + this.gl.deleteFramebuffer(framebuffer); + } + private updateLayerTextureId (materialId: number, layerId: number): void { const TextureID: AnimVector|number = this.model.Materials[materialId].Layers[layerId].TextureID; @@ -1105,11 +1834,17 @@ export class ModelRenderer { this.shaderProgramLocations.lightPosUniform = this.gl.getUniformLocation(shaderProgram, 'uLightPos'); this.shaderProgramLocations.lightColorUniform = this.gl.getUniformLocation(shaderProgram, 'uLightColor'); this.shaderProgramLocations.cameraPosUniform = this.gl.getUniformLocation(shaderProgram, 'uCameraPos'); + this.shaderProgramLocations.hasShadowMapUniform = this.gl.getUniformLocation(shaderProgram, 'uHasShadowMap'); this.shaderProgramLocations.shadowMapSamplerUniform = this.gl.getUniformLocation(shaderProgram, 'uShadowMapSampler'); this.shaderProgramLocations.shadowMapLightMatrixUniform = this.gl.getUniformLocation(shaderProgram, 'uShadowMapLightMatrix'); this.shaderProgramLocations.shadowBiasUniform = this.gl.getUniformLocation(shaderProgram, 'uShadowBias'); this.shaderProgramLocations.shadowSmoothingStepUniform = this.gl.getUniformLocation(shaderProgram, 'uShadowSmoothingStep'); + + this.shaderProgramLocations.hasEnvUniform = this.gl.getUniformLocation(shaderProgram, 'uHasEnv'); + this.shaderProgramLocations.irradianceMapUniform = this.gl.getUniformLocation(shaderProgram, 'uIrradianceMap'); + this.shaderProgramLocations.prefilteredEnvUniform = this.gl.getUniformLocation(shaderProgram, 'uPrefilteredEnv'); + this.shaderProgramLocations.brdfLUTUniform = this.gl.getUniformLocation(shaderProgram, 'uBRDFLUT'); } else { this.shaderProgramLocations.replaceableTypeUniform = this.gl.getUniformLocation(shaderProgram, 'uReplaceableType'); } @@ -1123,6 +1858,99 @@ export class ModelRenderer { this.gl.getUniformLocation(shaderProgram, `uNodesMatrices[${i}]`); } } + + if (this.isHD && isWebGL2(this.gl)) { + const envToCubemapVertex = this.envToCubemapVertexShader = getShader(this.gl, envToCubemapVertexShader, this.gl.VERTEX_SHADER); + const envToCubemapFragment = this.envToCubemapFragmentShader = getShader(this.gl, envToCubemapFragmentShader, this.gl.FRAGMENT_SHADER); + const envToCubemapShaderProgram = this.envToCubemapShaderProgram = this.gl.createProgram(); + this.gl.attachShader(envToCubemapShaderProgram, envToCubemapVertex); + this.gl.attachShader(envToCubemapShaderProgram, envToCubemapFragment); + this.gl.linkProgram(envToCubemapShaderProgram); + + if (!this.gl.getProgramParameter(envToCubemapShaderProgram, this.gl.LINK_STATUS)) { + alert('Could not initialise shaders'); + } + + this.gl.useProgram(envToCubemapShaderProgram); + + this.envToCubemapShaderProgramLocations.vertexPositionAttribute = this.gl.getAttribLocation(envToCubemapShaderProgram, 'aPos'); + this.envToCubemapShaderProgramLocations.pMatrixUniform = this.gl.getUniformLocation(envToCubemapShaderProgram, 'uPMatrix'); + this.envToCubemapShaderProgramLocations.mvMatrixUniform = this.gl.getUniformLocation(envToCubemapShaderProgram, 'uMVMatrix'); + this.envToCubemapShaderProgramLocations.envMapSamplerUniform = this.gl.getUniformLocation(envToCubemapShaderProgram, 'uEquirectangularMap'); + + const envVertex = this.envVertexShader = getShader(this.gl, envVertexShader, this.gl.VERTEX_SHADER); + const envFragment = this.envFragmentShader = getShader(this.gl, envFragmentShader, this.gl.FRAGMENT_SHADER); + const envShaderProgram = this.envShaderProgram = this.gl.createProgram(); + this.gl.attachShader(envShaderProgram, envVertex); + this.gl.attachShader(envShaderProgram, envFragment); + this.gl.linkProgram(envShaderProgram); + + if (!this.gl.getProgramParameter(envShaderProgram, this.gl.LINK_STATUS)) { + alert('Could not initialise shaders'); + } + + this.gl.useProgram(envShaderProgram); + + this.envShaderProgramLocations.vertexPositionAttribute = this.gl.getAttribLocation(envShaderProgram, 'aPos'); + this.envShaderProgramLocations.pMatrixUniform = this.gl.getUniformLocation(envShaderProgram, 'uPMatrix'); + this.envShaderProgramLocations.mvMatrixUniform = this.gl.getUniformLocation(envShaderProgram, 'uMVMatrix'); + this.envShaderProgramLocations.envMapSamplerUniform = this.gl.getUniformLocation(envShaderProgram, 'uEnvironmentMap'); + + + const convoluteDiffuseEnvVertex = this.convoluteDiffuseEnvVertexShader = getShader(this.gl, convoluteEnvDiffuseVertexShader, this.gl.VERTEX_SHADER); + const convoluteDiffuseEnvFragment = this.convoluteDiffuseEnvFragmentShader = getShader(this.gl, convoluteEnvDiffuseFragmentShader, this.gl.FRAGMENT_SHADER); + const convoluteDiffuseEnvShaderProgram = this.convoluteDiffuseEnvShaderProgram = this.gl.createProgram(); + this.gl.attachShader(convoluteDiffuseEnvShaderProgram, convoluteDiffuseEnvVertex); + this.gl.attachShader(convoluteDiffuseEnvShaderProgram, convoluteDiffuseEnvFragment); + this.gl.linkProgram(convoluteDiffuseEnvShaderProgram); + + if (!this.gl.getProgramParameter(convoluteDiffuseEnvShaderProgram, this.gl.LINK_STATUS)) { + alert('Could not initialise shaders'); + } + + this.gl.useProgram(convoluteDiffuseEnvShaderProgram); + + this.convoluteDiffuseEnvShaderProgramLocations.vertexPositionAttribute = this.gl.getAttribLocation(convoluteDiffuseEnvShaderProgram, 'aPos'); + this.convoluteDiffuseEnvShaderProgramLocations.pMatrixUniform = this.gl.getUniformLocation(convoluteDiffuseEnvShaderProgram, 'uPMatrix'); + this.convoluteDiffuseEnvShaderProgramLocations.mvMatrixUniform = this.gl.getUniformLocation(convoluteDiffuseEnvShaderProgram, 'uMVMatrix'); + this.convoluteDiffuseEnvShaderProgramLocations.envMapSamplerUniform = this.gl.getUniformLocation(convoluteDiffuseEnvShaderProgram, 'uEnvironmentMap'); + + + const prefilterEnvVertex = this.prefilterEnvVertexShader = getShader(this.gl, prefilterEnvVertexShader, this.gl.VERTEX_SHADER); + const prefilterEnvFragment = this.prefilterEnvFragmentShader = getShader(this.gl, prefilterEnvFragmentShader, this.gl.FRAGMENT_SHADER); + const prefilterEnvShaderProgram = this.prefilterEnvShaderProgram = this.gl.createProgram(); + this.gl.attachShader(prefilterEnvShaderProgram, prefilterEnvVertex); + this.gl.attachShader(prefilterEnvShaderProgram, prefilterEnvFragment); + this.gl.linkProgram(prefilterEnvShaderProgram); + + if (!this.gl.getProgramParameter(prefilterEnvShaderProgram, this.gl.LINK_STATUS)) { + alert('Could not initialise shaders'); + } + + this.gl.useProgram(prefilterEnvShaderProgram); + + this.prefilterEnvShaderProgramLocations.vertexPositionAttribute = this.gl.getAttribLocation(prefilterEnvShaderProgram, 'aPos'); + this.prefilterEnvShaderProgramLocations.pMatrixUniform = this.gl.getUniformLocation(prefilterEnvShaderProgram, 'uPMatrix'); + this.prefilterEnvShaderProgramLocations.mvMatrixUniform = this.gl.getUniformLocation(prefilterEnvShaderProgram, 'uMVMatrix'); + this.prefilterEnvShaderProgramLocations.envMapSamplerUniform = this.gl.getUniformLocation(prefilterEnvShaderProgram, 'uEnvironmentMap'); + this.prefilterEnvShaderProgramLocations.roughnessUniform = this.gl.getUniformLocation(prefilterEnvShaderProgram, 'uRoughness'); + + + const integrateBRDFVertex = this.integrateBRDFVertexShader = getShader(this.gl, integrateBRDFVertexShader, this.gl.VERTEX_SHADER); + const integrateBRDFFragment = this.integrateBRDFFragmentShader = getShader(this.gl, integrateBRDFFragmentShader, this.gl.FRAGMENT_SHADER); + const integrateBRDFShaderProgram = this.integrateBRDFShaderProgram = this.gl.createProgram(); + this.gl.attachShader(integrateBRDFShaderProgram, integrateBRDFVertex); + this.gl.attachShader(integrateBRDFShaderProgram, integrateBRDFFragment); + this.gl.linkProgram(integrateBRDFShaderProgram); + + if (!this.gl.getProgramParameter(integrateBRDFShaderProgram, this.gl.LINK_STATUS)) { + alert('Could not initialise shaders'); + } + + this.gl.useProgram(integrateBRDFShaderProgram); + + this.integrateBRDFShaderProgramLocations.vertexPositionAttribute = this.gl.getAttribLocation(integrateBRDFShaderProgram, 'aPos'); + } } private createWireframeBuffer (index: number): void { @@ -1171,7 +1999,7 @@ export class ModelRenderer { this.skinWeightBuffer[i] = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.skinWeightBuffer[i]); this.gl.bufferData(this.gl.ARRAY_BUFFER, geoset.SkinWeights, this.gl.STATIC_DRAW); - + this.tangentBuffer[i] = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.tangentBuffer[i]); this.gl.bufferData(this.gl.ARRAY_BUFFER, geoset.Tangents, this.gl.STATIC_DRAW); @@ -1198,6 +2026,98 @@ export class ModelRenderer { } } + private initCube (): void { + this.cubeVertexBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, 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, + ]), this.gl.STATIC_DRAW); + } + + private initSquare(): void { + this.squareVertexBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.squareVertexBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([ + -1.0, -1.0, + 1.0, -1.0, + -1.0, 1.0, + 1.0, -1.0, + 1.0, 1.0, + -1.0, 1.0, + ]), this.gl.STATIC_DRAW); + } + + private initBRDFLUT(): void { + this.brdfLUT = this.gl.createTexture(); + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.brdfLUT); + this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, BRDF_LUT_SIZE, BRDF_LUT_SIZE, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, null); + + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); + + const framebuffer = this.gl.createFramebuffer(); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer); + this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, this.brdfLUT, 0); + + this.gl.useProgram(this.integrateBRDFShaderProgram); + + this.gl.viewport(0, 0, BRDF_LUT_SIZE, BRDF_LUT_SIZE); + this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.squareVertexBuffer); + this.gl.enableVertexAttribArray(this.integrateBRDFShaderProgramLocations.vertexPositionAttribute); + this.gl.vertexAttribPointer(this.integrateBRDFShaderProgramLocations.vertexPositionAttribute, 2, this.gl.FLOAT, false, 0, 0); + + this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); + + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); + + this.gl.deleteFramebuffer(framebuffer); + } + /*private resetGlobalSequences (): void { for (let i = 0; i < this.rendererData.globalSequencesFrames.length; ++i) { this.rendererData.globalSequencesFrames[i] = 0; diff --git a/renderer/rendererData.ts b/renderer/rendererData.ts index e5cd889..030afc3 100644 --- a/renderer/rendererData.ts +++ b/renderer/rendererData.ts @@ -29,4 +29,8 @@ export interface RendererData { shadowBias: number; shadowSmoothingStep: number; textures: {[key: string]: WebGLTexture}; + requiredEnvMaps: {[key: string]: boolean}; + envTextures: {[key: string]: WebGLTexture}; + irradianceMap: {[key: string]: WebGLTexture}; + prefilteredEnvMap: {[key: string]: WebGLTexture}; } From dfae3542a91972a109612ca1cbdd7010e5b2c402 Mon Sep 17 00:00:00 2001 From: 4eb0da Date: Wed, 21 Aug 2024 03:16:43 +0300 Subject: [PATCH 2/2] ibl --- docs/preview/preview.ts | 8 ++- package.json | 2 +- renderer/modelRenderer.ts | 137 +++++++++++++++++++++++++++++--------- 3 files changed, 112 insertions(+), 35 deletions(-) diff --git a/docs/preview/preview.ts b/docs/preview/preview.ts index 0e231fe..f0be7b4 100755 --- a/docs/preview/preview.ts +++ b/docs/preview/preview.ts @@ -185,8 +185,7 @@ function drawScene() { modelRenderer.setCamera(lightPosition, lightQuat); modelRenderer.render(lightMVMatrix, pMatrix, { - wireframe: false, - env: false + wireframe: false }); gl.bindFramebuffer(gl.FRAMEBUFFER, null); @@ -196,9 +195,12 @@ function drawScene() { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); modelRenderer.setCamera(cameraPos, cameraQuat); + if (ibl) { + modelRenderer.renderEnvironment(mvMatrix, pMatrix); + } modelRenderer.render(mvMatrix, pMatrix, { wireframe, - env: ibl, + useEnvironmentMap: ibl, shadowMapTexture: shadow ? framebufferDepthTexture : undefined, shadowMapMatrix: shadow ? shadowMapMatrix : undefined, shadowBias: 1e-6, diff --git a/package.json b/package.json index d96c44f..1777513 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "war3-model", - "version": "3.1.2", + "version": "3.2.0", "description": "Warcraft 3 model parser, generator, convertor and previewer", "keywords": [ "warcraft3", diff --git a/renderer/modelRenderer.ts b/renderer/modelRenderer.ts index b1792b3..69d1cb2 100644 --- a/renderer/modelRenderer.ts +++ b/renderer/modelRenderer.ts @@ -263,7 +263,6 @@ const fragmentShaderHD = `#version 300 es void main(void) { vec2 texCoord = (uTVextexAnim * vec3(vTextureCoord.s, vTextureCoord.t, 1.)).st; - vec4 resultColor = texture(uSampler, texCoord); vec4 orm = texture(uOrmSampler, texCoord); @@ -277,7 +276,6 @@ const fragmentShaderHD = `#version 300 es baseColor.rgb = mix(baseColor.rgb, teamColor, teamColorFactor); baseColor.rgb = pow(baseColor.rgb, vec3(gamma)); - resultColor.rgb = mix(resultColor.rgb, resultColor.rgb * uReplaceableColor, teamColorFactor); vec3 normal = texture(uNormalSampler, texCoord).rgb; normal = normal * 2.0 - 1.0; normal.x = -normal.x; @@ -301,11 +299,9 @@ const fragmentShaderHD = `#version 300 es vec3 f = fresnelSchlick(max(dot(halfWay, viewDir), 0.), f0); vec3 kS = f; - vec3 kD = vec3(1.); + vec3 kD = vec3(1.) - kS; if (uHasEnv) { kD *= 1.0 - metallic; - } else { - kD = vec3(1.) - kS; } vec3 num = ndf * g * f; float denom = 4. * max(dot(normal, viewDir), 0.) * max(dot(normal, lightDir), 0.) + .0001; @@ -313,13 +309,6 @@ const fragmentShaderHD = `#version 300 es totalLight = (kD * baseColor.rgb / PI + specular) * radiance * lightFactor; - if (uHasEnv) { - vec3 prefilteredColor = textureLod(uPrefilteredEnv, reflected, roughness * MAX_REFLECTION_LOD).rgb; - vec3 f = fresnelSchlickRoughness(max(dot(normal, viewDir), 0.0), f0, roughness); - vec2 envBRDF = texture(uBRDFLUT, vec2(max(dot(normal, viewDir), 0.0), roughness)).rg; - specular = prefilteredColor * (f * envBRDF.x + envBRDF.y); - } - if (uHasShadowMap) { vec4 fragInLightPos = uShadowMapLightMatrix * vec4(vFragPos, 1.); vec3 shadowMapCoord = fragInLightPos.xyz / fragInLightPos.w; @@ -357,6 +346,11 @@ const fragmentShaderHD = `#version 300 es vec3 color; if (uHasEnv) { + vec3 f = fresnelSchlickRoughness(max(dot(normal, viewDir), 0.0), f0, roughness); + vec3 kS = f; + vec3 kD = vec3(1.0) - kS; + kD *= 1.0 - metallic; + vec3 diffuse = texture(uIrradianceMap, normal).rgb * baseColor.rgb; vec3 prefilteredColor = textureLod(uPrefilteredEnv, reflected, roughness * MAX_REFLECTION_LOD).rgb; vec2 envBRDF = texture(uBRDFLUT, vec2(max(dot(normal, viewDir), 0.0), roughness)).rg; @@ -370,6 +364,7 @@ const fragmentShaderHD = `#version 300 es color = ambient + totalLight; } + color = color / (vec3(1.) + color); color = pow(color, vec3(1. / gamma)); FragColor = vec4(color, 1.); @@ -503,6 +498,7 @@ const convoluteEnvDiffuseFragmentShader = ` uniform samplerCube uEnvironmentMap; const float PI = 3.14159265359; + const float gamma = 2.2; void main(void) { vec3 irradiance = vec3(0.0); @@ -525,7 +521,7 @@ const convoluteEnvDiffuseFragmentShader = ` // tangent space to world vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * normal; - irradiance += textureCube(uEnvironmentMap, sampleVec).rgb * cos(theta) * sin(theta); + irradiance += pow(textureCube(uEnvironmentMap, sampleVec).rgb, vec3(gamma)) * cos(theta) * sin(theta); nrSamples++; } } @@ -562,6 +558,7 @@ const prefilterEnvFragmentShader = `#version 300 es uniform float uRoughness; const float PI = 3.14159265359; + const float gamma = 2.2; float RadicalInverse_VdC(uint bits) { bits = (bits << 16u) | (bits >> 16u); @@ -615,7 +612,7 @@ const prefilterEnvFragmentShader = `#version 300 es float NdotL = max(dot(N, L), 0.0); if(NdotL > 0.0) { - prefilteredColor += texture(uEnvironmentMap, L).rgb * NdotL; + prefilteredColor += pow(texture(uEnvironmentMap, L).rgb, vec3(gamma)) * NdotL; totalWeight += NdotL; } } @@ -775,6 +772,7 @@ export class ModelRenderer { private gl: WebGL2RenderingContext | WebGLRenderingContext; private anisotropicExt: EXT_texture_filter_anisotropic | null; + private colorBufferFloatExt: EXT_color_buffer_float | null; private vertexShader: WebGLShader | null; private fragmentShader: WebGLShader | null; private shaderProgram: WebGLProgram | null; @@ -1077,6 +1075,84 @@ export class ModelRenderer { this.gl.deleteProgram(this.shaderProgram); this.shaderProgram = null; } + + if (this.envToCubemapShaderProgram) { + if (this.envToCubemapVertexShader) { + this.gl.detachShader(this.envToCubemapShaderProgram, this.envToCubemapVertexShader); + this.gl.deleteShader(this.envToCubemapVertexShader); + this.envToCubemapVertexShader = null; + } + if (this.envToCubemapFragmentShader) { + this.gl.detachShader(this.envToCubemapShaderProgram, this.envToCubemapFragmentShader); + this.gl.deleteShader(this.envToCubemapFragmentShader); + this.envToCubemapFragmentShader = null; + } + this.gl.deleteProgram(this.envToCubemapShaderProgram); + this.envToCubemapShaderProgram = null; + } + + if (this.envShaderProgram) { + if (this.envVertexShader) { + this.gl.detachShader(this.envShaderProgram, this.envVertexShader); + this.gl.deleteShader(this.envVertexShader); + this.envVertexShader = null; + } + if (this.envFragmentShader) { + this.gl.detachShader(this.envShaderProgram, this.envFragmentShader); + this.gl.deleteShader(this.envFragmentShader); + this.envFragmentShader = null; + } + this.gl.deleteProgram(this.envShaderProgram); + this.envShaderProgram = null; + } + + if (this.convoluteDiffuseEnvShaderProgram) { + if (this.convoluteDiffuseEnvVertexShader) { + this.gl.detachShader(this.convoluteDiffuseEnvShaderProgram, this.convoluteDiffuseEnvVertexShader); + this.gl.deleteShader(this.convoluteDiffuseEnvVertexShader); + this.convoluteDiffuseEnvVertexShader = null; + } + if (this.convoluteDiffuseEnvFragmentShader) { + this.gl.detachShader(this.convoluteDiffuseEnvShaderProgram, this.convoluteDiffuseEnvFragmentShader); + this.gl.deleteShader(this.convoluteDiffuseEnvFragmentShader); + this.convoluteDiffuseEnvFragmentShader = null; + } + this.gl.deleteProgram(this.convoluteDiffuseEnvShaderProgram); + this.convoluteDiffuseEnvShaderProgram = null; + } + + if (this.prefilterEnvShaderProgram) { + if (this.prefilterEnvVertexShader) { + this.gl.detachShader(this.prefilterEnvShaderProgram, this.prefilterEnvVertexShader); + this.gl.deleteShader(this.prefilterEnvVertexShader); + this.prefilterEnvVertexShader = null; + } + if (this.prefilterEnvFragmentShader) { + this.gl.detachShader(this.prefilterEnvShaderProgram, this.prefilterEnvFragmentShader); + this.gl.deleteShader(this.prefilterEnvFragmentShader); + this.prefilterEnvFragmentShader = null; + } + this.gl.deleteProgram(this.prefilterEnvShaderProgram); + this.prefilterEnvShaderProgram = null; + } + + if (this.integrateBRDFShaderProgram) { + if (this.integrateBRDFVertexShader) { + this.gl.detachShader(this.integrateBRDFShaderProgram, this.integrateBRDFVertexShader); + this.gl.deleteShader(this.integrateBRDFVertexShader); + this.integrateBRDFVertexShader = null; + } + if (this.integrateBRDFFragmentShader) { + this.gl.detachShader(this.integrateBRDFShaderProgram, this.integrateBRDFFragmentShader); + this.gl.deleteShader(this.integrateBRDFFragmentShader); + this.integrateBRDFFragmentShader = null; + } + this.gl.deleteProgram(this.integrateBRDFShaderProgram); + this.integrateBRDFShaderProgram = null; + } + + this.gl.deleteBuffer(this.cubeVertexBuffer); + this.gl.deleteBuffer(this.squareVertexBuffer); } public initGL (glContext: WebGL2RenderingContext | WebGLRenderingContext): void { @@ -1088,6 +1164,7 @@ export class ModelRenderer { this.gl.getExtension('MOZ_EXT_texture_filter_anisotropic') || this.gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic') ); + this.colorBufferFloatExt = this.gl.getExtension('EXT_color_buffer_float'); if (this.model.Version >= 1000 && isWebGL2(this.gl)) { this.model.Materials.forEach(material => { @@ -1215,23 +1292,19 @@ export class ModelRenderer { public render (mvMatrix: mat4, pMatrix: mat4, { wireframe, - env = true, + useEnvironmentMap = false, shadowMapTexture, shadowMapMatrix, shadowBias, shadowSmoothingStep } : { wireframe: boolean; - env?: boolean; + useEnvironmentMap?: boolean; shadowMapTexture?: WebGLTexture; shadowMapMatrix?: mat4; shadowBias?: number; shadowSmoothingStep?: number; }): void { - if (env) { - this.renderEnv(mvMatrix, pMatrix); - } - this.gl.useProgram(this.shaderProgram); this.gl.uniformMatrix4fv(this.shaderProgramLocations.pMatrixUniform, false, pMatrix); @@ -1301,7 +1374,7 @@ export class ModelRenderer { const envTexture = this.model.Textures[material.Layers[5]?.TextureID as number].Image; const irradianceMap = this.rendererData.irradianceMap[envTexture]; const prefilteredEnv = this.rendererData.prefilteredEnvMap[envTexture]; - if (env && irradianceMap) { + if (useEnvironmentMap && irradianceMap) { this.gl.uniform1i(this.shaderProgramLocations.hasEnvUniform, 1); this.gl.activeTexture(this.gl.TEXTURE4); this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, irradianceMap); @@ -1403,7 +1476,7 @@ export class ModelRenderer { this.ribbonsController.render(mvMatrix, pMatrix); } - private renderEnv (mvMatrix: mat4, pMatrix: mat4): void { + public renderEnvironment (mvMatrix: mat4, pMatrix: mat4): void { if (!isWebGL2(this.gl)) { return; } @@ -1578,7 +1651,7 @@ export class ModelRenderer { } private processEnvMaps (path: string): void { - if (!this.rendererData.requiredEnvMaps[path] || !this.rendererData.textures[path] || !isWebGL2(this.gl)) { + if (!this.rendererData.requiredEnvMaps[path] || !this.rendererData.textures[path] || !isWebGL2(this.gl) || !this.colorBufferFloatExt) { return; } @@ -1614,8 +1687,7 @@ export class ModelRenderer { this.gl.activeTexture(this.gl.TEXTURE1); this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, cubemap); for (let i = 0; i < 6; ++i) { - // this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGB16F, ENV_MAP_SIZE, ENV_MAP_SIZE, 0, this.gl.RGB, this.gl.FLOAT, null); - this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGB, ENV_MAP_SIZE, ENV_MAP_SIZE, 0, this.gl.RGB, this.gl.UNSIGNED_BYTE, null); + this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGBA16F, ENV_MAP_SIZE, ENV_MAP_SIZE, 0, this.gl.RGBA, this.gl.FLOAT, null); } this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); @@ -1663,8 +1735,7 @@ export class ModelRenderer { this.gl.activeTexture(this.gl.TEXTURE1); this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, diffuseCubemap); for (let i = 0; i < 6; ++i) { - // this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGB16F, ENV_MAP_SIZE, ENV_MAP_SIZE, 0, this.gl.RGB, this.gl.FLOAT, null); - this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGB, ENV_CONVOLUTE_DIFFUSE_SIZE, ENV_CONVOLUTE_DIFFUSE_SIZE, 0, this.gl.RGB, this.gl.UNSIGNED_BYTE, null); + this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGBA16F, ENV_CONVOLUTE_DIFFUSE_SIZE, ENV_CONVOLUTE_DIFFUSE_SIZE, 0, this.gl.RGBA, this.gl.FLOAT, null); } this.gl.texParameteri(this.gl.TEXTURE_CUBE_MAP, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); @@ -1709,15 +1780,15 @@ export class ModelRenderer { const prefilterCubemap = this.rendererData.prefilteredEnvMap[path] = this.gl.createTexture(); this.gl.activeTexture(this.gl.TEXTURE1); this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, prefilterCubemap); - this.gl.texStorage2D(this.gl.TEXTURE_CUBE_MAP, MAX_ENV_MIP_LEVELS, this.gl.RGBA8, ENV_PREFILTER_SIZE, ENV_PREFILTER_SIZE); + this.gl.texStorage2D(this.gl.TEXTURE_CUBE_MAP, MAX_ENV_MIP_LEVELS, this.gl.RGBA16F, ENV_PREFILTER_SIZE, ENV_PREFILTER_SIZE); // for (let i = 0; i < 6; ++i) { // this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGB, ENV_PREFILTER_SIZE, ENV_PREFILTER_SIZE, 0, this.gl.RGB, this.gl.UNSIGNED_BYTE, null); // } for (let mip = 0; mip < MAX_ENV_MIP_LEVELS; ++mip) { for (let i = 0; i < 6; ++i) { const size = ENV_PREFILTER_SIZE * .5 ** mip; - const data = new Uint8Array(size * size * 4); - this.gl.texSubImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, mip, 0, 0, size, size, this.gl.RGBA, this.gl.UNSIGNED_BYTE, data); + const data = new Float32Array(size * size * 4); + this.gl.texSubImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, mip, 0, 0, size, size, this.gl.RGBA, this.gl.FLOAT, data); } } @@ -2088,10 +2159,14 @@ export class ModelRenderer { } private initBRDFLUT(): void { + if (!isWebGL2(this.gl) || !this.colorBufferFloatExt) { + return; + } + this.brdfLUT = this.gl.createTexture(); this.gl.activeTexture(this.gl.TEXTURE0); this.gl.bindTexture(this.gl.TEXTURE_2D, this.brdfLUT); - this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, BRDF_LUT_SIZE, BRDF_LUT_SIZE, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, null); + this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RG16F, BRDF_LUT_SIZE, BRDF_LUT_SIZE, 0, this.gl.RG, this.gl.FLOAT, null); this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);