1. 从一张2D像素图到可跑可跳的3D场景这条路到底怎么走「未白镇」这三个字玩过经典像素RPG的人应该都不陌生——那种低分辨率、有限色板、俯视视角的小镇画面承载了太多人的童年记忆。但如果你是一个游戏开发者看到这张图的第一反应可能不是怀旧而是我能不能把它变成一个真正能跑能跳的3D场景能不能让角色走在石板路上、绕过房屋、穿过树丛而不是只盯着一张静态图片这个想法听起来跨度很大但实际拆开来看它是一条非常清晰的管线2D像素图 → 图像理解与结构拆解 → Blender中搭建3D几何与材质 → 导出标准格式 → 导入Unity或Godot完成场景组装与交互。整条链路里Codex扮演的是翻译官和结构分析师的角色Blender是施工队Unity/Godot则是最终的舞台。我最近完整跑了一遍这个流程从一张未白镇的像素截图出发最终在Godot里做出了一个可以自由行走的小镇场景在Unity里也复现了同样的效果。过程中踩了不少坑也总结出了一些常规教程里不会讲的细节。这篇文章会把整条管线拆开每一步为什么这么做、参数怎么定、哪里容易翻车都讲清楚。不管你是刚接触Blender的新手还是已经在用Unity/Godot做项目的开发者都能从里面找到可以直接抄作业的部分。提示本文涉及的工具有Codex、Blender、Unity、Godot版本建议使用Blender 4.x、Unity 2022 LTS以上、Godot 4.x避免因版本差异导致API或导出选项不一致。2. 像素图不是直接拿来建模的先让Codex帮你做结构拆解2.1 为什么不能直接把像素图丢进Blender当贴图很多人第一反应是把像素图导入Blender贴在一个平面上然后挤出几个方块不就完事了这个思路的问题在于像素图是俯视正交投影它没有透视信息也没有高度信息。你看到的房屋屋顶、树木冠层、道路边缘在2D图里都是压扁的。如果直接当贴图用做出来的场景会像一张纸片立在地上完全没有立体感。正确的做法是先把像素图拆解成语义图层——地面层、建筑层、植被层、装饰层、角色层。每一层对应不同的3D处理策略。地面层做平面网格加材质建筑层做体块挤出加屋顶结构植被层用面片加透明贴图或者低模替代装饰层单独摆放。这个拆解过程Codex可以帮你完成大部分重复性工作。2.2 用Codex做图像语义分析的实操方式Codex本身是一个代码生成与理解工具但它对图像的结构化描述能力也很强。我的做法是把像素图上传后用一段结构化的提示词让它输出每个区域的边界框坐标、语义标签、层级关系。比如{ region: house_01, bbox: [120, 80, 200, 160], label: building, sub_elements: [roof, wall, door, window], height_estimate: 2.5x_tile }这里的关键是height_estimate——像素图里没有高度但你可以根据常识和像素比例推算。一个标准RPG像素 tile 通常是16x16或32x32如果一栋房子的占地是4x3个tile那它的高度大概在2.5到3个tile之间。这个估算不需要精确后面在Blender里还可以调。Codex输出的这份JSON就是你后续在Blender里批量建模的施工图纸。没有这份图纸你就是在凭感觉摆方块有了它每个物体的位置、尺寸、层级都是确定的。2.3 拆解时最容易忽略的三个细节第一个是遮挡关系。像素图里树木可能挡住了房屋的一角如果你按可见部分建模房屋就会缺一块。解决办法是在Codex分析时要求它输出推断的完整轮廓而不是可见轮廓。第二个是色板映射。像素图的颜色数量有限但3D场景需要PBR材质。你不能直接把像素颜色当基础色而是要把每个色板颜色映射到一组材质参数基础色、粗糙度、金属度。比如深绿色对应植被-粗糙-非金属灰褐色对应石材-中等粗糙-非金属。第三个是tile对齐。像素图的地面通常是tile拼接的但3D网格如果按像素边界切会出现接缝。正确做法是按tile尺寸的整数倍切分网格并在Blender里开启吸附对齐。3. Blender里的体块搭建从平面坐标到三维空间3.1 建立参考平面与坐标映射进入Blender后的第一件事不是急着建模而是把像素图作为参考背景导入。具体操作在正交视图下按小键盘5切换添加一个平面把像素图贴上去然后调整平面尺寸让1个像素对应1个Blender单位或者0.1单位看你的场景尺度。我习惯用1 tile 1米的映射。如果像素图是32x32 tile那参考平面就是32x32米。这样后续所有物体的尺寸都有直观的物理意义——房屋高2.5米树木高4米道路宽2米。这个尺度在导入Unity/Godot后不需要再缩放省去很多麻烦。坐标映射的关键是原点对齐。把参考平面的左下角对准Blender的世界原点这样Codex输出的bbox坐标可以直接换算成Blender里的X/Y坐标。换算公式很简单blender_x bbox_x / tile_size blender_y bbox_y / tile_size其中tile_size是像素图中一个tile的像素宽度。3.2 建筑体块的挤出与屋顶处理建筑是最容易出效果的部分。以房屋为例操作链路是根据bbox在参考平面上画一个矩形面按E挤出高度设为height_estimate × tile_size选中顶面按I内插一个较小的面再按E向上挤出形成屋顶的坡度用CtrlB对边缘做倒角避免直角过于生硬这里有个经验屋顶不要做成尖顶。像素图里的屋顶通常是梯形或人字形如果你做成尖顶从俯视角看会变成一个点和原图差异很大。正确的做法是让屋顶的顶面保持一个较小的平面而不是收成一个点。墙面的材质可以用顶点色来区分。在Blender里给每个面指定顶点色导出后在Unity/Godot里用Shader读取顶点色作为基础色。这样你不需要为每个建筑单独做贴图一个材质就能覆盖所有建筑。3.3 植被与装饰物的低模替代方案树木是像素图里最麻烦的部分因为它的轮廓复杂、有透明区域。我的方案是用交叉面片cross plane加透明贴图。具体做法是在Blender里创建两个相互垂直的平面把像素图里树木的部分抠出来作为透明贴图贴上去调整平面尺寸让树木的高度和参考图一致导出时开启Alpha Clip避免透明边缘出现黑边这个方案的好处是面数极低两个面性能开销小而且在远处看效果很好。缺点是近距离看会穿帮所以适合中远景的植被。如果树木在玩家必经之路上可以换成低模圆柱加锥形冠层。装饰物比如路灯、栅栏、花坛直接用简单的几何体组合就行。关键是位置要准尺寸可以适当夸张一点让它们在3D视角下更醒目。4. 材质与光照让像素风在3D里不违和4.1 像素色板到PBR材质的映射策略像素图的颜色是固定的、有限的但3D场景的光照是动态的。如果你直接把像素颜色当基础色在动态光照下会显得很平甚至发灰。我的做法是把像素色板映射到一组PBR参数而不是直接映射颜色。具体来说先统计像素图里出现的所有颜色按色相和明度分成几组地面色、建筑色、植被色、装饰色。然后为每组定义一个材质模板色组基础色粗糙度金属度法线强度地面-石板灰褐色0.80.00.3建筑-墙面米白色0.70.00.2建筑-屋顶红棕色0.60.00.4植被-树叶深绿色0.90.00.1装饰-金属深灰色0.40.80.5基础色可以从像素颜色稍微提亮或降饱和因为3D光照会再叠加一层。粗糙度和金属度按材质常识设定法线强度用来控制表面细节的明显程度。4.2 光照布置模拟像素图的平光感像素图通常是无光影或简单投影的但3D场景需要光照才能看出立体感。这里有个矛盾光照太强像素风就没了光照太弱场景又看不清。我的方案是主光用平行光强度调低角度接近垂直。具体参数平行光强度1.5-2.0角度X50°、Y0°、Z30°阴影开启但软阴影。再加一个环境光强度0.3-0.5颜色偏冷。这样场景整体是亮的但物体之间有轻微的明暗对比不会完全平掉。如果想让像素感更强可以在Shader里加一个色阶化posterize处理把连续的光照结果量化成几个色阶。这个在Unity的Shader Graph或Godot的VisualShader里都很容易实现。4.3 阴影与AO的取舍像素图里通常没有复杂的阴影但3D场景如果没有阴影物体就会像浮在空中。我的建议是开启实时阴影但降低分辨率。在Unity里把阴影分辨率设为512或1024在Godot里把阴影贴图尺寸调小。这样阴影边缘会有像素化的颗粒感反而和像素风更搭。AO环境光遮蔽可以烘焙一张低分辨率的贴图或者直接用屏幕空间AO强度调低。目的是让物体接触面有轻微的暗角增强落地感但不要出现明显的黑边。5. 导出与导入Blender到Unity/Godot的格式选择5.1 FBX还是glTF两种格式的实测对比Blender导出到游戏引擎主流格式是FBX和glTF。我两种都试过结论是glTF更适合这个项目。原因如下对比项FBXglTF材质保留需要手动重连自动保留PBR参数顶点色支持但易丢失原生支持透明贴图需要额外设置自动识别Alpha文件体积较大较小Unity支持原生需要插件或2022 LTS以上Godot支持需要插件原生glTF的缺点是Unity对它的支持不如FBX成熟但在2022 LTS以上版本已经可以直接导入。Godot 4.x对glTF的支持非常好几乎不需要额外设置。5.2 导出前的检查清单导出前一定要检查这几项否则导入后会出现各种奇怪问题应用所有变换在Blender里按CtrlA选择全部变换避免缩放和旋转不一致合并重复顶点进入编辑模式按M合并减少面数检查法线方向按ShiftN重新计算法线确保朝外UV展开即使是用顶点色也要有一套基础UV方便后续加贴图命名规范物体名称用英文避免特殊字符方便在引擎里查找5.3 Unity导入后的材质修复Unity导入glTF后材质可能会变成默认的Standard Shader需要手动换成URP或HDRP的Lit Shader。如果用的是顶点色需要创建一个读取顶点色的Shader。最简单的做法是用Shader Graph新建一个Shader Graph选择URP Lit添加Vertex Color节点连接到Base Color把粗糙度和金属度暴露为属性方便调整保存后应用到所有建筑材质上Godot这边更简单导入glTF后材质会自动保留只需要在场景里调整光照和环境即可。6. 在Godot里让角色真正走起来6.1 场景组装与碰撞体设置导入glTF后Godot会生成一个场景树包含所有网格和材质。接下来要做的是创建一个Node3D作为场景根节点把导入的模型实例化到场景里为地面添加StaticBody3D和CollisionShape3D形状用WorldBoundaryShape3D或ConcavePolygonShape3D为建筑和树木添加StaticBody3D和简单的碰撞形状盒子或圆柱这里有个坑ConcavePolygonShape3D的性能开销很大如果地面网格很密碰撞检测会拖慢帧率。我的做法是地面用WorldBoundaryShape3D无限平面建筑用BoxShape3D树木用CylinderShape3D。这样碰撞体数量少性能好。6.2 角色控制器与相机跟随Godot 4.x自带的CharacterBody3D非常适合这个场景。基本设置extends CharacterBody3D export var speed 5.0 export var jump_velocity 4.5 func _physics_process(delta): var input_dir Input.get_vector(move_left, move_right, move_forward, move_back) var direction (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() if direction: velocity.x direction.x * speed velocity.z direction.z * speed else: velocity.x move_toward(velocity.x, 0, speed) velocity.z move_toward(velocity.z, 0, speed) if not is_on_floor(): velocity.y - 9.8 * delta elif Input.is_action_just_pressed(jump): velocity.y jump_velocity move_and_slide()相机跟随用SpringArm3D加Camera3D把SpringArm的长度设为8-10米角度设为-30°俯视。这样相机在角色后方能看到前方场景同时保持像素风的俯视感。6.3 像素风后处理色阶化与分辨率控制Godot的WorldEnvironment节点可以添加后处理效果。要做出像素风关键是两步第一步降低渲染分辨率。在项目设置里把display/window/size/viewport_width和viewport_height设为640x360或480x270然后开启stretch mode viewport。这样整个画面会被放大到窗口尺寸产生像素化的边缘。第二步色阶化。在WorldEnvironment的adjustments里开启posterize把色阶数设为8或16。这样光照结果会被量化成有限的色阶更接近像素图的观感。这两步做完整个场景的像素味就出来了而且角色移动时的画面抖动也会被像素化掩盖反而更有复古感。7. Unity这边的差异化处理与性能调优7.1 URP管线下的像素风Shader配置Unity这边我用的是URP管线因为它的性能开销比HDRP小适合这种低多边形场景。像素风Shader的配置思路和Godot类似但实现方式不同在URP Asset里把Render Scale设为0.5或更低降低渲染分辨率创建一个Full Screen Pass Renderer Feature添加色阶化后处理或者直接用Volume组件里的Color Adjustments和Posterize效果如果想让像素感更强可以在Shader里加一个像素化UV的处理把屏幕UV按像素块大小取整再采样颜色。这个在Shader Graph里用Floor节点就能实现。7.2 光照贴图烘焙与实时阴影的平衡Unity的实时阴影在低分辨率下会有明显的锯齿但烘焙光照贴图又会让场景失去动态感。我的折中方案是静态物体建筑、地面、树木用烘焙光照贴图分辨率设为每单位2-4 texel动态物体角色用实时阴影但只接收不投射主光用混合模式静态部分烘焙动态部分实时这样既保证了静态场景的像素风阴影质量又让角色有动态阴影整体性能也不错。7.3 从Blender到Unity的坐标系差异处理Blender是Z轴向上Unity是Y轴向上。导出glTF时Blender会自动转换坐标系但有时候会出现模型旋转90°的问题。解决办法是在导出设置里勾选Y Up或者在Unity导入后手动调整旋转。另外Blender的单位是米Unity的单位也是米但Unity的默认缩放是1Blender的默认缩放也是1所以尺寸通常不需要调整。如果发现模型过大或过小检查Blender里的Scene Properties里的Unit Scale是否被改过。8. 几个只有实际跑过一遍才会知道的坑8.1 透明贴图的排序问题树木和装饰物用了透明贴图后在Unity和Godot里都会遇到透明排序问题远处的树叶挡住了近处的树叶或者透明边缘出现黑边。解决办法是在材质里开启Alpha Clip而不是Alpha Blend这样透明部分会被直接裁掉不参与排序把透明物体的Render Queue设为Alpha Test确保在 opaque 之后渲染如果还有黑边检查贴图的Alpha通道是否预乘了颜色8.2 顶点色在导出时的丢失Blender的顶点色在导出glTF时如果材质里没有使用顶点色节点可能会被丢弃。解决办法是在Blender的材质里显式添加Color Attribute节点连接到Base Color。这样导出时顶点色会被写入glTF的COLOR_0属性Unity和Godot都能正确读取。8.3 像素图比例与3D视角的视觉偏差像素图是正交投影3D场景是透视投影。当你把相机放在俯视角时远处的物体会显得比像素图里小。这个偏差在场景边缘特别明显。我的调整方法是把相机设为正交投影或者用很小的FOV比如20°加远距离相机。这样透视变形最小和像素图的观感最接近。如果坚持用透视投影那就把场景尺寸缩小让相机离物体更近这样透视感会弱一些。8.4 性能优化面数与Draw Call的控制这个场景的面数其实很低一个建筑大概几十到几百个面一棵树两个面。但Draw Call可能会很多因为每个物体都是独立的Mesh。优化方法把相同材质的建筑合并成一个Mesh用CtrlJ在Blender里合并树木用GPU Instancing在Unity里开启Enable GPU Instancing在Godot里用MultiMeshInstance3D地面用一个大平面加tile贴图而不是每个tile一个物体这样Draw Call可以从几百降到几十帧率稳定在60fps以上。9. 这条管线还能怎么扩展跑通一遍之后我发现这条管线其实可以复用到很多场景。比如你有一张老游戏的截图想做成3D重制版或者你有一张手绘的场景概念图想快速搭建一个可交互的3D原型。核心思路都是一样的图像理解 → 结构拆解 → 体块搭建 → 材质映射 → 引擎组装。Codex在里面的角色可以进一步扩展让它直接生成Blender的Python脚本批量创建物体、指定材质、设置父子关系。这样你只需要把JSON图纸喂给它就能一键生成整个场景的体块。我试过用Codex生成一段Blender脚本把20多个建筑的创建过程自动化原本需要手动操作半小时的工作脚本跑完只要几秒钟。Blender这边也可以用Geometry Nodes做程序化生成比如根据像素图的色块分布自动生成地形高度。Unity和Godot那边则可以接入更复杂的角色控制器、NPC行为、交互系统把静态场景变成一个真正可玩的游戏原型。最后分享一个我在实际操作中的体会不要追求一次做到完美。第一遍跑通管线哪怕模型很粗糙、材质很简单只要能在引擎里走起来你就已经跨过了最大的门槛。后面的优化和细化都是在有了可运行版本之后才做的事。我第一版未白镇场景只用了不到两个小时搭建虽然简陋但角色能在里面跑动的那一刻成就感比任何精细模型都强。