标签为“Metal”的页面如下
2026-04-27
KisekiEngine 点光源阴影排查:从单张透视 Shadow Map 到 6 面 Shadow Map Array
开源项目地址:https://github.com/sznswjr/kiseki_engine
相关提交:6b97d64 Refactor point light shadow mapping
背景 KisekiEngine 的 garden scene 里有房子、树、围栏、长椅、邮箱和地面。前一轮已经实现了基础 Blinn-Phong 光照、纹理、OBJ 加载和一张 2D shadow map。最初看起来阴影已经出现了,但继续观察就会发现两个问题:
围栏、立柱这类细长物体在地面上的阴影缺失或不完整。 改成点光源 cubemap 思路后,地面阴影又出现过位置错乱。 这两个现象很容易被误判成 bias、PCF 或 shadow map 分辨率问题。实际根因更底层:光源类型、shadow map 投影模型和 cubemap face 坐标约定没有匹配好。
这篇文章记录这次排查和重构过程。
问题一:围栏阴影为什么会缺失 最早的实现方式是:把 shadow camera 放在点光源位置,然后使用一张 2D 透视 shadow map。
这听起来合理,因为点光源确实有一个位置。但透视 shadow map 表达的是一个视锥,它天然适合 Spot Light,不适合完整的 Point Light。
点光源向所有方向发光。单张 2D shadow map 最多只能覆盖一个方向的锥形区域。为了让它“尽量覆盖当前场景所有物体”,只能不断扩大 FOV。
在这个场景里,点光源位于:
scene.light.position = {3.0f, 8.0f, 4.0f}; 围栏位于地面南侧:
addOBJObject(scene, fenceMesh, modelTexture, (simd_float3){i * 2.
继续阅读
2026-04-18
在 Metal 引擎中实现透视阴影贴图
前言 有光的地方就有影子。但如果你写过渲染引擎,就会知道——光照模型(比如 Blinn-Phong)默认是不会产生阴影的。它只告诉你"这个点被光照了多亮",但不会告诉你"这个点有没有被别的东西挡住"。
KisekiEngine 之前已经实现了 Blinn-Phong 点光源光照,场景里有立方体、有地面、有光照明暗,但就是没有阴影。一个物体浮在地面上,下面却没有影子,看起来就像悬浮在空中一样不真实。
要加阴影,最经典的方案就是 Shadow Mapping(阴影贴图)。它的核心思想只需要一句话:
站在光源的位置看一眼,记下每个方向上最近的深度;然后在正常渲染时,把每个片元投影回光源视角,比较一下深度——如果片元比记录的深度更远,说明它被什么东西挡住了,那它就在阴影里。
接下来,我们一步一步把这个想法变成 Metal 代码。
整体架构:双 Pass 渲染 整个实现的核心是在同一个 Command Buffer 里做两个 Render Pass:
┌─────────────────────────────────────────────────┐ │ Command Buffer │ │ │ │ ┌─────────────────────┐ ┌──────────────────┐ │ │ │ Shadow Pass │ │ Main Pass │ │ │ │ │ │ │ │ │ │ 光源视角渲染深度 │──▶│ 正常渲染 + │ │ │ │ → Shadow Map │ │ 采样 Shadow Map │ │ │ │ (1024×1024) │ │ → 阴影判定 │ │ │ └─────────────────────┘ └──────────────────┘ │ └─────────────────────────────────────────────────┘ Pass 1(Shadow Pass):把光源当成一台摄像机,用透视投影渲染整个场景,但只写深度,不输出颜色。结果存到一张 1024×1024 的深度纹理里,这就是 shadow map。 Pass 2(Main Pass):用正常的摄像机渲染场景。在片段着色器里,把每个片元的世界坐标投影到光源空间,去 shadow map 里查一下深度,判断它是不是在阴影里。 下面按实现顺序逐步讲解。
继续阅读
2026-04-08
KisekiEngine 渲染 Bug 排查实录:从“立方体全亮、地面纹理消失”到 7/7 自动化测试全通过
开源项目地址:https://github.com/sznswjr/kiseki_engine
KisekiEngine 渲染 Bug 排查实录:从“立方体全亮、地面纹理消失”到 7/7 自动化测试全通过 背景 KisekiEngine 是一个基于 Metal 的 3D 图形引擎,实现了 Blinn-Phong 点光源光照、纹理贴图、OBJ 模型加载和多物体场景渲染。在完成所有功能后,用户报告了两个渲染问题:
立方体全亮:没有明暗面,整个立方体看起来亮度均匀 地面纹理消失:设置了 ground.png 纹理但地面显示为纯色 第一轮尝试:盲修(失败) 假设 1:uniform 布局不对齐 怀疑 C++ 端 simd_float3(16 字节)和 Metal shader 端 float3(12 字节)的对齐差异导致数据错位。
操作:将所有 simd_float3 + padding 改为 simd_float4 打包。
结果:编译通过但问题未解决。事后验证发现结构体偏移量实际上是一致的(offset 80 处 hasTexture),这个修改虽然没直接修到 bug,但让布局更安全了。
假设 2:无纹理物体残留纹理 怀疑光源球体(无纹理)渲染后残留了之前的纹理绑定。
操作:对无纹理物体显式调用 [encoder setFragmentTexture:nil atIndex:0]。
结果:问题仍未解决。
教训 盲修效率极低。每次修改都是猜测,无法验证是否真正触及根因。
转折点:先做验证工具,而不是继续猜 真正改变排查策略的一句要求是:
先不要解决问题,先设计一套测试工具验证修复到底正不正确。
这一步很关键。对于图形渲染问题,仅凭肉眼看窗口往往不够,必须建立一套可以重复执行的观测和验证机制。
第二轮:构建诊断系统 1)Debug Mode Shader 利用 Metal function constants 实现了多种 shader 可视化模式,用来分别观察不同信号:
继续阅读