公司动态

3D模型材质切换一学就会:从整体换肤到局部换装再到动态变色的完整攻略

📅 2026/8/15 18:08:18
3D模型材质切换一学就会:从整体换肤到局部换装再到动态变色的完整攻略
3D模型材质切换一学就会从整体换肤到局部换装再到动态变色的完整攻略【免费下载链接】godotGodot Engine – Multi-platform 2D and 3D game engine项目地址: https://gitcode.com/GitHub_Trending/go/godotGodot Engine 里 3D 模型的材质切换是角色换装、状态高亮、昼夜环境变化等玩法绕不开的一环。本文不讲晦涩理论直接用可运行的 GDScript 示例带你把整体换肤、局部换装、动态变色三件事一次学会每一段代码都附上为什么这样写和跑起来是什么效果新手也能照着抄。先搞懂一件事材质到底穿在模型的哪一层很多人第一次做换装时习惯在模型资源Mesh里直接改材质结果发现改完之后场景里所有用到这个模型的实例全都跟着变了还找不到原因。这是因为你改的是共享的底衣而不是这一件模型身上的外套。在 Godot 里一个 3D 模型通常由多个表面Surface组成比如角色的头、躯干、四肢可能就是不同的表面。材质的穿着关系分三层优先级从高到低是全局覆盖挂在MeshInstance3D上的material_override一穿就盖住整个模型的全部表面。表面覆盖通过set_surface_override_material设置的逐表面材质只影响指定的那一块。模型自带材质保存在 Mesh 资源内部、导入时生成的原始材质优先级最低。判断实际生效材质的这段逻辑就写在引擎源码 scene/3d/mesh_instance_3d.cpp 的get_active_material函数里先看全局覆盖再看表面覆盖最后才落到模型自带的材质上。 记住这个顺序后面所有代码就都好理解了——你做的所有操作其实都是在往覆盖层上放东西而不是动原始资源。最省事的一招material_override 一键给整个模型换皮如果你的需求是整个模型一起换比如普通状态和受击状态的切换那用全局覆盖就够了代码短到没有压力。它就像给模型套一件大披风什么表面细节都不用管。extends MeshInstance3D # 在编辑器里把两套材质拖进这两个槽位 export var style_a: Material export var style_b: Material var using_b : false func _ready(): # 开场先披上 A 这套 material_override style_a func _unhandled_input(event): # 按空格键在 A / B 两套皮肤之间来回切换 if event.is_action_pressed(ui_accept): using_b not using_b material_override style_b if using_b else style_a为什么这样写material_override是GeometryInstance3D提供的覆盖属性赋值后只影响当前这一个渲染实例工程里被导入的原始材质纹丝不动其他场景里的同类模型也不会被波及。运行效果场景跑起来后按一下空格模型整体立刻换一种外观再按一下又换回来。整个过程没有任何卡顿因为材质切换只是换了一个资源引用并不重新编译什么。这也是角色换装系统里最常用、最安全的基础操作。精确到块的换装用 set_surface_override_material 单独改某个表面全局覆盖好用但遇到只想给头发换个颜色、衣服保持原样这种需求就抓瞎了。这时候要动用表面覆盖能力它允许你按表面序号逐个指定材质就像给同一件外套的袖口、领口分别换不同的布料。extends MeshInstance3D # 数组下标和表面序号一一对应顺序别搞错 export var surface_style_list: Array[Material] [] func _ready(): if mesh null: return var surface_total : mesh.get_surface_count() print(当前模型共 %d 个表面 % surface_total) # 数量对不上就直接提醒避免越界翻车 if surface_style_list.size() ! surface_total: push_warning(材质数量(%d)和表面数量(%d)不一致 % [surface_style_list.size(), surface_total]) return # 循环把每个表面都套上指定材质 for i in surface_total: set_surface_override_material(i, surface_style_list[i]) # 暴露一个对外接口运行时单独换某一号表面 func swap_single_surface(surface_index: int, new_material: Material) - void: if mesh null or surface_index 0 or surface_index mesh.get_surface_count(): push_error(表面序号越界: %d % surface_index) return set_surface_override_material(surface_index, new_material)为什么这样写set_surface_override_material会先把材质写进内部的覆盖数组再同步给渲染服务器RS::instance_set_surface_override_material所以运行时调用是即时的。另外这些覆盖在检查器里会以surface_material_override/0、surface_material_override/1这样的属性出现你不写代码、直接在面板里拖拽也能手动调适合做原型验证。运行效果_ready时所有表面被一次性套上新材质之后调用swap_single_surface(0, some_material)只有 0 号表面发生变化其余表面保持原样。对角色来说这就意味着身体、衣服、头发可以各换各的互不干扰。不换材质也能玩出花运行时直接改属性实现动态变色有时候你并不想换材质而是想让同一块材质活起来——比如受伤时皮肤闪红、技能冷却时武器发光。这时可以只修改材质的颜色、金属度、粗糙度等参数。⚠️ 关键一步是先duplicate()一份副本再改否则你改的是工程里的原始资源等于把所有用它的模型都改了。extends MeshInstance3D export var base_style: StandardMaterial3D var live_style: StandardMaterial3D func _ready(): # 复制一份独立副本改色时绝不碰原始材质 live_style base_style.duplicate() as StandardMaterial3D material_override live_style # 启动无限循环的变色动画 run_color_cycle() func run_color_cycle() - void: var t : 0.0 while true: # 用正弦余弦让颜色在红蓝之间来回游走 var red : absf(sin(t)) var blue : absf(cos(t)) live_style.albedo_color Color(red, 0.2, blue) t get_process_delta_time() await get_tree().process_frame为什么这样写协程await get_tree().process_frame让颜色值逐帧平滑累加视觉上就是连续的渐变而不是跳变albedo_color是StandardMaterial3D的核心颜色属性改它最直观。运行效果模型颜色在红色与蓝色之间周期性地柔滑过渡像呼吸灯一样。把这里的正弦余弦换成噪声、时间戳或者游戏事件就能做出技能充能、中毒闪烁、昼夜变色等各种动态效果。实战串联把三招拼成一个角色换装系统现在把前面学的合起来搭一个身体、头发、衣服三部位独立换装的小系统。核心思路很简单每个部位一个MeshInstance3D节点各自维护一份候选材质列表按键时在列表里轮换。extends Node3D # 三个部位节点按角色实际结构挂好 onready var body $MeshInstance3D/Body onready var hair $MeshInstance3D/Hair onready var outfit $MeshInstance3D/Outfit # 每个部位的候选外观 var body_styles : [preload(res://materials/body_default.tres), preload(res://materials/body_tanned.tres)] var hair_styles : [preload(res://materials/hair_brown.tres), preload(res://materials/hair_blonde.tres)] var outfit_styles : [preload(res://materials/outfit_casual.tres), preload(res://materials/outfit_armor.tres)] # 记录每个部位当前选到第几套 var picked : { body: 0, hair: 0, outfit: 0 } func _unhandled_input(event): # 空格 / Enter / Tab 分别切换三个部位 if event.is_action_pressed(ui_accept): cycle_part(body, body_styles, body) elif event.is_action_pressed(ui_select): cycle_part(hair, hair_styles, hair) elif event.is_action_pressed(ui_focus_next): cycle_part(outfit, outfit_styles, outfit) # 统一的切到下一套逻辑避免三份重复代码 func cycle_part(part: MeshInstance3D, styles: Array, key: String) - void: picked[key] (picked[key] 1) % styles.size() part.material_override styles[picked[key]]运行效果按空格换身体肤色、按 Enter 换发型发色、按 Tab 换衣服款式三个部位互不牵连。把候选材质换成动画、贴图或 ShaderMaterial这套骨架能直接支撑更复杂的捏脸、时装商城等玩法。顺带提醒几个实战中的性能要点能复用的材质就复用别每帧new多表面切换尽量在同一帧内一次改完减少渲染状态抖动远处角色可以降低材质切换频率或用 LOD 简化。透明材质切换时注意绘制顺序防止出现多余的半透明层叠。总结三招在手换装自由回顾一下这次的核心收获先用material_override做整体换肤最省事再用set_surface_override_material做逐表面控制最精细最后配合duplicate()加属性修改让材质动起来。三者对应三层覆盖优先级理解了引擎源码 scene/3d/mesh_instance_3d.cpp 里的查找顺序你就能随心所欲地组合使用。想继续深入可以按这三个方向进阶ShaderMaterial 自定义着色器用 shader 写出金属溶解、描边、流光等材质切换玩法。材质实例化用MaterialInstance3D做参数批量控制一套材质驱动成百上千个对象。PBR 材质体系吃透物理渲染参数让换装后的角色在不同光照下都经得起细看。引擎自带的材质文档也在项目里随时可以翻doc/classes/Material.xml。如果你觉得这篇对你有用不妨点个赞、收个藏关注后不迷路。下一篇我会聊聊程序化生成材质纹理用代码直接造出可复用的贴图素材敬请期待【免费下载链接】godotGodot Engine – Multi-platform 2D and 3D game engine项目地址: https://gitcode.com/GitHub_Trending/go/godot创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考