Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Color blend mode support for vertex shaders #5874

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Change Log
==========

### 1.42 - 2017-02-01

* Added support for vertex shader uniforms when `tileset.colorBlendMode` is `MIX` or `REPLACE`. [#5874](https://github.com/AnalyticalGraphicsInc/cesium/pull/5874)

### 1.41 - 2018-01-02

* Breaking changes
Expand Down Expand Up @@ -52,6 +56,7 @@ Change Log
* Added `customTags` property to the UrlTemplateImageryProvider to allow custom keywords in the template URL. [#5696](https://github.com/AnalyticalGraphicsInc/cesium/pull/5696)
* Added `eyeSeparation` and `focalLength` properties to `Scene` to configure VR settings. [#5917](https://github.com/AnalyticalGraphicsInc/cesium/pull/5917)
* Improved CZML Reference Properties example [#5754](https://github.com/AnalyticalGraphicsInc/cesium/pull/5754)
>>>>>>> master
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A diff marker slipped through here.


### 1.38 - 2017-10-02

Expand Down
9 changes: 5 additions & 4 deletions Source/Scene/Batched3DModel3DTileContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,12 @@ 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 callback = batchTable.getVertexShaderCallback(true, batchIdAttributeName);
var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE', programId);
var callback = batchTable.getVertexShaderCallback(true, batchIdAttributeName, diffuseUniformName);
return defined(callback) ? callback(vs) : vs;
};
}
Expand All @@ -224,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;
};
Expand Down
62 changes: 45 additions & 17 deletions Source/Scene/Cesium3DTileBatchTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -846,14 +846,16 @@ 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');
// 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) {
Expand All @@ -867,9 +869,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) {
Expand All @@ -894,11 +896,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' +
'}';
}
Expand All @@ -907,21 +910,35 @@ define([
};
};

function getHighlightOnlyShader(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)
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) {
// 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 getHighlightOnlyShader(source);
return getDefaultShader(source, applyHighlight);
}

// Find the diffuse uniform. Examples matches:
Expand All @@ -932,7 +949,7 @@ define([

if (!defined(uniformMatch)) {
// Could not find uniform declaration of type vec3, vec4, or sampler2D
return getHighlightOnlyShader(source);
return getDefaultShader(source, applyHighlight);
}

var declaration = uniformMatch[0];
Expand All @@ -946,16 +963,20 @@ 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' +
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this because I noticed the == vec3(1.0) was too exact and started causing lots of aliasing on my desktop.

'{ \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 =
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';
Expand All @@ -971,7 +992,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' +
Expand All @@ -986,9 +1010,13 @@ define([
source + '\n' +
'void tile_color(vec4 tile_featureColor) \n' +
'{ \n' +
setColor +
applyHighlight +
'} \n';
setColor;

if (applyHighlight) {
source += highlight;
}

source += '} \n';

return source;
}
Expand All @@ -998,7 +1026,7 @@ define([
return;
}
return function(source) {
source = modifyDiffuse(source, diffuseUniformName);
source = modifyDiffuse(source, diffuseUniformName, true);
if (ContextLimits.maximumVertexTextureImageUnits > 0) {
// When VTF is supported, per-feature show/hide already happened in the fragment shader
source +=
Expand Down
4 changes: 4 additions & 0 deletions Source/Scene/Cesium3DTileColorBlendMode.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ define([
* When <code>REPLACE</code> or <code>MIX</code> are used and the source color is a glTF material, the technique must assign the
* <code>_3DTILESDIFFUSE</code> semantic to the diffuse color parameter. Otherwise only <code>HIGHLIGHT</code> is supported.
* </p>
* <p>
* 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.
* </p>
* <pre><code>
* "techniques": {
* "technique0": {
Expand Down
30 changes: 17 additions & 13 deletions Source/Scene/ModelInstanceCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand Down Expand Up @@ -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 + '"'
);
}
Expand All @@ -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');
Expand Down Expand Up @@ -339,19 +339,21 @@ define([
vertexShaderCached = instancedSource;

if (usesBatchTable) {
instancedSource = collection._batchTable.getVertexShaderCallback(true, 'a_batchId')(instancedSource);
var gltf = collection._model.gltf;
var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE', programId);
instancedSource = collection._batchTable.getVertexShaderCallback(true, 'a_batchId', diffuseUniformName)(instancedSource);
}

return instancedSource;
};
}

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;
Expand Down Expand Up @@ -399,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];
Expand All @@ -431,9 +433,11 @@ define([
}

function getVertexShaderNonInstancedCallback(collection) {
return function(vs) {
return function(vs, programId) {
if (defined(collection._batchTable)) {
vs = collection._batchTable.getVertexShaderCallback(true, 'a_batchId')(vs);
var gltf = collection._model.gltf;
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;
}
Expand Down
2 changes: 1 addition & 1 deletion Source/Scene/PointCloud3DTileContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,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);
}

Expand Down
9 changes: 8 additions & 1 deletion Source/Scene/getAttributeOrUniformBySemantic.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down