cesium
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
986 lines (885 loc) • 43.9 kB
JavaScript
define([
'./addToArray',
'./ForEach',
'./numberOfComponentsForType',
'./techniqueParameterForSemantic',
'./webGLConstantToGlslType',
'./glslTypeToWebGLConstant',
'../../Core/clone',
'../../Core/defined',
'../../Core/defaultValue',
'../../Core/WebGLConstants'
], function(
addToArray,
ForEach,
numberOfComponentsForType,
techniqueParameterForSemantic,
webGLConstantToGlslType,
glslTypeToWebGLConstant,
clone,
defined,
defaultValue,
WebGLConstants) {
'use strict';
/**
* @private
*/
function processModelMaterialsCommon(gltf, options) {
options = defaultValue(options, {});
if (!defined(gltf)) {
return undefined;
}
var hasExtension = false;
var extensionsRequired = gltf.extensionsRequired;
var extensionsUsed = gltf.extensionsUsed;
if (defined(extensionsUsed)) {
var index = extensionsUsed.indexOf('KHR_materials_common');
if (index >= 0) {
extensionsUsed.splice(index, 1);
hasExtension = true;
}
if (defined(extensionsRequired)) {
index = extensionsRequired.indexOf('KHR_materials_common');
if (index >= 0) {
extensionsRequired.splice(index, 1);
}
}
}
if (hasExtension) {
if (!defined(gltf.programs)) {
gltf.programs = [];
}
if (!defined(gltf.shaders)) {
gltf.shaders = [];
}
if (!defined(gltf.techniques)) {
gltf.techniques = [];
}
lightDefaults(gltf);
var lightParameters = generateLightParameters(gltf);
// Pre-processing to address incompatibilities between primitives using the same materials. Handles skinning and vertex color incompatibilities.
splitIncompatibleMaterials(gltf);
var techniques = {};
ForEach.material(gltf, function(material) {
if (defined(material.extensions) && defined(material.extensions.KHR_materials_common)) {
var khrMaterialsCommon = material.extensions.KHR_materials_common;
var techniqueKey = getTechniqueKey(khrMaterialsCommon);
var technique = techniques[techniqueKey];
if (!defined(technique)) {
technique = generateTechnique(gltf, khrMaterialsCommon, lightParameters, options);
techniques[techniqueKey] = technique;
}
// Take advantage of the fact that we generate techniques that use the
// same parameter names as the extension values.
material.values = {};
var values = khrMaterialsCommon.values;
for (var valueName in values) {
if (values.hasOwnProperty(valueName)) {
var value = values[valueName];
material.values[valueName] = value;
}
}
material.technique = technique;
delete material.extensions.KHR_materials_common;
if (Object.keys(material.extensions).length === 0) {
delete material.extensions;
}
}
});
if (defined(gltf.extensions)) {
delete gltf.extensions.KHR_materials_common;
if (Object.keys(gltf.extensions).length === 0) {
delete gltf.extensions;
}
}
// If any primitives have semantics that aren't declared in the generated
// shaders, we want to preserve them.
ensureSemanticExistence(gltf);
}
return gltf;
}
function generateLightParameters(gltf) {
var result = {};
var lights;
if (defined(gltf.extensions) && defined(gltf.extensions.KHR_materials_common)) {
lights = gltf.extensions.KHR_materials_common.lights;
}
if (defined(lights)) {
// Figure out which node references the light
var nodes = gltf.nodes;
for (var nodeName in nodes) {
if (nodes.hasOwnProperty(nodeName)) {
var node = nodes[nodeName];
if (defined(node.extensions) && defined(node.extensions.KHR_materials_common)) {
var nodeLightId = node.extensions.KHR_materials_common.light;
if (defined(nodeLightId) && defined(lights[nodeLightId])) {
lights[nodeLightId].node = nodeName;
}
delete node.extensions.KHR_materials_common;
}
}
}
// Add light parameters to result
var lightCount = 0;
for (var lightName in lights) {
if (lights.hasOwnProperty(lightName)) {
var light = lights[lightName];
var lightType = light.type;
if ((lightType !== 'ambient') && !defined(light.node)) {
delete lights[lightName];
continue;
}
var lightBaseName = 'light' + lightCount.toString();
light.baseName = lightBaseName;
switch (lightType) {
case 'ambient':
var ambient = light.ambient;
result[lightBaseName + 'Color'] = {
type: WebGLConstants.FLOAT_VEC3,
value: ambient.color
};
break;
case 'directional':
var directional = light.directional;
result[lightBaseName + 'Color'] = {
type: WebGLConstants.FLOAT_VEC3,
value: directional.color
};
if (defined(light.node)) {
result[lightBaseName + 'Transform'] = {
node: light.node,
semantic: 'MODELVIEW',
type: WebGLConstants.FLOAT_MAT4
};
}
break;
case 'point':
var point = light.point;
result[lightBaseName + 'Color'] = {
type: WebGLConstants.FLOAT_VEC3,
value: point.color
};
if (defined(light.node)) {
result[lightBaseName + 'Transform'] = {
node: light.node,
semantic: 'MODELVIEW',
type: WebGLConstants.FLOAT_MAT4
};
}
result[lightBaseName + 'Attenuation'] = {
type: WebGLConstants.FLOAT_VEC3,
value: [point.constantAttenuation, point.linearAttenuation, point.quadraticAttenuation]
};
break;
case 'spot':
var spot = light.spot;
result[lightBaseName + 'Color'] = {
type: WebGLConstants.FLOAT_VEC3,
value: spot.color
};
if (defined(light.node)) {
result[lightBaseName + 'Transform'] = {
node: light.node,
semantic: 'MODELVIEW',
type: WebGLConstants.FLOAT_MAT4
};
result[lightBaseName + 'InverseTransform'] = {
node: light.node,
semantic: 'MODELVIEWINVERSE',
type: WebGLConstants.FLOAT_MAT4,
useInFragment: true
};
}
result[lightBaseName + 'Attenuation'] = {
type: WebGLConstants.FLOAT_VEC3,
value: [spot.constantAttenuation, spot.linearAttenuation, spot.quadraticAttenuation]
};
result[lightBaseName + 'FallOff'] = {
type: WebGLConstants.FLOAT_VEC2,
value: [spot.fallOffAngle, spot.fallOffExponent]
};
break;
}
++lightCount;
}
}
}
return result;
}
function generateTechnique(gltf, khrMaterialsCommon, lightParameters, options) {
var optimizeForCesium = defaultValue(options.optimizeForCesium, false);
var hasCesiumRTCExtension = defined(gltf.extensions) && defined(gltf.extensions.CESIUM_RTC);
var addBatchIdToGeneratedShaders = defaultValue(options.addBatchIdToGeneratedShaders, false);
var techniques = gltf.techniques;
var shaders = gltf.shaders;
var programs = gltf.programs;
var lightingModel = khrMaterialsCommon.technique.toUpperCase();
var lights;
if (defined(gltf.extensions) && defined(gltf.extensions.KHR_materials_common)) {
lights = gltf.extensions.KHR_materials_common.lights;
}
var parameterValues = khrMaterialsCommon.values;
if (defined(khrMaterialsCommon.transparent)) {
parameterValues.transparent = khrMaterialsCommon.transparent;
}
if (defined(khrMaterialsCommon.doubleSided)) {
parameterValues.doubleSided = khrMaterialsCommon.doubleSided;
}
var jointCount = defaultValue(khrMaterialsCommon.jointCount, 0);
var primitiveInfo = khrMaterialsCommon.extras._pipeline.primitive;
var skinningInfo;
var hasSkinning = false;
var hasVertexColors = false;
if (defined(primitiveInfo)) {
skinningInfo = primitiveInfo.skinning;
hasSkinning = skinningInfo.skinned;
hasVertexColors = primitiveInfo.hasVertexColors;
}
var vertexShader = 'precision highp float;\n';
var fragmentShader = 'precision highp float;\n';
var hasNormals = (lightingModel !== 'CONSTANT');
// Add techniques
var techniqueParameters = {
// Add matrices
modelViewMatrix: {
semantic: hasCesiumRTCExtension ? 'CESIUM_RTC_MODELVIEW' : 'MODELVIEW',
type: WebGLConstants.FLOAT_MAT4
},
projectionMatrix: {
semantic: 'PROJECTION',
type: WebGLConstants.FLOAT_MAT4
}
};
if (hasNormals) {
techniqueParameters.normalMatrix = {
semantic: 'MODELVIEWINVERSETRANSPOSE',
type: WebGLConstants.FLOAT_MAT3
};
}
if (hasSkinning) {
techniqueParameters.jointMatrix = {
count: jointCount,
semantic: 'JOINTMATRIX',
type: WebGLConstants.FLOAT_MAT4
};
}
// Add material parameters
var lowerCase;
var hasTexCoords = false;
for (var name in parameterValues) {
//generate shader parameters for KHR_materials_common attributes
//(including a check, because some boolean flags should not be used as shader parameters)
if (parameterValues.hasOwnProperty(name) && (name !== 'transparent') && (name !== 'doubleSided')) {
var valType = getKHRMaterialsCommonValueType(name, parameterValues[name]);
lowerCase = name.toLowerCase();
if (!hasTexCoords && (valType === WebGLConstants.SAMPLER_2D)) {
hasTexCoords = true;
}
techniqueParameters[lowerCase] = {
type: valType
};
}
}
// Give the diffuse uniform a semantic to support color replacement in 3D Tiles
if (defined(techniqueParameters.diffuse) && optimizeForCesium) {
techniqueParameters.diffuse.semantic = '_3DTILESDIFFUSE';
}
// Copy light parameters into technique parameters
if (defined(lightParameters)) {
for (var lightParamName in lightParameters) {
if (lightParameters.hasOwnProperty(lightParamName)) {
techniqueParameters[lightParamName] = lightParameters[lightParamName];
}
}
}
// Generate uniforms object before attributes are added
var techniqueUniforms = {};
for (var paramName in techniqueParameters) {
if (techniqueParameters.hasOwnProperty(paramName) && paramName !== 'extras') {
var param = techniqueParameters[paramName];
techniqueUniforms['u_' + paramName] = paramName;
var arraySize = defined(param.count) ? '[' + param.count + ']' : '';
if (((param.type !== WebGLConstants.FLOAT_MAT3) && (param.type !== WebGLConstants.FLOAT_MAT4)) ||
param.useInFragment) {
fragmentShader += 'uniform ' + webGLConstantToGlslType(param.type) + ' u_' + paramName + arraySize + ';\n';
delete param.useInFragment;
} else {
vertexShader += 'uniform ' + webGLConstantToGlslType(param.type) + ' u_' + paramName + arraySize + ';\n';
}
}
}
// Add attributes with semantics
var vertexShaderMain = '';
if (hasSkinning) {
var i, j;
var numberOfComponents = numberOfComponentsForType(skinningInfo.type);
var matrix = false;
if (skinningInfo.type.indexOf('MAT') === 0) {
matrix = true;
numberOfComponents = Math.sqrt(numberOfComponents);
}
if (!matrix) {
for (i = 0; i < numberOfComponents; i++) {
if (i === 0) {
vertexShaderMain += ' mat4 skinMat = ';
} else {
vertexShaderMain += ' skinMat += ';
}
vertexShaderMain += 'a_weight[' + i + '] * u_jointMatrix[int(a_joint[' + i + '])];\n';
}
} else {
for (i = 0; i < numberOfComponents; i++) {
for (j = 0; j < numberOfComponents; j++) {
if (i === 0 && j === 0) {
vertexShaderMain += ' mat4 skinMat = ';
} else {
vertexShaderMain += ' skinMat += ';
}
vertexShaderMain += 'a_weight[' + i + '][' + j + '] * u_jointMatrix[int(a_joint[' + i + '][' + j + '])];\n';
}
}
}
}
// Add position always
var techniqueAttributes = {
a_position: 'position'
};
techniqueParameters.position = {
semantic: 'POSITION',
type: WebGLConstants.FLOAT_VEC3
};
vertexShader += 'attribute vec3 a_position;\n';
vertexShader += 'varying vec3 v_positionEC;\n';
if (hasSkinning) {
vertexShaderMain += ' vec4 pos = u_modelViewMatrix * skinMat * vec4(a_position,1.0);\n';
} else {
vertexShaderMain += ' vec4 pos = u_modelViewMatrix * vec4(a_position,1.0);\n';
}
vertexShaderMain += ' v_positionEC = pos.xyz;\n';
vertexShaderMain += ' gl_Position = u_projectionMatrix * pos;\n';
fragmentShader += 'varying vec3 v_positionEC;\n';
// Add normal if we don't have constant lighting
if (hasNormals) {
techniqueAttributes.a_normal = 'normal';
techniqueParameters.normal = {
semantic: 'NORMAL',
type: WebGLConstants.FLOAT_VEC3
};
vertexShader += 'attribute vec3 a_normal;\n';
vertexShader += 'varying vec3 v_normal;\n';
if (hasSkinning) {
vertexShaderMain += ' v_normal = u_normalMatrix * mat3(skinMat) * a_normal;\n';
} else {
vertexShaderMain += ' v_normal = u_normalMatrix * a_normal;\n';
}
fragmentShader += 'varying vec3 v_normal;\n';
}
// Add texture coordinates if the material uses them
var v_texcoord;
if (hasTexCoords) {
techniqueAttributes.a_texcoord_0 = 'texcoord_0';
techniqueParameters.texcoord_0 = {
semantic: 'TEXCOORD_0',
type: WebGLConstants.FLOAT_VEC2
};
v_texcoord = 'v_texcoord_0';
vertexShader += 'attribute vec2 a_texcoord_0;\n';
vertexShader += 'varying vec2 ' + v_texcoord + ';\n';
vertexShaderMain += ' ' + v_texcoord + ' = a_texcoord_0;\n';
fragmentShader += 'varying vec2 ' + v_texcoord + ';\n';
}
if (hasSkinning) {
techniqueAttributes.a_joint = 'joint';
var attributeType = getShaderVariable(skinningInfo.type);
var webGLConstant = glslTypeToWebGLConstant(attributeType);
techniqueParameters.joint = {
semantic: 'JOINT',
type: webGLConstant
};
techniqueAttributes.a_weight = 'weight';
techniqueParameters.weight = {
semantic: 'WEIGHT',
type: webGLConstant
};
vertexShader += 'attribute ' + attributeType + ' a_joint;\n';
vertexShader += 'attribute ' + attributeType + ' a_weight;\n';
}
if (hasVertexColors) {
techniqueAttributes.a_vertexColor = 'vertexColor';
techniqueParameters.vertexColor = {
semantic: 'COLOR_0',
type: WebGLConstants.FLOAT_VEC4
};
vertexShader += 'attribute vec4 a_vertexColor;\n';
vertexShader += 'varying vec4 v_vertexColor;\n';
vertexShaderMain += ' v_vertexColor = a_vertexColor;\n';
fragmentShader += 'varying vec4 v_vertexColor;\n';
}
if (addBatchIdToGeneratedShaders) {
techniqueAttributes.a_batchId = 'batchId';
techniqueParameters.batchId = {
semantic: '_BATCHID',
type: WebGLConstants.FLOAT
};
vertexShader += 'attribute float a_batchId;\n';
}
var hasSpecular = hasNormals && ((lightingModel === 'BLINN') || (lightingModel === 'PHONG')) &&
defined(techniqueParameters.specular) && defined(techniqueParameters.shininess) &&
(techniqueParameters.shininess > 0.0);
// Generate lighting code blocks
var hasNonAmbientLights = false;
var hasAmbientLights = false;
var fragmentLightingBlock = '';
for (var lightName in lights) {
if (lights.hasOwnProperty(lightName)) {
var light = lights[lightName];
var lightType = light.type.toLowerCase();
var lightBaseName = light.baseName;
fragmentLightingBlock += ' {\n';
var lightColorName = 'u_' + lightBaseName + 'Color';
var varyingDirectionName;
var varyingPositionName;
if (lightType === 'ambient') {
hasAmbientLights = true;
fragmentLightingBlock += ' ambientLight += ' + lightColorName + ';\n';
} else if (hasNormals) {
hasNonAmbientLights = true;
varyingDirectionName = 'v_' + lightBaseName + 'Direction';
varyingPositionName = 'v_' + lightBaseName + 'Position';
if (lightType !== 'point') {
vertexShader += 'varying vec3 ' + varyingDirectionName + ';\n';
fragmentShader += 'varying vec3 ' + varyingDirectionName + ';\n';
vertexShaderMain += ' ' + varyingDirectionName + ' = mat3(u_' + lightBaseName + 'Transform) * vec3(0.,0.,1.);\n';
if (lightType === 'directional') {
fragmentLightingBlock += ' vec3 l = normalize(' + varyingDirectionName + ');\n';
}
}
if (lightType !== 'directional') {
vertexShader += 'varying vec3 ' + varyingPositionName + ';\n';
fragmentShader += 'varying vec3 ' + varyingPositionName + ';\n';
vertexShaderMain += ' ' + varyingPositionName + ' = u_' + lightBaseName + 'Transform[3].xyz;\n';
fragmentLightingBlock += ' vec3 VP = ' + varyingPositionName + ' - v_positionEC;\n';
fragmentLightingBlock += ' vec3 l = normalize(VP);\n';
fragmentLightingBlock += ' float range = length(VP);\n';
fragmentLightingBlock += ' float attenuation = 1.0 / (u_' + lightBaseName + 'Attenuation.x + ';
fragmentLightingBlock += '(u_' + lightBaseName + 'Attenuation.y * range) + ';
fragmentLightingBlock += '(u_' + lightBaseName + 'Attenuation.z * range * range));\n';
} else {
fragmentLightingBlock += ' float attenuation = 1.0;\n';
}
if (lightType === 'spot') {
fragmentLightingBlock += ' float spotDot = dot(l, normalize(' + varyingDirectionName + '));\n';
fragmentLightingBlock += ' if (spotDot < cos(u_' + lightBaseName + 'FallOff.x * 0.5))\n';
fragmentLightingBlock += ' {\n';
fragmentLightingBlock += ' attenuation = 0.0;\n';
fragmentLightingBlock += ' }\n';
fragmentLightingBlock += ' else\n';
fragmentLightingBlock += ' {\n';
fragmentLightingBlock += ' attenuation *= max(0.0, pow(spotDot, u_' + lightBaseName + 'FallOff.y));\n';
fragmentLightingBlock += ' }\n';
}
fragmentLightingBlock += ' diffuseLight += ' + lightColorName + '* max(dot(normal,l), 0.) * attenuation;\n';
if (hasSpecular) {
if (lightingModel === 'BLINN') {
fragmentLightingBlock += ' vec3 h = normalize(l + viewDir);\n';
fragmentLightingBlock += ' float specularIntensity = max(0., pow(max(dot(normal, h), 0.), u_shininess)) * attenuation;\n';
} else { // PHONG
fragmentLightingBlock += ' vec3 reflectDir = reflect(-l, normal);\n';
fragmentLightingBlock += ' float specularIntensity = max(0., pow(max(dot(reflectDir, viewDir), 0.), u_shininess)) * attenuation;\n';
}
fragmentLightingBlock += ' specularLight += ' + lightColorName + ' * specularIntensity;\n';
}
}
fragmentLightingBlock += ' }\n';
}
}
if (!hasAmbientLights) {
// Add an ambient light if we don't have one
fragmentLightingBlock += ' ambientLight += vec3(0.2, 0.2, 0.2);\n';
}
if (!hasNonAmbientLights && (lightingModel !== 'CONSTANT')) {
if (optimizeForCesium) {
fragmentLightingBlock += ' vec3 l = normalize(czm_sunDirectionEC);\n';
} else {
fragmentLightingBlock += ' vec3 l = vec3(0.0, 0.0, 1.0);\n';
}
var minimumLighting = optimizeForCesium ? '0.2' : '0.0'; // Use strings instead of values as 0.0 -> 0 when stringified
fragmentLightingBlock += ' diffuseLight += vec3(1.0, 1.0, 1.0) * max(dot(normal,l), ' + minimumLighting + ');\n';
if (hasSpecular) {
if (lightingModel === 'BLINN') {
fragmentLightingBlock += ' vec3 h = normalize(l + viewDir);\n';
fragmentLightingBlock += ' float specularIntensity = max(0., pow(max(dot(normal, h), 0.), u_shininess));\n';
} else { // PHONG
fragmentLightingBlock += ' vec3 reflectDir = reflect(-l, normal);\n';
fragmentLightingBlock += ' float specularIntensity = max(0., pow(max(dot(reflectDir, viewDir), 0.), u_shininess));\n';
}
fragmentLightingBlock += ' specularLight += vec3(1.0, 1.0, 1.0) * specularIntensity;\n';
}
}
vertexShader += 'void main(void) {\n';
vertexShader += vertexShaderMain;
vertexShader += '}\n';
fragmentShader += 'void main(void) {\n';
var colorCreationBlock = ' vec3 color = vec3(0.0, 0.0, 0.0);\n';
if (hasNormals) {
fragmentShader += ' vec3 normal = normalize(v_normal);\n';
if (khrMaterialsCommon.doubleSided) {
fragmentShader += ' if (gl_FrontFacing == false)\n';
fragmentShader += ' {\n';
fragmentShader += ' normal = -normal;\n';
fragmentShader += ' }\n';
}
}
var finalColorComputation;
if (lightingModel !== 'CONSTANT') {
if (defined(techniqueParameters.diffuse)) {
if (techniqueParameters.diffuse.type === WebGLConstants.SAMPLER_2D) {
fragmentShader += ' vec4 diffuse = texture2D(u_diffuse, ' + v_texcoord + ');\n';
} else {
fragmentShader += ' vec4 diffuse = u_diffuse;\n';
}
fragmentShader += ' vec3 diffuseLight = vec3(0.0, 0.0, 0.0);\n';
colorCreationBlock += ' color += diffuse.rgb * diffuseLight;\n';
}
if (hasSpecular) {
if (techniqueParameters.specular.type === WebGLConstants.SAMPLER_2D) {
fragmentShader += ' vec3 specular = texture2D(u_specular, ' + v_texcoord + ').rgb;\n';
} else {
fragmentShader += ' vec3 specular = u_specular.rgb;\n';
}
fragmentShader += ' vec3 specularLight = vec3(0.0, 0.0, 0.0);\n';
colorCreationBlock += ' color += specular * specularLight;\n';
}
if (defined(techniqueParameters.transparency)) {
finalColorComputation = ' gl_FragColor = vec4(color * diffuse.a * u_transparency, diffuse.a * u_transparency);\n';
} else {
finalColorComputation = ' gl_FragColor = vec4(color * diffuse.a, diffuse.a);\n';
}
} else {
if (defined(techniqueParameters.transparency)) {
finalColorComputation = ' gl_FragColor = vec4(color * u_transparency, u_transparency);\n';
} else {
finalColorComputation = ' gl_FragColor = vec4(color, 1.0);\n';
}
}
if (hasVertexColors) {
colorCreationBlock += ' color *= v_vertexColor.rgb;\n';
}
if (defined(techniqueParameters.emission)) {
if (techniqueParameters.emission.type === WebGLConstants.SAMPLER_2D) {
fragmentShader += ' vec3 emission = texture2D(u_emission, ' + v_texcoord + ').rgb;\n';
} else {
fragmentShader += ' vec3 emission = u_emission.rgb;\n';
}
colorCreationBlock += ' color += emission;\n';
}
if (defined(techniqueParameters.ambient) || (lightingModel !== 'CONSTANT')) {
if (defined(techniqueParameters.ambient)) {
if (techniqueParameters.ambient.type === WebGLConstants.SAMPLER_2D) {
fragmentShader += ' vec3 ambient = texture2D(u_ambient, ' + v_texcoord + ').rgb;\n';
} else {
fragmentShader += ' vec3 ambient = u_ambient.rgb;\n';
}
} else {
fragmentShader += ' vec3 ambient = diffuse.rgb;\n';
}
colorCreationBlock += ' color += ambient * ambientLight;\n';
}
fragmentShader += ' vec3 viewDir = -normalize(v_positionEC);\n';
fragmentShader += ' vec3 ambientLight = vec3(0.0, 0.0, 0.0);\n';
// Add in light computations
fragmentShader += fragmentLightingBlock;
fragmentShader += colorCreationBlock;
fragmentShader += finalColorComputation;
fragmentShader += '}\n';
var techniqueStates;
if (parameterValues.transparent) {
techniqueStates = {
enable: [
WebGLConstants.DEPTH_TEST,
WebGLConstants.BLEND
],
functions: {
depthMask : [false],
blendEquationSeparate: [
WebGLConstants.FUNC_ADD,
WebGLConstants.FUNC_ADD
],
blendFuncSeparate: [
WebGLConstants.ONE,
WebGLConstants.ONE_MINUS_SRC_ALPHA,
WebGLConstants.ONE,
WebGLConstants.ONE_MINUS_SRC_ALPHA
]
}
};
} else if (khrMaterialsCommon.doubleSided) {
techniqueStates = {
enable: [
WebGLConstants.DEPTH_TEST
]
};
} else { // Not transparent or double sided
techniqueStates = {
enable: [
WebGLConstants.CULL_FACE,
WebGLConstants.DEPTH_TEST
]
};
}
// Add shaders
var vertexShaderId = addToArray(shaders, {
type: WebGLConstants.VERTEX_SHADER,
extras: {
_pipeline: {
source: vertexShader,
extension: '.glsl'
}
}
});
var fragmentShaderId = addToArray(shaders, {
type: WebGLConstants.FRAGMENT_SHADER,
extras: {
_pipeline: {
source: fragmentShader,
extension: '.glsl'
}
}
});
// Add program
var programAttributes = Object.keys(techniqueAttributes);
var programId = addToArray(programs, {
attributes: programAttributes,
fragmentShader: fragmentShaderId,
vertexShader: vertexShaderId
});
var techniqueId = addToArray(techniques, {
attributes: techniqueAttributes,
parameters: techniqueParameters,
program: programId,
states: techniqueStates,
uniforms: techniqueUniforms
});
return techniqueId;
}
function getKHRMaterialsCommonValueType(paramName, paramValue) {
var value;
// Backwards compatibility for COLLADA2GLTF v1.0-draft when it encoding
// materials using KHR_materials_common with explicit type/value members
if (defined(paramValue.value)) {
value = paramValue.value;
} else if (defined(paramValue.index)) {
value = [paramValue.index];
} else {
value = paramValue;
}
switch (paramName) {
case 'ambient':
return value.length === 1 ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4;
case 'diffuse':
return value.length === 1 ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4;
case 'emission':
return value.length === 1 ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4;
case 'specular':
return value.length === 1 ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4;
case 'shininess':
return WebGLConstants.FLOAT;
case 'transparency':
return WebGLConstants.FLOAT;
// these two are usually not used directly within shaders,
// they are just added here for completeness
case 'transparent':
return WebGLConstants.BOOL;
case 'doubleSided':
return WebGLConstants.BOOL;
}
}
function getTechniqueKey(khrMaterialsCommon) {
var techniqueKey = '';
techniqueKey += 'technique:' + khrMaterialsCommon.technique + ';';
var values = khrMaterialsCommon.values;
var keys = Object.keys(values).sort();
var keysCount = keys.length;
for (var i = 0; i < keysCount; ++i) {
var name = keys[i];
if (values.hasOwnProperty(name)) {
techniqueKey += name + ':' + getKHRMaterialsCommonValueType(name, values[name]);
techniqueKey += ';';
}
}
var doubleSided = defaultValue(khrMaterialsCommon.doubleSided, defaultValue(khrMaterialsCommon.values.doubleSided, false));
techniqueKey += doubleSided.toString() + ';';
var transparent = defaultValue(khrMaterialsCommon.transparent, defaultValue(khrMaterialsCommon.values.transparent, false));
techniqueKey += transparent.toString() + ';';
var jointCount = defaultValue(khrMaterialsCommon.jointCount, 0);
techniqueKey += jointCount.toString() + ';';
var primitiveInfo = khrMaterialsCommon.extras._pipeline.primitive;
if (defined(primitiveInfo)) {
var skinningInfo = primitiveInfo.skinning;
if (jointCount > 0) {
techniqueKey += skinningInfo.type + ';';
}
techniqueKey += primitiveInfo.hasVertexColors;
}
return techniqueKey;
}
function lightDefaults(gltf) {
if (!defined(gltf.extensions)) {
gltf.extensions = {};
}
var extensions = gltf.extensions;
if (!defined(extensions.KHR_materials_common)) {
extensions.KHR_materials_common = {};
}
var khrMaterialsCommon = extensions.KHR_materials_common;
if (!defined(khrMaterialsCommon.lights)) {
khrMaterialsCommon.lights = {};
}
var lights = khrMaterialsCommon.lights;
var lightsLength = lights.length;
for (var lightId = 0; lightId < lightsLength; lightId++) {
var light = lights[lightId];
if (light.type === 'ambient') {
if (!defined(light.ambient)) {
light.ambient = {};
}
var ambientLight = light.ambient;
if (!defined(ambientLight.color)) {
ambientLight.color = [1.0, 1.0, 1.0];
}
} else if (light.type === 'directional') {
if (!defined(light.directional)) {
light.directional = {};
}
var directionalLight = light.directional;
if (!defined(directionalLight.color)) {
directionalLight.color = [1.0, 1.0, 1.0];
}
} else if (light.type === 'point') {
if (!defined(light.point)) {
light.point = {};
}
var pointLight = light.point;
if (!defined(pointLight.color)) {
pointLight.color = [1.0, 1.0, 1.0];
}
pointLight.constantAttenuation = defaultValue(pointLight.constantAttenuation, 1.0);
pointLight.linearAttenuation = defaultValue(pointLight.linearAttenuation, 0.0);
pointLight.quadraticAttenuation = defaultValue(pointLight.quadraticAttenuation, 0.0);
} else if (light.type === 'spot') {
if (!defined(light.spot)) {
light.spot = {};
}
var spotLight = light.spot;
if (!defined(spotLight.color)) {
spotLight.color = [1.0, 1.0, 1.0];
}
spotLight.constantAttenuation = defaultValue(spotLight.constantAttenuation, 1.0);
spotLight.fallOffAngle = defaultValue(spotLight.fallOffAngle, 3.14159265);
spotLight.fallOffExponent = defaultValue(spotLight.fallOffExponent, 0.0);
spotLight.linearAttenuation = defaultValue(spotLight.linearAttenuation, 0.0);
spotLight.quadraticAttenuation = defaultValue(spotLight.quadraticAttenuation, 0.0);
}
}
}
function getShaderVariable(type) {
if (type === 'SCALAR') {
return 'float';
}
return type.toLowerCase();
}
function ensureSemanticExistenceForPrimitive(gltf, primitive) {
var accessors = gltf.accessors;
var materials = gltf.materials;
var techniques = gltf.techniques;
var programs = gltf.programs;
var shaders = gltf.shaders;
var attributes = primitive.attributes;
var material = materials[primitive.material];
var technique = techniques[material.technique];
var program = programs[technique.program];
var vertexShader = shaders[program.vertexShader];
for (var semantic in attributes) {
if (attributes.hasOwnProperty(semantic)) {
if (!defined(techniqueParameterForSemantic(technique, semantic))) {
var accessorId = attributes[semantic];
var accessor = accessors[accessorId];
var lowerCase = semantic.toLowerCase();
if (lowerCase.charAt(0) === '_') {
lowerCase = lowerCase.slice(1);
}
var attributeName = 'a_' + lowerCase;
technique.parameters[lowerCase] = {
semantic: semantic,
type: accessor.componentType
};
technique.attributes[attributeName] = lowerCase;
program.attributes.push(attributeName);
var pipelineExtras = vertexShader.extras._pipeline;
var shaderText = pipelineExtras.source;
shaderText = 'attribute ' + getShaderVariable(accessor.type) + ' ' + attributeName + ';\n' + shaderText;
pipelineExtras.source = shaderText;
}
}
}
}
function ensureSemanticExistence(gltf) {
ForEach.mesh(gltf, function(mesh) {
ForEach.meshPrimitive(mesh, function(primitive) {
ensureSemanticExistenceForPrimitive(gltf, primitive);
});
});
}
function splitIncompatibleMaterials(gltf) {
var accessors = gltf.accessors;
var materials = gltf.materials;
ForEach.mesh(gltf, function(mesh) {
ForEach.meshPrimitive(mesh, function(primitive) {
var materialId = primitive.material;
var material = materials[materialId];
if (defined(material.extensions) && defined(material.extensions.KHR_materials_common)) {
var khrMaterialsCommon = material.extensions.KHR_materials_common;
var jointAccessorId = primitive.attributes.JOINT;
var componentType;
var type;
if (defined(jointAccessorId)) {
var jointAccessor = accessors[jointAccessorId];
componentType = jointAccessor.componentType;
type = jointAccessor.type;
}
var isSkinned = defined(jointAccessorId);
var hasVertexColors = defined(primitive.attributes.COLOR_0);
var primitiveInfo = khrMaterialsCommon.extras._pipeline.primitive;
if (!defined(primitiveInfo)) {
khrMaterialsCommon.extras._pipeline.primitive = {
skinning : {
skinned: isSkinned,
componentType: componentType,
type: type
},
hasVertexColors : hasVertexColors
};
} else if ((primitiveInfo.skinning.skinned !== isSkinned) || (primitiveInfo.skinning.type !== type) || (primitiveInfo.hasVertexColors !== hasVertexColors)) {
// This primitive uses the same material as another one that either:
// * Isn't skinned
// * Uses a different type to store joints and weights
// * Doesn't have vertex colors
var clonedMaterial = clone(material, true);
clonedMaterial.extensions.KHR_materials_common.extras._pipeline.primitive = {
skinning : {
skinned: isSkinned,
componentType: componentType,
type: type
},
hasVertexColors : hasVertexColors
};
// Split this off as a separate material
materialId = addToArray(materials, clonedMaterial);
primitive.material = materialId;
}
}
});
});
}
return processModelMaterialsCommon;
});