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

Update technique render states and support for KHR_blend #387

Merged
merged 12 commits into from
Jul 11, 2018
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Content pipeline tools for optimizing [glTF](https://www.khronos.org/gltf) asset
Supports common operations including:
* Converting glTF to glb (and reverse)
* Saving buffers/textures as embedded or separate files
* Converting glTF 1.0 models to glTF 2.0 (using the [KHR_techniques_webgl](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_techniques_webgl) extension)
* Converting glTF 1.0 models to glTF 2.0 (using the [KHR_techniques_webgl](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_techniques_webgl) and [EXT_blend](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_blend) extensions)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is EXT_blend expected to be the final name, or will it be KHR_blend? It seems like a bunch of extension proposals have that prefix but I'm not sure whether that's the actual prefix or just a placeholder.


`gltf-pipeline` can be used as a command-line tool or Node.js module.

Expand Down
133 changes: 133 additions & 0 deletions lib/moveTechniqueRenderStates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
'use strict';

var Cesium = require('cesium');
var addExtensionsUsed = require('./addExtensionsUsed');
var ForEach = require('./ForEach');

var defaultValue = Cesium.defaultValue;
var defined = Cesium.defined;
var WebGLConstants = Cesium.WebGLConstants;

module.exports = moveTechniqueRenderStates;

var defaultBlendEquation = [
WebGLConstants.FUNC_ADD,
WebGLConstants.FUNC_ADD
];

var defaultBlendFactors = [
WebGLConstants.ONE,
WebGLConstants.ZERO,
WebGLConstants.ONE,
WebGLConstants.ZERO
];

function isStateEnabled(renderStates, state) {
var enabled = renderStates.enable;
if (!defined(enabled)) {
return false;
}

return (enabled.indexOf(state) > -1);
}

var supportedBlendFactors = [
WebGLConstants.ZERO,
WebGLConstants.ONE,
WebGLConstants.SRC_COLOR,
WebGLConstants.ONE_MINUS_SRC_COLOR,
WebGLConstants.SRC_ALPHA,
WebGLConstants.ONE_MINUS_SRC_ALPHA,
WebGLConstants.DST_ALPHA,
WebGLConstants.ONE_MINUS_DST_ALPHA,
WebGLConstants.DST_COLOR,
WebGLConstants.ONE_MINUS_DST_COLOR
];

// If any of the blend factors are not supported, return the default
function getSupportedBlendFactors(value, defaultValue) {
if (!defined(value)) {
return defaultValue;
}

for (var i = 0; i < 4; i++) {
if (supportedBlendFactors.indexOf(value[i]) === -1) {
return defaultValue;
}
}

return value;
}

/**
* Move glTF 1.0 technique render states to glTF 2.0 materials properties and EXT_blend extension.
*
* @param {Object} gltf A javascript object containing a glTF asset.
* @returns {Object} The updated glTF asset.
*
* @private
*/
function moveTechniqueRenderStates(gltf) {
var blendingForTechnique = {};
var materialPropertiesForTechnique = {};
var techniquesLegacy = gltf.techniques;
if (!defined(techniquesLegacy)) {
return gltf;
}

ForEach.technique(gltf, function (techniqueLegacy, techniqueIndex) {
var renderStates = techniqueLegacy.states;
if (defined(renderStates)) {
var materialProperties = materialPropertiesForTechnique[techniqueIndex] = {};

// If BLEND is enabled, the material should have alpha mode BLEND
if (isStateEnabled(renderStates, WebGLConstants.BLEND)) {
materialProperties.alphaMode = 'BLEND';

var blendFunctions = renderStates.functions;
if (defined(blendFunctions) && (defined(blendFunctions.blendEquationSeparate)
|| defined(blendFunctions.blendFuncSeparate))) {
blendingForTechnique[techniqueIndex] = {
blendEquation: defaultValue(blendFunctions.blendEquationSeparate, defaultBlendEquation),
blendFactors: getSupportedBlendFactors(blendFunctions.blendFuncSeparate, defaultBlendFactors)
};
}
}

// If CULL_FACE is not enabled, the material should be doubleSided
if (!isStateEnabled(renderStates, WebGLConstants.CULL_FACE)) {
materialProperties.doubleSided = true;
}

delete techniqueLegacy.states;
}
});

if (Object.keys(blendingForTechnique).length > 0) {
if (!defined(gltf.extensions)) {
gltf.extensions = {};
}

addExtensionsUsed(gltf, 'EXT_blend');
}

ForEach.material(gltf, function (material) {
if (defined(material.technique)) {
var materialProperties = materialPropertiesForTechnique[material.technique];
ForEach.objectLegacy(materialProperties, function (value, property) {
material[property] = value;
});

var blending = blendingForTechnique[material.technique];
if (defined(blending)) {
if (!defined(material.extensions)) {
material.extensions = {};
}

material.extensions.EXT_blend = blending;
}
}
});

return gltf;
}
5 changes: 4 additions & 1 deletion lib/updateVersion.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ var findAccessorMinMax = require('./findAccessorMinMax');
var ForEach = require('./ForEach');
var getAccessorByteStride = require('./getAccessorByteStride');
var numberOfComponentsForType = require('./numberOfComponentsForType');
var removeUnusedElements = require('./removeUnusedElements');
var moveTechniqueRenderStates = require('./moveTechniqueRenderStates');
var moveTechniquesToExtension = require('./moveTechniquesToExtension');
var removeUnusedElements = require('./removeUnusedElements');

var Cartesian3 = Cesium.Cartesian3;
var clone = Cesium.clone;
Expand Down Expand Up @@ -841,6 +842,8 @@ function glTF10to20(gltf) {
underscoreApplicationSpecificSemantics(gltf);
// Clamp camera parameters
clampCameraParameters(gltf);
// Move legacy technique render states to material properties and add EXT_blend extension blending functions
moveTechniqueRenderStates(gltf);
// Add material techniques to KHR_techniques_webgl extension, removing shaders, programs, and techniques
moveTechniquesToExtension(gltf);
// Remove empty arrays
Expand Down
219 changes: 219 additions & 0 deletions specs/lib/moveTechniqueRenderStatesSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
'use strict';
var Cesium = require('cesium');
var moveTechniqueRenderStates = require('../../lib/moveTechniqueRenderStates');

var WebGLConstants = Cesium.WebGLConstants;

var gltf = {
programs: {
program_0: {
attributes: [
'a_normal',
'a_position',
'a_texcoord0'
],
fragmentShader: 'BoxTextured0FS',
vertexShader: 'BoxTextured0VS'
}
},
shaders: {
BoxTextured0FS: {
type: WebGLConstants.FRAGMENT_SHADER,
uri: 'BoxTextured0FS.glsl'
},
BoxTextured0VS: {
type: WebGLConstants.VERTEX_SHADER,
uri: 'BoxTextured0VS.glsl'
}
},
techniques: {
technique0: {
states: {
enable: [
WebGLConstants.DEPTH_TEST,
WebGLConstants.BLEND,
WebGLConstants.CULL_FACE
]
}
}
},
materials: [
{
technique: 'technique0'
}
]
};

describe('updateMaterialProperties', function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Update the name to moveTechniqueToRenderStates.

it('sets material.doubleSided property if CULL_FACE is not enabled', function () {
var gltfWithUpdatedMaterials = moveTechniqueRenderStates(gltf);
Copy link
Contributor

Choose a reason for hiding this comment

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

gltf should be cloned first.

Copy link
Contributor

@lilleyse lilleyse Jun 29, 2018

Choose a reason for hiding this comment

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

This may also be causing intermittent failures when tests are run in random order, which I think jasmine does now?

var material = gltfWithUpdatedMaterials.materials[0];
expect(material.doubleSided).toBeUndefined();

var gltfDoubleSided = JSON.parse(JSON.stringify(gltf));
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe use Cesium's clone function instead. clone(gltf, true).

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 was having issues with the depth of the cloning when it came to arrays (even when using the deep clone argument). This was much more reliable.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is that a Cesium bug? I don't think that should be happening...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The documentation isn't partially clear on what the behavior should be: https://cesiumjs.org/Cesium/Build/Documentation/clone.html

Copy link
Contributor

Choose a reason for hiding this comment

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

Alright, if this way works consistently then let's go with it.

gltfDoubleSided.techniques.technique0.states = {
enable : [
WebGLConstants.DEPTH_TEST,
WebGLConstants.BLEND
]
};

gltfWithUpdatedMaterials = moveTechniqueRenderStates(gltfDoubleSided);
material = gltfWithUpdatedMaterials.materials[0];
expect(material.doubleSided).toBe(true);
});

it('sets alphaMode and moves technique render state blending functions to material EXT_blend extension', function() {
var gltfWithBlendFunctions = JSON.parse(JSON.stringify(gltf));

gltfWithBlendFunctions.techniques.technique0.states = {
enable: [
WebGLConstants.DEPTH_TEST,
WebGLConstants.BLEND,
WebGLConstants.CULL_FACE
],
functions: {
blendEquationSeparate: [
WebGLConstants.FUNC_ADD,
WebGLConstants.FUNC_ADD
],
blendFuncSeparate: [
WebGLConstants.ONE,
WebGLConstants.ONE_MINUS_SRC_ALPHA,
WebGLConstants.ONE,
WebGLConstants.ONE_MINUS_SRC_ALPHA
]
}
};

var gltfWithBlendExtension = moveTechniqueRenderStates(gltfWithBlendFunctions);
expect(gltfWithBlendExtension.extensionsUsed.indexOf('EXT_blend')).toBeGreaterThan(-1);
expect(gltfWithBlendExtension.extensionsRequired).toBeUndefined();

var material = gltfWithBlendExtension.materials[0];
expect(material.alphaMode).toBe('BLEND');
expect(material.extensions).toBeDefined();
var materialBlending = material.extensions.EXT_blend;
expect(materialBlending).toBeDefined();
expect(materialBlending.blendEquation).toEqual([
WebGLConstants.FUNC_ADD,
WebGLConstants.FUNC_ADD
]);
expect(materialBlending.blendFactors).toEqual([
WebGLConstants.ONE,
WebGLConstants.ONE_MINUS_SRC_ALPHA,
WebGLConstants.ONE,
WebGLConstants.ONE_MINUS_SRC_ALPHA
]);
});

it('provides defaults for extension properties if not provided', function () {
var gltfWithBlendFunctions = JSON.parse(JSON.stringify(gltf));
gltfWithBlendFunctions.techniques.technique0.states = {
enable: [
WebGLConstants.DEPTH_TEST,
WebGLConstants.BLEND,
WebGLConstants.CULL_FACE
],
functions: {
blendFuncSeparate: [
WebGLConstants.ONE,
WebGLConstants.ONE_MINUS_SRC_ALPHA,
WebGLConstants.ONE,
WebGLConstants.ONE_MINUS_SRC_ALPHA
]
}
};

var gltfWithBlendExtension = moveTechniqueRenderStates(gltfWithBlendFunctions);
var materialBlending = gltfWithBlendExtension.materials[0].extensions.EXT_blend;
expect(materialBlending).toBeDefined();
expect(materialBlending.blendEquation).toEqual([
WebGLConstants.FUNC_ADD,
WebGLConstants.FUNC_ADD
]);
expect(materialBlending.blendFactors).toEqual([
WebGLConstants.ONE,
WebGLConstants.ONE_MINUS_SRC_ALPHA,
WebGLConstants.ONE,
WebGLConstants.ONE_MINUS_SRC_ALPHA
]);

gltfWithBlendFunctions.techniques.technique0.states = {
enable: [
WebGLConstants.DEPTH_TEST,
WebGLConstants.BLEND,
WebGLConstants.CULL_FACE
],
functions: {
blendEquationSeparate: [
WebGLConstants.FUNC_ADD,
WebGLConstants.FUNC_ADD
]
}
};

gltfWithBlendExtension = moveTechniqueRenderStates(gltfWithBlendFunctions);
materialBlending = gltfWithBlendExtension.materials[0].extensions.EXT_blend;
expect(materialBlending).toBeDefined();
expect(materialBlending.blendEquation).toEqual([
WebGLConstants.FUNC_ADD,
WebGLConstants.FUNC_ADD
]);
expect(materialBlending.blendFactors).toEqual([
WebGLConstants.ONE,
WebGLConstants.ZERO,
WebGLConstants.ONE,
WebGLConstants.ZERO
]);
});

it('falls back to default blending factors if unsupported factor is found', function () {
var gltfWithBlendFunctions = JSON.parse(JSON.stringify(gltf));
gltfWithBlendFunctions.techniques.technique0.states = {
enable: [
WebGLConstants.DEPTH_TEST,
WebGLConstants.BLEND,
WebGLConstants.CULL_FACE
],
functions : {
blendFuncSeparate: [
WebGLConstants.SRC_ALPHA_SATURATE,
WebGLConstants.ONE_MINUS_SRC_ALPHA,
WebGLConstants.SRC_ALPHA_SATURATE,
WebGLConstants.ONE_MINUS_SRC_ALPHA
]
}
};

var gltfWithBlendExtension = moveTechniqueRenderStates(gltfWithBlendFunctions);

var materialBlending = gltfWithBlendExtension.materials[0].extensions.EXT_blend;
expect(materialBlending).toBeDefined();
expect(materialBlending.blendFactors).toEqual([
WebGLConstants.ONE,
WebGLConstants.ZERO,
WebGLConstants.ONE,
WebGLConstants.ZERO
]);
});

it('does not set alphaMode or add EXT_blend if no blending is found in render states', function () {
var gltfWithoutBlending = JSON.parse(JSON.stringify(gltf));
delete gltfWithoutBlending.techniques.technique0.states;
gltfWithoutBlending.techniques.technique0.states = {
enable: [
WebGLConstants.DEPTH_TEST,
WebGLConstants.CULL_FACE
]
};

var updatedGltf = moveTechniqueRenderStates(gltfWithoutBlending);
expect(updatedGltf.extensionsUsed).toBeUndefined();
expect(updatedGltf.extensionsRequired).toBeUndefined();

var material = updatedGltf.materials[0];
expect(material.alphaMode).toBeUndefined();
expect(material.extensions).toBeUndefined();
});
});
Loading