Skip to content
Merged
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
10 changes: 5 additions & 5 deletions docs/TSL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1133,8 +1133,8 @@ const matcap = texture( matcapMap, matcapUV );

| Variable | Description | Type |
| -- | -- | -- |
| `directionToColor( value )` | Converts direction vector to color. | `color` |
| `colorToDirection( value )` | Converts color to direction vector. | `vec3` |
| `packNormalToRGB( value )` | Converts normal vector to color. | `color` |
| `unpackRGBToNormal( value )` | Converts color to normal vector. | `vec3` |

## Render Pipeline

Expand Down Expand Up @@ -1172,18 +1172,18 @@ MRT allows capturing multiple outputs from a single render pass. Instead of rend
Use `setMRT()` with the `mrt()` function to define which outputs to capture:

```js
import { pass, mrt, output, normalView, velocity, directionToColor } from 'three/tsl';
import { pass, mrt, output, normalView, velocity, packNormalToRGB } from 'three/tsl';

const scenePass = pass( scene, camera );

scenePass.setMRT( mrt( {
output: output, // Final color output
normal: directionToColor( normalView ), // View-space normals encoded as colors
normal: packNormalToRGB( normalView ), // View-space normals encoded as colors
velocity: velocity // Motion vectors for temporal effects
} ) );
```

Each MRT entry accepts any TSL node, allowing you to customize outputs using formulas, encoders, or material accessors. For example, `directionToColor( normalView )` encodes view-space normals into RGB values. You can use any TSL function to transform, combine, or encode data before writing to the render target.
Each MRT entry accepts any TSL node, allowing you to customize outputs using formulas, encoders, or material accessors. For example, `packNormalToRGB( normalView )` encodes view-space normals into RGB values. You can use any TSL function to transform, combine, or encode data before writing to the render target.

Within a TSL function `Fn( ( { material, object } ) => { ... } )`, you have complete access to the current material and object being rendered, enabling full customization of outputs.

Expand Down

Large diffs are not rendered by default.

Binary file not shown.
72 changes: 33 additions & 39 deletions examples/webgl_geometry_text_stroke.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';
import { Font } from 'three/addons/loaders/FontLoader.js';
import { unzipSync, strFromU8 } from 'three/addons/libs/fflate.module.js';
import { FontLoader } from 'three/addons/loaders/FontLoader.js';

let camera, scene, renderer;

Expand All @@ -54,59 +53,54 @@
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );

new THREE.FileLoader()
.setResponseType( 'arraybuffer' )
.load( 'fonts/MPLUSRounded1c/MPLUSRounded1c-Regular.typeface.json.zip', function ( data ) {
const loader = new FontLoader();
loader.load( 'fonts/MPLUSRounded1c/MPLUSRounded1c-Regular.typeface.json', function ( font ) {

const zip = unzipSync( new Uint8Array( data ) );
const strArray = strFromU8( new Uint8Array( zip[ 'MPLUSRounded1c-Regular.typeface.json' ].buffer ) );
const color = new THREE.Color( 0x006699 );

const font = new Font( JSON.parse( strArray ) );
const color = new THREE.Color( 0x006699 );
const matDark = new THREE.MeshBasicMaterial( {
color: color,
side: THREE.DoubleSide
} );

const matDark = new THREE.MeshBasicMaterial( {
color: color,
side: THREE.DoubleSide
} );
const matLite = new THREE.MeshBasicMaterial( {
color: color,
transparent: true,
opacity: 0.4,
side: THREE.DoubleSide
} );

const matLite = new THREE.MeshBasicMaterial( {
color: color,
transparent: true,
opacity: 0.4,
side: THREE.DoubleSide
} );
const material = {
dark: matDark,
lite: matLite,
color: color
};

const material = {
dark: matDark,
lite: matLite,
color: color
};
const english = ' Three.js\nStroke text.'; // Left to right

const english = ' Three.js\nStroke text.'; // Left to right
const hebrew = 'טקסט קו'; // Right to left

const hebrew = 'טקסט קו'; // Right to left
const chinese = '文字描邊'; // Top to bottom

const chinese = '文字描邊'; // Top to bottom
const message1 = generateStrokeText( font, material, english, 80, 'ltr' );

const message1 = generateStrokeText( font, material, english, 80, 'ltr' );
const message2 = generateStrokeText( font, material, hebrew, 80, 'rtl' );

const message2 = generateStrokeText( font, material, hebrew, 80, 'rtl' );
const message3 = generateStrokeText( font, material, chinese, 80, 'tb' );

const message3 = generateStrokeText( font, material, chinese, 80, 'tb' );
message1.position.x = - 100;

message1.position.x = - 100;
message2.position.x = - 100;
message2.position.y = - 300;

message2.position.x = - 100;
message2.position.y = - 300;
message3.position.x = 300;
message3.position.y = - 300;

message3.position.x = 300;
message3.position.y = - 300;
scene.add( message1, message2, message3 );

scene.add( message1, message2, message3 );
render();

render();

} ); //end load function
} ); //end load function

renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
"files": [
"build",
"examples/jsm",
"examples/fonts",
"LICENSE",
"package.json",
"README.md",
Expand Down
2 changes: 2 additions & 0 deletions src/Three.TSL.js
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,8 @@ export const toneMappingExposure = TSL.toneMappingExposure;
export const toonOutlinePass = TSL.toonOutlinePass;
export const transformDirection = TSL.transformDirection;
export const transformNormal = TSL.transformNormal;
export const transformNormalByInverseViewMatrix = TSL.transformNormalByInverseViewMatrix;
export const transformNormalByViewMatrix = TSL.transformNormalByViewMatrix;
export const transformNormalToView = TSL.transformNormalToView;
export const transformedClearcoatNormalView = TSL.transformedClearcoatNormalView;
export const transformedNormalView = TSL.transformedNormalView;
Expand Down
22 changes: 11 additions & 11 deletions src/nodes/accessors/Normal.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { attribute } from '../core/AttributeNode.js';
import { cameraViewMatrix } from './Camera.js';
import { modelNormalMatrix, modelWorldMatrix } from './ModelNode.js';
import { mat3, vec3, Fn } from '../tsl/TSLBase.js';
import { mat3, vec3, Fn, addMethodChaining } from '../tsl/TSLBase.js';
import { positionView } from './Position.js';
import { directionToFaceDirection } from '../display/FrontFacingNode.js';
import { warn } from '../../utils.js';
Expand Down Expand Up @@ -74,7 +74,7 @@ export const normalViewGeometry = /*@__PURE__*/ ( Fn( ( builder ) => {
*/
export const normalWorldGeometry = /*@__PURE__*/ ( Fn( ( builder ) => {

let normal = normalViewGeometry.transformDirection( cameraViewMatrix );
let normal = normalViewGeometry.transformNormalByInverseViewMatrix( cameraViewMatrix );

if ( builder.isFlatShading() !== true ) {

Expand Down Expand Up @@ -124,7 +124,7 @@ export const normalView = /*@__PURE__*/ ( Fn( ( builder ) => {
* @tsl
* @type {Node<vec3>}
*/
export const normalWorld = /*@__PURE__*/ normalView.transformDirection( cameraViewMatrix ).toVar( 'normalWorld' );
export const normalWorld = /*@__PURE__*/ normalView.transformNormalByInverseViewMatrix( cameraViewMatrix ).toVar( 'normalWorld' );

/**
* TSL object that represents the clearcoat vertex normal of the current rendered object in view space.
Expand Down Expand Up @@ -153,24 +153,24 @@ export const clearcoatNormalView = /*@__PURE__*/ ( Fn( ( { subBuildFn, context }
}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'clearcoatNormalView' );

/**
* Transforms the normal with the given matrix.
* Transforms the normal by the normal matrix of the given matrix and then normalizes the result.
*
* @tsl
* @function
* @param {Node<vec3>} normal - The normal.
* @param {Node<mat3>} [matrix=modelWorldMatrix] - The matrix.
* @param {Node<mat3|mat4>} [matrix=modelWorldMatrix] - The matrix.
* @return {Node<vec3>} The transformed normal.
*/
export const transformNormal = /*@__PURE__*/ Fn( ( [ normal, matrix = modelWorldMatrix ] ) => {

const m = mat3( matrix );
const normalMatrix = mat3( matrix ).inverse().transpose();

const transformedNormal = normal.div( vec3( m[ 0 ].dot( m[ 0 ] ), m[ 1 ].dot( m[ 1 ] ), m[ 2 ].dot( m[ 2 ] ) ) );

return m.mul( transformedNormal ).xyz;
return normalMatrix.mul( normal ).normalize();

} );

addMethodChaining( 'transformNormal', transformNormal );

/**
* Transforms the given normal from local to view space.
*
Expand All @@ -186,15 +186,15 @@ export const transformNormalToView = /*@__PURE__*/ Fn( ( [ normal ], builder ) =

if ( modelNormalViewMatrix ) {

return modelNormalViewMatrix.transformDirection( normal );
return normal.transformNormalByViewMatrix( modelNormalViewMatrix );

}

//

const transformedNormal = modelNormalMatrix.mul( normal );

return cameraViewMatrix.transformDirection( transformedNormal );
return transformedNormal.transformNormalByViewMatrix( cameraViewMatrix );

} );

Expand Down
6 changes: 3 additions & 3 deletions src/nodes/accessors/ReflectVector.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cameraViewMatrix } from './Camera.js';
import { cameraWorldMatrix } from './Camera.js';
import { normalView } from './Normal.js';
import { positionViewDirection } from './Position.js';
import { materialRefractionRatio } from './MaterialProperties.js';
Expand All @@ -25,12 +25,12 @@ export const refractView = /*@__PURE__*/ positionViewDirection.negate().refract(
* @tsl
* @type {Node<vec3>}
*/
export const reflectVector = /*@__PURE__*/ reflectView.transformDirection( cameraViewMatrix ).toVar( 'reflectVector' );
export const reflectVector = /*@__PURE__*/ reflectView.transformDirection( cameraWorldMatrix ).toVar( 'reflectVector' );

/**
* Used for sampling cube maps when using cube refraction mapping.
*
* @tsl
* @type {Node<vec3>}
*/
export const refractVector = /*@__PURE__*/ refractView.transformDirection( cameraViewMatrix ).toVar( 'reflectVector' );
export const refractVector = /*@__PURE__*/ refractView.transformDirection( cameraWorldMatrix ).toVar( 'refractVector' );
4 changes: 2 additions & 2 deletions src/nodes/accessors/Tangent.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { attribute } from '../core/AttributeNode.js';
import { cameraViewMatrix } from './Camera.js';
import { cameraWorldMatrix } from './Camera.js';
import { modelViewMatrix } from './ModelNode.js';
import { Fn, vec4 } from '../tsl/TSLBase.js';
import { tangentViewFrame } from './TangentUtils.js';
Expand Down Expand Up @@ -57,4 +57,4 @@ export const tangentView = /*@__PURE__*/ ( Fn( ( builder ) => {
* @tsl
* @type {Node<vec3>}
*/
export const tangentWorld = /*@__PURE__*/ tangentView.transformDirection( cameraViewMatrix ).toVarying( 'v_tangentWorld' ).normalize().toVar( 'tangentWorld' );
export const tangentWorld = /*@__PURE__*/ tangentView.transformDirection( cameraWorldMatrix ).toVarying( 'v_tangentWorld' ).normalize().toVar( 'tangentWorld' );
4 changes: 2 additions & 2 deletions src/nodes/lighting/EnvironmentNode.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import LightingNode from './LightingNode.js';
import { isolate } from '../core/IsolateNode.js';
import { roughness, clearcoatRoughness } from '../core/PropertyNode.js';
import { cameraViewMatrix } from '../accessors/Camera.js';
import { cameraWorldMatrix } from '../accessors/Camera.js';
import { normalView, clearcoatNormalView, normalWorld } from '../accessors/Normal.js';
import { positionViewDirection } from '../accessors/Position.js';
import { float, pow4 } from '../tsl/TSLBase.js';
Expand Down Expand Up @@ -144,7 +144,7 @@ const createRadianceContext = ( roughnessNode, normalViewNode ) => {
// Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane.
reflectVec = pow4( roughnessNode ).mix( reflectVec, normalViewNode ).normalize();

reflectVec = reflectVec.transformDirection( cameraViewMatrix );
reflectVec = reflectVec.transformDirection( cameraWorldMatrix );

}

Expand Down
48 changes: 38 additions & 10 deletions src/nodes/math/MathNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,25 +181,23 @@ class MathNode extends TempNode {

} else if ( method === MathNode.TRANSFORM_DIRECTION ) {

// dir can be either a direction vector or a normal vector
// upper-left 3x3 of matrix is assumed to be orthogonal
// pre-multiplies the direction by the matrix and normalizes the result

let tA = aNode;
let tB = bNode;
let matrixNode, directionNode;

if ( builder.isMatrix( tA.getNodeType( builder ) ) ) {
if ( builder.isMatrix( aNode.getNodeType( builder ) ) ) {

tB = vec4( vec3( tB ), 0.0 );
matrixNode = aNode;
directionNode = bNode;

} else {

tA = vec4( vec3( tA ), 0.0 );
matrixNode = bNode;
directionNode = aNode;

}

const mulNode = mul( tA, tB ).xyz;

outputNode = normalize( mulNode );
outputNode = normalize( mul( matrixNode, vec4( vec3( directionNode ), 0.0 ) ).xyz );

}

Expand Down Expand Up @@ -988,6 +986,34 @@ export const pow4 = ( x ) => mul( x, x, x, x );
*/
export const transformDirection = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.TRANSFORM_DIRECTION ).setParameterLength( 2 );

/**
* Transforms a normal vector by the view matrix and then normalizes the result.
*
* The upper-left 3x3 of the view matrix is assumed to be orthonormal, so the
* normal can be transformed directly without involving the normal matrix.
*
* @tsl
* @function
* @param {Node<vec3>} normal - The normal vector, given in world space.
* @param {Node<mat3|mat4>} viewMatrix - The view matrix.
* @returns {Node<vec3>} The normal vector in view space.
*/
export const transformNormalByViewMatrix = ( normal, viewMatrix ) => normalize( mul( viewMatrix, vec4( vec3( normal ), 0.0 ) ).xyz );

/**
* Transforms a normal vector by the inverse of the view matrix and then normalizes the result.
*
* The upper-left 3x3 of the view matrix is assumed to be orthonormal, so post-multiplying
* by the view matrix is equivalent to pre-multiplying by its inverse.
*
* @tsl
* @function
* @param {Node<vec3>} normal - The normal vector, given in view space.
* @param {Node<mat3|mat4>} viewMatrix - The view matrix.
* @returns {Node<vec3>} The normal vector in world space.
*/
export const transformNormalByInverseViewMatrix = ( normal, viewMatrix ) => normalize( vec4( vec3( normal ), 0.0 ).mul( viewMatrix ).xyz );

/**
* Returns the cube root of a number.
*
Expand Down Expand Up @@ -1188,6 +1214,8 @@ addMethodChaining( 'pow2', pow2 );
addMethodChaining( 'pow3', pow3 );
addMethodChaining( 'pow4', pow4 );
addMethodChaining( 'transformDirection', transformDirection );
addMethodChaining( 'transformNormalByViewMatrix', transformNormalByViewMatrix );
addMethodChaining( 'transformNormalByInverseViewMatrix', transformNormalByInverseViewMatrix );
addMethodChaining( 'mix', mixElement );
addMethodChaining( 'clamp', clamp );
addMethodChaining( 'refract', refract );
Expand Down
Loading
Loading