From c85a8380fcb6f6b57659eea2596182b92d8c32e6 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 2 Oct 2017 16:01:21 -0400 Subject: [PATCH 1/5] Doc clarification for color blend mode when feature is white --- Source/Scene/Cesium3DTileColorBlendMode.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Scene/Cesium3DTileColorBlendMode.js b/Source/Scene/Cesium3DTileColorBlendMode.js index b069783eabcf..2d7803dbc3bb 100644 --- a/Source/Scene/Cesium3DTileColorBlendMode.js +++ b/Source/Scene/Cesium3DTileColorBlendMode.js @@ -11,6 +11,10 @@ define([ * When REPLACE or MIX are used and the source color is a glTF material, the technique must assign the * _3DTILESDIFFUSE semantic to the diffuse color parameter. Otherwise only HIGHLIGHT is supported. *

+ *

+ * A feature whose color evaluates to white (1.0, 1.0, 1.0) is always rendered without color blending, regardless of the + * tileset's color blend mode. + *

*

      * "techniques": {
      *   "technique0": {

From ec154ca6006b9142a78459cb2dc659cb2a930142 Mon Sep 17 00:00:00 2001
From: Sean Lilley 
Date: Mon, 2 Oct 2017 18:23:56 -0400
Subject: [PATCH 2/5] Support tileset.colorBlendMode of REPLACE or MIX with
 uniforms in vertex shader

---
 Source/Scene/Batched3DModel3DTileContent.js |  3 +-
 Source/Scene/Cesium3DTileBatchTable.js      | 62 +++++++++++++++------
 Source/Scene/ModelInstanceCollection.js     |  8 ++-
 Source/Scene/PointCloud3DTileContent.js     |  2 +-
 4 files changed, 55 insertions(+), 20 deletions(-)

diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js
index 7db1c61386cb..8eb2eecd05cc 100644
--- a/Source/Scene/Batched3DModel3DTileContent.js
+++ b/Source/Scene/Batched3DModel3DTileContent.js
@@ -208,7 +208,8 @@ define([
             var batchTable = content._batchTable;
             var gltf = content._model.gltf;
             var batchIdAttributeName = getBatchIdAttributeName(gltf);
-            var callback = batchTable.getVertexShaderCallback(true, batchIdAttributeName);
+            var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE');
+            var callback = batchTable.getVertexShaderCallback(true, batchIdAttributeName, diffuseUniformName);
             return defined(callback) ? callback(vs) : vs;
         };
     }
diff --git a/Source/Scene/Cesium3DTileBatchTable.js b/Source/Scene/Cesium3DTileBatchTable.js
index f2b10bc06534..f4400bf0f358 100644
--- a/Source/Scene/Cesium3DTileBatchTable.js
+++ b/Source/Scene/Cesium3DTileBatchTable.js
@@ -846,14 +846,14 @@ define([
             '} \n';
     }
 
-    Cesium3DTileBatchTable.prototype.getVertexShaderCallback = function(handleTranslucent, batchIdAttributeName) {
+    Cesium3DTileBatchTable.prototype.getVertexShaderCallback = function(handleTranslucent, batchIdAttributeName, diffuseUniformName) {
         if (this.featuresLength === 0) {
             return;
         }
 
         var that = this;
         return function(source) {
-            var renamedSource = ShaderSource.replaceMain(source, 'tile_main');
+            var renamedSource = modifyDiffuse(source, diffuseUniformName, true);
             var newMain;
 
             if (ContextLimits.maximumVertexTextureImageUnits > 0) {
@@ -864,9 +864,9 @@ define([
                     'varying vec4 tile_featureColor; \n' +
                     'void main() \n' +
                     '{ \n' +
-                    '    tile_main(); \n' +
                     '    vec2 st = computeSt(' + batchIdAttributeName + '); \n' +
                     '    vec4 featureProperties = texture2D(tile_batchTexture, st); \n' +
+                    '    tile_color(featureProperties); \n' +
                     '    float show = ceil(featureProperties.a); \n' +      // 0 - false, non-zeo - true
                     '    gl_Position *= show; \n';                          // Per-feature show/hide
                 if (handleTranslucent) {
@@ -891,11 +891,12 @@ define([
                     '    tile_featureColor = featureProperties; \n' +
                     '}';
             } else {
+                // When VTF is not supported, color blend mode MIX will look incorrect due to the feature's color not being available in the vertex shader
                 newMain =
                     'varying vec2 tile_featureSt; \n' +
                     'void main() \n' +
                     '{ \n' +
-                    '    tile_main(); \n' +
+                    '    tile_color(vec4(1.0)); \n' +
                     '    tile_featureSt = computeSt(' + batchIdAttributeName + '); \n' +
                     '}';
             }
@@ -904,21 +905,37 @@ define([
         };
     };
 
-    function getHighlightOnlyShader(source) {
+    function getHighlightOnlyFragmentShader(source) {
         source = ShaderSource.replaceMain(source, 'tile_main');
         return source +
+               'uniform float tile_colorBlend; \n' +
                'void tile_color(vec4 tile_featureColor) \n' +
                '{ \n' +
                '    tile_main(); \n' +
-               '    gl_FragColor *= tile_featureColor; \n' +
+               '    gl_FragColor.a *= tile_featureColor.a; \n' +
+               '    float highlight = ceil(tile_colorBlend); \n' +
+               '    gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n' +
                '} \n';
     }
 
-    function modifyDiffuse(source, diffuseUniformName) {
+    function getPassthroughVertexShader(source) {
+        source = ShaderSource.replaceMain(source, 'tile_main');
+        return source +
+               'void tile_color(vec4 tile_featureColor) \n' +
+               '{ \n' +
+               '    tile_main(); \n' +
+               '} \n';
+    }
+
+    function getDefaultShader(source, isVertexShader) {
+        return isVertexShader ? getPassthroughVertexShader(source) : getHighlightOnlyFragmentShader(source);
+    }
+
+    function modifyDiffuse(source, diffuseUniformName, isVertexShader) {
         // If the glTF does not specify the _3DTILESDIFFUSE semantic, return a basic highlight shader.
         // Otherwise if _3DTILESDIFFUSE is defined prefer the shader below that can switch the color mode at runtime.
         if (!defined(diffuseUniformName)) {
-            return getHighlightOnlyShader(source);
+            return getDefaultShader(source, isVertexShader);
         }
 
         // Find the diffuse uniform. Examples matches:
@@ -929,7 +946,7 @@ define([
 
         if (!defined(uniformMatch)) {
             // Could not find uniform declaration of type vec3, vec4, or sampler2D
-            return getHighlightOnlyShader(source);
+            return getDefaultShader(source, isVertexShader);
         }
 
         var declaration = uniformMatch[0];
@@ -943,19 +960,29 @@ define([
         // Replace: tile_colorBlend is 1.0 and the tile color is used
         // Mix: tile_colorBlend is between 0.0 and 1.0, causing the source color and tile color to mix
         var finalDiffuseFunction =
+            'bool isWhite(vec3 color) \n' +
+            '{ \n' +
+            '    return all(greaterThan(color, vec3(1.0 - czm_epsilon3))); \n' +
+            '} \n' +
             'vec4 tile_diffuse_final(vec4 sourceDiffuse, vec4 tileDiffuse) \n' +
             '{ \n' +
             '    vec4 blendDiffuse = mix(sourceDiffuse, tileDiffuse, tile_colorBlend); \n' +
-            '    vec4 diffuse = (tileDiffuse.rgb == vec3(1.0)) ? sourceDiffuse : blendDiffuse; \n' +
+            '    vec4 diffuse = isWhite(tileDiffuse.rgb) ? sourceDiffuse : blendDiffuse; \n' +
             '    return vec4(diffuse.rgb, sourceDiffuse.a); \n' +
             '} \n';
 
         // The color blend mode is intended for the RGB channels so alpha is always just multiplied.
         // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight)
-        var applyHighlight =
-            '    gl_FragColor.a *= tile_featureColor.a; \n' +
-            '    float highlight = ceil(tile_colorBlend); \n' +
-            '    gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n';
+        var applyHighlight = '';
+
+        if (!isVertexShader) {
+            // Highlight color is always applied in the fragment shader, from either here or in getHighlightOnlyFragmentShader.
+            // No need to apply the highlight color in the vertex shader as well.
+            applyHighlight =
+                '    gl_FragColor.a *= tile_featureColor.a; \n' +
+                '    float highlight = ceil(tile_colorBlend); \n' +
+                '    gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n';
+        }
 
         var setColor;
         if (type === 'vec3' || type === 'vec4') {
@@ -968,7 +995,10 @@ define([
                 '    tile_diffuse = tile_diffuse_final(source, tile_featureColor); \n' +
                 '    tile_main(); \n';
         } else if (type === 'sampler2D') {
-            regex = new RegExp('texture2D\\(' + diffuseUniformName + '.*?\\)', 'g');
+            // Regex handles up to one level of nested parentheses:
+            // E.g. texture2D(u_diffuse, uv)
+            // E.g. texture2D(u_diffuse, computeUV(index))
+            regex = new RegExp('texture2D\\(' + diffuseUniformName + '.*?(\\)\\)|\\))', 'g');
             source = source.replace(regex, 'tile_diffuse_final($&, tile_diffuse)');
             setColor =
                 '    tile_diffuse = tile_featureColor; \n' +
@@ -995,7 +1025,7 @@ define([
             return;
         }
         return function(source) {
-            source = modifyDiffuse(source, diffuseUniformName);
+            source = modifyDiffuse(source, diffuseUniformName, false);
             if (ContextLimits.maximumVertexTextureImageUnits > 0) {
                 // When VTF is supported, per-feature show/hide already happened in the fragment shader
                 source +=
diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js
index 514e34323921..3aaaf5f7a976 100644
--- a/Source/Scene/ModelInstanceCollection.js
+++ b/Source/Scene/ModelInstanceCollection.js
@@ -339,7 +339,9 @@ define([
             vertexShaderCached = instancedSource;
 
             if (usesBatchTable) {
-                instancedSource = collection._batchTable.getVertexShaderCallback(true, 'a_batchId')(instancedSource);
+                var gltf = collection._model.gltf;
+                var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE');
+                instancedSource = collection._batchTable.getVertexShaderCallback(true, 'a_batchId', diffuseUniformName)(instancedSource);
             }
 
             return instancedSource;
@@ -433,7 +435,9 @@ define([
     function getVertexShaderNonInstancedCallback(collection) {
         return function(vs) {
             if (defined(collection._batchTable)) {
-                vs = collection._batchTable.getVertexShaderCallback(true, 'a_batchId')(vs);
+                var gltf = collection._model.gltf;
+                var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE');
+                vs = collection._batchTable.getVertexShaderCallback(true, 'a_batchId', diffuseUniformName)(vs);
                 // Treat a_batchId as a uniform rather than a vertex attribute
                 vs = 'uniform float a_batchId\n;' + vs;
             }
diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js
index d82a88667bb4..5420ded36273 100644
--- a/Source/Scene/PointCloud3DTileContent.js
+++ b/Source/Scene/PointCloud3DTileContent.js
@@ -1040,7 +1040,7 @@ define([
 
         if (hasBatchTable) {
             // Batched points always use the HIGHLIGHT color blend mode
-            drawVS = batchTable.getVertexShaderCallback(false, 'a_batchId')(drawVS);
+            drawVS = batchTable.getVertexShaderCallback(false, 'a_batchId', undefined)(drawVS);
             drawFS = batchTable.getFragmentShaderCallback(false, undefined)(drawFS);
         }
 

From 65cf58b12f5c031dc5c4406b2641dff0e0482b4d Mon Sep 17 00:00:00 2001
From: Sean Lilley 
Date: Mon, 2 Oct 2017 20:03:37 -0400
Subject: [PATCH 3/5] Updated CHANGES.md

---
 CHANGES.md                             | 5 +++++
 Source/Scene/Cesium3DTileBatchTable.js | 2 ++
 2 files changed, 7 insertions(+)

diff --git a/CHANGES.md b/CHANGES.md
index 432dff7bc1a0..613e04e18e62 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,10 @@
 Change Log
 ==========
+
+### 1.39 - 2017-11-01
+
+* Added support for vertex shader uniforms when `tileset.colorBlendMode` is  `MIX` or `REPLACE`. [#5874])https://github.com/AnalyticalGraphicsInc/cesium/pull/5874)
+
 ### 1.38 - 2017-10-02
 
 * Breaking changes
diff --git a/Source/Scene/Cesium3DTileBatchTable.js b/Source/Scene/Cesium3DTileBatchTable.js
index f4400bf0f358..8ce6e2f7ec44 100644
--- a/Source/Scene/Cesium3DTileBatchTable.js
+++ b/Source/Scene/Cesium3DTileBatchTable.js
@@ -906,6 +906,8 @@ define([
     };
 
     function getHighlightOnlyFragmentShader(source) {
+        // The color blend mode is intended for the RGB channels so alpha is always just multiplied.
+        // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight)
         source = ShaderSource.replaceMain(source, 'tile_main');
         return source +
                'uniform float tile_colorBlend; \n' +

From 3c80affe9f6d1bb881da8c542ca606c81b20aa91 Mon Sep 17 00:00:00 2001
From: Sean Lilley 
Date: Tue, 3 Oct 2017 10:08:46 -0400
Subject: [PATCH 4/5] Slight reorganization / renaming

---
 Source/Scene/Cesium3DTileBatchTable.js | 64 ++++++++++++--------------
 1 file changed, 30 insertions(+), 34 deletions(-)

diff --git a/Source/Scene/Cesium3DTileBatchTable.js b/Source/Scene/Cesium3DTileBatchTable.js
index 8ce6e2f7ec44..abb98f7f2f1e 100644
--- a/Source/Scene/Cesium3DTileBatchTable.js
+++ b/Source/Scene/Cesium3DTileBatchTable.js
@@ -853,7 +853,9 @@ define([
 
         var that = this;
         return function(source) {
-            var renamedSource = modifyDiffuse(source, diffuseUniformName, true);
+            // If the color blend mode is HIGHLIGHT, the highlight color will always be applied in the fragment shader.
+            // No need to apply the highlight color in the vertex shader as well.
+            var renamedSource = modifyDiffuse(source, diffuseUniformName, false);
             var newMain;
 
             if (ContextLimits.maximumVertexTextureImageUnits > 0) {
@@ -905,10 +907,19 @@ define([
         };
     };
 
-    function getHighlightOnlyFragmentShader(source) {
+    function getDefaultShader(source, applyHighlight) {
+        source = ShaderSource.replaceMain(source, 'tile_main');
+
+        if (!applyHighlight) {
+            return source +
+                   'void tile_color(vec4 tile_featureColor) \n' +
+                   '{ \n' +
+                   '    tile_main(); \n' +
+                   '} \n';
+        }
+
         // The color blend mode is intended for the RGB channels so alpha is always just multiplied.
         // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight)
-        source = ShaderSource.replaceMain(source, 'tile_main');
         return source +
                'uniform float tile_colorBlend; \n' +
                'void tile_color(vec4 tile_featureColor) \n' +
@@ -920,24 +931,11 @@ define([
                '} \n';
     }
 
-    function getPassthroughVertexShader(source) {
-        source = ShaderSource.replaceMain(source, 'tile_main');
-        return source +
-               'void tile_color(vec4 tile_featureColor) \n' +
-               '{ \n' +
-               '    tile_main(); \n' +
-               '} \n';
-    }
-
-    function getDefaultShader(source, isVertexShader) {
-        return isVertexShader ? getPassthroughVertexShader(source) : getHighlightOnlyFragmentShader(source);
-    }
-
-    function modifyDiffuse(source, diffuseUniformName, isVertexShader) {
-        // If the glTF does not specify the _3DTILESDIFFUSE semantic, return a basic highlight shader.
+    function modifyDiffuse(source, diffuseUniformName, applyHighlight) {
+        // If the glTF does not specify the _3DTILESDIFFUSE semantic, return the default shader.
         // Otherwise if _3DTILESDIFFUSE is defined prefer the shader below that can switch the color mode at runtime.
         if (!defined(diffuseUniformName)) {
-            return getDefaultShader(source, isVertexShader);
+            return getDefaultShader(source, applyHighlight);
         }
 
         // Find the diffuse uniform. Examples matches:
@@ -948,7 +946,7 @@ define([
 
         if (!defined(uniformMatch)) {
             // Could not find uniform declaration of type vec3, vec4, or sampler2D
-            return getDefaultShader(source, isVertexShader);
+            return getDefaultShader(source, applyHighlight);
         }
 
         var declaration = uniformMatch[0];
@@ -975,16 +973,10 @@ define([
 
         // The color blend mode is intended for the RGB channels so alpha is always just multiplied.
         // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight)
-        var applyHighlight = '';
-
-        if (!isVertexShader) {
-            // Highlight color is always applied in the fragment shader, from either here or in getHighlightOnlyFragmentShader.
-            // No need to apply the highlight color in the vertex shader as well.
-            applyHighlight =
-                '    gl_FragColor.a *= tile_featureColor.a; \n' +
-                '    float highlight = ceil(tile_colorBlend); \n' +
-                '    gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n';
-        }
+        var highlight =
+            '    gl_FragColor.a *= tile_featureColor.a; \n' +
+            '    float highlight = ceil(tile_colorBlend); \n' +
+            '    gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n';
 
         var setColor;
         if (type === 'vec3' || type === 'vec4') {
@@ -1015,9 +1007,13 @@ define([
             source + '\n' +
             'void tile_color(vec4 tile_featureColor) \n' +
             '{ \n' +
-            setColor +
-            applyHighlight +
-            '} \n';
+            setColor;
+
+        if (applyHighlight) {
+            source += highlight;
+        }
+
+        source += '} \n';
 
         return source;
     }
@@ -1027,7 +1023,7 @@ define([
             return;
         }
         return function(source) {
-            source = modifyDiffuse(source, diffuseUniformName, false);
+            source = modifyDiffuse(source, diffuseUniformName, true);
             if (ContextLimits.maximumVertexTextureImageUnits > 0) {
                 // When VTF is supported, per-feature show/hide already happened in the fragment shader
                 source +=

From b2fa293809365013ecac1f4a70deab39736d2e25 Mon Sep 17 00:00:00 2001
From: Sean Lilley 
Date: Wed, 4 Oct 2017 09:46:46 -0400
Subject: [PATCH 5/5] Look for semantic by programId

---
 Source/Scene/Batched3DModel3DTileContent.js   |  8 +++---
 Source/Scene/ModelInstanceCollection.js       | 26 +++++++++----------
 .../Scene/getAttributeOrUniformBySemantic.js  |  9 ++++++-
 3 files changed, 25 insertions(+), 18 deletions(-)

diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js
index 8eb2eecd05cc..e3100dc9df6e 100644
--- a/Source/Scene/Batched3DModel3DTileContent.js
+++ b/Source/Scene/Batched3DModel3DTileContent.js
@@ -204,11 +204,11 @@ define([
     }
 
     function getVertexShaderCallback(content) {
-        return function(vs) {
+        return function(vs, programId) {
             var batchTable = content._batchTable;
             var gltf = content._model.gltf;
             var batchIdAttributeName = getBatchIdAttributeName(gltf);
-            var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE');
+            var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE', programId);
             var callback = batchTable.getVertexShaderCallback(true, batchIdAttributeName, diffuseUniformName);
             return defined(callback) ? callback(vs) : vs;
         };
@@ -225,10 +225,10 @@ define([
     }
 
     function getFragmentShaderCallback(content) {
-        return function(fs) {
+        return function(fs, programId) {
             var batchTable = content._batchTable;
             var gltf = content._model.gltf;
-            var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE');
+            var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE', programId);
             var callback = batchTable.getFragmentShaderCallback(true, diffuseUniformName);
             return defined(callback) ? callback(fs) : fs;
         };
diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js
index 3aaaf5f7a976..8a290a7ab0e4 100644
--- a/Source/Scene/ModelInstanceCollection.js
+++ b/Source/Scene/ModelInstanceCollection.js
@@ -223,9 +223,9 @@ define([
         BoundingSphere.expand(this._boundingSphere, translation, this._boundingSphere);
     };
 
-    function getInstancedUniforms(collection, programName) {
+    function getInstancedUniforms(collection, programId) {
         if (defined(collection._instancedUniformsByProgram)) {
-            return collection._instancedUniformsByProgram[programName];
+            return collection._instancedUniformsByProgram[programId];
         }
 
         var instancedUniformsByProgram = {};
@@ -259,7 +259,7 @@ define([
                                     uniformMap[uniformName] = semantic;
                                 } else {
                                     throw new RuntimeError('Shader program cannot be optimized for instancing. ' +
-                                        'Parameter "' + parameter + '" in program "' + programName +
+                                        'Parameter "' + parameter + '" in program "' + programId +
                                         '" uses unsupported semantic "' + semantic + '"'
                                     );
                                 }
@@ -270,14 +270,14 @@ define([
             }
         }
 
-        return instancedUniformsByProgram[programName];
+        return instancedUniformsByProgram[programId];
     }
 
     var vertexShaderCached;
 
     function getVertexShaderCallback(collection) {
-        return function(vs, programName) {
-            var instancedUniforms = getInstancedUniforms(collection, programName);
+        return function(vs, programId) {
+            var instancedUniforms = getInstancedUniforms(collection, programId);
             var usesBatchTable = defined(collection._batchTable);
 
             var renamedSource = ShaderSource.replaceMain(vs, 'czm_instancing_main');
@@ -340,7 +340,7 @@ define([
 
             if (usesBatchTable) {
                 var gltf = collection._model.gltf;
-                var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE');
+                var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE', programId);
                 instancedSource = collection._batchTable.getVertexShaderCallback(true, 'a_batchId', diffuseUniformName)(instancedSource);
             }
 
@@ -349,11 +349,11 @@ define([
     }
 
     function getFragmentShaderCallback(collection) {
-        return function(fs) {
+        return function(fs, programId) {
             var batchTable = collection._batchTable;
             if (defined(batchTable)) {
                 var gltf = collection._model.gltf;
-                var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE');
+                var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE', programId);
                 fs = batchTable.getFragmentShaderCallback(true, diffuseUniformName)(fs);
             }
             return fs;
@@ -401,13 +401,13 @@ define([
     }
 
     function getUniformMapCallback(collection, context) {
-        return function(uniformMap, programName, node) {
+        return function(uniformMap, programId, node) {
             uniformMap = clone(uniformMap);
             uniformMap.czm_instanced_modifiedModelView = createModifiedModelView(collection, context);
             uniformMap.czm_instanced_nodeTransform = createNodeTransformFunction(node);
 
             // Remove instanced uniforms from the uniform map
-            var instancedUniforms = getInstancedUniforms(collection, programName);
+            var instancedUniforms = getInstancedUniforms(collection, programId);
             for (var uniform in instancedUniforms) {
                 if (instancedUniforms.hasOwnProperty(uniform)) {
                     delete uniformMap[uniform];
@@ -433,10 +433,10 @@ define([
     }
 
     function getVertexShaderNonInstancedCallback(collection) {
-        return function(vs) {
+        return function(vs, programId) {
             if (defined(collection._batchTable)) {
                 var gltf = collection._model.gltf;
-                var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE');
+                var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE', programId);
                 vs = collection._batchTable.getVertexShaderCallback(true, 'a_batchId', diffuseUniformName)(vs);
                 // Treat a_batchId as a uniform rather than a vertex attribute
                 vs = 'uniform float a_batchId\n;' + vs;
diff --git a/Source/Scene/getAttributeOrUniformBySemantic.js b/Source/Scene/getAttributeOrUniformBySemantic.js
index b30affe7240b..d391a18c8e99 100644
--- a/Source/Scene/getAttributeOrUniformBySemantic.js
+++ b/Source/Scene/getAttributeOrUniformBySemantic.js
@@ -7,14 +7,21 @@ define([
     /**
      * Return the uniform or attribute that has the given semantic.
      *
+     * @param {Object} gltf The gltf asset.
+     * @param {String} semantic The semantic to look for in the technique's attribute and uniform parameters
+     * @param {Number} [programId] Only look at techniques that use this program
+     *
      * @private
      */
-    function getAttributeOrUniformBySemantic(gltf, semantic) {
+    function getAttributeOrUniformBySemantic(gltf, semantic, programId) {
         var techniques = gltf.techniques;
         var parameter;
         for (var techniqueName in techniques) {
             if (techniques.hasOwnProperty(techniqueName)) {
                 var technique = techniques[techniqueName];
+                if (defined(programId) && (technique.program !== programId)) {
+                    continue;
+                }
                 var parameters = technique.parameters;
                 var attributes = technique.attributes;
                 var uniforms = technique.uniforms;