diff --git a/CHANGES.md b/CHANGES.md index 8cafa41909bd..1ab7b1274297 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -41,6 +41,8 @@ Change Log * Fixed crash bug in PolylineCollection when a polyline was updated and removed at the same time. [#6455](https://github.com/AnalyticalGraphicsInc/cesium/pull/6455) * Fixed Imagery Layers Texture Filters Sandcastle example. [#6472](https://github.com/AnalyticalGraphicsInc/cesium/pull/6472). * Fixed a bug causing Cesium 3D Tilesets to not clip properly when tiles were unloaded and reloaded. [#6484](https://github.com/AnalyticalGraphicsInc/cesium/issues/6484) +* Improved rendering of glTF models that don't contain normals with a temporary unlit shader workaround. [#6501](https://github.com/AnalyticalGraphicsInc/cesium/pull/6501) +* Fixed rendering of glTF models with emissive-only materials. [#6501](https://github.com/AnalyticalGraphicsInc/cesium/pull/6501) ### 1.44 - 2018-04-02 diff --git a/Source/ThirdParty/GltfPipeline/processPbrMetallicRoughness.js b/Source/ThirdParty/GltfPipeline/processPbrMetallicRoughness.js index eb3c0effc1c9..54383682051f 100644 --- a/Source/ThirdParty/GltfPipeline/processPbrMetallicRoughness.js +++ b/Source/ThirdParty/GltfPipeline/processPbrMetallicRoughness.js @@ -30,7 +30,7 @@ define([ var hasPbrMetallicRoughness = false; ForEach.material(gltf, function(material) { - if (defined(material.pbrMetallicRoughness)) { + if (isPbrMaterial(material)) { hasPbrMetallicRoughness = true; } }); @@ -46,17 +46,17 @@ define([ gltf.techniques = []; } - // Pre-processing to address incompatibilities between primitives using the same materials. Handles skinning and vertex color incompatibilities. + // Pre-processing to address incompatibilities between primitives using the same materials. splitIncompatibleMaterials(gltf); ForEach.material(gltf, function(material, materialIndex) { - var pbrMetallicRoughness = material.pbrMetallicRoughness; - if (defined(pbrMetallicRoughness)) { - var technique = generateTechnique(gltf, material, materialIndex, options); - - material.values = pbrMetallicRoughness; - material.technique = technique; - delete material.pbrMetallicRoughness; + if (isPbrMaterial(material)) { + var values = {}; + var technique = generateTechnique(gltf, material, materialIndex, values, options); + gltf.materials[materialIndex] = { + values : values, + technique : technique + }; } }); @@ -68,7 +68,18 @@ define([ return gltf; } - function generateTechnique(gltf, material, materialIndex, options) { + function isPbrMaterial(material) { + return defined(material.pbrMetallicRoughness) || + defined(material.normalTexture) || + defined(material.occlusionTexture) || + defined(material.emissiveTexture) || + defined(material.emissiveFactor) || + defined(material.alphaMode) || + defined(material.alphaCutoff) || + defined(material.doubleSided); + } + + function generateTechnique(gltf, material, materialIndex, values, options) { var optimizeForCesium = defaultValue(options.optimizeForCesium, false); var hasCesiumRTCExtension = defined(gltf.extensions) && defined(gltf.extensions.CESIUM_RTC); var addBatchIdToGeneratedShaders = defaultValue(options.addBatchIdToGeneratedShaders, false); @@ -77,7 +88,15 @@ define([ var shaders = gltf.shaders; var programs = gltf.programs; - var parameterValues = material.pbrMetallicRoughness; + var parameterValues = values; + var pbrMetallicRoughness = material.pbrMetallicRoughness; + if (defined(pbrMetallicRoughness)) { + for (var parameterName in pbrMetallicRoughness) { + if (pbrMetallicRoughness.hasOwnProperty(parameterName)) { + parameterValues[parameterName] = pbrMetallicRoughness[parameterName]; + } + } + } for (var additional in material) { if (material.hasOwnProperty(additional) && ((additional.indexOf('Texture') >= 0) || additional.indexOf('Factor') >= 0) || additional === 'doubleSided') { parameterValues[additional] = material[additional]; @@ -98,34 +117,34 @@ define([ var skinningInfo; var hasSkinning = false; var hasVertexColors = false; + var hasMorphTargets = false; + var hasNormals = false; + var hasTangents = false; + var hasTexCoords = false; if (defined(primitiveInfo)) { skinningInfo = primitiveInfo.skinning; hasSkinning = skinningInfo.skinned && (joints.length > 0); hasVertexColors = primitiveInfo.hasVertexColors; + hasMorphTargets = primitiveInfo.hasMorphTargets; + hasNormals = primitiveInfo.hasNormals; + hasTangents = primitiveInfo.hasTangents; + hasTexCoords = primitiveInfo.hasTexCoords; } - var hasNormals = true; - var hasTangents = false; - var hasMorphTargets = false; var morphTargets; - ForEach.mesh(gltf, function(mesh) { - ForEach.meshPrimitive(mesh, function(primitive) { - if (primitive.material === materialIndex) { - var targets = primitive.targets; - if (!hasMorphTargets && defined(targets)) { - hasMorphTargets = true; - morphTargets = targets; - } - var attributes = primitive.attributes; - for (var attribute in attributes) { - if (attribute.indexOf('TANGENT') >= 0) { - hasTangents = true; + if (hasMorphTargets) { + ForEach.mesh(gltf, function(mesh) { + ForEach.meshPrimitive(mesh, function(primitive) { + if (primitive.material === materialIndex) { + var targets = primitive.targets; + if (defined(targets)) { + morphTargets = targets; } } - } + }); }); - }); + } // Add techniques var techniqueParameters = { @@ -164,16 +183,10 @@ define([ } // Add material parameters - var hasTexCoords = false; for (var name in parameterValues) { - //generate shader parameters if (parameterValues.hasOwnProperty(name)) { - var valType = getPBRValueType(name); - if (!hasTexCoords && (valType === WebGLConstants.SAMPLER_2D)) { - hasTexCoords = true; - } techniqueParameters[name] = { - type : valType + type : getPBRValueType(name) }; } } @@ -237,9 +250,11 @@ define([ type : WebGLConstants.FLOAT_VEC3 }; vertexShader += 'attribute vec3 a_position;\n'; - vertexShader += 'varying vec3 v_positionEC;\n'; - if (optimizeForCesium) { - vertexShader += 'varying vec3 v_positionWC;\n'; + if (hasNormals) { + vertexShader += 'varying vec3 v_positionEC;\n'; + if (optimizeForCesium) { + vertexShader += 'varying vec3 v_positionWC;\n'; + } } // Morph Target Weighting @@ -280,16 +295,14 @@ define([ } else { vertexShaderMain += ' vec4 position = vec4(weightedPosition, 1.0);\n'; } - if (optimizeForCesium) { + if (hasNormals && optimizeForCesium) { vertexShaderMain += ' v_positionWC = (czm_model * position).xyz;\n'; } vertexShaderMain += ' position = u_modelViewMatrix * position;\n'; - vertexShaderMain += ' v_positionEC = position.xyz;\n'; - vertexShaderMain += ' gl_Position = u_projectionMatrix * position;\n'; - fragmentShader += 'varying vec3 v_positionEC;\n'; - if (optimizeForCesium) { - fragmentShader += 'varying vec3 v_positionWC;\n'; + if (hasNormals) { + vertexShaderMain += ' v_positionEC = position.xyz;\n'; } + vertexShaderMain += ' gl_Position = u_projectionMatrix * position;\n'; // Final normal computation if (hasNormals) { @@ -307,6 +320,11 @@ define([ } fragmentShader += 'varying vec3 v_normal;\n'; + fragmentShader += 'varying vec3 v_positionEC;\n'; + + if (optimizeForCesium) { + fragmentShader += 'varying vec3 v_positionWC;\n'; + } } // Read tangents if available @@ -387,40 +405,42 @@ define([ vertexShader += '}\n'; // Fragment shader lighting - fragmentShader += 'const float M_PI = 3.141592653589793;\n'; - - fragmentShader += 'vec3 lambertianDiffuse(vec3 diffuseColor) \n' + - '{\n' + - ' return diffuseColor / M_PI;\n' + - '}\n\n'; - - fragmentShader += 'vec3 fresnelSchlick2(vec3 f0, vec3 f90, float VdotH) \n' + - '{\n' + - ' return f0 + (f90 - f0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0);\n' + - '}\n\n'; - - fragmentShader += 'vec3 fresnelSchlick(float metalness, float VdotH) \n' + - '{\n' + - ' return metalness + (vec3(1.0) - metalness) * pow(1.0 - VdotH, 5.0);\n' + - '}\n\n'; - - fragmentShader += 'float smithVisibilityG1(float NdotV, float roughness) \n' + - '{\n' + - ' float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;\n' + - ' return NdotV / (NdotV * (1.0 - k) + k);\n' + - '}\n\n'; - - fragmentShader += 'float smithVisibilityGGX(float roughness, float NdotL, float NdotV) \n' + - '{\n' + - ' return smithVisibilityG1(NdotL, roughness) * smithVisibilityG1(NdotV, roughness);\n' + - '}\n\n'; - - fragmentShader += 'float GGX(float roughness, float NdotH) \n' + - '{\n' + - ' float roughnessSquared = roughness * roughness;\n' + - ' float f = (NdotH * roughnessSquared - NdotH) * NdotH + 1.0;\n' + - ' return roughnessSquared / (M_PI * f * f);\n' + - '}\n\n'; + if (hasNormals) { + fragmentShader += 'const float M_PI = 3.141592653589793;\n'; + + fragmentShader += 'vec3 lambertianDiffuse(vec3 diffuseColor) \n' + + '{\n' + + ' return diffuseColor / M_PI;\n' + + '}\n\n'; + + fragmentShader += 'vec3 fresnelSchlick2(vec3 f0, vec3 f90, float VdotH) \n' + + '{\n' + + ' return f0 + (f90 - f0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0);\n' + + '}\n\n'; + + fragmentShader += 'vec3 fresnelSchlick(float metalness, float VdotH) \n' + + '{\n' + + ' return metalness + (vec3(1.0) - metalness) * pow(1.0 - VdotH, 5.0);\n' + + '}\n\n'; + + fragmentShader += 'float smithVisibilityG1(float NdotV, float roughness) \n' + + '{\n' + + ' float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;\n' + + ' return NdotV / (NdotV * (1.0 - k) + k);\n' + + '}\n\n'; + + fragmentShader += 'float smithVisibilityGGX(float roughness, float NdotL, float NdotV) \n' + + '{\n' + + ' return smithVisibilityG1(NdotL, roughness) * smithVisibilityG1(NdotV, roughness);\n' + + '}\n\n'; + + fragmentShader += 'float GGX(float roughness, float NdotH) \n' + + '{\n' + + ' float roughnessSquared = roughness * roughness;\n' + + ' float f = (NdotH * roughnessSquared - NdotH) * NdotH + 1.0;\n' + + ' return roughnessSquared / (M_PI * f * f);\n' + + '}\n\n'; + } fragmentShader += 'vec3 SRGBtoLINEAR3(vec3 srgbIn) \n' + '{\n' + @@ -503,109 +523,114 @@ define([ } fragmentShader += ' vec3 baseColor = baseColorWithAlpha.rgb;\n'; - // Add metallic-roughness to fragment shader - if (defined(parameterValues.metallicRoughnessTexture)) { - fragmentShader += ' vec3 metallicRoughness = texture2D(u_metallicRoughnessTexture, ' + v_texcoord + ').rgb;\n'; - fragmentShader += ' float metalness = clamp(metallicRoughness.b, 0.0, 1.0);\n'; - fragmentShader += ' float roughness = clamp(metallicRoughness.g, 0.04, 1.0);\n'; - if (defined(parameterValues.metallicFactor)) { - fragmentShader += ' metalness *= u_metallicFactor;\n'; - } - if (defined(parameterValues.roughnessFactor)) { - fragmentShader += ' roughness *= u_roughnessFactor;\n'; + + if (hasNormals) { + // Add metallic-roughness to fragment shader + if (defined(parameterValues.metallicRoughnessTexture)) { + fragmentShader += ' vec3 metallicRoughness = texture2D(u_metallicRoughnessTexture, ' + v_texcoord + ').rgb;\n'; + fragmentShader += ' float metalness = clamp(metallicRoughness.b, 0.0, 1.0);\n'; + fragmentShader += ' float roughness = clamp(metallicRoughness.g, 0.04, 1.0);\n'; + if (defined(parameterValues.metallicFactor)) { + fragmentShader += ' metalness *= u_metallicFactor;\n'; + } + if (defined(parameterValues.roughnessFactor)) { + fragmentShader += ' roughness *= u_roughnessFactor;\n'; + } + } else { + if (defined(parameterValues.metallicFactor)) { + fragmentShader += ' float metalness = clamp(u_metallicFactor, 0.0, 1.0);\n'; + } else { + fragmentShader += ' float metalness = 1.0;\n'; + } + if (defined(parameterValues.roughnessFactor)) { + fragmentShader += ' float roughness = clamp(u_roughnessFactor, 0.04, 1.0);\n'; + } else { + fragmentShader += ' float roughness = 1.0;\n'; + } } - } else { - if (defined(parameterValues.metallicFactor)) { - fragmentShader += ' float metalness = clamp(u_metallicFactor, 0.0, 1.0);\n'; + fragmentShader += ' vec3 v = -normalize(v_positionEC);\n'; + + // Generate fragment shader's lighting block + if (optimizeForCesium) { + // The Sun is brighter than your average light source, and has a yellowish tint balanced by the Earth's ambient blue. + fragmentShader += ' vec3 lightColor = vec3(1.5, 1.4, 1.2);\n'; + fragmentShader += ' vec3 l = normalize(czm_sunDirectionEC);\n'; } else { - fragmentShader += ' float metalness = 1.0;\n'; + fragmentShader += ' vec3 lightColor = vec3(1.0, 1.0, 1.0);\n'; + fragmentShader += ' vec3 l = vec3(0.0, 0.0, 1.0);\n'; } - if (defined(parameterValues.roughnessFactor)) { - fragmentShader += ' float roughness = clamp(u_roughnessFactor, 0.04, 1.0);\n'; + fragmentShader += ' vec3 h = normalize(v + l);\n'; + if (optimizeForCesium) { + fragmentShader += ' vec3 r = normalize(czm_inverseViewRotation * normalize(reflect(v, n)));\n'; + // Figure out if the reflection vector hits the ellipsoid + fragmentShader += ' czm_ellipsoid ellipsoid = czm_getWgs84EllipsoidEC();\n'; + fragmentShader += ' float vertexRadius = length(v_positionWC);\n'; + fragmentShader += ' float horizonDotNadir = 1.0 - min(1.0, ellipsoid.radii.x / vertexRadius);\n'; + fragmentShader += ' float reflectionDotNadir = dot(r, normalize(v_positionWC));\n'; + // Flipping the X vector is a cheap way to get the inverse of czm_temeToPseudoFixed, since that's a rotation about Z. + fragmentShader += ' r.x = -r.x;\n'; + fragmentShader += ' r = -normalize(czm_temeToPseudoFixed * r);\n'; + fragmentShader += ' r.x = -r.x;\n'; } else { - fragmentShader += ' float roughness = 1.0;\n'; + fragmentShader += ' vec3 r = normalize(reflect(v, n));\n'; + } + fragmentShader += ' float NdotL = clamp(dot(n, l), 0.001, 1.0);\n'; + fragmentShader += ' float NdotV = abs(dot(n, v)) + 0.001;\n'; + fragmentShader += ' float NdotH = clamp(dot(n, h), 0.0, 1.0);\n'; + fragmentShader += ' float LdotH = clamp(dot(l, h), 0.0, 1.0);\n'; + fragmentShader += ' float VdotH = clamp(dot(v, h), 0.0, 1.0);\n'; + + fragmentShader += ' vec3 f0 = vec3(0.04);\n'; + fragmentShader += ' float alpha = roughness * roughness;\n'; + fragmentShader += ' vec3 diffuseColor = baseColor * (1.0 - metalness) * (1.0 - f0);\n'; + fragmentShader += ' vec3 specularColor = mix(f0, baseColor, metalness);\n'; + fragmentShader += ' float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);\n'; + fragmentShader += ' vec3 r90 = vec3(clamp(reflectance * 25.0, 0.0, 1.0));\n'; + fragmentShader += ' vec3 r0 = specularColor.rgb;\n'; + + fragmentShader += ' vec3 F = fresnelSchlick2(r0, r90, VdotH);\n'; + fragmentShader += ' float G = smithVisibilityGGX(alpha, NdotL, NdotV);\n'; + fragmentShader += ' float D = GGX(alpha, NdotH);\n'; + + fragmentShader += ' vec3 diffuseContribution = (1.0 - F) * lambertianDiffuse(diffuseColor);\n'; + fragmentShader += ' vec3 specularContribution = F * G * D / (4.0 * NdotL * NdotV);\n'; + fragmentShader += ' vec3 color = NdotL * lightColor * (diffuseContribution + specularContribution);\n'; + + if (optimizeForCesium) { + fragmentShader += ' float inverseRoughness = 1.04 - roughness;\n'; + fragmentShader += ' inverseRoughness *= inverseRoughness;\n'; + fragmentShader += ' vec3 sceneSkyBox = textureCube(czm_environmentMap, r).rgb * inverseRoughness;\n'; + + fragmentShader += ' float atmosphereHeight = 0.05;\n'; + fragmentShader += ' float blendRegionSize = 0.1 * ((1.0 - inverseRoughness) * 8.0 + 1.1 - horizonDotNadir);\n'; + fragmentShader += ' float blendRegionOffset = roughness * -1.0;\n'; + fragmentShader += ' float farAboveHorizon = clamp(horizonDotNadir - blendRegionSize * 0.5 + blendRegionOffset, 1.0e-10 - blendRegionSize, 0.99999);\n'; + fragmentShader += ' float aroundHorizon = clamp(horizonDotNadir + blendRegionSize * 0.5, 1.0e-10 - blendRegionSize, 0.99999);\n'; + fragmentShader += ' float farBelowHorizon = clamp(horizonDotNadir + blendRegionSize * 1.5, 1.0e-10 - blendRegionSize, 0.99999);\n'; + fragmentShader += ' float smoothstepHeight = smoothstep(0.0, atmosphereHeight, horizonDotNadir);\n'; + + fragmentShader += ' vec3 belowHorizonColor = mix(vec3(0.1, 0.15, 0.25), vec3(0.4, 0.7, 0.9), smoothstepHeight);\n'; + fragmentShader += ' vec3 nadirColor = belowHorizonColor * 0.5;\n'; + fragmentShader += ' vec3 aboveHorizonColor = mix(vec3(0.9, 1.0, 1.2), belowHorizonColor, roughness * 0.5);\n'; + fragmentShader += ' vec3 blueSkyColor = mix(vec3(0.18, 0.26, 0.48), aboveHorizonColor, reflectionDotNadir * inverseRoughness * 0.5 + 0.75);\n'; + fragmentShader += ' vec3 zenithColor = mix(blueSkyColor, sceneSkyBox, smoothstepHeight);\n'; + + fragmentShader += ' vec3 blueSkyDiffuseColor = vec3(0.7, 0.85, 0.9);\n'; + fragmentShader += ' float diffuseIrradianceFromEarth = (1.0 - horizonDotNadir) * (reflectionDotNadir * 0.25 + 0.75) * smoothstepHeight;\n'; + fragmentShader += ' float diffuseIrradianceFromSky = (1.0 - smoothstepHeight) * (1.0 - (reflectionDotNadir * 0.25 + 0.25));\n'; + fragmentShader += ' vec3 diffuseIrradiance = blueSkyDiffuseColor * clamp(diffuseIrradianceFromEarth + diffuseIrradianceFromSky, 0.0, 1.0);\n'; + + fragmentShader += ' float notDistantRough = (1.0 - horizonDotNadir * roughness * 0.8);\n'; + fragmentShader += ' vec3 specularIrradiance = mix(zenithColor, aboveHorizonColor, smoothstep(farAboveHorizon, aroundHorizon, reflectionDotNadir) * notDistantRough);\n'; + fragmentShader += ' specularIrradiance = mix(specularIrradiance, belowHorizonColor, smoothstep(aroundHorizon, farBelowHorizon, reflectionDotNadir) * inverseRoughness);\n'; + fragmentShader += ' specularIrradiance = mix(specularIrradiance, nadirColor, smoothstep(farBelowHorizon, 1.0, reflectionDotNadir) * inverseRoughness);\n'; + + fragmentShader += ' vec2 brdfLut = texture2D(czm_brdfLut, vec2(NdotV, 1.0 - roughness)).rg;\n'; + fragmentShader += ' vec3 IBLColor = (diffuseIrradiance * diffuseColor) + (specularIrradiance * SRGBtoLINEAR3(specularColor * brdfLut.x + brdfLut.y));\n'; + fragmentShader += ' color += IBLColor;\n'; } - } - fragmentShader += ' vec3 v = -normalize(v_positionEC);\n'; - - // Generate fragment shader's lighting block - if (optimizeForCesium) { - // The Sun is brighter than your average light source, and has a yellowish tint balanced by the Earth's ambient blue. - fragmentShader += ' vec3 lightColor = vec3(1.5, 1.4, 1.2);\n'; - fragmentShader += ' vec3 l = normalize(czm_sunDirectionEC);\n'; - } else { - fragmentShader += ' vec3 lightColor = vec3(1.0, 1.0, 1.0);\n'; - fragmentShader += ' vec3 l = vec3(0.0, 0.0, 1.0);\n'; - } - fragmentShader += ' vec3 h = normalize(v + l);\n'; - if (optimizeForCesium) { - fragmentShader += ' vec3 r = normalize(czm_inverseViewRotation * normalize(reflect(v, n)));\n'; - // Figure out if the reflection vector hits the ellipsoid - fragmentShader += ' czm_ellipsoid ellipsoid = czm_getWgs84EllipsoidEC();\n'; - fragmentShader += ' float vertexRadius = length(v_positionWC);\n'; - fragmentShader += ' float horizonDotNadir = 1.0 - min(1.0, ellipsoid.radii.x / vertexRadius);\n'; - fragmentShader += ' float reflectionDotNadir = dot(r, normalize(v_positionWC));\n'; - // Flipping the X vector is a cheap way to get the inverse of czm_temeToPseudoFixed, since that's a rotation about Z. - fragmentShader += ' r.x = -r.x;\n'; - fragmentShader += ' r = -normalize(czm_temeToPseudoFixed * r);\n'; - fragmentShader += ' r.x = -r.x;\n'; } else { - fragmentShader += ' vec3 r = normalize(reflect(v, n));\n'; - } - fragmentShader += ' float NdotL = clamp(dot(n, l), 0.001, 1.0);\n'; - fragmentShader += ' float NdotV = abs(dot(n, v)) + 0.001;\n'; - fragmentShader += ' float NdotH = clamp(dot(n, h), 0.0, 1.0);\n'; - fragmentShader += ' float LdotH = clamp(dot(l, h), 0.0, 1.0);\n'; - fragmentShader += ' float VdotH = clamp(dot(v, h), 0.0, 1.0);\n'; - - fragmentShader += ' vec3 f0 = vec3(0.04);\n'; - fragmentShader += ' float alpha = roughness * roughness;\n'; - fragmentShader += ' vec3 diffuseColor = baseColor * (1.0 - metalness) * (1.0 - f0);\n'; - fragmentShader += ' vec3 specularColor = mix(f0, baseColor, metalness);\n'; - fragmentShader += ' float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);\n'; - fragmentShader += ' vec3 r90 = vec3(clamp(reflectance * 25.0, 0.0, 1.0));\n'; - fragmentShader += ' vec3 r0 = specularColor.rgb;\n'; - - fragmentShader += ' vec3 F = fresnelSchlick2(r0, r90, VdotH);\n'; - fragmentShader += ' float G = smithVisibilityGGX(alpha, NdotL, NdotV);\n'; - fragmentShader += ' float D = GGX(alpha, NdotH);\n'; - - fragmentShader += ' vec3 diffuseContribution = (1.0 - F) * lambertianDiffuse(diffuseColor);\n'; - fragmentShader += ' vec3 specularContribution = F * G * D / (4.0 * NdotL * NdotV);\n'; - fragmentShader += ' vec3 color = NdotL * lightColor * (diffuseContribution + specularContribution);\n'; - - if (optimizeForCesium) { - fragmentShader += ' float inverseRoughness = 1.04 - roughness;\n'; - fragmentShader += ' inverseRoughness *= inverseRoughness;\n'; - fragmentShader += ' vec3 sceneSkyBox = textureCube(czm_environmentMap, r).rgb * inverseRoughness;\n'; - - fragmentShader += ' float atmosphereHeight = 0.05;\n'; - fragmentShader += ' float blendRegionSize = 0.1 * ((1.0 - inverseRoughness) * 8.0 + 1.1 - horizonDotNadir);\n'; - fragmentShader += ' float blendRegionOffset = roughness * -1.0;\n'; - fragmentShader += ' float farAboveHorizon = clamp(horizonDotNadir - blendRegionSize * 0.5 + blendRegionOffset, 1.0e-10 - blendRegionSize, 0.99999);\n'; - fragmentShader += ' float aroundHorizon = clamp(horizonDotNadir + blendRegionSize * 0.5, 1.0e-10 - blendRegionSize, 0.99999);\n'; - fragmentShader += ' float farBelowHorizon = clamp(horizonDotNadir + blendRegionSize * 1.5, 1.0e-10 - blendRegionSize, 0.99999);\n'; - fragmentShader += ' float smoothstepHeight = smoothstep(0.0, atmosphereHeight, horizonDotNadir);\n'; - - fragmentShader += ' vec3 belowHorizonColor = mix(vec3(0.1, 0.15, 0.25), vec3(0.4, 0.7, 0.9), smoothstepHeight);\n'; - fragmentShader += ' vec3 nadirColor = belowHorizonColor * 0.5;\n'; - fragmentShader += ' vec3 aboveHorizonColor = mix(vec3(0.9, 1.0, 1.2), belowHorizonColor, roughness * 0.5);\n'; - fragmentShader += ' vec3 blueSkyColor = mix(vec3(0.18, 0.26, 0.48), aboveHorizonColor, reflectionDotNadir * inverseRoughness * 0.5 + 0.75);\n'; - fragmentShader += ' vec3 zenithColor = mix(blueSkyColor, sceneSkyBox, smoothstepHeight);\n'; - - fragmentShader += ' vec3 blueSkyDiffuseColor = vec3(0.7, 0.85, 0.9);\n'; - fragmentShader += ' float diffuseIrradianceFromEarth = (1.0 - horizonDotNadir) * (reflectionDotNadir * 0.25 + 0.75) * smoothstepHeight;\n'; - fragmentShader += ' float diffuseIrradianceFromSky = (1.0 - smoothstepHeight) * (1.0 - (reflectionDotNadir * 0.25 + 0.25));\n'; - fragmentShader += ' vec3 diffuseIrradiance = blueSkyDiffuseColor * clamp(diffuseIrradianceFromEarth + diffuseIrradianceFromSky, 0.0, 1.0);\n'; - - fragmentShader += ' float notDistantRough = (1.0 - horizonDotNadir * roughness * 0.8);\n'; - fragmentShader += ' vec3 specularIrradiance = mix(zenithColor, aboveHorizonColor, smoothstep(farAboveHorizon, aroundHorizon, reflectionDotNadir) * notDistantRough);\n'; - fragmentShader += ' specularIrradiance = mix(specularIrradiance, belowHorizonColor, smoothstep(aroundHorizon, farBelowHorizon, reflectionDotNadir) * inverseRoughness);\n'; - fragmentShader += ' specularIrradiance = mix(specularIrradiance, nadirColor, smoothstep(farBelowHorizon, 1.0, reflectionDotNadir) * inverseRoughness);\n'; - - fragmentShader += ' vec2 brdfLut = texture2D(czm_brdfLut, vec2(NdotV, 1.0 - roughness)).rg;\n'; - fragmentShader += ' vec3 IBLColor = (diffuseIrradiance * diffuseColor) + (specularIrradiance * SRGBtoLINEAR3(specularColor * brdfLut.x + brdfLut.y));\n'; - fragmentShader += ' color += IBLColor;\n'; + fragmentShader += ' vec3 color = baseColor;\n'; } if (defined(parameterValues.occlusionTexture)) { @@ -763,7 +788,7 @@ define([ var attributes = primitive.attributes; for (var target in targets) { - if (defined(target)) { + if (targets.hasOwnProperty(target)) { var targetAttributes = targets[target]; for (var attribute in targetAttributes) { if (attribute !== 'extras') { @@ -829,14 +854,9 @@ define([ var isSkinned = defined(jointAccessorId); var hasVertexColors = defined(primitive.attributes.COLOR_0); var hasMorphTargets = defined(primitive.targets); - - var hasTangents = false; - var attributes = primitive.attributes; - for (var attribute in attributes) { - if (attribute.indexOf('TANGENT') >= 0) { - hasTangents = true; - } - } + var hasNormals = defined(primitive.attributes.NORMAL); + var hasTangents = defined(primitive.attributes.TANGENT); + var hasTexCoords = defined(primitive.attributes.TEXCOORD_0); var primitiveInfo = material.extras._pipeline.primitive; if (!defined(primitiveInfo)) { @@ -848,19 +868,23 @@ define([ }, hasVertexColors : hasVertexColors, hasMorphTargets : hasMorphTargets, - hasTangents : hasTangents + hasNormals : hasNormals, + hasTangents : hasTangents, + hasTexCoords : hasTexCoords }; } else if ((primitiveInfo.skinning.skinned !== isSkinned) || (primitiveInfo.skinning.type !== type) || (primitiveInfo.hasVertexColors !== hasVertexColors) || (primitiveInfo.hasMorphTargets !== hasMorphTargets) || - (primitiveInfo.hasTangents !== hasTangents)) { + (primitiveInfo.hasNormals !== hasNormals) || + (primitiveInfo.hasTangents !== hasTangents) || + (primitiveInfo.hasTexCoords !== hasTexCoords)) { // 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, tangents, or morph targets + // * Doesn't have vertex colors, morph targets, normals, tangents, or texCoords var clonedMaterial = clone(material, true); - clonedMaterial.extras._pipeline.skinning = { + clonedMaterial.extras._pipeline.primitive = { skinning : { skinned : isSkinned, componentType : componentType, @@ -868,7 +892,9 @@ define([ }, hasVertexColors : hasVertexColors, hasMorphTargets : hasMorphTargets, - hasTangents : hasTangents + hasNormals : hasNormals, + hasTangents : hasTangents, + hasTexCoords : hasTexCoords }; // Split this off as a separate material materialId = addToArray(materials, clonedMaterial); diff --git a/Specs/Data/Models/PBR/BoxEmissive/BoxEmissive.gltf b/Specs/Data/Models/PBR/BoxEmissive/BoxEmissive.gltf new file mode 100644 index 000000000000..e6e975b15f7c --- /dev/null +++ b/Specs/Data/Models/PBR/BoxEmissive/BoxEmissive.gltf @@ -0,0 +1,158 @@ +{ + "accessors": [ + { + "componentType": 5126, + "count": 20, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3", + "bufferView": 0, + "byteOffset": 0 + }, + { + "componentType": 5126, + "count": 20, + "min": [ + 0, + 0 + ], + "max": [ + 1, + 1 + ], + "type": "VEC2", + "bufferView": 1, + "byteOffset": 0 + }, + { + "componentType": 5123, + "count": 36, + "min": [ + 0 + ], + "max": [ + 19 + ], + "type": "SCALAR", + "bufferView": 2, + "byteOffset": 0 + } + ], + "asset": { + "generator": "obj2gltf", + "version": "2.0" + }, + "buffers": [ + { + "name": "box-emissive", + "byteLength": 15964, + "uri": "data:application/octet-stream;base64,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" + } + ], + "bufferViews": [ + { + "name": "bufferView_0", + "buffer": 0, + "byteLength": 240, + "byteOffset": 0, + "byteStride": 12, + "target": 34962 + }, + { + "name": "bufferView_1", + "buffer": 0, + "byteLength": 160, + "byteOffset": 240, + "byteStride": 8, + "target": 34962 + }, + { + "name": "bufferView_2", + "buffer": 0, + "byteLength": 72, + "byteOffset": 400, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 472, + "byteLength": 15490 + } + ], + "images": [ + { + "mimeType": "image/jpeg", + "bufferView": 3 + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0, + 0, + 1 + ] + }, + "emissiveTexture": { + "index": 0 + }, + "emissiveFactor": [ + 1, + 1, + 1 + ] + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 2, + "material": 0, + "mode": 4 + } + ] + } + ], + "nodes": [ + { + "mesh": 0 + } + ], + "samplers": [ + { + "magFilter": 9729, + "minFilter": 9986, + "wrapS": 10497, + "wrapT": 10497 + } + ], + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "textures": [ + { + "sampler": 0, + "source": 0 + } + ] +} diff --git a/Specs/Scene/ModelSpec.js b/Specs/Scene/ModelSpec.js index 63f45050380d..e9d5c5a4cf50 100644 --- a/Specs/Scene/ModelSpec.js +++ b/Specs/Scene/ModelSpec.js @@ -125,6 +125,7 @@ defineSuite([ var animatedMorphCubeUrl = './Data/Models/PBR/AnimatedMorphCube/AnimatedMorphCube.gltf'; var twoSidedPlaneUrl = './Data/Models/PBR/TwoSidedPlane/TwoSidedPlane.gltf'; var vertexColorTestUrl = './Data/Models/PBR/VertexColorTest/VertexColorTest.gltf'; + var emissiveUrl = './Data/Models/PBR/BoxEmissive/BoxEmissive.gltf'; var dracoCompressedModelUrl = './Data/Models/DracoCompression/CesiumMilkTruck/CesiumMilkTruck.gltf'; var dracoCompressedModelWithAnimationUrl = './Data/Models/DracoCompression/CesiumMan/CesiumMan.gltf'; @@ -2243,7 +2244,7 @@ defineSuite([ }); }); - it('load a glTF 2.0 with vertex colors', function() { + it('loads a glTF 2.0 with vertex colors', function() { return loadModel(vertexColorTestUrl).then(function(m) { m.show = true; checkVertexColors(m); @@ -2251,6 +2252,21 @@ defineSuite([ }); }); + it('loads a glTF 2.0 with an emissive texture and no normals', function() { + return loadModel(emissiveUrl).then(function(model) { + model.show = true; + model.zoomTo(); + expect(scene).toRenderAndCall(function(rgba) { + // Emissive texture is red + expect(rgba[0]).toBeGreaterThan(10); + expect(rgba[1]).toBeLessThan(10); + expect(rgba[2]).toBeLessThan(10); + }); + + primitives.remove(model); + }); + }); + function testBoxSideColors(m) { var rotateX = Matrix3.fromRotationX(CesiumMath.toRadians(90.0)); var rotateY = Matrix3.fromRotationY(CesiumMath.toRadians(90.0));