Pascal 3D 编辑器材质与主题体系详解:从表面角色到场景主题的完整着色管线

发布时间:2026/9/12 17:08:37
Pascal 3D 编辑器材质与主题体系详解:从表面角色到场景主题的完整着色管线 Pascal 3D 编辑器材质与主题体系详解从表面角色到场景主题的完整着色管线【免费下载链接】editorOpen-source 3D architectural editor with a local CLI, MCP tools, and practical workflows for humans and AI agents.项目地址: https://gitcode.com/GitHub_Trending/editor93/editor本文档基于wiki/architecture/materials-and-themes.md编写结合packages/viewer、packages/core、packages/nodes等源码展开。文中提到的文件路径均相对仓库根目录。导读本指南系统讲解开源 3D 建筑编辑器 Pascal当前仓库中表面颜色surface colour的完整机制从节点如何声明表面角色surfaceRole、颜色如何被解析与缓存到四种颜色预设colorPreset与十余种场景主题sceneTheme如何正交叠加、以及自定义网格Block的面级材质槽Slots与纹理世界尺度UV 以米为单位约定。读完本文你将掌握 Pascal 查看器packages/viewer中shading、textures、colorPreset、sceneTheme、shadows、edges六大外观轴的全部含义与协作方式理解无纹理表面永远使用主题化角色颜色这一核心规则并能独立为仓库新增一个场景主题或为自定义网格接入MaterialRef材质系统。一、外观轴The axes六个正交的外观状态Pascal 把节点外观拆成一组互相正交的状态轴全部集中保存在查看器的useViewerstore 中packages/viewer/src/store/use-viewer.ts状态取值控制内容shadingsolid \| renderedsolidMeshLambertNodeMaterial无 SSGI/AOrenderedMeshStandardNodeMaterial SSGI/AO屏幕空间全局光照/环境光遮蔽texturesboolean拥有真实材质/预设的表面是否显示其纹理贴图colorPresetclay \| white \| mono \| blueprint无纹理表面的按角色基础调色板sceneTheme主题 idstudio、mediterranean、night、verdant等光照 背景 地面 按角色的颜色色调详见场景主题shadowsboolean平行光投影常开的 key light见 packages/viewer/src/components/viewer/lights.tsxedgesoff \| soft \| strong屏幕空间墨水描边在post-processing.tsx中处理lib/ink-edges.ts从 use-viewer.ts 的状态定义可以看到shading、textures、colorPreset等字段均带对应的setXxxsetter且shadingByContext让编辑器editor默认使用solid、社区查看器viewer默认使用rendered——两者可以有不同的默认着色模式。关键设计shading/textures/colorPreset按上下文context持久化而shadingByContext正是PartialRecordRenderContext, RenderShading类型RenderContext editor | viewer即同一场景在编辑器与查看器里可以分别记住各自的着色偏好。二、表面角色Surface rolesCore 只存令牌不存颜色core包为每个注册表种类registry kind提供一个可选的表面角色令牌声明在其NodeDefinition上packages/core/src/registry/types.tssurfaceRole?: wall | floor | ceiling | roof | joinery | glazing | furnishing这一设计非常克制core只存储令牌字符串不携带任何颜色也从不 import three.js。颜色解析完全发生在查看器层。令牌的意义在于墙wall、楼板slab、柱column等不同种类可以通过各自的surfaceRole从同一份调色板里解析出不同的颜色——同一个clay预设墙是#dcd6c7屋顶是#b8ad96玻璃是#c8d4dc。从源码可见SurfaceRole类型被packages/viewer、packages/core多处引用是整个材质体系的最小契约单元。三、解析颜色单一事实来源与缓存键颜色解析的唯一事实来源在 packages/viewer/src/lib/materials.tsresolveSurfaceColor(role, colorPreset, sceneThemeId?) // getSceneTheme(sceneThemeId).clayTints?.[role] // theme override, if any // ?? PRESET_PALETTES[colorPreset][role] // else the preset palette实际实现materials.ts为export function resolveSurfaceColor( role: SurfaceRole, preset: ColorPreset, sceneThemeId?: string, ): string { // 主题可以按角色覆盖颜色例如地中海主题的蓝色屋顶未覆盖时回退到所选颜色预设的调色板。 const tints sceneThemeId ? getSceneTheme(sceneThemeId).clayTints : undefined return tints?.[role] ?? (PRESET_PALETTES[preset] ?? CLAY_PALETTE)[role] }3.1 四种颜色预设的真实色值四个预设分别定义在 materials.ts 中每个都覆盖全部 7 种表面角色clay黏土灰| 角色 | 色值 | |---|---| | wall |#dcd6c7| | floor |#cfc8b6| | ceiling |#e4ded0| | roof |#b8ad96| | joinery |#c4bba6| | glazing |#c8d4dc| | furnishing |#d2ccbe|white白色——源码注释特别说明albedo 被钳制在约 0.83 线性值最大通道#eb因为真实白漆反射率约 80%纯白 albedo 会杀死 GI/阴影对比度 wall#ebeae6、floor#e7e4dd、ceiling#ebeae6、roof#dedbd2、joinery#e5e2d9、glazing#dbe8ee、furnishing#e9e7e1。mono单色灰wall#c8c8c8、floor#b8b8b8、ceiling#d8d8d8、roof#9a9a9a、joinery#adadad、glazing#c2cbd0、furnishing#c0c0c0。blueprint蓝图蓝wall#90a9c7、floor#7f98ba、ceiling#aec0d8、roof#5f789b、joinery#6f86a8、glazing#b6d7ea、furnishing#8ba2bf。3.2createSurfaceRoleMaterial与缓存键createSurfaceRoleMaterial(role, colorPreset, side?, sceneThemeId?)把解析出的颜色包装成一个受光照的MeshLambertNodeMaterialmaterials.ts并按role-preset-side-sceneTheme组合做缓存const cacheKey ${role}-${preset}-${resolvedSide}-${sceneThemeId ?? base}缓存键正是每个消费方都必须把sceneTheme一路传下来的根本原因——否则切换主题时会命中旧主题的陈旧缓存材质。另外两个实现细节值得注意glazing 特殊处理玻璃角色强制使用FrontSiderole glazing ? THREE.FrontSide : ...且depthWrite: false、opacity: 0.25、transparent: true。原因是 MRT scenePassSSGI 的 diffuseColor/normal 目标中任何DoubleSideNodeMaterial 都会触发 WebGPU 渲染管线校验失败back-face 变体缺少 MRT 输出报错 Color target has no corresponding fragment stage output 并污染整个渲染上下文。需要双面可见时应把宿主网格旋转 180° 让 FrontSide 朝向观察者。所有缓存材质都打上userData.__pascalCachedMaterial true标记供几何重建时区分共享缓存材质与节点私有材质。3.3 材质缓存与纹理加载管线packages/viewer/src/lib/materials.ts 还维护了四类缓存materialCache预设/普通材质、defaultMaterialCache默认色材质、surfaceRoleMaterialCache角色材质、textureCache纹理。纹理支持.ktx2格式通过共享的 KTX2Loader 转码支持在 viewer 初始化时检测一次与普通图片两种加载路径且wrapS/wrapT/repeat/rotation/flipY等贴图属性均来自MaterialMapProperties见 packages/core/src/material-library.ts 中每个目录项的mapProperties。四、核心规则两种模式下无纹理表面都用主题色这是整个外观体系最重要的一条规则对于没有声明插槽默认值slot defaults的种类有纹理仅当该节点显式带有materialPreset或material时才成立textures关闭→ 每个表面都使用resolveSurfaceColor(role, …)textures开启→ 有纹理的表面显示纹理无纹理表面仍然使用resolveSurfaceColor而不是硬编码的白色/灰色默认值。因此选择地中海Mediterranean主题会得到蓝色屋顶 暖色墙而且完全不需要动textures开关。系统不存在全白模式——无纹理永远意味着主题化的角色颜色。4.1 各种类的接入位置种类角色颜色应用位置wallpackages/viewer/src/systems/wall/wall-materials.tsgetMaterialsForWall每帧由wall-cutout.tsx重新应用roof / roof-segmentpackages/viewer/src/systems/roof/roof-materials.tsgetRoofMaterialArrayslabpackages/nodes/src/slab/geometry.tsgetSlabSlotMaterialceilingpackages/nodes/src/ceiling/renderer.tsx通用注册表种类packages/viewer/src/systems/geometry/geometry-system.tsx →applyDefaultSurfaceRoletextures 关闭时door / windowpackages/viewer/src/systems/door/door-system.tsx / window-system.tsxstair / column / item / elevatorpackages/nodes/kind/renderer.tsx每个接入点都会从useViewer读取shading/textures/colorPreset/sceneTheme或从GeometrySystem线程化传入并且必须把sceneTheme放进材质缓存键和重建依赖数组中否则切换主题不会重新着色。4.2 GeometrySystem 的联动机制geometry-system.tsx 用useEffect把外观状态作为故意的重建触发器shading、textures、colorPreset、sceneTheme任一变化都会把所有声明了def.geometry的节点重新标记为 dirty从而触发几何重建并取用新外观。源码中注释明确说明这四个值是re-run TRIGGERS而非函数体读取值删除它们会静默破坏外观模式切换。同时def.geometryKey机制把全局渲染输入折叠进缓存键geometry-system.tsxconst builtKey ${shading}|${textures}|${colorPreset}|${sceneTheme}|${def.geometryKey(effectiveNode)}|${childLiveOverrideKey}这样主题/着色变化永远不会被geometryKey的输入未变则跳过重建逻辑误跳过而当!textures def.surfaceRole时系统调用applyDefaultSurfaceRole(built, def.surfaceRole, colorPreset, sceneTheme)第 234-236 行统一给通用几何体套上角色颜色。4.3 天花板与楼板的插槽默认值细节天花板和楼板在带色textures开启模式下使用声明的插槽默认值declared slot defaults。实现上有几点工程细节见 geometry-system.tsx 与 slab 几何代码天花板底面在两种外观下都使用不透明的BackSide材质只有ceiling-grid做混合blend。楼板顶面、侧面/底面与可选的地形裙边terrain skirt网格可以分别批量处理batch。扁平插槽默认值按颜色、粗糙度与着色模式共享 viewer 缓存slab 的旧版缓存材质携带__pascalCachedMaterial标记使几何重建后共享材质仍存活。透明的插槽覆盖slot overrides自己绘制自己不参与共享缓存。五、自定义网格Block的面级材质MaterialRef 与 SlotsBlock自定义网格通过稳定的、用户可命名的对象插槽复用MaterialRef模型BlockNode.slots把插槽 ID 映射到scene:或library:材质引用slotNames存储用户可编辑的插槽标签每个BlockFace.materialSlot存储一个插槽 IDbody是永久基础插槽也是未绑定/未解析插槽的回退目标。几何构建器为每个拓扑面topology face生成一个 Three.js group并按节点稳定的插槽 ID 顺序生成材质数组渲染材质顺序发布在userData.slotIds每个面的顶点范围记录在geometry.userData.blockFaces。5.1 绘制能力Paint的命中映射Paint 工具重新对网格做射线检测raycast把命中三角形通过这些顶点范围映射到稳定的拓扑面 ID因此预览与提交只影响该面。每个面的 UV 保留下文世界尺度投影契约。5.2 插槽交互编辑器中的 Slots 集合Block 检查器把这一集合称为Slots。交互规则用户可重命名插槽Paint 工具通过可复用的 scene-material 数据块改变插槽材质。编辑模式下选中一个或多个面后点击某个插槽立即把那些面绑定到该插槽——没有单独的 Assign / Select / Deselect 按钮行。在已选面的情况下添加插槽同一场景更新中创建插槽、绑定那些面、并分配一个明显不同的生成强调材质accent material——这样在用户选定最终涂装材质之前新表面在编辑模式和渲染模型中都能肉眼可见。无选中面时 Add Slot 是 no-op避免产生不可见的无用插槽。删除非 body 插槽在同一节点更新中把所有已分配面重新映射回bodybody成为激活的赋值来源可复用的 scene/library 材质仍可供其他节点使用。5.3 全局 Paint 工具与拓扑操作符的确定性全局 Paint 工具解析命中面的已分配插槽改变该插槽的材质绑定。全新网格的每个面都绑定到body所以第一次涂装会更新整个网格一旦面被分配到命名插槽涂装其中任意面都会更新使用该插槽的所有面。一次性材质在创建可复用 scene 材质前会先复用结构匹配的 scene 材质。擦除Erase清除插槽绑定body回到墙角色默认值其他未绑定插槽回退到body。拓扑操作符保持赋值确定性保留与变换的面保持其插槽挤出盖/侧、内缩盖/环继承源面环切loop-cut片段继承被切分的面倒角带bevel bands与混合材质溶解使用稳定topology.faces顺序中的第一个相邻面删除使用某插槽的最后一个面不会删除其可复用材质。5.4 外部插件渲染器的接入契约插件渲染器通过公开的pascal-app/viewer表面遵循同样的四个轴。对导入的层级结构先一次性捕获其作者材质再响应式地应用以下映射宿主状态导入材质带色 Rendered作者材质带色 Solid缓存 Lambert 变体保留颜色、albedo 贴图、alpha 与插槽MonochromecreateSurfaceRoleMaterial(surfaceRole, colorPreset, side, sceneTheme)适配器属于插件渲染器——因为它拥有层级结构知道哪些表面是 furnishing、glazing 还是其他角色。材质交换发生在偏好变化时绝不在useFrame中。销毁 loader 拥有的层级结构前先恢复作者材质只销毁插件拥有的变体宿主缓存的角色材质保持不动。另外描边edges与昂贵的渲染管线不需要插件材质钩子——它们是覆盖SCENE_LAYER的屏幕空间宿主通道。放置幽灵placement ghosts应使用编辑器叠加层overlay layer保持清晰且不进入场景深度/法线目标。六、场景主题Scene themes一个SceneThemepackages/viewer/src/lib/scene-themes.ts把定义一个look所需的一切打包字段驱动appearance: light \| dark2D 场景 chrome——画布背景、网格线颜色、测量标签/光标对比度没有独立的浅/深色开关主题拥有这一切background3D 背景在 packages/viewer/src/components/viewer/post-processing.tsx 中与无几何处混合backgroundSky?可选的天顶颜色后处理管线渲染从该色顶部到background地平线的垂直屏幕空间渐变省略则用纯backgroundground场地地面填充nodes/site/renderer.tsx与无限地面遮挡平面viewer/ground-occluder.tsx。与background分离使深色主题得到受光照的中调地面而非近黑lights/ambient/hemi灯光装置lights.tsx一盏 key light 投射阴影toneMappingExposure渲染器曝光clayTints?每SurfaceRole的颜色覆盖叠加在colorPreset之上编辑器 UI chrome 永远是深色的固定document.body.classList.add(dark)与appearance无关。6.1 内置主题速查仓库内置 9 个主题scene-themes.tsid名称appearancebackgroundground代表性 clayTintsstudioStudiolight#fbfbfa#e9e7e2wall#e9e5db/ roof#c4bba6paperPaperlight#ede9df#e7e1d3wall#efe9da/ roof#b9b09asunsetSunsetlight#f6e8d4#ecd9bfwall#f3e3cf/ roof#a6764fovercastOvercastlight#e6e7e6#dadcd9wall#dedfdc/ roof#a3a49eblueprintBlueprintlight#dde6ef#c9d6e6wall#9fb6d2/ roof#5f789bmediterraneanMediterraneanlight#bdd6e8#ddd2bbwall#f6f1e6/roof#3e6585蓝twilightTwilightdark#3a3550#67618awall#c5b9cf/ roof#5b4f74nightNightdark#1f2433#4a5470wall#aab3c6/ roof#5b6680verdantVerdantlight#d6e4d2#c7d6b4wall#eef0e6/ roof#6f8a5a主题查找函数getSceneTheme(id)未命中时回退到SCENE_THEMES[0]即studioSCENE_THEME_IDS导出全部 id 供 UI 使用。6.2 添加一个主题把SceneTheme追加到SCENE_THEMES数组并填齐所有必填字段即可。clayTints是Partial类型——省略的角色自动回退到当前colorPreset。主题选择器工具栏 社区 overlay会基于clayTints叠加在background上渲染 2×2 色块因此至少填充wall/roof/floor/glazing才能得到像样的预览色块。七、纹理世界尺度UV 以米为单位每一个程序化表面生成的 UV 都以米为单位1 个 UV 单位 1 米。这是一条全局契约参与方包括wallpackages/viewer/src/systems/wall/wall-system.tsxExtrudeGeometryslabpackages/viewer/src/systems/slab/slab-system.tsxgeneratePositiveSlabGeometry、generatePoolGeometryceilingpackages/viewer/src/systems/ceiling/ceiling-system.tsxroofpackages/viewer/src/systems/roof/roof-system.tsxchimney / dormerpackages/nodes/src/chimney/geometry.tsGLB 物品插槽遵循同样的约 1 UV 单位/米作者约定由插槽验证器的 UV 存在性检查和 item-authoring 中的 Blender 配方强制保证。这是作者要求不是渲染期修正。因此目录材质catalog material的repeatmapProperties.repeatX/repeatY见 packages/core/src/material-library.ts就是一个按材质的全局尺度设置每米瓷砖数repeat: 1→ 1 块/米repeat: 0.4→ 每 2.5 米一块repeat: 1.5→ 1.5 块/米。repeat是材质的属性对使用它的每个表面都相同永远不是按物品或按表面的。自定义 repeat 值是有意为之的材质尺度而非逐表面的 hack。在材质库实现中MaterialCatalogItem携带preset: MaterialPresetPayload其mapProperties内含完整的color、roughness、metalness、repeatX/repeatY、rotation、wrapS/wrapT、normalScaleX/Y、emissiveColor/Intensity、displacementScale、transparent、opacity、side等参数packages/core/src/material-library.ts。查看器侧的 materials.tsapplyMaterialMapProperties会把这些参数逐一映射到 three.js 材质roughness、metalness、displacementScale、bumpScale、aoMapIntensity、lightMapIntensity、normalScale、emissive、opacity、side等并同步wrapS/wrapT/repeat/rotation/flipY贴图属性。渲染时纹理的 repeat 由resolveTextureRepeat(repeat, scale)解析优先二维数组[x, y]其次标量x/y 相同再次{x, y}对象最后回退到scale或 1materials.ts。八、实现要点速查给源码读者主题必须进缓存键createSurfaceRoleMaterial的缓存键为role-preset-side-sceneThemeId任何新接入点漏传sceneTheme都会导致切换主题后颜色不刷新。四个重建触发器shading / textures / colorPreset / sceneTheme是GeometrySystem的故意 re-run 触发器删除它们会破坏外观切换见 geometry-system.tsx 的 biome-ignore 注释。glazing 用 FrontSideMRT 渲染管线对DoubleSideNodeMaterial 的校验限制是玻璃强制FrontSide的根因需要双面显示时旋转网格 180°。无全白模式无纹理表面永远解析为主题化角色颜色这是 Mediterranean 蓝屋顶等效果的机制来源。UV 契约是作者级约定1 UV 单位 1 米程序化表面与 GLB 物品均遵守repeat 每米瓷砖数是材质属性而非表面属性。外部插件材质交换只在偏好变化时进行绝不在useFrame销毁顺序为恢复作者材质 → 只销毁插件变体 → 不动宿主缓存角色材质。相关文档wiki/architecture/item-authoring.md —— GLB 物品与 UV 作者约定wiki/architecture/node-definitions.md ——NodeDefinition、geometry与几何系统wiki/architecture/renderers.md —— 渲染器架构wiki/architecture/materials-and-themes.md —— 本文档原始出处【免费下载链接】editorOpen-source 3D architectural editor with a local CLI, MCP tools, and practical workflows for humans and AI agents.项目地址: https://gitcode.com/GitHub_Trending/editor93/editor创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考