TSL文档译本留存学习

THREEJS着色语言(TSL)规范

文档翻译来源 https://github.com/puxiao/threejs-shading-language-tutorial/blob/main/01%20Three.js%E7%9D%80%E8%89%B2%E8%AF%AD%E8%A8%80(TSL)%E8%A7%84%E8%8C%83.md?plain=1 感谢作者。

以下是对 Threejs Shading Language 英文的机翻 + 个人翻译。 想看英文原文的,可以查看:github.com/mrdoob/thre...

特别提醒:

  • 本文档是基于 2025.01.21 TSL 规范基础上翻译的。
  • 本文将 "Threejs Shading Language" 简称为 TSL 或 Threejs着色语言。
  • 本文并不会严格按照英文原文的方式排版
  • 对于一些比较重要的英文词语,我会在翻译中文的同时使用 XX(xx) 的形式保留英文原词。
  • 对于一些拗口、不容易理解的英文原文,我会在下面增加一些 "译者注"。
  • 若有翻译、理解不正确的地方,还请批评指正。

TSL 规范

  • 介绍
    • 为什么选择 TSL ?
    • 简单示例
    • 构建体系
  • 常量和显示转换
  • 转换
  • 统一变量
    • 更新
  • 分量重组
  • 运算符
  • Fn函数
  • 条件语句
  • 数学
  • 方法链
  • 纹理
  • 属性
  • 坐标位置
  • 法线
  • 切线
  • 双切线
  • 相机
  • 模型
  • 屏幕
  • 视口
  • 混合模式
  • 反射
  • UV工具
  • 插值
  • 随机
  • 振荡器
  • 方向与颜色转换
  • 函数
  • 节点材质
    • 基础部分
    • 虚线节点材质
    • Phong网格节点材质
    • 标准网格节点材质
    • 物理网格节点材质
    • 精灵节点材质
  • 将常见的 GLSL 与 TSL 属性对比表

TSL介绍

为什么选择 TSL ?

对于大多数开发人员来说创建着色器一直都是一个高级步骤,许多游戏开发人员甚至从来没有从头开始创建过 GLSL 代码。当今业界采用的着色器图形解决方案使开发人员能够更专注于动态,以创建必要的图形效果来满足他们项目的需求。

本项目的目标是创建一个易于使用的着色器创建环境。实现复杂的效果,最初需要在渲染器(renderer)上便携,以后就可以在 TSL 上编写。

TSL 除了简化着色器创建之外,还带来了其他好处:

  • 渲染器无关性(renderer agnostic):代码不依赖于特定的渲染器。
  • 摇树优化(tree shaking):代码优化的稳定性,材质的所有复杂逻辑都可以导入到不同模块中,并且在这个过程中进行 "摇树优化" 而不会出现问题。

简单示例

细节图(detail map)使游戏中的模型看起来更加真实,因为它为模型表面添加了裂纹和凸起等微小细节。在下面例子中我们将缩放 UV 以改善近距离观察时的细节,并与基础纹理相乘。

以前实现的方式

我们通过 .onBeforeCompile() 实现:

ini 复制代码
const material = new THREE.MeshStandardMaterial();
material.map = colorMap;
material.onBeforeCompile = ( shader ) => {

	shader.uniforms.detailMap = { value: detailMap };

	let token = '#define STANDARD';

	let insert = /* glsl */`
		uniform sampler2D detailMap;
	`;

	shader.fragmentShader = shader.fragmentShader.replace( token, token + insert );

	token = '#include <map_fragment>';

	insert = /* glsl */`
		diffuseColor *= texture2D( detailMap, vMapUv * 10.0 );
	`;

	shader.fragmentShader = shader.fragmentShader.replace( token, token + insert );

};

在后续中任何简单的效果调整都会使 .onBeforeCompile 中代码变得越来越复杂。像我们今天在 Threejs 社区中看到无数这种类型的参数化材质,它们彼此之间无法沟通、重用。

新的实现方式

如果采用 TSL 代码如下:

ini 复制代码
import { texture, uv } from 'three/tsl';

const detail = texture( detailMap, uv().mul( 10 ) );

const material = new THREE.MeshStandardNodeMaterial();
material.colorNode = texture( colorMap ).mul( detail );

是不是代码简单很多了。

并且 TSL 能够将代码编译成不同的输出,例如 WebGPU 对应的 WGSL 和 WebGL对应的 GLSL。

此外还可以自动优化着色器图形 节点(Node) 代码。让开发人员可以专注于生产力,一些图形管理底层代码交给 节点系统(Node System) 处理。

图形着色器的另外一个重要特性是:我们不再需要关心组件的创建顺序,因为 节点系统(Node System) 只会声明并包含一次。

假设您将 positionWorld 导入到代码中,即使多个组件都试用它,为获得 positionWorld 而执行的计算也只会执行一次,其他的(如 normalWorld、modelPosition)也一样。

构建体系

所有 TSL 组件都从 节点(Node) 扩展而来。节点允许与任何其他组件进行通信,值转换可以是自动的或手动的。节点可以接受父级期望的输出值并修改自己的输出片段。

在着色器构建过程中可以使用 摇树优化(tree shaking) 来调节它们,节点将拥有几何体、材质、渲染器等其他重要信息,这些信息将会影响输出的类型和值。

负责构建代码的类是 节点构建器(NodeBuilder),它可以扩展到任何输出编程语言。

目前 节点构造器(NodeBuilder) 有 2 个扩展类:

  • 针对 WebGPU 的 WGSLNodeBuilder
  • 针对 WebGL 2 的 GLSLNodeBuilder

构建过程由 3 个部分组成:设置(setup)、解析(anslyze)、生成(generate)

  • 设置(setup):使用 TSL 为节点输出创建完整的自定义代码。节点本身可以使用其他节点,可以没有数据输入但一定有数据输出。
  • 解析(anslyze):此过程将检查创建的节点,为生成代码片段创建有用的信息,例如是否需要创建缓存、变量来优化节点。
  • 生成(generate):每一个节点将生成一段 代码片段(string)。任何节点也可以在 着色器流程 中创建代码,支持多行。

节点还有一个由 update() 函数调用的本地更新过程,这些事件由 framerender对象绘制(object draw) 调用。

节点还支持使用 serialize()deserialize() 来序列化或反序列化。

常量和显式转换

输入函数可用于创建常量并进行显式转换。

如果输出和输入的类型不同,也会自动执行转换。

函数 返回常量或类型转换
float( node|number ) float
int( node|number ) int
uint( node|number ) uint
bool( node|value ) boolean
color( node|hex|r,g,b ) color
vec2( node|Vector2|x,y ) vec2
vec3( node|Vector3|x,y,z ) vec3
vec4( node|Vector4|x,y,z,w ) vec4
mat2( node|Matrix2|a,b,c,d ) mat2
mat3( node|Matrix3|a,b,c,d,e,f,g,h,i ) mat3
mat4( node|Matrix4|a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p ) mat4
ivec2( node|x,y ) ivec2
ivec3( node|x,y,z ) ivec3
ivec4( node|x,y,z,w ) ivec4
uvec2( node|x,y ) uvec2
uvec3( node|x,y,z ) uvec3
uvec4( node|x,y,z,w ) uvec4
bvec2( node|x,y ) bvec2
bvec3( node|x,y,z ) bvec3
bvec4( node|x,y,z,w ) bvec4

示例:

scss 复制代码
import { color, vec2, positionWorld } from 'three/tsl';

// 常量赋值
material.colorNode = color( 0x0066ff );

// 显示转换赋值
material.colorNode = vec2( positionWorld ); // 结果 positionWorld.xy

转换

也可以使用下面函数执行转换。

函数 返回常量或类型转换
.toFloat() float
.toInt() int
.toUint() uint
.toBool() boolean
.toColor() color
.toVec2() vec2
.toVec3() vec3
.toVec4() vec4
.toMat2() mat2
.toMat3() mat3
.toMat4() mat4
.toIVec2() ivec2
.toIVec3() ivec3
.toIVec4() ivec4
.toUVec2() uvec2
.toUVec3() uvec3
.toUVec4() uvec4
.toBVec2() bvec2
.toBVec3() bvec3
.toBVec4() bvec4

示例:

javascript 复制代码
import { positionWorld } from 'three/tsl';

// 转换
material.colorNode = positionWorld.toVec2(); // 结果 positionWorld.xy

统一变量

统一变量(Uniform)可用于更新颜色、光照、变换等变量的值,而无需重新创建着色器程序。从 GPU 的角度来看他们是真正的变量。

  • uniform( boolean | number | Color | Vector2 | Vector3 | Vector4 | Matrix3 | Matrix4, type = null ):动态值

示例:

ini 复制代码
const posY = uniform( mesh.position.y );

// 可以使用 posY.value 手动更新该值
posY.value = mesh.position.y;

material.colorNode = posY;

uniform.on*Update()

还可以创建更新事件 uniforms,用户可以定义:

  • .onObjectUpdate(function):每次在 材质(material) 中使用此节点渲染网格等对象时,它都会更新。
  • .onRenderUpdate(function):每次渲染、共享材质、雾、色调映射时都会更新一次。
  • .onFrameUpdate(function):每帧会更新一次,建议用于每帧只更新一次的值,无论帧的渲染过程(render pass)何时进行,例如计时器(timer)

示例:

typescript 复制代码
const posY = uniform( 0 );

// 或使用事件自动完成
// { object } 是当前渲染对象
posY.onObjectUpdate( ( { object } ) => object.position.y );

material.colorNode = posY;

分量重组

分量重组(Swizzling) 是一种允许你使用 TSL 中的特定符号、重新排序或复制向量组件的技术。

译者注:"swizzling" 单词本意为 "调酒",这里将其翻译为 "分量重组"。

这是通过组合标识符来实现的:

ini 复制代码
const original = vec3( 1.0, 2.0, 3.0 ); // (x, y, z)
const swizzled = original.zyx; // 分量重组之后 = (3.0, 2.0, 1.0)

类似的还有 xyzwrgbastpq

运算符

名称 描述
.add( node | value, ... ) 两个或多个值的加法
.sub( node | value ) 两个或多个值的减法
.mul( node | value ) 两个或多个值的乘积
.div( node | value ) 两个或多个值的除法
.assign( node | value ) 合并属性
.mod( node | value ) 取余
.modInt( node | value ) 取余,余数为整数
.equal( node | value ) 是否相等
.notEqual( node | value ) 是否不相等
.lessThan( node | value ) 是否小于
.greaterThan( node | value ) 是否大于
.lessThanEqual( node | value ) 是否小于等于
.greaterThanEqual( node | value ) 是否大于等于
.and( node | value ) 执行逻辑与
.or( node | value ) 执行逻辑或
.not( node | value ) 执行逻辑非
.xor( node | value ) 执行逻辑异或
.bitAnd( node | value ) 按位与运算
.bitNot( node | value ) 按位非运算
.bitOr( node | value ) 按位或运算
.bitXor( node | value ) 按位异或运算
.shiftLeft( node | value ) 将节点向左移动
.shiftRight( node | value ) 将节点向右移动

示例:

ini 复制代码
const a = float( 1 );
const b = float( 2 );

const result = a.add( b ); // 输出: 3

Fn函数

Fn(function)

可以使用经典 JS 函数或 Fn() 接口。主要区别在于 Fn() 创建了一个可控环境,允许使用堆栈,在堆栈中可以使用赋值和条件,而经典函数只允许内联方法。

示例:

scss 复制代码
// TSL 函数
const oscSine = Fn( ( [ t = timer ] ) => {

	return t.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5 );

} );

// 经典 JS 内联函数
export const oscSine = ( t = timer ) => t.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5 );

以上两者都可以用来调用 oscSin(value)

TSL允许将参数作为对象输入,这对于具有许多可选参数的函数很有用。

示例:

scss 复制代码
const oscSine = Fn( ( { timer = timerGlobal } ) => {
	return timer.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5   );
} );

const value = oscSine( { timer: value } );

如果想使用(例如 webpack)兼容的导出功能 tree shaking,请记得使用 /*@__PURE__*/

javascript 复制代码
export const oscSawtooth = /*@__PURE__*/ Fn( ( [ timer = timerGlobal ] ) => timer.fract() );

条件语句

If-else

If-else 条件语句可以在 tsnFun() 中使用。条件语句在 TSL 中使用 If 构建:

vbnet 复制代码
If( conditional, function )
.ElseIf( conditional, function )
.Else( function )

注意这里的 "If" 开头的 "i" 是大写的

示例:

在下面的例子中,我们将 y 位置限制为 10。

ini 复制代码
const limitPosition = Fn( ( { position } ) => {

	const limit = 10;

	// 使用 `.toVar()` 转换为变量,以便能够使用赋值
	const result = position.toVec3().toVar();

	If( result.y.greaterThan( limit ), () => {

		result.y = limit;

	} );

	return result;

} );

material.positionNode = limitPosition( { position: positionLocal } );

使用示例 elseif

ini 复制代码
const limitPosition = Fn( ( { position } ) => {

	const limit = 10;

	// 使用 `.toVar()` 转换为变量,以便能够使用赋值
	const result = position.toVec3().toVar();

	If( result.y.greaterThan( limit ), () => {

		result.y = limit;

	} ).ElseIf( result.y.lessThan( limit ), () => {

		result.y = limit;

	} );

	return result;

} );

material.positionNode = limitPosition( { position: positionLocal } );

三元运算

If-else 不同,三元条件将返回一个值,并且可以在 Fn() 之外使用。

使用 select()

ini 复制代码
const result = select( value.greaterThan( 1 ), 1.0, value );

在 JS 中等价的应该是:value > 1 ? 1.0 : value

数学

名称 描述
EPSION 用于处理浮点精度错误的小值。
INFINITY 代表无穷大
abs( x ) 绝对值
acos( x ) 反余弦
all( x ) 如果 x 的所有组成部分都为真,则返回 true
any( x ) 如果 x 的任意一组成部分为真,则返回 true
asin( x ) 反正弦
atan( y, x ) 反正切
atan2( y, x ) 反正切
bitcast( x, y ) 将值的各个位重新解释为不同的类型。
cbrt( x ) 立方根
ceil( x ) 向上取整
clamp( x, min, max ) 将 x 限定在 min 和 max 范围之间
cos( x ) 余弦
cross( x, y ) 计算两个向量的叉积
dFdx( p ) x 的偏导数
dFdy( p ) y 的偏导数
degrees( radians ) 将弧度转化为度数
difference( x, y ) 计算两个值的绝对差
distance( x, y ) 计算两点之间的距离
dot( x, y ) 点积
equals( x, y ) 检查是否相等
exp( x ) 返回自然指数
exp2( x ) 返回 2 的参数幂
faceforward( N, I, Nref ) 返回指向另一个向量相同方向的向量。
floor( x ) 向下取整
fract( x ) 小数部分
fwidth( x ) 返回x和y的绝对导数之和。
inverseSqrt( x ) 返回参数平方根的倒数。
invert( x ) 反转 alpha 参数(1. - x)。
length( x ) 计算向量的长度
lengthSq( x ) 计算向量的平方长度。
log( x ) 返回参数的自然对数。
log2( x ) 返回参数以 2 为底的对数。
max( x, y ) 取最大
min( x, y ) 取最小
mix( x, y, a ) 在两个值之间进行线性插值
negate( x ) 取反
normalize( x ) 向量归一化
oneMinus( x ) 返回 1 减去参数
pow( x, y )
pow2( x ) 平方
pow3( x ) 立方
pow4( x ) 四次方
radians( degrees ) 将度数转换为弧度
reciprocal( x ) 返回参数的倒数 (1/x)
reflect( I, N ) 计算入射矢量的反射方向
refract( I, N, eta ) 计算入射矢量的折射方向
round( x ) 四舍五入取整
saturate( x ) 将值限制在 0 和 1 之间
sign( x ) 提取参数的符号
sin( x ) 正弦
smoothstep( e0, e1, x ) 在两个值之间执行 Hermite 插值
sqrt( x ) 平方根
step( edge, x ) 通过比较两个值来生成阶跃函数
tan( x ) 正切
transformDirection( dir, matrix ) 用矩阵变换向量的方向,然后对结果进行归一化
trunc( x ) 截断参数,删除小数部分

示例:

c 复制代码
const value = float( -1 );

// 也可以使用 `value.abs()`
const positiveValue = abs( value ); // output: 1

方法链

方法链(Method chaining) 仅包含运算符、转换器、数学和一些核心函数。但是这些函数可用于任何 节点。

示例:

oneMinus() 是一个数学函数,例如 abs()sin()。此示例使用 .oneMinus() 类中的内置函数返回一个新节点,而不是像经典 C 函数 oneMinus( texture(map).rgb )

ini 复制代码
// 它将反转纹理颜色
material.colorNode = texture( map ).rgb.oneMinus();

可以在任意节点上使用数学运算符,例如:

ini 复制代码
const contrast = .5;
const brightness = .5;

material.colorNode = texture( map ).mul( contrast ).add( brightness );

纹理

texture( texture, uv = uv(), level = null )

类型 vec4,从纹理中检索纹素。

cubeTexture( texture, uvw = reflectVector, level = null )

类型 vec4,从立方体纹理中检索纹素。

triplanarTexture( textureX, textureY = null, textureZ = null, scale = float( 1 ), position = positionLocal, normal = normalLocal )

类型 vec4,根据提供的参数使用三平面贴图映射计算纹理。

属性

attribute( name, type = null, default = null )

类型为 any,使用名称和类型获取几何属性。

uv( index = 0 )

类型为 vec2,UV 属性名为 uv + index

vertexColor( index = 0 )

类型为 color,设置节点指定索引的顶点颜色。

坐标位置

  • positionGeometry:几何体的位置坐标

  • positionLocal:局部相对坐标

    positionLocal 表示通过蒙皮(skinning)、变形器(morpher)等进行修改后的位置。

  • positionWorld:世界坐标

  • positionWorldDirection:归一化的世界方向

  • positionView:视线位置

  • positionViewDirection:归一化的视线方向

以上类型均为 vec3

法线

  • normalGeometry:几何体法线

  • normalLocal:局部法线

  • normalView:视图法线

  • normalWorld:世界法线

  • transformedNormalView:视图空间中变换后的法线

  • transformedNormalWorld:世界空间中变换后的法线

  • transformedClearcoatNormalView:在视图空间中变换后的透明涂层法线

    transformedXxx 这种表示经过蒙皮(skinning)、变形器(morpher)等修改后的法线

以上类型均为 vec3

切线

  • tangentGeometry:几何体切线
  • tangentLocal:局部切线
  • tangentView:视图切线
  • tangentWorld:世界切线
  • transformedTangentView:视图空间中变换后的切线
  • transformedTangentWorld:世界空间中变换后的切线

以上类型均为 vec3

双切线

  • bitangentGeometry:几何体中归一化的双切线
  • bitangentLocal:局部空间中归一化的双切线
  • bitangentView:视图空间中归一化的双切线
  • bitangentWorld:世界空间中归一化的双切线
  • transformedBitangentView:视图空间变换后归一化的双切线
  • transformedBitangentWorld:世界空间变换后归一化的双切线

以上类型均为 vec3

相机

  • cameraNear:相机的近平面距离,类型为 float
  • cameraFar:相机的远平面距离,类型为 float
  • cameraLogDepth:相机的对数深度值,类型为 float
  • cameraProjectionMatrix:相机的投影矩阵,类型为 mat4
  • cameraProjectionMatrixInverse:相机的逆投影矩阵,类型为 mat4
  • cameraViewMatrix:相机的视图矩阵,类型为 mat4
  • cameraWorldMatrix:相机的世界矩阵,类型为 mat4
  • cameraNormalMatrix:相机的法线矩阵,类型为 mat4
  • cameraPosition:相机的世界位置,类型为 vec3

模型

  • modelDirection:模型的方向,类型为 vec3
  • modelViewMatrix:模型的视图空间矩阵,类型为 mat4
  • modelNormalMatrix:模型的法线矩阵,类型为 mat3
  • modelWorldMatrix:模型的世界矩阵,类型为 mat4
  • modelPosition:模型的位置,类型为 vec3
  • modelScale:模型的缩放,类型为 vec3
  • modelViewPosition:模型的视图空间位置,类型为 vec3
  • modelWorldMatrixInverse:模型的逆世界矩阵,类型为 mat4
  • highpModelViewMatrix:使用 64 位 CPU 计算的模型视图空间矩阵,类型为 mat4
  • highpModelNormalViewMatrix:使用 64 位 CPU 计算的模型视图空间法线矩阵,类型为 mat3

屏幕

屏幕节点将返回与当前 帧缓冲区(frame buffer) 相关的值,可以是归一化的,也可以是考虑当前 像素比(Pixel Ratio) 的物理像素单位。

  • screenUV:返回归一化的帧缓冲区坐标,类型为 vec2
  • screenCoordinate:以物理像素单位返回帧缓冲区坐标,类型为 vec2
  • screentSize:以物理像素单位返回帧缓冲区大小,类型为 vec2

视口

视口(viewport) 受到 renderer.setViewport() 中定义的区域影响,与渲染器中定义的 逻辑像素单位(logical pixel units) 不同,它使用 物理像素单位(physical pixel units) 来考虑当前的像素比。

  • viewportUV:归一化的视口坐标,类型为 vec2
  • viewport:以物理像素单位返回视口尺寸,类型为 vec4
  • viewportCoordinate:以物理像素单位返回视口坐标,类型为 vec2
  • viewportSize:以物理像素单位返回视口大小,类型为 vec2

混合模式

  • blendBurn( a, b ):返回刻录混合模式结果
  • blendDodge( a, b ):返回减淡混合模式结果
  • blendOverlay( a, b ):返回覆盖混合模式结果
  • blendScreen( a, b ):返回屏幕混合模式结果
  • blendColor( a, b ):返回颜色混合模式结果

以上返回类型均为 color

反射

  • reflectView:计算视图空间中的反射方向
  • reflectVector:将反射方向变换至世界空间

以上类型均为 vec3

UV工具

  • matcapUV:材质捕获(matcap)纹理 的 UV 坐标

    译者注:matcap 是单词 "material capture(捕获)" 的简写

  • rotateUV( uv, rotation, centerNode = vec2( 0.5 ) ):围绕中心点旋转 UV 坐标

  • spherizeUV( uv, strength, centerNode = vec2( 0.5 ) ):利用围绕中心点的球面效果扭曲 UV 坐标

  • spritesheetUV( count, uv = uv(), frame = float( 0 ) ):根据帧数、UV 坐标 和帧索引计算精灵图的 UV 坐标

  • equirectUV( direction = positionWorldDirection ):根据方向向量计算等矩形映射的 UV 坐标

以上返回类型均为 vec2

示例:

ini 复制代码
import { texture, matcapUV } from 'three/tsl';

const matcap = texture( matcapMap, matcapUV );

插值

  • remap( node, inLow, inHigh, outLow = float( 0 ), outHigh = float( 1 ) ):将一个值从一个范围重新映射到另外一个范围
  • remapClamp( node, inLow, inHigh, outLow = float( 0 ), outHigh = float( 1 ) ):将一个值从一个范围重新映射到另一个范围,并进行限制

以上返回类型均为 any

随机

  • hash( seed ):从给定的 "种子(seed)" 生成 [0,1] 范围内的哈希值,返回类型为 float
  • range( min, max ):生成介于最小值和最大值之间的一系列属性值。当您想在实例之间而不是像素之间随机化值时,属性随机化非常有用。返回类型为 any

振荡器

  • oscSine( timer = timerGlobal ):根据计时器产生正弦波振荡
  • oscSquare( timer = timerGlobal ):根据计时器产生方波振荡
  • oscTriangle( timer = timerGlobal ):根据计时器产生三角波振荡
  • oscSawtooth( timer = timerGlobal ):根据计时器产生锯齿波振荡

以上返回类型均为 float

方向与颜色转换

  • directionToColor( value ):将方向向量转换为颜色,返回类型为 color
  • colorToDirection( value ):将颜色转换为方向向量,返回类型为 vec3

函数

.toVar(name=null)

要从节点创建变量,请使用 .toVar()

参数 name 用于为其添加名称,否则节点系统将自动命名,它在调试或使用 wgslFn 访问时很有用。

ini 复制代码
const uvScaled = uv().mul( 10 ).toVar();

material.colorNode = texture( map, uvScaled );

varying(node,name=null)

假设你想在 顶点阶段(vertex stage) 优化其中某些计算,但又在 片段阶段(frame stage) 类似于 material.colorNode 的插槽中使用它。

译者注:直白一点就是说在 顶点阶段 创建一个变量 传递到 片段阶段

例如:

ini 复制代码
// 乘法(multiplication)将在顶点阶段执行
const normalView = varying( modelNormalMatrix.mul( normalLocal ) );

// 归一化(normalize)将在片段阶段执行
// 因为默认情况下 .colorNode 是片段阶段的插槽
material.colorNode = normalView.normalize();

varying 的第一个参数 modelNormalMatrix.mul( normalLocal ) 将在顶点阶段执行,并且 varying() 返回的值将是可变的,因为我们在 WGSL/GLSL 中使用它,这可以优化片段阶段的额外计算。

第二个参数允许你为变量添加自定义名称。

如果 varying() 仅添加到 .positionNode,它将仅返回一个简单变量,而不会创建真正的变量。

译者注:用代码来解释上面这句话

scss 复制代码
// 这样使用只会返回一个简单变量,不会创建 varying
const position = varying(positionNode.positionNode);

// 正确的使用方式是直接对节点使用
const position = varying(positionNode);

这是因为:

  1. .positionNode 已经是一个处理过的节点属性
  2. 这种情况下 varying() 无法正确识别需要在着色器之间传递的数据
  3. 系统会退化为返回一个普通变量而不是创建真正的 varying 变量

总之,在使用 varying() 时要直接对原始节点使用,而不是对节点的属性使用。

节点材质

请参阅下文了解 节点材质(NodeMaterial) 的更多输入信息。

核心
  • fragmentNode:替换片段阶段使用的内置材质逻辑,类型为 vec4。
  • vertexNode:替换顶点阶段使用的内置材质逻辑,类型为 vec4。
  • geometryNode:允许执行 TSL 函数来处理几何体,类型为 Fn()
基本的
  • .colorNode:替换 material.color * material.map 逻辑,对应 materialColor,类型为 vec4
  • .depthNode:自定义 depth 输出,对应 depth,类型为 float
  • .opacityNode:替换 material.opacity * material.alphaMap 逻辑,对应 materialOpacity,类型为 float
  • .alphaTestNode:设置阈值以丢弃低于不透明的像素,对应 materialAlphaTest,类型为 float
灯光
  • .emissiveNode:替换 material.emissive * material.emissiveIntensity * material.emissiveMap 逻辑,对应 materialEmissive,类型为 color
  • .normalNode:表示视图空间中的法线方向,替换 material.normalMap * material.normalScalematerial.bumpMap * material.bumpScale 逻辑,对应 materialNormal,类型为 vec3
  • .lightsNode:定义材质将使用的灯光和照明模型。类型为 lights()
  • .envNode:替换 material.envMap * material.envMapRotation * material.envMapIntensity,类型为 color
背景
  • .backdropNode:在应用 镜面反射(specular) 之前渲染节点输入,这对透视(transmission)和折射(refraction)材质很有用。类型为 vec3

  • .backdropAlphaNode:定义 backdropNode 的透明通道,类型为 float。

    译者注:alpha 指 rgba 中的 a 的分量值

位置
  • .positionNode:表示局部空间中的顶点位置,替换 material.displacementMap * material.displacementScale + material.displacementBias 逻辑,对应 positionLocal,类型为 vec3
阴影
  • .castShadowNode:控制材质投射阴影的颜色和透明度,类型为 vec4
  • .receivedShadowNode:处理投射在材质上的阴影,类型为 Fn()
  • .shadowPositionNode:定义世界空间中阴影投影位置,对应 shadowPositionWorld,类型为 vec3
  • aoNode:替换 material.aoMap * aoMapIntensity 逻辑,对应 materialAO,类型为 float
输出
  • .mrtNode:定义一个与 pass() 中定义的 MRT 不同的 MRT。类型为 mrt()
  • .outputNode:定义材质的最终输出,对应 output,类型为 vec4

虚线节点材质

  • .dashScaleNode:替换 material.scale 逻辑,对应 materialLineScale
  • .dashSizeNode:替换 material.dashSize 逻辑,对应 materialLineDashSize
  • .gapSizeNode:替换 material.gapSize,对应 materialLineGapSize
  • .offsetNode:替换 material.dashOffset,对应 materialLineDashOffset

以上类型均为 float

Phong网格节点材质

  • .shininessNode:替换 material.shininess,对应 materialShininess,类型为 float
  • .specularNode:替换 material.specular,对应 materialSpecular,类型为 color

标准网格节点材质

  • .metalnessNode:替换 material.metalness * material.metalnessMap,对应 metalnessNode
  • .roughnessNode:替换 material.roughness * material.roughnessMap,对应 roughnessNode

以上类型均为 float

物理网格节点材质

  • .clearcoatNode: 替换 material.clearcoat * material.clearcoatMap 逻辑,对应 materialClearcoat,类型为 float
  • .clearcoatRoughnessNode:替换 material.clearcoatRoughness * material.clearcoatRoughnessMap,对应 materialClearcoatRoughness,类型为 float
  • .clearcoatNormalNode:替换 material.clearcoatNormalMap * material.clearcoatNormalMapScale,对应 materialClearcoatNormal,类型为 vec3
  • .sheenNode:替换 material.sheenColor * material.sheenColorMap,对应 materialSheen ,类型为 color
  • .iridescenceNode:替换 material.iridescence,对应 materialIridescence,类型为 float
  • .iridescenceIORNode:替换 material.iridescenceIOR,对应 materialIridescenceIOR,类型为 float
  • .iridescenceThicknessNode:替换 material.iridescenceThicknessRange * material.iridescenceThicknessMap,对应 materialIridescenceThickness,类型为 float
  • .specularIntensityNode:替换 material.specularIntensity * material.specularIntensityMap,对应 materialSpecularIntensity,类型为 float
  • .specularColorNode:替换 material.specularColor * material.specularColorMap,对应 materialSpecularColor,类型为 color
  • .iorNode:替换 material.ior,对应 materialIOR,类型为 float
  • .transmissionNode:替换 material.transmission * material.transmissionMap,对应 materialTransmission,类型为 color
  • .thicknessNode:替换 material.thickness * material.thicknessMap,对应 materialTransmission,类型为 float
  • .attenuationDistanceNode:替换 material.attenuationDistance,对应 materialAttenuationDistance,类型为 float
  • .attenuationColorNode:替换 material.attenuationColor,对应 materialAttenuationColor,类型为 color
  • .dispersionNode:替换 material.dispersion,对应 materialDispersion,类型为 float
  • .anisotropyNode:替换 material.anisotropy * material.anisotropyMap,对应 materialAnisotropy,类型为 vec2

精灵节点材质

  • .positionNode:定义位置
  • .rotationNode:定义旋转
  • .scaleNode:定义缩放

以上类型均为 vec3

将常见的 GLSL 与 TSL 属性对比表

| GLSL | TSL | 类型 |
|-------------------|------------------------|------|------|
| position | positionGeometry | vec3 |
| transformed | positionLocal | vec3 |
| transformedNormal | normalLocal | vec3 |
| vWorldPosition | positionWorld | vec3 |
| vColor | vertexColor() | vec3 |
| vUv` | `uv | uv() | vec2 |
| vNormal | normalView | vec3 |
| viewMatrix | cameraViewMatrix | mat4 |
| modelMatrix | modelWorldMatrix | mat4 |
| modelViewMatrix | modelViewMatrix | mat4 |
| projectionMatrix | cameraProjectionMatrix | mat4 |
| diffuseColor | material.colorNode | vec4 |
| gl_FragColor | material.fragmentNode | vec4 |

文档翻译来源 https://github.com/puxiao/threejs-shading-language-tutorial/blob/main/01%20Three.js%E7%9D%80%E8%89%B2%E8%AF%AD%E8%A8%80(TSL)%E8%A7%84%E8%8C%83.md?plain=1 感谢作者。

相关推荐
getapi1 小时前
Flutter 强制横屏
前端·javascript·flutter
白嫖一茶1 小时前
QQ风格客服聊天窗口
javascript·css·css3
纪元A梦2 小时前
华为OD机试真题——天然蓄水库(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
java·c语言·javascript·c++·python·华为od·go
枫super3 小时前
Day-03 前端 Web-Vue & Axios 基础
前端·javascript·vue.js
zhangbao90s4 小时前
Tauri 与 Electron 对比:性能、包大小及实际权衡
javascript·node.js
你的人类朋友4 小时前
飞速入门 Axon:Node.js 微服务的轻量级选择
javascript·后端·node.js
Moment4 小时前
拆包的艺术:Webpack SplitChunksPlugin 执行流程全流程揭秘 🤩🤩🤩
前端·javascript·webpack
那小孩儿4 小时前
最简单的new Date()展示,也能做优化
前端·javascript·性能优化
pink大呲花4 小时前
Vue.js 中 v-if 的使用及其原理
前端·javascript·vue.js
Carlos_sam5 小时前
Openlayers:flat样式介绍
javascript