ソースを参照

Merge pull request #15611 from gkjohnson/add-material-stencil

Add Stencil Parameter to Materials
WestLangley 6 年 前
コミット
b64348cfd5

+ 45 - 0
docs/api/en/constants/Materials.html

@@ -92,7 +92,52 @@
 		[page:Constant MixOperation] uses reflectivity to blend between the two colors.<br />
 		[page:Constant AddOperation] adds the two colors.
 		</p>
+		
+		<h2>Stencil Functions</h2>
+		<code>
+		THREE.NeverStencilFunc
+		THREE.LessStencilFunc
+		THREE.EqualStencilFunc
+		THREE.LessEqualStencilFunc
+		THREE.GreaterStencilFunc
+		THREE.NotEqualStencilFunc
+		THREE.GreaterEqualStencilFunc
+		THREE.AlwaysStencilFunc
+		</code>
+		<p>
+		Which stencil function the material uses to determine whether or not to perform a stencil operation.<br />
+		[page:Materials NeverStencilFunc] will never return true.<br />
+		[page:Materials LessStencilFunc] will return true if the stencil reference value is less than the current stencil value.<br />
+		[page:Materials EqualStencilFunc] will return true if the stencil reference value is equal to the current stencil value.<br />
+		[page:Materials LessEqualStencilFunc] will return true if the stencil reference value is less than or equal to the current stencil value.<br />
+		[page:Materials GreaterStencilFunc] will return true if the stencil reference value is greater than the current stencil value.<br />
+		[page:Materials NotEqualStencilFunc] will return true if the stencil reference value is not equal to the current stencil value.<br />
+		[page:Materials GreaterEqualStencilFunc] will return true if the stencil reference value is greater than or equal to the current stencil value.<br />
+		[page:Materials AlwaysStencilFunc] will always return true.<br />
+		</p>
 
+		<h2>Stencil Operations</h2>
+		<code>
+		THREE.ZeroStencilOp
+		THREE.KeepStencilOp
+		THREE.ReplaceStencilOp
+		THREE.IncrementStencilOp
+		THREE.DecrementStencilOp
+		THREE.IncrementWrapStencilOp
+		THREE.DecrementWrapStencilOp
+		THREE.InvertStencilOp
+		</code>
+		<p>
+		Which stencil operation the material will perform on the stencil buffer pixel if the provided stencil function passes.<br />
+		[page:Materials ZeroStencilOp] will set the stencil value to 0.<br />
+		[page:Materials KeepStencilOp] will not change the current stencil value.<br />
+		[page:Materials ReplaceStencilOp] will replace the stencil value with the specified stencil reference value.<br />
+		[page:Materials IncrementStencilOp] will increment the current stencil value by 1.<br />
+		[page:Materials DecrementStencilOp] will decrement the current stencil value by 1.<br />
+		[page:Materials IncrementWrapStencilOp] will increment the current stencil value by 1. If the value increments past 255 it will be set to 0.<br />
+		[page:Materials DecrementWrapStencilOp] will increment the current stencil value by 1. If the value decrements below 0 it will be set to 255.<br />
+		[page:Materials InvertStencilOp] will perform a bitwise iversion of the current stencil value.<br />
+		</p>
 
 		<h2>Source</h2>
 

+ 35 - 0
docs/api/en/materials/Material.html

@@ -123,6 +123,41 @@
 		When drawing 2D overlays it can be useful to disable the depth writing in order to layer several things together without creating z-index artifacts.
 		</p>
 
+		<h3>[property:Boolean stencilWrite]</h3>
+		<p>
+		Whether rendering this material has any effect on the stencil buffer. Default is *false*.
+		</p>
+
+		<h3>[property:Boolean stencilFunc]</h3>
+		<p>
+		The stencil comparison function to use. Default is [page:Materials AlwaysStencilFunc]. See stencil function [page:Materials constants] for all possible values.
+		</p>
+
+		<h3>[property:Integer stencilRef]</h3>
+		<p>
+		The value to use when performing stencil comparisons or stencil operations. Default is *0*.
+		</p>
+
+		<h3>[property:Boolean stencilMask]</h3>
+		<p>
+		The bit mask to use when comparing against or writing to the stencil buffer. Default is *0xFF*.
+		</p>
+
+		<h3>[property:Integer stencilFail]</h3>
+		<p>
+		Which stencil operation to perform when the comparison function returns false. Default is [page:Materials KeepStencilOp]. See the stencil operations [page:Materials constants] for all possible values.
+		</p>
+
+		<h3>[property:Boolean stencilZFail]</h3>
+		<p>
+		Which stencil operation to perform when the comparison function returns true but the depth test fails. Default is [page:Materials KeepStencilOp]. See the stencil operations [page:Materials constants] for all possible values.
+		</p>
+
+		<h3>[property:Boolean stencilZPass]</h3>
+		<p>
+		Which stencil operation to perform when the comparison function returns true and the depth test passes. Default is [page:Materials KeepStencilOp]. See the stencil operations [page:Materials constants] for all possible values.
+		</p>
+
 		<h3>[property:Boolean flatShading]</h3>
 		<p>
 		Define whether the material is rendered with flat shading. Default is false.

+ 1 - 0
examples/files.js

@@ -12,6 +12,7 @@ var files = {
 		"webgl_clipping",
 		"webgl_clipping_advanced",
 		"webgl_clipping_intersection",
+		"webgl_clipping_stencil",
 		"webgl_decals",
 		"webgl_depth_texture",
 		"webgl_effects_anaglyph",

+ 325 - 0
examples/webgl_clipping_stencil.html

@@ -0,0 +1,325 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - clipping stencil</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				color: #ffffff;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+				font-weight: bold;
+
+				background-color: #000000;
+				margin: 0px;
+				overflow: hidden;
+			}
+			#info {
+				color: #fff;
+				position: absolute;
+				top: 0px; width: 100%;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body>
+
+		<div id="info"><a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - solid geometry with clip planes and stencil materials</div>
+
+		<script type="module">
+			import * as THREE from '../build/three.module.js';
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+			import { GUI } from './jsm/libs/dat.gui.module.js';
+			import Stats from './jsm/libs/stats.module.js';
+
+			var camera, scene, renderer, startTime, object, stats;
+			var planes, planeObjects, planeHelpers;
+			var clock;
+
+			var params = {
+
+				animate: true,
+				planeX: {
+
+					constant: 0,
+					negated: false,
+					displayHelper: false
+
+				},
+				planeY: {
+
+					constant: 0,
+					negated: false,
+					displayHelper: false
+
+				},
+				planeZ: {
+
+					constant: 0,
+					negated: false,
+					displayHelper: false
+
+				}
+
+
+			};
+
+			init();
+			animate();
+
+			function createPlaneStencilGroup( geometry, plane, renderOrder ) {
+
+				var group = new THREE.Group();
+				var baseMat = new THREE.MeshBasicMaterial();
+				baseMat.depthWrite = false;
+				baseMat.depthTest = false;
+				baseMat.colorWrite = false;
+				baseMat.stencilWrite = true;
+				baseMat.stencilFunc = THREE.AlwaysStencilFunc;
+
+				// back faces
+				var mat0 = baseMat.clone();
+				mat0.side = THREE.BackSide;
+				mat0.clippingPlanes = [ plane ];
+				mat0.stencilFail = THREE.IncrementWrapStencilOp;
+				mat0.stencilZFail = THREE.IncrementWrapStencilOp;
+				mat0.stencilZPass = THREE.IncrementWrapStencilOp;
+
+				var mesh0 = new THREE.Mesh( geometry, mat0 );
+				mesh0.renderOrder = renderOrder;
+				group.add( mesh0 );
+
+				// front faces
+				var mat1 = baseMat.clone();
+				mat1.side = THREE.FrontSide;
+				mat1.clippingPlanes = [ plane ];
+				mat1.stencilFail = THREE.DecrementWrapStencilOp;
+				mat1.stencilZFail = THREE.DecrementWrapStencilOp;
+				mat1.stencilZPass = THREE.DecrementWrapStencilOp;
+
+				var mesh1 = new THREE.Mesh( geometry, mat1 );
+				mesh1.renderOrder = renderOrder;
+
+				group.add( mesh1 );
+
+				return group;
+
+			}
+
+			function init() {
+
+				clock = new THREE.Clock();
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 36, window.innerWidth / window.innerHeight, 1, 100 );
+				camera.position.set( 2, 2, 2 );
+
+				scene.add( new THREE.AmbientLight( 0xffffff, 0.5 ) );
+
+				var dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
+				dirLight.position.set( 5, 10, 7.5 );
+				dirLight.castShadow = true;
+				dirLight.shadow.camera.right = 2;
+				dirLight.shadow.camera.left = - 2;
+				dirLight.shadow.camera.top	= 2;
+				dirLight.shadow.camera.bottom = - 2;
+
+				dirLight.shadow.mapSize.width = 1024;
+				dirLight.shadow.mapSize.height = 1024;
+				scene.add( dirLight );
+
+				planes = [
+					new THREE.Plane( new THREE.Vector3( - 1, 0, 0 ), 0 ),
+					new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 0 ),
+					new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 0 )
+				];
+
+				planeHelpers = planes.map( p => new THREE.PlaneHelper( p, 2, 0xffffff ) );
+				planeHelpers.forEach( ph => {
+
+					ph.visible = false;
+					scene.add( ph );
+
+				} );
+
+				var geometry = new THREE.TorusKnotBufferGeometry( 0.4, 0.15, 220, 60 );
+				object = new THREE.Group();
+				scene.add( object );
+
+				// Set up clip plane rendering
+				planeObjects = [];
+				var planeGeom = new THREE.PlaneBufferGeometry( 4, 4 );
+				for ( var i = 0; i < 3; i ++ ) {
+
+					var poGroup = new THREE.Group();
+					var plane = planes[ i ];
+					var stencilGroup = createPlaneStencilGroup( geometry, plane, i + 1 )
+
+					// plane is clipped by the other clipping planes
+					var planeMat =
+						new THREE.MeshStandardMaterial( {
+
+							color: 0xE91E63,
+							metalness: 0.1,
+							roughness: 0.75,
+							clippingPlanes: planes.filter( p => p !== plane ),
+
+							stencilWrite: true,
+							stencilRef: 0,
+							stencilFunc: THREE.NotEqualStencilFunc,
+							stencilFail: THREE.ReplaceStencilOp,
+							stencilZFail: THREE.ReplaceStencilOp,
+							stencilZPass: THREE.ReplaceStencilOp,
+
+						} );
+					var po = new THREE.Mesh( planeGeom, planeMat );
+					po.onAfterRender = function ( renderer ) {
+
+						renderer.clearStencil();
+
+					};
+					po.renderOrder = i + 1.1;
+
+					object.add( stencilGroup );
+					poGroup.add( po );
+					planeObjects.push( po );
+					scene.add( poGroup );
+
+				}
+
+				var material = new THREE.MeshStandardMaterial( {
+
+					color: 0xFFC107,
+					metalness: 0.1,
+					roughness: 0.75,
+					clippingPlanes: planes,
+					clipShadows: true,
+					shadowSide: THREE.DoubleSide,
+
+				} );
+
+				// add the color
+				var clippedColorFront = new THREE.Mesh( geometry, material );
+				clippedColorFront.castShadow = true;
+				clippedColorFront.renderOrder = 6;
+				object.add( clippedColorFront );
+
+
+				var ground = new THREE.Mesh(
+					new THREE.PlaneBufferGeometry( 9, 9, 1, 1 ),
+					new THREE.ShadowMaterial( { color: 0, opacity: 0.25, side: THREE.DoubleSide } )
+				);
+
+				ground.rotation.x = - Math.PI / 2; // rotates X/Y to X/Z
+				ground.position.y = - 1;
+				ground.receiveShadow = true;
+				scene.add( ground );
+
+				// Stats
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				// Renderer
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.shadowMap.enabled = true;
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setClearColor( 0x263238 );
+				window.addEventListener( 'resize', onWindowResize, false );
+				document.body.appendChild( renderer.domElement );
+
+				renderer.localClippingEnabled = true;
+
+				// Controls
+				var controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 2;
+				controls.maxDistance = 20;
+				controls.update();
+
+				// GUI
+				var gui = new GUI();
+				gui.add( params, 'animate' );
+
+				var planeX = gui.addFolder( 'planeX' );
+				planeX.add( params.planeX, 'displayHelper' ).onChange( v => planeHelpers[ 0 ].visible = v );
+				planeX.add( params.planeX, 'constant' ).min( - 1 ).max( 1 ).onChange( d => planes[ 0 ].constant = d );
+				planeX.add( params.planeX, 'negated' ).onChange( d => {
+
+					planes[ 0 ].negate();
+					params.planeX.constant = planes[ 0 ].constant;
+
+				} );
+				planeX.open();
+
+				var planeY = gui.addFolder( 'planeY' );
+				planeY.add( params.planeY, 'displayHelper' ).onChange( v => planeHelpers[ 1 ].visible = v );
+				planeY.add( params.planeY, 'constant' ).min( - 1 ).max( 1 ).onChange( d => planes[ 1 ].constant = d );
+				planeY.add( params.planeY, 'negated' ).onChange( d => {
+
+					planes[ 1 ].negate();
+					params.planeY.constant = planes[ 1 ].constant;
+
+				} );
+				planeY.open();
+
+				var planeZ = gui.addFolder( 'planeZ' );
+				planeZ.add( params.planeZ, 'displayHelper' ).onChange( v => planeHelpers[ 2 ].visible = v );
+				planeZ.add( params.planeZ, 'constant' ).min( - 1 ).max( 1 ).onChange( d => planes[ 2 ].constant = d );
+				planeZ.add( params.planeZ, 'negated' ).onChange( d => {
+
+					planes[ 2 ].negate();
+					params.planeZ.constant = planes[ 2 ].constant;
+
+				} );
+				planeZ.open();
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				var delta = clock.getDelta();
+
+				requestAnimationFrame( animate );
+
+				if ( params.animate ) {
+
+					object.rotation.x += delta * 0.5;
+					object.rotation.y += delta * 0.2;
+
+				}
+
+				for ( var i = 0; i < planeObjects.length; i ++ ) {
+
+					var plane = planes[ i ];
+					var po = planeObjects[ i ];
+					plane.coplanarPoint( po.position );
+					po.lookAt(
+						po.position.x - plane.normal.x,
+						po.position.y - plane.normal.y,
+						po.position.z - plane.normal.z,
+					);
+
+				}
+
+				stats.begin();
+				renderer.render( scene, camera );
+				stats.end();
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 22 - 0
src/constants.d.ts

@@ -252,3 +252,25 @@ export const RGBADepthPacking: DepthPackingStrategies;
 export enum NormalMapTypes {}
 export const TangentSpaceNormalMap: NormalMapTypes;
 export const ObjectSpaceNormalMap: NormalMapTypes;
+
+// Stencil Op types
+export enum StencilOp {}
+export const ZeroStencilOp: StencilOp;
+export const KeepStencilOp: StencilOp;
+export const ReplaceStencilOp: StencilOp;
+export const IncrementStencilOp: StencilOp;
+export const DecrementStencilOp: StencilOp;
+export const IncrementWrapStencilOp: StencilOp;
+export const DecrementWrapStencilOp: StencilOp;
+export const InvertStencilOp: StencilOp;
+
+// Stencil Func types
+export enum StencilFunc {}
+export const NeverStencilFunc: StencilFunc;
+export const LessStencilFunc: StencilFunc;
+export const EqualStencilFunc: StencilFunc;
+export const LessEqualStencilFunc: StencilFunc;
+export const GreaterStencilFunc: StencilFunc;
+export const NotEqualStencilFunc: StencilFunc;
+export const GreaterEqualStencilFunc: StencilFunc;
+export const AlwaysStencilFunc: StencilFunc;

+ 18 - 0
src/constants.js

@@ -147,3 +147,21 @@ export var BasicDepthPacking = 3200;
 export var RGBADepthPacking = 3201;
 export var TangentSpaceNormalMap = 0;
 export var ObjectSpaceNormalMap = 1;
+
+export var ZeroStencilOp = 0;
+export var KeepStencilOp = 7680;
+export var ReplaceStencilOp = 7681;
+export var IncrementStencilOp = 7682;
+export var DecrementStencilOp = 7683;
+export var IncrementWrapStencilOp = 34055;
+export var DecrementWrapStencilOp = 34056;
+export var InvertStencilOp = 5386;
+
+export var NeverStencilFunc = 512;
+export var LessStencilFunc = 513;
+export var EqualStencilFunc = 514;
+export var LessEqualStencilFunc = 515;
+export var GreaterStencilFunc = 516;
+export var NotEqualStencilFunc = 517;
+export var GreaterEqualStencilFunc = 518;
+export var AlwaysStencilFunc = 519;

+ 44 - 0
src/materials/Material.d.ts

@@ -10,6 +10,8 @@ import {
 	DepthModes,
 	Side,
 	Colors,
+	StencilFunc,
+	StencilOp
 } from '../constants';
 
 // Materials //////////////////////////////////////////////////////////////////////////////////
@@ -49,6 +51,13 @@ export interface MaterialParameters {
 	vertexColors?: Colors;
 	vertexTangents?: boolean;
 	visible?: boolean;
+	stencilWrite?: boolean;
+	stencilFunc?: StencilFunc;
+	stencilRef?: number;
+	stencilMask?: number;
+	stencilFail?: StencilOp;
+	stencilZFail?: StencilOp;
+	stencilZPass?: StencilOp;
 }
 
 /**
@@ -144,6 +153,41 @@ export class Material extends EventDispatcher {
 	 */
 	id: number;
 
+	/**
+   * Whether rendering this material has any effect on the stencil buffer. Default is *false*.
+   */
+	stencilWrite: boolean;
+
+	/**
+   * The stencil comparison function to use. Default is {@link AlwaysStencilFunc}. See stencil operation constants for all possible values.
+   */
+	stencilFunc: StencilFunc;
+
+	/**
+   * The value to use when performing stencil comparisons or stencil operations. Default is *0*.
+   */
+	stencilRef: number;
+
+	/**
+   * The bit mask to use when comparing against or writing to the stencil buffer. Default is *0xFF*.
+   */
+	stencilMask: number;
+
+	/**
+   * Which stencil operation to perform when the comparison function returns false. Default is {@link KeepStencilOp}. See the stencil operation constants for all possible values.
+   */
+	stencilFail: StencilOp;
+
+	/**
+   * Which stencil operation to perform when the comparison function returns true but the depth test fails. Default is {@link KeepStencilOp}. See the stencil operation constants for all possible values.
+   */
+	stencilZFail: StencilOp;
+
+	/**
+   * Which stencil operation to perform when the comparison function returns true and the depth test passes. Default is {@link KeepStencilOp}. See the stencil operation constants for all possible values.
+   */
+	stencilZPass: StencilOp;
+
 	/**
 	 * Used to check whether this or derived classes are materials. Default is true.
 	 * You should not change this, as it used internally for optimisation.

+ 25 - 1
src/materials/Material.js

@@ -1,5 +1,5 @@
 import { EventDispatcher } from '../core/EventDispatcher.js';
-import { NoColors, FrontSide, FlatShading, NormalBlending, LessEqualDepth, AddEquation, OneMinusSrcAlphaFactor, SrcAlphaFactor } from '../constants.js';
+import { NoColors, FrontSide, FlatShading, NormalBlending, LessEqualDepth, AddEquation, OneMinusSrcAlphaFactor, SrcAlphaFactor, AlwaysStencilFunc, KeepStencilOp } from '../constants.js';
 import { _Math } from '../math/Math.js';
 
 /**
@@ -41,6 +41,14 @@ function Material() {
 	this.depthTest = true;
 	this.depthWrite = true;
 
+	this.stencilFunc = AlwaysStencilFunc;
+	this.stencilRef = 0;
+	this.stencilMask = 0xff;
+	this.stencilFail = KeepStencilOp;
+	this.stencilZFail = KeepStencilOp;
+	this.stencilZPass = KeepStencilOp;
+	this.stencilWrite = false;
+
 	this.clippingPlanes = null;
 	this.clipIntersection = false;
 	this.clipShadows = false;
@@ -240,6 +248,14 @@ Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 		data.depthTest = this.depthTest;
 		data.depthWrite = this.depthWrite;
 
+		data.stencilWrite = this.stencilWrite;
+		data.stencilFunc = this.stencilFunc;
+		data.stencilRef = this.stencilRef;
+		data.stencilMask = this.stencilMask;
+		data.stencilFail = this.stencilFail;
+		data.stencilZFail = this.stencilZFail;
+		data.stencilZPass = this.stencilZPass;
+
 		// rotation (SpriteMaterial)
 		if ( this.rotation && this.rotation !== 0 ) data.rotation = this.rotation;
 
@@ -333,6 +349,14 @@ Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 		this.depthTest = source.depthTest;
 		this.depthWrite = source.depthWrite;
 
+		this.stencilWrite = source.stencilWrite;
+		this.stencilFunc = source.stencilFunc;
+		this.stencilRef = source.stencilRef;
+		this.stencilMask = source.stencilMask;
+		this.stencilFail = source.stencilFail;
+		this.stencilZFail = source.stencilZFail;
+		this.stencilZPass = source.stencilZPass;
+
 		this.colorWrite = source.colorWrite;
 
 		this.precision = source.precision;

+ 9 - 0
src/renderers/webgl/WebGLState.js

@@ -677,6 +677,15 @@ function WebGLState( gl, extensions, utils, capabilities ) {
 		depthBuffer.setMask( material.depthWrite );
 		colorBuffer.setMask( material.colorWrite );
 
+		var stencilWrite = material.stencilWrite;
+		stencilBuffer.setTest( stencilWrite );
+		if ( stencilWrite ) {
+
+			stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilMask );
+			stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass );
+
+		}
+
 		setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
 
 	}