@tolokoban/tgd
Version:
ToloGameDev library for WebGL2
186 lines • 14.4 kB
JavaScript
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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