下载 App
10/162 浏览案例分享
【案例分享】星火材质系统:只用三个 Technique,PBR 参数怎么定档,贴图类 Technique 为什么接不上纯色
投稿方向:星火编辑器论坛「案例分享」专区 / 生态贡献度
类型:图文案例
素材来源:《别停下》开发实录 —— engine-docs/recipes/materials.md 与 api/graphics.md,对照 examples/22-third-person-shooter/network/Shared.lua 里的 CreatePBRMaterial
系列:第 23 篇
一句话结论
程序化纯色材质只有三个 Technique 可用,多猜一个都是白搭;猜错的代价是零报错——材质照样挂上去,模型照样画出来,只是变成一整片白或者一片黑,Metallic、Roughness 一个都不生效。
《别停下》的关卡里,所有不需要贴图的东西(路障、管道、栏杆、台阶、玻璃、灯管)都压在这套纯色材质上。下面是我稳定下来的一版。
一、只用这三个,别猜第四个
效果:Technique 路径
不透明 PBR:Techniques/PBR/PBRNoTexture.xml
透明 PBR:Techniques/PBR/PBRNoTextureAlpha.xml
无光照:Techniques/NoTextureUnlit.xml
三个的输入都是标量参数:MatDiffColor 一个颜色,Metallic、Roughness 两个 0~1 的数。没有纹理槽位,不依赖 UV。
为什么别的 Technique 接不上
Techniques/PBR/PBRMetallicRoughDiffNormalSpec.xml、Techniques/PBR/PBRDiffNormal.xml 这两个名字看着更"专业",但它们的 shader 是从纹理槽位里取值的——TU_DIFFUSE 拿底色,TU_NORMAL 拿法线,TU_SPECULAR 拿高光。它们要的是贴图,不是 MatDiffColor。
把纯色参数设上去会怎样?什么也不会发生。shader 里根本没有叫 Metallic 的 uniform,你写进去的值就悬在那儿,没人读。画面照常渲染,用的是没绑贴图时采样出来的默认值——通常是一整片白。
这跟"参数写错了"不是一回事。参数名没错,只是这套参数不属于那个 technique。
SetTechnique 第一个参数是槽位索引。纯色材质只有一个槽位,写 0 就行,不要 SetNumTechniques(3) 去凑多槽位——多出来的槽位是给按距离切换 LOD 材质用的,纯色场景用不上。
二、PBR 参数对照表
Metallic 只有两档语义:0 是绝缘体(塑料、木头、石头、皮革),1 是导体(铁、铝、金、铬)。中间值只在做旧、生锈、脏污这种过渡带上用。
Roughness 是连续的,它决定高光是"一个点"还是"一片糊"。
材质感:Metallic Roughness 我在《别停下》里挂在哪
光滑金属(铬、镜面钢):0.9–1.0 0.1–0.2 栏杆、管道
磨砂金属(拉丝铝、喷砂铁):0.9–1.0 0.4–0.6 货架、通风口、路障支架
塑料:0.0 0.3–0.5 路障本体、道具外壳
橡胶:0.0 0.7–0.9 轮胎、防撞条
陶瓷:0.0 0.1–0.3 台阶、洗手台
石头 / 混凝土:0.0 0.75–0.95 地基、挡墙
木头(原木):0.0 0.6–0.8 地板、木箱
皮革:0.0 0.55–0.75 座椅、装备包
上釉陶瓷 / 车漆:0.0 0.05–0.15 杯子、车身
前五行是官方文档给的档位,后面几行我按同一个语义推的。推法一句话:表面越平、越能照出清晰倒影,Roughness 越低;越哑、越散,Roughness 越高。
调一个新材质,我的顺序是固定的:先把 Metallic 拍成 0 或 1 的二选一,再把 Roughness 从 0.1 开始按 0.2 的步长往上扫,停在"高光刚好散开但还看得见形状"的那一档。反着来会一直在两个变量之间来回猜。
金属度有个反直觉的地方:Metallic = 1.0 的物体,颜色来自反射,不来自它本身。场景里没有环境反射(IBL / Skybox),全金属件就是一片死黑。
非金属还多一个可调项 MatSpecColor,第四个分量是镜面强度。塑料、陶瓷这类绝缘体的镜面反射只有 4% 左右,所以我一般写 Color(0.5, 0.5, 0.5, 1.0) 到 Color(0.2, 0.2, 0.2, 1.0),很少动它。金属件不用设,金属的反射由 Metallic 接管。
三、能直接抄的一版代码
-- ============================================================
-- 程序化纯色材质工厂
-- ============================================================
local TECH_PBR = "Techniques/PBR/PBRNoTexture.xml"
local TECH_PBR_ALPHA = "Techniques/PBR/PBRNoTextureAlpha.xml"
local TECH_UNLIT = "Techniques/NoTextureUnlit.xml"
---@param color Color 基础色
---@param metallic number 0=绝缘体 1=金属
---@param roughness number 0=光滑 1=粗糙
---@param techniquePath string|nil 默认走不透明 PBR
---@return Material|nil
local function MakeSolidMaterial(color, metallic, roughness, techniquePath)
local path = techniquePath or TECH_PBR
local tech = cache:GetResource("Technique", path)
if tech == nil then
print("Technique 没找到: " .. path) -- 猜路径的唯一止损点
return nil
end
local mat = Material:new()
mat:SetTechnique(0, tech)
mat:SetShaderParameter("MatDiffColor", Variant(color))
mat:SetShaderParameter("Metallic", Variant(metallic))
mat:SetShaderParameter("Roughness", Variant(roughness))
return mat
end
挂到模型上:
-- 一个不透明的路障
local node = scene_:CreateChild("Barrier")
node.position = Vector3(3.0, 0.5, 0.0)
local obj = node:CreateComponent("StaticModel")
obj.model = BoxGeometry(1, 1, 1):ToModel()
obj.material = MakeSolidMaterial(Color(0.85, 0.35, 0.16, 1.0), 0.0, 0.45)
obj.castShadows = true
一份材质挂 N 个节点,顶点和材质都只占一份:
local shared = MakeSolidMaterial(Color(0.90, 0.90, 0.92, 1.0), 0.0, 0.40)
for i = 1, 40 do
local n = scene_:CreateChild("Barrier" .. i)
n.position = Vector3(i * 1.5, 0.5, 0.0)
n:CreateComponent("StaticModel").material = shared
end
第 41 个想单独改成红色,克隆一份再改:
local red = shared:Clone()
red:SetShaderParameter("MatDiffColor", Variant(Color(0.95, 0.15, 0.12, 1.0)))
barrier41:GetComponent("StaticModel").material = red
玻璃,走透明 PBR:
local glass = MakeSolidMaterial(Color(0.60, 0.75, 0.85, 0.30), 0.0, 0.05, TECH_PBR_ALPHA)
-- A 是覆盖度:0.10 像一层薄雾,0.30 玻璃,0.80 接近磨砂厚板
灯管,走无光照 + 自发光:
local lamp = MakeSolidMaterial(Color(1.0, 0.9, 0.7, 1.0), 0.0, 0.4, TECH_UNLIT)
lamp:SetShaderParameter("MatEmissiveColor", Variant(Color(6.0, 3.0, 1.2)))
-- 自发光颜色可以推过 1.0,配合 Bloom 才真的"亮"
NoTextureUnlit 不吃光照,也不吃阴影。做霓虹灯箱、发光条、UI 面板这类"自己会亮"的东西正合适;拿它做受光物体,会得到一个贴在所有阴影上的扁平色块。无光照分支读的是 MatDiffColor,不是 MatEmissiveColor——这两个别混,混了就是"设了没反应"。
四、踩过的坑
坑 1:Technique 路径靠猜。
-- ❌ 猜了个名字更长的,GetResource 返回 nil,SetTechnique(0, nil) 不报错
mat:SetTechnique(0, cache:GetResource("Technique", "Techniques/PBR/PBRMetallicRoughDiffNormalSpec.xml"))
mat:SetShaderParameter("MatDiffColor", Variant(Color(0.8, 0.3, 0.2, 1.0)))
mat:SetShaderParameter("Metallic", Variant(0.0))
mat:SetShaderParameter("Roughness", Variant(0.5))
-- 渲染出来是一整片白,三个参数一个都没生效
-- ✅ 纯色就走纯色的 Technique
mat:SetTechnique(0, cache:GetResource("Technique", "Techniques/PBR/PBRNoTexture.xml"))
坑 2:给没有法线的几何体挂 PBR。
-- ❌ 线框没有法线,光照分支算出来是黑的,屏幕上什么都看不见
mat:SetTechnique(0, cache:GetResource("Technique", "Techniques/PBR/PBRNoTexture.xml"))
EdgesGeometry(box, 30):ToModel()
-- ✅ 无光照
mat:SetTechnique(0, cache:GetResource("Technique", "Techniques/NoTextureUnlit.xml"))
坑 3:共享材质被就地改掉。
-- ❌ 40 个路障共用一份,第 41 个改成红色,40 个一起变红
shared:SetShaderParameter("MatDiffColor", Variant(Color(0.95, 0.15, 0.12, 1.0)))
-- ✅ 要单独改就先 Clone
local red = shared:Clone()
red:SetShaderParameter("MatDiffColor", Variant(Color(0.95, 0.15, 0.12, 1.0)))
坑 4:金属度给满,然后怪场景黑。
-- ❌ Metallic=1.0 + Roughness=0.15,环境反射还是旧数据 → 一片死黑
mat:SetShaderParameter("Metallic", Variant(1.0))
mat:SetShaderParameter("Roughness", Variant(0.15))
-- ✅ 金属靠环境反射出效果,先把环境光跟天空对齐
local zone = lg:GetComponent("Zone", true)
zone.ambientSource = AMBIENT_SKYBOX
zone.ambientIntensity = 1.0 -- 默认 0.3,走烘焙模式时必须推到 1.0
坑 5:透明件用了不透明 Technique。
-- ❌ PBRNoTexture 不参与 alpha 混合,A 写 0.30 还是实心
mat:SetTechnique(0, cache:GetResource("Technique", "Techniques/PBR/PBRNoTexture.xml"))
mat:SetShaderParameter("MatDiffColor", Variant(Color(0.6, 0.75, 0.85, 0.30)))
-- ✅ 透明换 Alpha 版
mat:SetTechnique(0, cache:GetResource("Technique", "Techniques/PBR/PBRNoTextureAlpha.xml"))
坑 6:把自发光当灯泡用。
-- ❌ 0~1 的自发光只是"不吃阴影的亮色",看不出在发光
mat:SetShaderParameter("MatEmissiveColor", Variant(Color(1.0, 0.6, 0.3)))
-- ✅ 想要发光就把颜色推过 1.0,并且场景得开 Bloom
mat:SetShaderParameter("MatEmissiveColor", Variant(Color(6.0, 3.0, 1.2)))
六个坑里,坑 1 和坑 5 吃掉了我最多时间。共同点:画面里有东西,控制台里没字。
五、适用边界
能做:纯色实体、半透明件、自发光件;一份材质挂任意多个节点;运行时改 MatDiffColor / Metallic / Roughness;Clone() 出变体做局部覆盖;NoTextureUnlit 配 1.0 以上的颜色做霓虹和灯丝。
做不了:
- 任何贴图需求。木纹、砖缝、法线、遮罩、粗糙度贴图,都得走带纹理槽位的 Technique,或者干脆写 SurfaceShader(assets/Shaders/BLGL/*.shader + mat:SetSurfaceShader(...))。
- 顶点色。程序化几何体这套 API 不产顶点色,纯色材质也不读顶点色。
- 卡通、描边、溶解、流光、UV 动画。这些属于 SurfaceShader 的活,别在 Technique 列表里找。
- 每个节点一份独立材质。Material 是共享资源,40 个节点共用一份是常态,不是 bug。要独立就显式 Clone()。
- 参数名跨 technique 通用。Metallic / Roughness 只在不透明和透明这两个 PBR technique 上有对应 uniform;挂到 NoTextureUnlit 上,那两行是空转。
六、可迁移的经验
材质"配了没反应"的时候,我按顺序问三句话,比翻 shader 快得多。
这个 technique 的输入是什么? 标量参数,还是纹理槽位。输入对不上,你设什么都是空转。
我设的参数名在这个 shader 里存在吗? 不存在就静默丢弃,不报错。这类最难查,因为代码看起来完全正确,你会在参数值上反复改,而问题在参数名。
第三句是关于几何体的:它带不带光照需要的属性? 没有法线的线框、没有切线的自定义网格,挂 PBR 就是黑的。
还有一条跟材质无关但同样管用的:遇到路径靠猜的 API,第一件事是判 nil。 cache:GetResource 找不到资源返回 nil,SetTechnique(0, nil) 不报错。我在工厂函数里留的那行 print 救过我三次。
(完)
星火编辑器-新手教程-关于多游戏模式的配置和触发的获取截图
星火编辑器-新手教程-关于多游戏模式的配置和触发的获取
HELLO 大家好,我是阿木木牧。最近第一版的内部小范围(几人)第一个图的测试已经结束了。 整体游戏以装备数值的崩坏而告终~ (虽然大家都很欢快。。。) 现在准备开始开发下个阶段的内容啦,就是将游戏模式拆分出来。分成单人模式,多人组队模式(合作PVE),多人匹配模式(对抗模式)。那么,怎么配置呢?说来也很简单哈~ 1,在游戏编辑器中,数据编辑器,找到地图设置。 2,编辑地图设置。比如我希望最大数量
14 赞
3 回复
00:19
如何实现单位的随意拖拽摆放?【星火编辑器教程】截图
如何实现单位的随意拖拽摆放?【星火编辑器教程】
很多小伙伴想做自走棋类的游戏,那么就会涉及到单位的一个最基础的操作,就是选中单位并拖拽和摆放,那如何实现单位的拖拽摆放呢,今天就来教大家如何实现这样的功能。实现效果如下视频所示。 教程开始 第一步: 打开编辑器的触发编辑器,新建一个文件夹(我就叫“拖拽功能”,方便对拖拽功能的逻辑进行归类,如果你不建文件夹也没关系,习惯而已) 第二步: 点击文件夹右边的+号,添加一个变量,我这里叫“变量_目前选中的
19 赞
12 回复
无须申请,星火编辑器现已公测开放下载截图
无须申请,星火编辑器现已公测开放下载
—————————————————— 各位关注星火编辑器的小伙伴: 感谢大家一直以来对编辑器支持! 破晓已去,曙光降临! 星火编辑器现已开启公测! · 关于下载和资格申请 现已无需申请,请去下面的官方首页直接下载! 缘起测试是星火编辑器自诞生以来第一次对外测试! 从2022年八月开始到十一月,历时三个月。 缘起之后缘聚测试,历时又七个月! 缘聚之后,破晓降临,历时又十二个月! 曾经微弱的星火现已渐
官方
331 赞
375 回复