nanogl-gltf
Version:
163 lines (162 loc) • 6.99 kB
JavaScript
import PbrMetallicRoughness from './PbrMetallicRoughness';
import { isWebgl2 } from 'nanogl/types';
import Gltf2 from '../types/Gltf2';
import GltfTypes from '../types/GltfTypes';
import Input from 'nanogl-pbr/Input';
import { default as BaseMaterial } from 'nanogl-pbr/Material';
import Gltf from '../Gltf';
import Flag from 'nanogl-pbr/Flag';
import { isAllZeros } from '../lib/Utils';
import { StandardPass } from 'nanogl-pbr/StandardPass';
import { MetalnessSurface } from 'nanogl-pbr/PbrSurface';
const SRC_ALPHA = 0x0302;
const ONE_MINUS_SRC_ALPHA = 0x0303;
/**
* The GltfBaseMaterial element contains the base properties and methods for all Material classes coming from a glTF file
*/
export class GltfBaseMaterial {
constructor() {
this.gltftype = GltfTypes.MATERIAL;
}
get materialPass() {
return this._materialPass;
}
/**
* Create a nanogl-pbr Material object for a given Primitive, using the Pass created in setupMaterials().
* @param gltf GLTF where the Primitive comes from
* @param node Parent Node of the Primitive, unused here
* @param primitive Primitive to create the Material for
*/
createMaterialForPrimitive(gltf, node, primitive) {
const gl = gltf.gl;
this._materialPass.version.set(isWebgl2(gl) ? '300 es' : '100');
const material = new BaseMaterial(gl, this._materialPass.name);
material.addPass(this._materialPass, 'color');
const normal = primitive.attributes.getSemantic('NORMAL');
const tangent = primitive.attributes.getSemantic('TANGENT');
material.inputs.add(new Flag('hasNormals', normal !== null));
material.inputs.add(new Flag('hasTangents', tangent !== null));
const vcColorAttrib = primitive.attributes.getSemantic('COLOR_0');
if (vcColorAttrib !== null) {
// vertex color
const vcInput = new Input('vertexColor', 3);
vcInput.attachAttribute(vcColorAttrib.glslname);
material.inputs.add(vcInput);
}
material.addPass(gltf.depthPass, 'depth');
return material;
}
/**
* Parse the Material data, fill the emissiveFactor, alphaMode, alphaCutoff and doubleSided properties,
* create the PbrMetallicRoughness, NormalTexture, OcclusionTexture and EmissiveTexture elements if needed,
* and call the setupMaterials() method.
*
* Is async as it may need to wait for PbrMetallicRoughness, NormalTexture, OcclusionTexture and EmissiveTexture elements to be created.
* @param gltfLoader GLTFLoader to use
* @param data Data to parse
*/
async parse(gltfLoader, data) {
var _a, _b;
this.emissiveFactor = new Float32Array(data.emissiveFactor || [0, 0, 0]);
this.alphaMode = data.alphaMode || Gltf2.MaterialAlphaMode.OPAQUE;
this.alphaCutoff = (_a = data.alphaCutoff) !== null && _a !== void 0 ? _a : 0.5;
this.doubleSided = (_b = data.doubleSided) !== null && _b !== void 0 ? _b : false;
await this.parsePbrInputsData(gltfLoader, data);
if (data.normalTexture !== undefined) {
this.normalTexture = await gltfLoader._loadElement(data.normalTexture);
}
if (data.occlusionTexture !== undefined) {
this.occlusionTexture = await gltfLoader._loadElement(data.occlusionTexture);
}
if (data.emissiveTexture !== undefined) {
this.emissiveTexture = await gltfLoader._loadElement(data.emissiveTexture);
}
this.name = data.name;
this.setupMaterials();
}
/**
* Parse the PbrMetallicRoughness data, filling the pbrMetallicRoughness property with a new PbrMetallicRoughness element.
* @param gltfLoader GLTFLoader to use
* @param data Data to parse
*/
async parsePbrInputsData(gltfLoader, data) {
if (data.pbrMetallicRoughness !== undefined) {
this.pbrInputsData = this.pbrMetallicRoughness = new PbrMetallicRoughness();
await this.pbrMetallicRoughness.parse(gltfLoader, data.pbrMetallicRoughness);
}
}
/**
* Configure PBR Surface for a given Pass with this Material's options.
* If this.pbrInputsData is defined, call its setupPass() method, otherwise set a MetalnessSurface.
* @param pass Pass to configure
*/
configurePbrSurface(pass) {
if (this.pbrInputsData !== undefined) {
this.pbrInputsData.setupPass(pass);
}
else {
pass.setSurface(new MetalnessSurface());
}
}
/**
* Configure alpha rendering mode for a given Pass with this Material's options
* (depth mask, blend, blendFunc, mask, alphaMode and alphaCutoff uniform)
* @param pass Pass to configure
*/
configureAlpha(pass) {
if (this.alphaMode === Gltf2.MaterialAlphaMode.BLEND) {
pass.glconfig.depthMask(false);
pass.glconfig.enableBlend();
pass.glconfig.blendFunc(SRC_ALPHA, ONE_MINUS_SRC_ALPHA);
pass.mask = Gltf.getRenderConfig().blendedMask;
}
else {
pass.mask = Gltf.getRenderConfig().opaqueMask;
}
pass.alphaMode.set(this.alphaMode);
if (this.alphaMode === Gltf2.MaterialAlphaMode.MASK) {
pass.alphaCutoff.attachUniform('uAlphaCutoff').set(this.alphaCutoff);
}
}
}
/**
* Basic PBR implementation of GltfBaseMaterial using a nanogl-pbr StandardPass
*/
export default class Material extends GltfBaseMaterial {
/**
* Creates a StandardPass and attach emissive, normal and occlusion textures with samplers and their strength factors.
* Also configure alpha rendering mode and PBR surface.
*/
setupMaterials() {
const pass = new StandardPass(this.name);
pass.glconfig.enableDepthTest();
pass.glconfig.enableCullface(!this.doubleSided);
pass.doubleSided.set(this.doubleSided);
this.configureAlpha(pass);
this.configurePbrSurface(pass);
if (this.emissiveTexture) {
const sampler = this.emissiveTexture.createSampler('emissive');
pass.emissive.attach(sampler);
}
if (!isAllZeros(this.emissiveFactor)) {
pass.emissiveFactor.attachUniform('uEmissFactor').set(...this.emissiveFactor);
}
const nrm = this.normalTexture;
if (nrm) {
const sampler = nrm.createSampler('nrmmap');
pass.normal.attach(sampler);
if (nrm.scale !== 1) {
pass.normalScale.attachUniform('uNormalScale').set(nrm.scale);
}
}
const occlu = this.occlusionTexture;
if (occlu) {
const sampler = occlu.createSampler('occlu');
pass.occlusion.attach(sampler);
if (occlu.strength !== 1) {
pass.occlusionStrength.attachUniform('uOccluStrength').set(occlu.strength);
}
}
this._materialPass = pass;
}
}