Ver Fonte

Merge pull request #11863 from WestLangley/dev-rotate_tex

Added support for texture rotation
Mr.doob há 8 anos atrás
pai
commit
fd145057f0

+ 17 - 0
docs/api/math/Matrix3.html

@@ -169,6 +169,23 @@ m.elements = [ 11, 21, 31,
 		<h3>[method:Matrix3 setFromMatrix4]( [page:Matrix4 m] )</h3>
 		<div>Set this matrx to the upper 3x3 matrix of the Matrix4 [page:Matrix4 m].</div>
 
+		<h3>
+			[method:Matrix3 setUvTransform](
+			[page:Float tx], [page:Float ty], [page:Float sx], [page:Float sy],
+			[page:Float rotation], [page:Float cx], [page:Float cy] )
+		</h3>
+		<div>
+		[page:Float tx] - offset x<br />
+		[page:Float ty] - offset y<br />
+		[page:Float sx] - repeat x<br />
+		[page:Float sy] - repeat y<br />
+		[page:Float rotation] - rotation (in radians)<br />
+		[page:Float cx] - center x of rotation<br />
+		[page:Float cy] - center y of rotation<br /><br />
+
+		Sets the UV transform matrix from offset, repeat, rotation, and center.
+		</div>
+
 		<h3>[method:Array toArray]( [page:Array array], [page:Integer offset] )</h3>
 		<div>
 		[page:Array array] - (optional) array to store the resulting vector in. If not given a new array will be created.<br />

+ 5 - 0
docs/api/math/Vector2.html

@@ -97,6 +97,11 @@
 		Computes the angle in radians of this vector with respect to the positive x-axis.
 		</div>
 
+		<h3>[method:Vector2 applyMatrix3]( [page:Matrix3 m] )</h3>
+		<div>
+		Multiplies this vector (with an implicit 1 as the 3rd component) by m.
+		</div>
+
 		<h3>[method:Vector2 ceil]()</h3>
 		<div>
 		The [page:.x x] and [page:.y y] components of the vector are rounded up to the nearest integer value.

+ 20 - 0
docs/api/textures/Texture.html

@@ -147,6 +147,26 @@
 		assigned to achieve the desired repetiton.
 		</div>
 
+		<h3>[property:number rotation]</h3>
+		<div>
+		How much the texture is rotated around the center point ( 0.5, 0.5 ), in radians. Postive values are counter-clockwise. Default is *0*.
+		</div>
+
+		<h3>[property:boolean matrixAutoUpdate]</h3>
+		<div>
+		Whether to update the texture's uv-transform [property:Matrix3 matrix] based on the [property:Vector2 offset],
+		[property:Vector2 repeat], and [property:number rotation] settings. True by default.
+		Set this to false if you are specifying the uv-transform matrix directly.
+		</div>
+
+		<h3>[property:Matrix3 matrix]</h3>
+		<div>
+		The uv-transform matrix for the texture. Updated by the renderer from the texture properties [property:Vector2 offset], [property:Vector2 repeat],
+		and [property:number rotation] when the texture's [property:boolean matrixAutoUpdate] property is true.
+		When [property:boolean matrixAutoUpdate] property is false, this matrix may be set manually.
+		Default is the indentity matrix.
+		</div>
+
 		<h3>[property:boolean generateMipmaps]</h3>
 		<div>
 		Whether to generate mipmaps (if possible) for a texture. True by default. Set this to false if you are

+ 174 - 0
examples/webgl_materials_texture_rotation.html

@@ -0,0 +1,174 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - materials - texture - rotation</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 {
+				margin: 0px;
+				background-color: #050505;
+				color: #fff;
+				overflow: hidden;
+			}
+
+			a { color: #e00 }
+
+			#info {
+				position: absolute;
+				top: 0px; width: 100%;
+				color: #ffffff;
+				padding: 5px;
+				font-family: Monospace;
+				font-size: 13px;
+				text-align: center;
+			}
+
+		</style>
+	</head>
+
+	<body>
+
+		<div id="info">
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl - texture rotation
+		</div>
+
+		<script src="../build/three.js"></script>
+
+		<script src="js/controls/OrbitControls.js"></script>
+
+		<script src="js/libs/dat.gui.min.js"></script>
+
+		<script src="js/Detector.js"></script>
+
+		<script>
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var mesh, renderer, scene, camera;
+
+			var gui;
+
+			var API = {
+				offsetX: 0,
+				offsetY: 0,
+				repeatX: 0.25,
+				repeatY: 0.25,
+				rotation: Math.PI / 4, // positive is counter-clockwise
+				centerX: 0.5,
+				centerY: 0.5
+			};
+
+			init();
+			render();
+
+
+			function init() {
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
+				camera.position.set( 10, 15, 25 );
+				scene.add( camera );
+
+				var controls = new THREE.OrbitControls( camera, renderer.domElement );
+				controls.addEventListener( 'change', render );
+				controls.minDistance = 20;
+				controls.maxDistance = 50;
+				controls.maxPolarAngle = Math.PI / 2;
+
+				var geometry = new THREE.BoxGeometry( 10, 10, 10 );
+
+				var loader = new THREE.TextureLoader();
+				var texture = loader.load( 'textures/UV_Grid_Sm.jpg', render );
+				texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
+
+				texture.matrixAutoUpdate = false; // set this to false to update texture.matrix manually
+
+				var material = new THREE.MeshBasicMaterial( { map: texture } );
+
+				mesh = new THREE.Mesh( geometry, material );
+				scene.add( mesh );
+
+				updateUvTransform();
+
+				initGui();
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				render();
+
+			}
+
+			function updateUvTransform() {
+
+				var texture = mesh.material.map;
+
+				if ( texture.matrixAutoUpdate === true ) {
+
+					texture.offset.set( API.offsetX, API.offsetY );
+					texture.repeat.set( API.repeatX, API.repeatY );
+					texture.rotation = API.rotation; // rotation is around [ 0.5, 0.5 ]
+
+				} else {
+
+					// one way...
+					//texture.matrix.setUvTransform( API.offsetX, API.offsetY, API.repeatX, API.repeatY, API.rotation, API.centerX, API.centerY );
+
+					// another way...
+					texture.matrix
+					    .identity()
+					    .translate( - API.centerX, - API.centerY )
+					    .rotate( API.rotation )					// I don't understand how rotation can preceed scale, but it seems to be required...
+					    .scale( API.repeatX, API.repeatY )
+					    .translate( API.centerX, API.centerY )
+					    .translate( API.offsetX, API.offsetY );
+
+				}
+
+				render();
+
+			}
+
+			function initGui() {
+
+				var drop;
+
+				gui = new dat.GUI();
+
+				gui.add( API, 'offsetX', 0.0, 1.0 ).name( 'offset.x' ).onChange( updateUvTransform );
+				gui.add( API, 'offsetY', 0.0, 1.0 ).name( 'offset.y' ).onChange( updateUvTransform );
+				gui.add( API, 'repeatX', 0.25, 2.0 ).name( 'repeat.x' ).onChange( updateUvTransform );
+				gui.add( API, 'repeatY', 0.25, 2.0 ).name( 'repeat.y' ).onChange( updateUvTransform );
+				gui.add( API, 'rotation', - 2.0, 2.0 ).name( 'rotation' ).onChange( updateUvTransform );
+				gui.add( API, 'centerX', 0.0, 1.0 ).name( 'center.x' ).onChange( updateUvTransform );
+				gui.add( API, 'centerY', 0.0, 1.0 ).name( 'center.y' ).onChange( updateUvTransform );
+
+			}
+
+		</script>
+
+	</body>
+
+</html>

+ 9 - 0
examples/webgl_raycast_texture.html

@@ -66,6 +66,9 @@
 				Repeat : X <input type="number" value="1" step="0.1" onchange="setRepeatU(this)" />
 				Y <input type="number" value="1" step="0.1" onchange="setRepeatV(this)" />
 			</div>
+			<div class="control">
+				Rotation : <input type="number" value="0" step="0.1" onchange="setRotation(this)" />
+			</div>
 		</fieldset>
 		<script src="../build/three.js"></script>
 		<script>
@@ -373,6 +376,12 @@
 
 			}
 
+			function setRotation( that ) {
+
+				circleTexture.rotation = parseFloat( that.value );
+
+			}
+
 		</script>
 	</body>
 </html>

+ 57 - 0
src/math/Matrix3.js

@@ -273,6 +273,63 @@ Object.assign( Matrix3.prototype, {
 
 	},
 
+	setUvTransform: function ( tx, ty, sx, sy, rotation, cx, cy ) {
+
+		var c = Math.cos( rotation );
+		var s = Math.sin( rotation );
+
+		this.set(
+			sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx,
+			- sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty,
+			0, 0, 1
+		);
+
+	},
+
+	scale: function ( sx, sy ) {
+
+		var te = this.elements;
+
+		te[ 0 ] *= sx; te[ 3 ] *= sx; te[ 6 ] *= sx;
+		te[ 1 ] *= sy; te[ 4 ] *= sy; te[ 7 ] *= sy;
+
+		return this;
+
+	},
+
+	rotate: function ( theta ) {
+
+		var c = Math.cos( theta );
+		var s = Math.sin( theta );
+
+		var te = this.elements;
+
+		var a11 = te[ 0 ], a12 = te[ 3 ], a13 = te[ 6 ];
+		var a21 = te[ 1 ], a22 = te[ 4 ], a23 = te[ 7 ];
+
+		te[ 0 ] = c * a11 + s * a21;
+		te[ 3 ] = c * a12 + s * a22;
+		te[ 6 ] = c * a13 + s * a23;
+
+		te[ 1 ] = - s * a11 + c * a21;
+		te[ 4 ] = - s * a12 + c * a22;
+		te[ 7 ] = - s * a13 + c * a23;
+
+		return this;
+
+	},
+
+	translate: function ( tx, ty ) {
+
+		var te = this.elements;
+
+		te[ 0 ] += tx * te[ 2 ]; te[ 3 ] += tx * te[ 5 ]; te[ 6 ] += tx * te[ 8 ];
+		te[ 1 ] += ty * te[ 2 ]; te[ 4 ] += ty * te[ 5 ]; te[ 7 ] += ty * te[ 8 ];
+
+		return this;
+
+	},
+
 	equals: function ( matrix ) {
 
 		var te = this.elements;

+ 12 - 0
src/math/Vector2.js

@@ -237,6 +237,18 @@ Object.assign( Vector2.prototype, {
 
 	},
 
+	applyMatrix3: function ( m ) {
+
+		var x = this.x, y = this.y;
+		var e = m.elements;
+
+		this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ];
+		this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ];
+
+		return this;
+
+	},
+
 	min: function ( v ) {
 
 		this.x = Math.min( this.x, v.x );

+ 18 - 6
src/renderers/WebGLRenderer.js

@@ -1990,10 +1990,16 @@ function WebGLRenderer( parameters ) {
 
 			}
 
-			var offset = uvScaleMap.offset;
-			var repeat = uvScaleMap.repeat;
+			if ( uvScaleMap.matrixAutoUpdate === true ) {
 
-			uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y );
+			    var offset = uvScaleMap.offset;
+			    var repeat = uvScaleMap.repeat;
+			    var rotation = uvScaleMap.rotation;
+			    uvScaleMap.matrix.setUvTransform( offset.x, offset.y, repeat.x, repeat.y, rotation, 0.5, 0.5 );
+
+			}
+
+			uniforms.uvTransform.value.copy( uvScaleMap.matrix );
 
 		}
 
@@ -2025,10 +2031,16 @@ function WebGLRenderer( parameters ) {
 
 		if ( material.map !== null ) {
 
-			var offset = material.map.offset;
-			var repeat = material.map.repeat;
+			if ( material.map.matrixAutoUpdate === true ) {
+
+			    var offset = material.map.offset;
+			    var repeat = material.map.repeat;
+			    var rotation = material.map.rotation;
+			    material.map.matrix.setUvTransform( offset.x, offset.y, repeat.x, repeat.y, rotation, 0.5, 0.5 );
+
+			}
 
-			uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y );
+			uniforms.uvTransform.value.copy( material.map.matrix );
 
 		}
 

+ 2 - 1
src/renderers/shaders/ShaderChunk/map_particle_fragment.glsl

@@ -1,6 +1,7 @@
 #ifdef USE_MAP
 
-	vec4 mapTexel = texture2D( map, vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y ) * offsetRepeat.zw + offsetRepeat.xy );
+	vec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;
+	vec4 mapTexel = texture2D( map, uv );
 	diffuseColor *= mapTexelToLinear( mapTexel );
 
 #endif

+ 1 - 1
src/renderers/shaders/ShaderChunk/map_particle_pars_fragment.glsl

@@ -1,6 +1,6 @@
 #ifdef USE_MAP
 
-	uniform vec4 offsetRepeat;
+	uniform mat3 uvTransform;
 	uniform sampler2D map;
 
 #endif

+ 1 - 1
src/renderers/shaders/ShaderChunk/uv_pars_vertex.glsl

@@ -1,6 +1,6 @@
 #if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )
 
 	varying vec2 vUv;
-	uniform vec4 offsetRepeat;
+	uniform mat3 uvTransform;
 
 #endif

+ 1 - 1
src/renderers/shaders/ShaderChunk/uv_vertex.glsl

@@ -1,5 +1,5 @@
 #if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )
 
-	vUv = uv * offsetRepeat.zw + offsetRepeat.xy;
+	vUv = ( uvTransform * vec3( uv, 1 ) ).xy;
 
 #endif

+ 3 - 2
src/renderers/shaders/UniformsLib.js

@@ -1,6 +1,7 @@
 import { Vector4 } from '../../math/Vector4';
 import { Color } from '../../math/Color';
 import { Vector2 } from '../../math/Vector2';
+import { Matrix3 } from '../../math/Matrix3';
 import { DataTexture } from '../../textures/DataTexture';
 
 /**
@@ -15,7 +16,7 @@ var UniformsLib = {
 		opacity: { value: 1.0 },
 
 		map: { value: null },
-		offsetRepeat: { value: new Vector4( 0, 0, 1, 1 ) },
+		uvTransform: { value: new Matrix3() },
 
 		alphaMap: { value: null },
 
@@ -180,7 +181,7 @@ var UniformsLib = {
 		size: { value: 1.0 },
 		scale: { value: 1.0 },
 		map: { value: null },
-		offsetRepeat: { value: new Vector4( 0, 0, 1, 1 ) }
+		uvTransform: { value: new Matrix3() }
 
 	}
 

+ 15 - 2
src/textures/Texture.js

@@ -9,6 +9,7 @@ import { UVMapping } from '../constants';
 import { MirroredRepeatWrapping, ClampToEdgeWrapping, RepeatWrapping, LinearEncoding, UnsignedByteType, RGBAFormat, LinearMipMapLinearFilter, LinearFilter } from '../constants';
 import { _Math } from '../math/Math';
 import { Vector2 } from '../math/Vector2';
+import { Matrix3 } from '../math/Matrix3';
 
 var textureId = 0;
 
@@ -38,6 +39,10 @@ function Texture( image, mapping, wrapS, wrapT, magFilter, minFilter, format, ty
 
 	this.offset = new Vector2( 0, 0 );
 	this.repeat = new Vector2( 1, 1 );
+	this.rotation = 0;
+
+	this.matrixAutoUpdate = true;
+	this.matrix = new Matrix3();
 
 	this.generateMipmaps = true;
 	this.premultiplyAlpha = false;
@@ -102,6 +107,10 @@ Object.assign( Texture.prototype, EventDispatcher.prototype, {
 
 		this.offset.copy( source.offset );
 		this.repeat.copy( source.repeat );
+		this.rotation = source.rotation;
+
+		this.matrixAutoUpdate = source.matrixAutoUpdate;
+		this.matrix.copy( source.matrix );
 
 		this.generateMipmaps = source.generateMipmaps;
 		this.premultiplyAlpha = source.premultiplyAlpha;
@@ -175,6 +184,11 @@ Object.assign( Texture.prototype, EventDispatcher.prototype, {
 
 			repeat: [ this.repeat.x, this.repeat.y ],
 			offset: [ this.offset.x, this.offset.y ],
+			rotation: this.rotation,
+
+			matrixAutoUpdate: this.matrixAutoUpdate,
+			matrix: this.matrix.toArray(),
+
 			wrap: [ this.wrapS, this.wrapT ],
 
 			minFilter: this.minFilter,
@@ -225,8 +239,7 @@ Object.assign( Texture.prototype, EventDispatcher.prototype, {
 
 		if ( this.mapping !== UVMapping ) return;
 
-		uv.multiply( this.repeat );
-		uv.add( this.offset );
+		uv.applyMatrix3( this.matrix );
 
 		if ( uv.x < 0 || uv.x > 1 ) {