diff --git a/src/materials/Material.js b/src/materials/Material.js index c08a70e4f115ea..f5c33c8be752be 100644 --- a/src/materials/Material.js +++ b/src/materials/Material.js @@ -296,6 +296,53 @@ class Material extends EventDispatcher { */ this.stencilWrite = false; + /** + * The stencil comparison function for back faces, or `null` to use + * {@link Material#stencilFunc} for both faces. + * + * @type {?number} + * @default null + */ + this.stencilBackFunc = null; + + /** + * The stencil reference value for back faces, or `null` to use + * {@link Material#stencilRef} for both faces. + * + * @type {?number} + * @default null + */ + this.stencilBackRef = null; + + /** + * Which stencil operation to perform on back faces when the comparison + * function returns `false`, or `null` to use {@link Material#stencilFail}. + * + * @type {?number} + * @default null + */ + this.stencilBackFail = null; + + /** + * Which stencil operation to perform on back faces when the comparison + * function returns `true` but the depth test fails, or `null` to use + * {@link Material#stencilZFail}. + * + * @type {?number} + * @default null + */ + this.stencilBackZFail = null; + + /** + * Which stencil operation to perform on back faces when the comparison + * function returns `true` and the depth test passes, or `null` to use + * {@link Material#stencilZPass}. + * + * @type {?number} + * @default null + */ + this.stencilBackZPass = null; + /** * User-defined clipping planes specified as THREE.Plane objects in world * space. These planes apply to the objects this material is attached to. @@ -813,6 +860,12 @@ class Material extends EventDispatcher { if ( this.stencilZPass !== KeepStencilOp ) data.stencilZPass = this.stencilZPass; if ( this.stencilWrite === true ) data.stencilWrite = this.stencilWrite; + if ( this.stencilBackFunc !== null ) data.stencilBackFunc = this.stencilBackFunc; + if ( this.stencilBackRef !== null ) data.stencilBackRef = this.stencilBackRef; + if ( this.stencilBackFail !== null ) data.stencilBackFail = this.stencilBackFail; + if ( this.stencilBackZFail !== null ) data.stencilBackZFail = this.stencilBackZFail; + if ( this.stencilBackZPass !== null ) data.stencilBackZPass = this.stencilBackZPass; + // rotation (SpriteMaterial) if ( this.rotation !== undefined && this.rotation !== 0 ) data.rotation = this.rotation; @@ -931,6 +984,12 @@ class Material extends EventDispatcher { this.stencilZPass = source.stencilZPass; this.stencilWrite = source.stencilWrite; + this.stencilBackFunc = source.stencilBackFunc; + this.stencilBackRef = source.stencilBackRef; + this.stencilBackFail = source.stencilBackFail; + this.stencilBackZFail = source.stencilBackZFail; + this.stencilBackZPass = source.stencilBackZPass; + const srcPlanes = source.clippingPlanes; let dstPlanes = null; diff --git a/src/renderers/webgl-fallback/utils/WebGLState.js b/src/renderers/webgl-fallback/utils/WebGLState.js index adf1eae5944f91..4394c1220e5819 100644 --- a/src/renderers/webgl-fallback/utils/WebGLState.js +++ b/src/renderers/webgl-fallback/utils/WebGLState.js @@ -67,13 +67,21 @@ class WebGLState { this.currentDepthReversed = false; this.currentDepthFunc = null; this.currentDepthMask = null; + this.currentStencilSeparate = false; this.currentStencilFunc = null; this.currentStencilRef = null; this.currentStencilFuncMask = null; + this.currentStencilFuncBack = null; + this.currentStencilRefBack = null; + this.currentStencilFuncMaskBack = null; this.currentStencilFail = null; this.currentStencilZFail = null; this.currentStencilZPass = null; + this.currentStencilFailBack = null; + this.currentStencilZFailBack = null; + this.currentStencilZPassBack = null; this.currentStencilMask = null; + this.currentStencilMaskBack = null; this.currentLineWidth = null; this.currentClippingPlanes = 0; @@ -829,10 +837,30 @@ class WebGLState { */ setStencilMask( stencilMask ) { - if ( this.currentStencilMask !== stencilMask ) { + if ( this.currentStencilSeparate === true || this.currentStencilMask !== stencilMask ) { this.gl.stencilMask( stencilMask ); this.currentStencilMask = stencilMask; + this.currentStencilMaskBack = stencilMask; + this.currentStencilSeparate = false; + + } + + } + + setStencilMaskSeparate( stencilMask, stencilMaskBack ) { + + if ( + this.currentStencilSeparate === false || + this.currentStencilMask !== stencilMask || + this.currentStencilMaskBack !== stencilMaskBack + ) { + + this.gl.stencilMaskSeparate( this.gl.FRONT, stencilMask ); + this.gl.stencilMaskSeparate( this.gl.BACK, stencilMaskBack ); + this.currentStencilMask = stencilMask; + this.currentStencilMaskBack = stencilMaskBack; + this.currentStencilSeparate = true; } @@ -850,7 +878,8 @@ class WebGLState { */ setStencilFunc( stencilFunc, stencilRef, stencilMask ) { - if ( this.currentStencilFunc !== stencilFunc || + if ( this.currentStencilSeparate === true || + this.currentStencilFunc !== stencilFunc || this.currentStencilRef !== stencilRef || this.currentStencilFuncMask !== stencilMask ) { @@ -859,6 +888,37 @@ class WebGLState { this.currentStencilFunc = stencilFunc; this.currentStencilRef = stencilRef; this.currentStencilFuncMask = stencilMask; + this.currentStencilFuncBack = stencilFunc; + this.currentStencilRefBack = stencilRef; + this.currentStencilFuncMaskBack = stencilMask; + this.currentStencilSeparate = false; + + } + + } + + setStencilFuncSeparate( stencilFunc, stencilRef, stencilMask, stencilFuncBack, stencilRefBack, stencilMaskBack ) { + + if ( + this.currentStencilSeparate === false || + this.currentStencilFunc !== stencilFunc || + this.currentStencilRef !== stencilRef || + this.currentStencilFuncMask !== stencilMask || + this.currentStencilFuncBack !== stencilFuncBack || + this.currentStencilRefBack !== stencilRefBack || + this.currentStencilFuncMaskBack !== stencilMaskBack + ) { + + this.gl.stencilFuncSeparate( this.gl.FRONT, stencilFunc, stencilRef, stencilMask ); + this.gl.stencilFuncSeparate( this.gl.BACK, stencilFuncBack, stencilRefBack, stencilMaskBack ); + + this.currentStencilFunc = stencilFunc; + this.currentStencilRef = stencilRef; + this.currentStencilFuncMask = stencilMask; + this.currentStencilFuncBack = stencilFuncBack; + this.currentStencilRefBack = stencilRefBack; + this.currentStencilFuncMaskBack = stencilMaskBack; + this.currentStencilSeparate = true; } @@ -877,7 +937,8 @@ class WebGLState { */ setStencilOp( stencilFail, stencilZFail, stencilZPass ) { - if ( this.currentStencilFail !== stencilFail || + if ( this.currentStencilSeparate === true || + this.currentStencilFail !== stencilFail || this.currentStencilZFail !== stencilZFail || this.currentStencilZPass !== stencilZPass ) { @@ -886,6 +947,37 @@ class WebGLState { this.currentStencilFail = stencilFail; this.currentStencilZFail = stencilZFail; this.currentStencilZPass = stencilZPass; + this.currentStencilFailBack = stencilFail; + this.currentStencilZFailBack = stencilZFail; + this.currentStencilZPassBack = stencilZPass; + this.currentStencilSeparate = false; + + } + + } + + setStencilOpSeparate( stencilFail, stencilZFail, stencilZPass, stencilFailBack, stencilZFailBack, stencilZPassBack ) { + + if ( + this.currentStencilSeparate === false || + this.currentStencilFail !== stencilFail || + this.currentStencilZFail !== stencilZFail || + this.currentStencilZPass !== stencilZPass || + this.currentStencilFailBack !== stencilFailBack || + this.currentStencilZFailBack !== stencilZFailBack || + this.currentStencilZPassBack !== stencilZPassBack + ) { + + this.gl.stencilOpSeparate( this.gl.FRONT, stencilFail, stencilZFail, stencilZPass ); + this.gl.stencilOpSeparate( this.gl.BACK, stencilFailBack, stencilZFailBack, stencilZPassBack ); + + this.currentStencilFail = stencilFail; + this.currentStencilZFail = stencilZFail; + this.currentStencilZPass = stencilZPass; + this.currentStencilFailBack = stencilFailBack; + this.currentStencilZFailBack = stencilZFailBack; + this.currentStencilZPassBack = stencilZPassBack; + this.currentStencilSeparate = true; } @@ -924,9 +1016,38 @@ class WebGLState { this.setStencilTest( stencilWrite ); if ( stencilWrite ) { - this.setStencilMask( material.stencilWriteMask ); - this.setStencilFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); - this.setStencilOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); + const hasSeparateBack = + material.stencilBackFunc !== null || + material.stencilBackFail !== null || + material.stencilBackZFail !== null || + material.stencilBackZPass !== null || + material.stencilBackRef !== null; + + if ( hasSeparateBack ) { + + this.setStencilMaskSeparate( material.stencilWriteMask, material.stencilWriteMask ); + + this.setStencilFuncSeparate( + material.stencilFunc, material.stencilRef, material.stencilFuncMask, + material.stencilBackFunc !== null ? material.stencilBackFunc : material.stencilFunc, + material.stencilBackRef !== null ? material.stencilBackRef : material.stencilRef, + material.stencilFuncMask + ); + + this.setStencilOpSeparate( + material.stencilFail, material.stencilZFail, material.stencilZPass, + material.stencilBackFail !== null ? material.stencilBackFail : material.stencilFail, + material.stencilBackZFail !== null ? material.stencilBackZFail : material.stencilZFail, + material.stencilBackZPass !== null ? material.stencilBackZPass : material.stencilZPass + ); + + } else { + + this.setStencilMask( material.stencilWriteMask ); + this.setStencilFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); + this.setStencilOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); + + } } diff --git a/src/renderers/webgl/WebGLState.js b/src/renderers/webgl/WebGLState.js index 12b39b301a422e..3aba6f7918a620 100644 --- a/src/renderers/webgl/WebGLState.js +++ b/src/renderers/webgl/WebGLState.js @@ -235,13 +235,21 @@ function WebGLState( gl, extensions ) { let locked = false; + let currentStencilSeparate = false; let currentStencilMask = null; + let currentStencilMaskBack = null; let currentStencilFunc = null; let currentStencilRef = null; let currentStencilFuncMask = null; + let currentStencilFuncBack = null; + let currentStencilRefBack = null; + let currentStencilFuncMaskBack = null; let currentStencilFail = null; let currentStencilZFail = null; let currentStencilZPass = null; + let currentStencilFailBack = null; + let currentStencilZFailBack = null; + let currentStencilZPassBack = null; let currentStencilClear = null; return { @@ -266,10 +274,28 @@ function WebGLState( gl, extensions ) { setMask: function ( stencilMask ) { - if ( currentStencilMask !== stencilMask && ! locked ) { + if ( ( currentStencilSeparate === true || currentStencilMask !== stencilMask ) && ! locked ) { gl.stencilMask( stencilMask ); currentStencilMask = stencilMask; + currentStencilMaskBack = stencilMask; + currentStencilSeparate = false; + + } + + }, + + setMaskSeparate: function ( stencilMask, stencilMaskBack ) { + + if ( ! locked && ( currentStencilSeparate === false || + currentStencilMask !== stencilMask || + currentStencilMaskBack !== stencilMaskBack ) ) { + + gl.stencilMaskSeparate( gl.FRONT, stencilMask ); + gl.stencilMaskSeparate( gl.BACK, stencilMaskBack ); + currentStencilMask = stencilMask; + currentStencilMaskBack = stencilMaskBack; + currentStencilSeparate = true; } @@ -277,7 +303,8 @@ function WebGLState( gl, extensions ) { setFunc: function ( stencilFunc, stencilRef, stencilMask ) { - if ( currentStencilFunc !== stencilFunc || + if ( currentStencilSeparate === true || + currentStencilFunc !== stencilFunc || currentStencilRef !== stencilRef || currentStencilFuncMask !== stencilMask ) { @@ -286,6 +313,35 @@ function WebGLState( gl, extensions ) { currentStencilFunc = stencilFunc; currentStencilRef = stencilRef; currentStencilFuncMask = stencilMask; + currentStencilFuncBack = stencilFunc; + currentStencilRefBack = stencilRef; + currentStencilFuncMaskBack = stencilMask; + currentStencilSeparate = false; + + } + + }, + + setFuncSeparate: function ( stencilFunc, stencilRef, stencilMask, stencilFuncBack, stencilRefBack, stencilMaskBack ) { + + if ( currentStencilSeparate === false || + currentStencilFunc !== stencilFunc || + currentStencilRef !== stencilRef || + currentStencilFuncMask !== stencilMask || + currentStencilFuncBack !== stencilFuncBack || + currentStencilRefBack !== stencilRefBack || + currentStencilFuncMaskBack !== stencilMaskBack ) { + + gl.stencilFuncSeparate( gl.FRONT, stencilFunc, stencilRef, stencilMask ); + gl.stencilFuncSeparate( gl.BACK, stencilFuncBack, stencilRefBack, stencilMaskBack ); + + currentStencilFunc = stencilFunc; + currentStencilRef = stencilRef; + currentStencilFuncMask = stencilMask; + currentStencilFuncBack = stencilFuncBack; + currentStencilRefBack = stencilRefBack; + currentStencilFuncMaskBack = stencilMaskBack; + currentStencilSeparate = true; } @@ -293,7 +349,8 @@ function WebGLState( gl, extensions ) { setOp: function ( stencilFail, stencilZFail, stencilZPass ) { - if ( currentStencilFail !== stencilFail || + if ( currentStencilSeparate === true || + currentStencilFail !== stencilFail || currentStencilZFail !== stencilZFail || currentStencilZPass !== stencilZPass ) { @@ -302,6 +359,35 @@ function WebGLState( gl, extensions ) { currentStencilFail = stencilFail; currentStencilZFail = stencilZFail; currentStencilZPass = stencilZPass; + currentStencilFailBack = stencilFail; + currentStencilZFailBack = stencilZFail; + currentStencilZPassBack = stencilZPass; + currentStencilSeparate = false; + + } + + }, + + setOpSeparate: function ( stencilFail, stencilZFail, stencilZPass, stencilFailBack, stencilZFailBack, stencilZPassBack ) { + + if ( currentStencilSeparate === false || + currentStencilFail !== stencilFail || + currentStencilZFail !== stencilZFail || + currentStencilZPass !== stencilZPass || + currentStencilFailBack !== stencilFailBack || + currentStencilZFailBack !== stencilZFailBack || + currentStencilZPassBack !== stencilZPassBack ) { + + gl.stencilOpSeparate( gl.FRONT, stencilFail, stencilZFail, stencilZPass ); + gl.stencilOpSeparate( gl.BACK, stencilFailBack, stencilZFailBack, stencilZPassBack ); + + currentStencilFail = stencilFail; + currentStencilZFail = stencilZFail; + currentStencilZPass = stencilZPass; + currentStencilFailBack = stencilFailBack; + currentStencilZFailBack = stencilZFailBack; + currentStencilZPassBack = stencilZPassBack; + currentStencilSeparate = true; } @@ -328,13 +414,21 @@ function WebGLState( gl, extensions ) { locked = false; + currentStencilSeparate = false; currentStencilMask = null; + currentStencilMaskBack = null; currentStencilFunc = null; currentStencilRef = null; currentStencilFuncMask = null; + currentStencilFuncBack = null; + currentStencilRefBack = null; + currentStencilFuncMaskBack = null; currentStencilFail = null; currentStencilZFail = null; currentStencilZPass = null; + currentStencilFailBack = null; + currentStencilZFailBack = null; + currentStencilZPassBack = null; currentStencilClear = null; } @@ -775,9 +869,38 @@ function WebGLState( gl, extensions ) { stencilBuffer.setTest( stencilWrite ); if ( stencilWrite ) { - stencilBuffer.setMask( material.stencilWriteMask ); - stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); - stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); + const hasSeparateBack = + material.stencilBackFunc !== null || + material.stencilBackFail !== null || + material.stencilBackZFail !== null || + material.stencilBackZPass !== null || + material.stencilBackRef !== null; + + if ( hasSeparateBack ) { + + stencilBuffer.setMaskSeparate( material.stencilWriteMask, material.stencilWriteMask ); + + stencilBuffer.setFuncSeparate( + material.stencilFunc, material.stencilRef, material.stencilFuncMask, + material.stencilBackFunc !== null ? material.stencilBackFunc : material.stencilFunc, + material.stencilBackRef !== null ? material.stencilBackRef : material.stencilRef, + material.stencilFuncMask + ); + + stencilBuffer.setOpSeparate( + material.stencilFail, material.stencilZFail, material.stencilZPass, + material.stencilBackFail !== null ? material.stencilBackFail : material.stencilFail, + material.stencilBackZFail !== null ? material.stencilBackZFail : material.stencilZFail, + material.stencilBackZPass !== null ? material.stencilBackZPass : material.stencilZPass + ); + + } else { + + stencilBuffer.setMask( material.stencilWriteMask ); + stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); + stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); + + } } diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 8204da58f43ca3..09f52376084505 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -1858,6 +1858,8 @@ class WebGPUBackend extends Backend { data.stencilWrite !== material.stencilWrite || data.stencilFunc !== material.stencilFunc || data.stencilFail !== material.stencilFail || data.stencilZFail !== material.stencilZFail || data.stencilZPass !== material.stencilZPass || data.stencilFuncMask !== material.stencilFuncMask || data.stencilWriteMask !== material.stencilWriteMask || + data.stencilBackFunc !== material.stencilBackFunc || data.stencilBackRef !== material.stencilBackRef || + data.stencilBackFail !== material.stencilBackFail || data.stencilBackZFail !== material.stencilBackZFail || data.stencilBackZPass !== material.stencilBackZPass || data.side !== material.side || data.alphaToCoverage !== material.alphaToCoverage || data.sampleCount !== sampleCount || data.colorSpace !== colorSpace || data.colorFormat !== colorFormat || data.depthStencilFormat !== depthStencilFormat || @@ -1874,6 +1876,8 @@ class WebGPUBackend extends Backend { data.stencilWrite = material.stencilWrite; data.stencilFunc = material.stencilFunc; data.stencilFail = material.stencilFail; data.stencilZFail = material.stencilZFail; data.stencilZPass = material.stencilZPass; data.stencilFuncMask = material.stencilFuncMask; data.stencilWriteMask = material.stencilWriteMask; + data.stencilBackFunc = material.stencilBackFunc; data.stencilBackRef = material.stencilBackRef; + data.stencilBackFail = material.stencilBackFail; data.stencilBackZFail = material.stencilBackZFail; data.stencilBackZPass = material.stencilBackZPass; data.side = material.side; data.alphaToCoverage = material.alphaToCoverage; data.sampleCount = sampleCount; data.colorSpace = colorSpace; @@ -1917,6 +1921,8 @@ class WebGPUBackend extends Backend { material.stencilWrite, material.stencilFunc, material.stencilFail, material.stencilZFail, material.stencilZPass, material.stencilFuncMask, material.stencilWriteMask, + material.stencilBackFunc, material.stencilBackRef, + material.stencilBackFail, material.stencilBackZFail, material.stencilBackZPass, material.side, frontFaceCW, utils.getSampleCountRenderContext( renderContext ), diff --git a/src/renderers/webgpu/utils/WebGPUPipelineUtils.js b/src/renderers/webgpu/utils/WebGPUPipelineUtils.js index 75a947b416e45d..8c0c6de0a9f1e0 100644 --- a/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +++ b/src/renderers/webgpu/utils/WebGPUPipelineUtils.js @@ -133,7 +133,7 @@ class WebGPUPipelineUtils { if ( material.stencilWrite === true ) { stencilFront = { - compare: this._getStencilCompare( material ), + compare: this._getStencilCompare( material.stencilFunc ), failOp: this._getStencilOperation( material.stencilFail ), depthFailOp: this._getStencilOperation( material.stencilZFail ), passOp: this._getStencilOperation( material.stencilZPass ) @@ -250,7 +250,13 @@ class WebGPUPipelineUtils { if ( renderStencil === true ) { depthStencil.stencilFront = stencilFront; - depthStencil.stencilBack = stencilFront; // apply the same stencil ops to both faces, matching gl.stencilOp() which is not face-separated + depthStencil.stencilBack = material.stencilWrite === true ? { + compare: material.stencilBackFunc !== null ? this._getStencilCompare( material.stencilBackFunc ) : stencilFront.compare, + failOp: material.stencilBackFail !== null ? this._getStencilOperation( material.stencilBackFail ) : stencilFront.failOp, + depthFailOp: material.stencilBackZFail !== null ? this._getStencilOperation( material.stencilBackZFail ) : stencilFront.depthFailOp, + passOp: material.stencilBackZPass !== null ? this._getStencilOperation( material.stencilBackZPass ) : stencilFront.passOp + } : stencilFront; + depthStencil.stencilReadMask = material.stencilFuncMask; depthStencil.stencilWriteMask = material.stencilWriteMask; @@ -575,15 +581,13 @@ class WebGPUPipelineUtils { * Returns the GPU stencil compare function which is required for the pipeline creation. * * @private - * @param {Material} material - The material. + * @param {number} stencilFunc - The stencil comparison function constant. * @return {string} The GPU stencil compare function. */ - _getStencilCompare( material ) { + _getStencilCompare( stencilFunc ) { let stencilCompare; - const stencilFunc = material.stencilFunc; - switch ( stencilFunc ) { case NeverStencilFunc: diff --git a/test/unit/src/materials/Material.tests.js b/test/unit/src/materials/Material.tests.js index c8b8642a71bf89..a7ae8be23daa22 100644 --- a/test/unit/src/materials/Material.tests.js +++ b/test/unit/src/materials/Material.tests.js @@ -57,6 +57,56 @@ export default QUnit.module( 'Materials', () => { } ); + QUnit.test( 'stencilBack defaults', ( assert ) => { + + const object = new Material(); + + assert.strictEqual( object.stencilBackFunc, null, 'stencilBackFunc defaults to null' ); + assert.strictEqual( object.stencilBackRef, null, 'stencilBackRef defaults to null' ); + assert.strictEqual( object.stencilBackFail, null, 'stencilBackFail defaults to null' ); + assert.strictEqual( object.stencilBackZFail, null, 'stencilBackZFail defaults to null' ); + assert.strictEqual( object.stencilBackZPass, null, 'stencilBackZPass defaults to null' ); + + } ); + + QUnit.test( 'stencilBack copy', ( assert ) => { + + const source = new Material(); + source.stencilBackFunc = 1; + source.stencilBackRef = 2; + source.stencilBackFail = 3; + source.stencilBackZFail = 4; + source.stencilBackZPass = 5; + + const copy = new Material().copy( source ); + + assert.strictEqual( copy.stencilBackFunc, 1, 'stencilBackFunc copied' ); + assert.strictEqual( copy.stencilBackRef, 2, 'stencilBackRef copied' ); + assert.strictEqual( copy.stencilBackFail, 3, 'stencilBackFail copied' ); + assert.strictEqual( copy.stencilBackZFail, 4, 'stencilBackZFail copied' ); + assert.strictEqual( copy.stencilBackZPass, 5, 'stencilBackZPass copied' ); + + } ); + + QUnit.test( 'stencilBack serialization', ( assert ) => { + + const object = new Material(); + object.stencilBackFunc = 1; + object.stencilBackRef = 2; + object.stencilBackFail = 3; + object.stencilBackZFail = 4; + object.stencilBackZPass = 5; + + const json = object.toJSON(); + + assert.strictEqual( json.stencilBackFunc, 1, 'stencilBackFunc serialized' ); + assert.strictEqual( json.stencilBackRef, 2, 'stencilBackRef serialized' ); + assert.strictEqual( json.stencilBackFail, 3, 'stencilBackFail serialized' ); + assert.strictEqual( json.stencilBackZFail, 4, 'stencilBackZFail serialized' ); + assert.strictEqual( json.stencilBackZPass, 5, 'stencilBackZPass serialized' ); + + } ); + } ); } );