UNPKG

@tolokoban/tgd

Version:

ToloGameDev library for WebGL2

186 lines 14.4 kB
import { TgdVec4 } from "./../math/index.js"; import { TgdMaterial } from "./material.js"; import { TgdLight } from "./../light/index.js"; import { tgdCodeConstants } from "./../code/index.js"; export class TgdMaterialGltf extends TgdMaterial { constructor(options) { const uniforms = { uniLight: "vec3", uniLightDir: "vec3", uniAmbient: "vec3", uniSpecularExponent: "float", uniSpecularIntensity: "float", uniModelViewMatrix: "mat4", uniCameraPosition: "vec3", texNormal: "sampler2D", texAlbedo: "sampler2D", texEmission: "sampler2D", texMetallicRoughness: "sampler2D", texSkybox: "samplerCube", }; if (options.textures.occlusion) { uniforms["texOcclusion"] = "sampler2D"; } const varyings = { varPosition: "vec4", varNormal: "vec3", varUV: "vec2", }; super({ uniforms, varyings, vertexShaderCode: () => [ `varUV = ${this.attUV};`, `varNormal = mat3(uniTransfoMatrix) * ${this.attNormal};`, `varPosition = uniTransfoMatrix * ${this.attPosition};`, ], extraFragmentShaderFunctions: { ...tgdCodeConstants("PI"), DistributionGGX: [ "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 / max(denom, 0.001);", ], "}", ], GeometrySchlickGGX: [ "float GeometrySchlickGGX(float NdotV, float roughness) {", [ "float r = (roughness + 1.0);", "float k = (r * r) / 8.0;", "return NdotV / (NdotV * (1.0 - k) + k);", ], "}", ], GeometrySmith: [ "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);", "return GeometrySchlickGGX(NdotV, roughness) * GeometrySchlickGGX(NdotL, roughness);", ], "}", ], fresnelSchlick: [ "vec3 fresnelSchlick(float cosTheta, vec3 F0) {", ["return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);"], "}", ], fresnelSchlickRoughness: [ "// Roughness-dependent Fresnel for IBL", "vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {", ["return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);"], "}", ], getNormalFromMap: [ "// --- TBN Generation (Screen-space derivatives) ---", "vec3 getNormalFromMap() {", [ "vec3 tangentNormal = texture(texNormal, varUV).xyz * 2.0 - 1.0;", "", "vec3 Q1 = dFdx(varPosition.xyz);", "vec3 Q2 = dFdy(varPosition.xyz);", "vec2 st1 = dFdx(varUV);", "vec2 st2 = dFdy(varUV);", "", "vec3 N = normalize(varNormal);", "vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);", "vec3 B = -normalize(cross(N, T));", "mat3 TBN = mat3(T, B, N);", "", "return normalize(TBN * tangentNormal);", ], "}", ], }, fragmentShaderCode: [ "// 1. Fetch Material Properties", "vec3 albedo = texture(texAlbedo, varUV).rgb;", "albedo = pow(albedo, vec3(2.2)); // Convert to Linear", "float roughness = texture(texMetallicRoughness, varUV).g;", "float metallic = texture(texMetallicRoughness, varUV).b;", options.textures.occlusion ? "float ao = texture(texOcclusion, varUV).r;" : "// No ambient occlusion texture", "vec3 emission = texture(texEmission, varUV).rgb;", "", "// 2. Setup Vectors", "vec3 N = getNormalFromMap();", "vec3 V = normalize(uniCameraPosition - varPosition.xyz);", "vec3 R = reflect(-V, N); ", "", "// 3. Fresnel Reflectance at Zero Incidence (F0)", "vec3 F0 = vec3(0.04); ", "F0 = mix(F0, albedo, metallic);", "", "// 4. Lighting Calculation (Using Skybox as a Simple Light Source)", "// For a full PBR, you'd iterate over point lights here. ", "// Below is the Ambient/IBL contribution which defines the look.", "", "vec3 F = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness);", "", "vec3 kS = F;", "vec3 kD = (1.0 - kS) * (1.0 - metallic);", "", "// Simple IBL Diffuse", "vec3 irradiance = texture(texSkybox, N).rgb;", "vec3 diffuse = irradiance * albedo;", "", "// Simple IBL Specular (Sampling skybox based on roughness)", "// Note: In a real engine, you'd use a pre-filtered mipmapped radiance map", "vec3 prefilteredColor = textureLod(texSkybox, R, roughness * 7.0).rgb; ", "vec3 specular = prefilteredColor * F;", "", "// 5. Combine and Apply AO/Emission", options.textures.occlusion ? "vec3 ambient = (kD * diffuse + specular) * ao;" : "vec3 ambient = (kD * diffuse + specular);", "vec3 color = ambient + emission;", "", "// 6. Tonemapping and Gamma Correction", "color = color / (color + vec3(1.0)); // Reinhard", "color = pow(color, vec3(1.0/2.2));", "", "return vec4(color, 1.0);", ], setUniforms: ({ program, context }) => { program.uniform3fv("uniCameraPosition", context.camera.transfo.actualPosition); const { textureNormal, textureAlbedo, textureEmission, textureOcclusion, textureMetallicRoughness, textureSkybox, } = this; let unit = 0; textureNormal.activate(unit++, program, "texNormal"); textureAlbedo.activate(unit++, program, "texAlbedo"); textureEmission.activate(unit++, program, "texEmission"); if (textureOcclusion) textureOcclusion.activate(unit++, program, "texOcclusion"); textureMetallicRoughness.activate(unit++, program, "texMetallicRoughness"); textureSkybox.activate(unit++, program, "texSkybox"); }, delete: () => { this.textureNormal.delete(); this.textureAlbedo.delete(); this.textureEmission.delete(); this.textureOcclusion?.delete(); this.textureMetallicRoughness.delete(); this.textureSkybox.delete(); }, }); this.light = new TgdLight(); this.ambient = new TgdLight({ color: new TgdVec4(0.2, 0.1, 0, 0) }); this.specularExponent = 32; this.specularIntensity = 0.8; this.textureNormal = options.textures.normal; this.textureAlbedo = options.textures.albedo; this.textureEmission = options.textures.emission; this.textureOcclusion = options.textures.occlusion; this.textureMetallicRoughness = options.textures.metallicRoughness; this.textureSkybox = options.textures.skybox; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2x0Zi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9tYXRlcmlhbC9nbHRmLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxXQUFXLENBQUE7QUFDbkMsT0FBTyxFQUFFLFdBQVcsRUFBc0IsTUFBTSxZQUFZLENBQUE7QUFDNUQsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLFlBQVksQ0FBQTtBQUVyQyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxXQUFXLENBQUE7QUFhNUMsTUFBTSxPQUFPLGVBQWdCLFNBQVEsV0FBVztJQWE1QyxZQUFZLE9BQTJGO1FBQ25HLE1BQU0sUUFBUSxHQUF5QztZQUNuRCxRQUFRLEVBQUUsTUFBTTtZQUNoQixXQUFXLEVBQUUsTUFBTTtZQUNuQixVQUFVLEVBQUUsTUFBTTtZQUNsQixtQkFBbUIsRUFBRSxPQUFPO1lBQzVCLG9CQUFvQixFQUFFLE9BQU87WUFDN0Isa0JBQWtCLEVBQUUsTUFBTTtZQUMxQixpQkFBaUIsRUFBRSxNQUFNO1lBQ3pCLFNBQVMsRUFBRSxXQUFXO1lBQ3RCLFNBQVMsRUFBRSxXQUFXO1lBQ3RCLFdBQVcsRUFBRSxXQUFXO1lBQ3hCLG9CQUFvQixFQUFFLFdBQVc7WUFDakMsU0FBUyxFQUFFLGFBQWE7U0FDM0IsQ0FBQTtRQUNELElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUM3QixRQUFRLENBQUMsY0FBYyxDQUFDLEdBQUcsV0FBVyxDQUFBO1FBQzFDLENBQUM7UUFDRCxNQUFNLFFBQVEsR0FBMkM7WUFDckQsV0FBVyxFQUFFLE1BQU07WUFDbkIsU0FBUyxFQUFFLE1BQU07WUFDakIsS0FBSyxFQUFFLE1BQU07U0FDaEIsQ0FBQTtRQUVELEtBQUssQ0FBQztZQUNGLFFBQVE7WUFDUixRQUFRO1lBQ1IsZ0JBQWdCLEVBQUUsR0FBRyxFQUFFLENBQUM7Z0JBQ3BCLFdBQVcsSUFBSSxDQUFD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