three.js WebGPU 节点式像素化后期处理:PixelationPassNode 源码与实战完全指南
2026/9/8 16:37:11 网站建设 项目流程

three.js WebGPU 节点式像素化后期处理:PixelationPassNode 源码与实战完全指南

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

像素化(Pixelation)是复古游戏与像素艺术风格最常用的一种后期处理手法。在 three.js 的 WebGPU/TSL 渲染链路中,PixelationPassNode将传统"低分辨率渲染 + 颜色/法线/深度边缘检测"组合封装成一个可直接插入RenderPipeline的美感渲染通道(beauty pass)。本文以 PixelationPassNode 官方文档 为骨架,结合examples/jsm/tsl/display/PixelationPassNode.js源码与官方示例逐步剖析其继承关系、构造参数、公开属性/方法,并给出一份完整可运行的实战代码与参数调优建议。读完本文,你将能够在自己的 WebGPU 后处理管线中复现出带单像素描边的高质量像素化画面。

PixelationPassNode 是什么

PixelationPassNode是一个特殊的渲染通道节点(render pass node),它以低分辨率渲染场景,从而在视觉上产生"像素方块"效果,同时通过检测深度边缘与法线边缘,在色块交界处保留(或增强)描边细节,避免低分辨率采样把模型轮廓糊成一团。

它属于 three.js 的WebGPU/TSL(Three Shading Language)节点体系,其类层次为:

EventDispatcher → Node → TempNode → PassNode → PixelationPassNode

其中父类PassNode的实现位于 src/nodes/display/PassNode.js,负责把场景与相机渲染到内部纹理(即所谓 beauty pass);PixelationPassNode则在其基础上改写渲染分辨率与采样方式,实现像素化输出。

与其功能相对应、但运行在传统 WebGL/EffectComposer链路中的同类组件是 examples/jsm/postprocessing/RenderPixelatedPass.js(非节点式 Pass),可作为迁移或对比参考。

导入方式

PixelationPassNode属于插件(addon)模块,不会随 three.js 核心自动打包,必须显式导入。推荐使用其附带的TSL 工厂函数pixelationPass

import { pixelationPass } from 'three/addons/tsl/display/PixelationPassNode.js';

若希望使用类本身做类型判断或继承扩展,也可以直接导入默认导出:

import PixelationPassNode from 'three/addons/tsl/display/PixelationPassNode.js';

从源码可见该模块共导出了两个符号:PixelationPassNode.js 中pixelationPass只是对new PixelationPassNode(...)的语法糖封装,两者等价:

export const pixelationPass = ( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) => new PixelationPassNode( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength );

构造函数与参数详解

new PixelationPassNode( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength )

源码中的完整签名与默认值(见 PixelationPassNode.js):

constructor( scene, camera, pixelSize = 6, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 )
参数类型默认值含义
sceneScene要被渲染的场景
cameraCamera渲染场景所用的相机
pixelSizeNode<float>|number6像素尺寸,即最终色块边长对应的"缩小倍数"
normalEdgeStrengthNode<float>|number0.3法线边缘强度
depthEdgeStrengthNode<float>|number0.4深度边缘强度

参数解析要点:

  • scenecamera会被透传给父类PassNode(内部渲染目标的光圈被设为PassNode.COLOR)。
  • 三个数值参数不仅可以是普通数字,还可以是TSL 节点对象(如uniform(...)包出的变量),从而支持运行时通过 GUI 或动画实时调节。官方示例 examples/webgpu_postprocessing_pixel.html 正是把三个参数全部做成uniform
    effectController = { pixelSize: uniform( 6 ), normalEdgeStrength: uniform( 0.3 ), depthEdgeStrength: uniform( 0.4 ), pixelAlignedPanning: true };

公开属性

.pixelSize : number(默认6

像素大小。除了在 shader 中控制色块粒度外,它还会影响内部渲染目标的分辨率(见下文setSize说明)。调大数值色块更大、画面更"糊"。

.normalEdgeStrength : number(默认0.3

法线边缘强度。控制几何表面朝向突变处(如立方体的棱)边缘的检测力度与明暗对比。

.depthEdgeStrength : number(默认0.4

深度边缘强度。控制前后景深度突变处(物体轮廓、穿插交界)边缘的强度。通常可略大于法线边缘强度以强化轮廓。

.isPixelationPassNode : boolean(只读,默认true

用于类型检测的只读标志。在拿到不确定的节点对象时,可借助node.isPixelationPassNode === true判断其是否为像素化通道节点。

公开方法

.setSize( width : number, height : number )

设置通道(内部渲染目标)的尺寸。这是像素化效果的关键,见 PixelationPassNode.js:

setSize( width, height ) { const pixelSize = this.pixelSize.value ? this.pixelSize.value : this.pixelSize; const adjustedWidth = Math.floor( width / pixelSize ); const adjustedHeight = Math.floor( height / pixelSize ); super.setSize( adjustedWidth, adjustedHeight ); }

也就是说,通道内部实际渲染分辨率会被压缩为⌊width / pixelSize⌋ × ⌊height / pixelSize⌋,画面先以小分辨率渲染、再被放大回目标尺寸,从而形成像素方块。注意这里兼容了"pixelSize是 uniform 节点"的情形:会优先读取this.pixelSize.value,因此运行时改大pixelSize也能让内部目标分辨率同步下降。

该方法重写了父类 PassNode#setSize。

.setup( builder : NodeBuilder ) : PixelationNode

节点构建方法,用于装配效果对应的 TSL 着色代码,返回一个内部的PixelationNode。其实现(见 PixelationPassNode.js)先从父类取出三张内部缓冲纹理,再送入像素化计算节点:

setup() { const color = super.getTextureNode( 'output' ); const depth = super.getTextureNode( 'depth' ); const normal = super.getTextureNode( 'normal' ); return pixelation( color, depth, normal, this.pixelSize, this.normalEdgeStrength, this.depthEdgeStrength ); }

该方法重写了 PassNode#setup。

底层实现原理:低分辨率渲染 + 双通道边缘检测

要写出稳定的像素化效果,单纯降低分辨率是不够的——边缘检测必须拿到几何信息。PixelationPassNode的源码揭示了完整的实现链路。

1. 最近邻采样与 MRT 几何缓冲

构造函数中向父类传递了NearestFilter采样设置,并配置了一套MRT(多渲染目标)

super( PassNode.COLOR, scene, camera, { minFilter: NearestFilter, magFilter: NearestFilter } ); // ... this._mrt = mrt( { output: output, // 颜色输出 normal: normalView // 视角空间法线 } );

最近邻过滤保证色块边缘锐利、无线性插值的模糊过渡;而 MRT 则在渲染场景的同时额外产出法线信息,供后续边缘检测使用(深度信息由渲染目标本身附带)。

2. 每帧刷新分辨率 uniform

内部的PixelationNode继承自TempNode,将其updateType设为NodeUpdateType.FRAME(每帧更新),并在update()回调中把输入纹理的宽高及倒数写入一个vec4uniform:

this._resolution.value.set( width, height, 1 / width, 1 / height );

resolution.xy为全分辨率尺寸,resolution.zw则是单像素对应的归一化 UV 步长,供后续邻域采样使用(见 PixelationPassNode.js)。

3. 深度边缘检测(depthEdgeIndicator)

对当前像素的上、下、左、右四个邻域分别采样深度并计算差值,再经由smoothstep( 0.01, 0.02, ... )floor(...).div(2)归一化为0 或 0.5两级深度边缘指示值——只有深度突变足够明显的像素才会被标记(源码 PixelationPassNode.js)。

4. 法线边缘检测(normalEdgeIndicator / neighborNormalEdgeIndicator)

法线边缘的判断要更精细,其规则可以从代码注释中读出:

  • 只有**较浅(更靠近相机)**的一侧像素才被允许报告法线边缘,借助深度差值符号作为门控(depthIndicator);
  • 用"邻域法线与当前法线越接近越趋近 1"的normalIndicator避免同一面内部被误判;
  • 最终输出1 - dot(normal, neighborNormal)与两个指示器相乘的累计值,并通过step( 0.1, indicator )二值化(源码 PixelationPassNode.js)。

从源码结构看,其中硬编码的normalEdgeBias = vec3(1,1,1)处还留有注释"This should probably be a parameter",说明法线偏置方向目前是固定值,未来存在参数化的可能。

5. 边缘与色块融合

像素化主函数Fn()内使用If按需开启各条分支——当某个强度参数为0时完全跳过对应的边缘计算,节省开销(源码 PixelationPassNode.js)。最终亮度修正强度为:

const strength = dei.greaterThan( 0 ) .select( float( 1.0 ).sub( dei.mul( this.depthEdgeStrength ) ), nei.mul( this.normalEdgeStrength ).add( 1 ) ); return vec4( texel.mul( strength ).rgb, texel.a );

即深度边缘处的像素按depthEdgeStrength变暗形成轮廓;非法线/深度边缘的区域则整体提亮为1 + normalEdgeStrength,这正是"像素块 + 单像素描边"视觉风格的来源(见 PixelationPassNode.js)。

完整实战示例

官方演示页 examples/webgpu_postprocessing_pixel.html 是集成该节点的最小可运行样例,其完整使用方式如下。

步骤 1:配置 importmap

由于该示例同时使用 WebGPU 渲染器与 TSL 构建产物,需要把相关入口映射到build目录下的模块:

<script type="importmap"> { "imports": { "three": "../build/three.webgpu.js", "three/webgpu": "../build/three.webgpu.js", "three/tsl": "../build/three.tsl.js", "three/addons/": "./jsm/" } } </script>

步骤 2:创建渲染管线并挂载节点

import * as THREE from 'three/webgpu'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { uniform } from 'three/tsl'; import { pixelationPass } from 'three/addons/tsl/display/PixelationPassNode.js'; renderer = new THREE.WebGPURenderer(); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setAnimationLoop( animate ); renderer.shadowMap.enabled = true; document.body.appendChild( renderer.domElement ); // 把三个效果参数做成可实时调用的 uniform const effectController = { pixelSize: uniform( 6 ), normalEdgeStrength: uniform( 0.3 ), depthEdgeStrength: uniform( 0.4 ), pixelAlignedPanning: true }; // 将像素化通道作为输出节点 const renderPipeline = new THREE.RenderPipeline( renderer ); const scenePass = pixelationPass( scene, camera, effectController.pixelSize, effectController.normalEdgeStrength, effectController.depthEdgeStrength ); renderPipeline.outputNode = scenePass;

之后在动画循环中只需调用renderPipeline.render(),每帧都会先以低分辨率渲染场景,再套用边缘检测与色块放大。

步骤 3:用参数面板实时调参

官方示例借助WebGPURenderer.inspector暴露出三个核心参数(区间即官方示例推荐的调试范围):

const gui = renderer.inspector.createParameters( 'Settings' ); gui.add( effectController.pixelSize, 'value', 1, 16, 1 ).name( 'Pixel Size' ); gui.add( effectController.normalEdgeStrength, 'value', 0, 2, 0.05 ).name( 'Normal Edge Strength' ); gui.add( effectController.depthEdgeStrength, 'value', 0, 1, 0.05 ).name( 'Depth Edge Strength' ); gui.add( effectController, 'pixelAlignedPanning' );

关于"像素对齐平移"(pixelAlignedPanning)

示例使用正交相机,并提供辅助函数pixelAlignFrustum():它会根据当前pixelSize计算出单块像素对应的世界单位,再微调相机平截体(frustum),使相机平移时色块始终与屏幕像素网格对齐,避免画面出现"滑动抖动"。只有当相机是正交相机时才需要这一辅助;透视相机下该技术不适用。

需要 WebGL 旧管线怎么办

如果你的项目仍运行在WebGLRenderer + EffectComposer上,可改用同主题的传统 Pass 类RenderPixelatedPass(见 RenderPixelatedPass.js):

const renderPixelatedPass = new RenderPixelatedPass( 6, scene, camera ); composer.addPass( renderPixelatedPass );

其构造参数含义(pixelSize、以及通过options.normalEdgeStrength/options.depthEdgeStrength传入的边缘强度)与节点版一一对应,便于两个渲染后端之间迁移。

参数调优建议

综合源码逻辑与示例 GUI 区间,可按以下思路调试:

目标建议做法
色块大小调节pixelSize(示例区间1 ~ 16,步长 1)。1约等于无像素化,6左右是较自然的复古色块;过大时几何会丢失,需依赖更强的边缘描边兜底
轮廓更"黑"、更明显增大depthEdgeStrength(示例区间0 ~ 1,步长 0.05)。只对深度突变处生效,不易误伤平滑表面
转折面棱线更清晰增大normalEdgeStrength(示例区间0 ~ 2,步长 0.05)。对立方体棱、多面体转折效果显著
关闭某一类边缘直接把对应强度设为0。源码中会跳过该分支的采样与计算,还能略微省下 GPU 开销

需要注意:pixelSize会同时改变内部渲染分辨率,因此该数值越小、渲染开销越高;场景较大或需要 60 FPS 时建议先用pixelSize = 8起步观察观感再回退。

小结

PixelationPassNode用约 330 行源码,把"低分辨率场景渲染 + MRT 几何缓冲 + 深度/法线邻域边缘检测 + 最近邻放大"这一套像素化方案完整封装成 WebGPU/TSL 渲染管线中即插即用的节点,并允许通过 TSL 节点参数实现运行时实时调节。它的直接使用入口是 TSL 工厂函数pixelationPass,内部逻辑可对照 examples/jsm/tsl/display/PixelationPassNode.js 阅读,运行效果可参考 webgpu_postprocessing_pixel.html。若需要更完整的后处理编排知识,可继续阅读 PostProcessing 与 RenderPipeline 文档,以及其父类 PassNode 的详细 API。

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询