关于 scenekit-product-stages
PropMotion 是一项 Skill,提供在 SwiftUI 应用中使用 SceneKit 动画呈现单个 3D 产品对象的生产实践,面向制作编排式主视觉对象的应用开发者,涵盖舞台、动作、灯光、阴影及渲染问题。
主要功能
- 01透明 SceneKit 舞台搭建
- 02由协调器管理场景图
- 03对象关键帧动作编排
- 04灯光与阴影技巧
- 05渲染问题排查指南
适用场景
- 01在 SwiftUI 页面加入舞台式 3D 产品对象
- 02制作对象入场、退场和抛掷动作
- 03排查 SceneKit 阴影或动画问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dembsky/ propmotion --skill scenekit-product-stages安装后在 Claude Code 中输入/scenekit-product-stages确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/baked-animation.mdcamera-choreography.mddeformable-surfaces.mdfirst-frame-and-warmup.mdfog-and-mist-sheets.mdgestures-and-haptics.mdhdr-environments.mdinstancing-and-swarms.mdmesh-surgery.mdmodeling-actors.mdmotion-from-reference.mdmulti-actor-physics.mdphysics-without-engine.mdrelief-actors.mdrendering-contract.mdsequencing-stages.mdshadows-and-lights.mdstage-recipe.md详细介绍
PropMotion
让一个 3D 对象在 SwiftUI 应用中完成表演。针对一项常见工作的生产级模式:在透明的 SceneKit 舞台上呈现精致的产品角色——包括入场、退场、抛掷、阴影、触觉反馈,以及那些会耗费数天的隐蔽陷阱。
什么时候应该使用 SceneKit
坦诚看待这个框架的定位:
- SceneKit 处于维护模式。对于 3D 需求较重的新应用(资源管线、USD/USDZ、AR、大型世界),优先考虑 RealityKit。
- SceneKit 仍是在 SwiftUI 应用中加入装饰性 3D 角色的最快途径:透明的
SCNView可与任意 SwiftUI 布局合成,几何着色器修饰符只需一段 MSL 字符串,CoreAnimation 互操作成熟,而且这里的一切都能在普通 UIKit 视图中运行,无需设置会话。 - 如果 3D 元素是一个经过编排的主角对象,可直接套用本技能中的方案。如果它是一个完整的交互式世界,请停下来优先考虑 RealityKit。
核心舞台方案
舞台由以下部分组成:透明的 SCNView、负责管理场景图的 @MainActor 协调器、位于站立视线高度的摄像机、三灯布光加一盏专用阴影灯、接触阴影,以及承影平面。角色通过预先烘焙的关键帧动画进行表演。
struct ProductStage: UIViewRepresentable {
let item: Item
// The key must change ONLY when the scene must visibly change.
private var stateKey: String { "\(item.id)" }
func makeCoordinator() -> Coordinator { Coordinator() }
func makeUIView(context: Context) -> SCNView {
let view = SCNView()
view.backgroundColor = .clear // the stage composites over SwiftUI
view.isOpaque = false
view.antialiasingMode = .multisampling4X
view.scene = context.coordinator.buildScene()
view.pointOfView = context.coordinator.cameraNode
context.coordinator.install(item)
context.coordinator.markState(stateKey)
// First-frame warm-up, two-key ignition: compile shaders off
// the critical path, park the actor offstage, and gate the first
// entrance on BOTH a minimum delay and prepare's completion.
context.coordinator.parkOffstage()
if let scene = view.scene {
view.prepare([scene]) { _ in
DispatchQueue.main.async { context.coordinator.markPipelinesWarm() }
}
}
context.coordinator.scheduleFirstEntrance()
return view
}
func updateUIView(_ view: SCNView, context: Context) {
// State-key diffing: SwiftUI re-renders must never replay entrances.
guard context.coordinator.stateKey != stateKey else { return }
context.coordinator.markState(stateKey)
context.coordinator.transition(to: item)
}
}
各部分按构建顺序排列:
- 透明视图标志(
backgroundColor = .clear、isOpaque = false),启用 4x MSAA。背景由宿主屏幕提供;场景本身不带背景框。 - 协调器管理节点层级,每种运动分别使用一个节点(位移、升降、偏航、旋转),这样独立动画就不会争用同一个变换。
- 摄像机:设置
projectionDirection = .horizontal,使角色尺寸随舞台宽度变化;抬高摄像机位置并略微向下倾斜,呈现站立观察者的视角。保持摄像机静止;移动摄像机会显得不自然。 - 灯光:暖色主光、低亮度冷色补光、硬朗轮廓光、仅作为基础亮度的环境光,再加上一盏独立的投射阴影方向光,从角色后上方照射,避免影响可见的造型。
- 地面接触效果:在角色下方放置柔和的深色渐变平面(接触阴影),再放置一个不可见的
.shadowOnly平面作为承影面,以呈现真实阴影。 - 反射材质应采样小型程序化环境贴图,而不是照片。摄像机后方的区域保持暗色。
- 所有编排都通过预先烘焙的
CAKeyframeAnimation实现,并用代次令牌加以保护,确保任何新节拍都能取代尚未执行的操作。
包含完整代码的详细说明:references/stage-recipe.md
最耗费时间的陷阱
| 问题 | 修复方法 |
|---|---|
| 开启 MSAA 时,延迟阴影始终无法渲染,且控制台没有任何错误 | 使用 shadowMode = .forward,并添加 .shadowOnly 接收器。排查缺失的阴影时,先为场景添加一个可见的灰色 Lambert 地面。 |
新建 SCNView 的第一帧会在入场动画播放期间编译 Metal 管线 | 在后台对场景调用 prepare([scene]),让角色暂时停在画外,并延迟首次入场。切勿在第一帧启动动画。 |
fillMode = .forwards + isRemovedOnCompletion = false 会固定展示状态,而且移除操作是针对单个对象的 | 统一使用一个 clearAnimations,清理节点、所有子几何体、灯光和正在运行的动作,并从每个入口调用它。 |
| 正面观看真实金属时,渲染结果像一个灰色或黑色的洞 | 正对镜面时,镜面会反射摄像机后方的环境区域。保留已核准的涂装底层,并添加一个带有独立反射贴图的薄型叠加层。 |
切换 castsShadow 会在一帧内突然出现模糊的半影;对于正向定向阴影,shadowBias 和灯光的 categoryBitMask 都会被忽略 | 始终保持 castsShadow 开启,并让 shadowColor 的 alpha 随动作同步变化。 |
| 固定的预热延迟在冷启动设备和 Simulator 上仍会卡顿;粒子效果的第一帧会编译自己的管线,并在效果触发时恰好掉帧 | 双条件点火:只有满足最短延迟且 prepare 的 completion handler 已触发,才允许入场。使用与实际效果标志匹配的零不透明度粒子爆发来预热粒子管线。 |
默认的 UIGraphicsImageRenderer 格式会继承屏幕缩放比例,使生成的每张纹理像素数膨胀 9 倍——对于实时重绘的纹理(如每次按键都重新绘制的雕刻文字)尤其致命 | 将渲染器格式的 scale 固定为 1,并根据角色在屏幕上的投影尺寸设置画布大小;将多输入重新定位合并为每个更新周期只渲染一次。 |
| 根据照片重现真实物体的运动,会得到看似可靠却无法侧向运动的绑定——每次修复都会暴露一个新的错误 | 静态图像记录的是姿势,而非路径或机构;拟合终止姿势无法确定运动轨迹。应对运动路径建模:使用带有直接姿势控制的校准绑定,由所有者参照实物制作关键帧。 |
| 关键帧动画在关键帧处有规律地顿挫,而且各种渲染和计时修复都无法解决 | 顿挫已烘焙进曲线中:在间距不均匀的关键帧上使用均匀的 Catmull-Rom 基函数,会导致每个节点处的速度发生跳变。根据距离相位,使用按区间长度加权的 Hermite 切线(或自然三次样条)进行插值,并通过数值连续性检查来判定是否通过。 |
| 从较大的模型中切出的子网格测量结果就像整个物体一样,而且没有任何错误 | 切割只裁剪了索引缓冲区;顶点缓冲区仍包含原始模型的所有顶点。只测量子网格索引引用的顶点。 |
将方形图像指定给 scene.lightingEnvironment 会被静默忽略——没有反射,所有镜面材质都渲染为黑色,控制台也没有输出 | 将环境贴图绘制为受识别的立方体贴图布局;最简单的是 2:1 的球形画布(1024x512)。只有 material.reflective 接受方形球形贴图。 |
| 仅由着色器时钟驱动动画的场景只绘制一帧后便冻结;间隔数秒截取的两张图像像素完全相同 | 按需渲染循环无法感知着色器时间:设置 rendersContinuously = true,并通过像素差异验证运动,绝不要凭肉眼判断。 |
| 着色器时间图案上的速度旋钮在跳变时会让图案瞬移;对旋钮做补间又会让流动明显加速,减速时甚至会倒流 | 相位必须是速度的积分,而不能是 speed * absoluteTime:在渲染器委托中累计时钟,将速度平滑过渡到目标值,并让时钟只向前推进。 |
| 几何修改器驱动的半透明薄片在每条折叠轮廓上都会出现明亮的细发丝;掠射角渐隐出现条带,要么保留锐利细线,要么产生笔直的暗条纹 | 修改器移动的是顶点,而非法线:根据波形的解析斜率倾斜法线,然后按厚度补偿因子 (1+k)*facing/(facing+k) 缩放 alpha——过渡平滑,切线方向为零,正对视线的雾效不受影响。 |
| 由手绘环境光照亮的铬材质会渲染成卡通金属:一片边缘生硬的纯白高光,反射被色调分离成灰色条带 | 手绘贴图的亮度上限为 1.0,没有可供压缩的动态范围。使用作为 FILE URL 传入的摄影 .hdr HDRI(通过 UIImage 重新编码会被静默钳制回 LDR),并在摄像机上启用 wantsHDR。 |
lightingEnvironment 无法控制朝向,而全景图正前方的灯会在反射正前方形成一大片光斑 | 改为旋转摄像机绑定:对于对称角色和径向地面,画面构图保持不变,只有反射布局会变化。构建屏幕空间手势时要考虑绑定(通过 pointOfView 转换坐标轴)。 |
完全启用 clearCoat 会让白色电介质材质的珍珠质变成带白色核心的铬质效果;两种表面处理最终会变成同一种外观 | clearCoat 是镜面层。珍珠质需要约 0.3-0.4 的值和约 0.2 的粗糙度——在乳白色上呈现光泽,而非银色。 |
点击着色器变形的角色时,恰好无法命中凸起处——hitTest 只能检测未变形的网格 | 为角色添加一个尺寸偏大的隐形碰撞体(colorBufferWriteMask = []),使用 .all 进行命中测试,并按节点名称筛选。 |
| 冻结时钟后,理应完全相同的快照却出现非零差异,但肉眼看不出区别 | 自适应曝光会根据场景历史,让同一时刻渲染出不同结果:设置 wantsExposureAdaptation = false,并手动固定曝光。 |
| 粒子碎屑会无限滑动或始终无法停下,而且每次调整摩擦力都会让情况变得更糟 | particleFriction 与物理直觉相反:1.0 表示自由滑动,0.0 表示固定不动。较低的值(约 0.25)才能让颗粒在最后一次跳动后停住。 |
| 机械动作在运行时生成的表面(如切割面或翻倒后朝上的底面)渲染得接近黑色,而角色其他部分看起来正常 | 几乎与视轴平行的面无法接收到主光和投射阴影的光源。为场景添加真实的环境底光,并检查机械动作可能产生的每种朝向下的光照效果,而不只是创作时的姿势。 |
| 舞台悄无声息地一片空白——没有错误、没有场景,也没有任何可供调试的内容 | 64 位哈希运算后进行缩窄初始化(Int32(...))会在运行时触发陷阱,而且在异步任务内部,崩溃无法显现。使用 truncatingIfNeeded 按目标位宽执行哈希运算,并在调试场景逻辑之前检查系统崩溃报告。 |
Reference map
- references/stage-recipe.md:完整舞台、视图设置、协调器、节点层级、摄像机、灯光组、接触阴影团和承接面、以编程方式创建的环境贴图和纹理、舞台上的撞击粒子、舞台演员上的着色器修饰符(线性空间 uniform 陷阱)、保持舞台的 SwiftUI 身份(重新挂载和更新风暴陷阱)。
- references/first-frame-and-warmup.md:首次绘制时 Metal 管线编译造成的卡顿及两种解决方法:通过延迟入场预热(升级为由 prepare 完成状态控制的双键点火),或保持场景挂载并处于预热状态,然后重新定位;粒子管线预热;通过 signpost 和设备端卡顿跟踪证明解决方法有效。
- references/shadows-and-lights.md:所有静默阴影陷阱、可靠的 forward + shadowOnly 组合、阴影可见性的动画、适度的灯光预算、正面受光的金属。
- references/baked-animation.md:烘焙关键帧为何胜过计时器、烘焙前设计多阶段节拍的提示表、接缝分类(C1 与接触冲量)、重量感设计、密集采样、清理记录、生成令牌、在动画中途接管节点、滚动而不滑动、沿地面路径滚动(转向、屏幕空间调度、调试轨迹)、变换以外的通道(变形器权重、镜头值、着色器 uniform)、将弹簧作为创作素材、每项属性仅由一个所有者控制。
- references/motion-from-reference.md:复现真实物体的运动——静态图和视频能告诉你什么、不能告诉你什么;对路径建模,而非对机制建模;校准装置(由创作者摆放演员并保存关键帧);手动保存姿势的距离参数化和加权插值;均匀 Catmull-Rom 陷阱;数值连续性门槛;可从当前阶段退出的无缝余弦状态循环;测量裁剪后的子网格。
- references/physics-without-engine.md:手工编写的固定步长积分器并烘焙为关键帧、以普通数字表示的墙壁和地板、由接触驱动的触觉反馈、绕边缘支点翻倒,以及它为何胜过用 SCNPhysics 编排场景。
- references/mesh-surgery.md:运行时拆分演员——将网格作为普通数组处理、将滑动手势投射为穿过摄像机的切割平面、把三角形裁剪成两个密封部分、让封盖变得简单的凸性原理、为单个径向图案服务的平面封盖 UV、用闭合网格的体积和质心积分决定哪个部分下落、适用于任意碎块的支撑点下落积分器、按住瞄准并在松开时提交、可承受反复切割的脚本化切割。
- references/relief-actors.md:通过 CPU 高度场创建模压浮雕物体——同时绘制高度和类别图(一个网格上呈现多种表面效果)、抛物线特征轮廓、轮廓的三项同时保证、从更模糊的场副本计算法线、正面静止观察平面镜、收窄初始化哈希陷阱、在朝离线渲染效果精修前先确定光栅上限。
- references/gestures-and-haptics.md:无需命中测试即可通过平移手势抓取、轨迹球(通过摄像机将屏幕轴提升至世界空间、将四元数叠加到创作好的运动上、基于舞台时钟的轻扫惯性、轻点与平移手势共存)、按住时的柔性限制、松手速度、撞击触觉反馈、脚本化节拍不受手指实时操作干扰(有界重试、带令牌的轮询、隐藏演员的手势防护、接管即结束按住契约)、Reduce Motion 分类法、通过 VoiceOver 访问不可见舞台、与 SwiftUI 手势共存。
- references/sequencing-stages.md:将多个舞台作为一部影片来调度——以节拍为数据的主时钟、墙上时钟漂移陷阱、待退出动作结束后再切换镜头、提前挂载下一舞台以进行预热、舞台在自身切镜后继续存在(余烟)、根据用户交互重新设定时间线基准、录制提前量、逐帧验证切镜。
- references/modeling-actors.md:用基本图元打造可信的主角物体——先确定真实世界的比例,再凭目测调整;用环带制作凹陷表面;管状结构加圆环的轮廓;径向图案的可辨识度;凹面逐实例倾斜;开口缝隙;将浮雕特征做成几何体(绝不绘制);深色舞台上的缎面金属反照率;为以文件形式载入的演员设置启用条件。
- references/camera-choreography.md:让摄像机成为表演者——环绕装置、烘焙的揭示式飞行、沿轨道移动时的焦点跟随、带惯性的拖动环绕和飞回初始位置、以 SCNFloor 反射增强落地感、在向下照射的灯光下进行哑光场景调度、跟踪装置的约束(以及为何绝不能将其用于演员)。
- references/instancing-and-swarms.md:同时呈现数十个演员——蜂群共用一个几何体、用槽位堆叠而非物理模拟、从落点反向求解抛物线、翻滚动作混合至静止姿势、带种子的随机性、通过调度错开出现时间、破坏爆发效果(这是引擎粒子胜过烘焙的唯一场景,以及相应可免于遵守确定性的例外)、预算说明。
- references/fog-and-mist-sheets.md:将连续的蒸汽流(通风气流、蒸汽、薄雾)实现为一个半透明薄片——使用几何修饰符实现波浪效果,使用片段修饰符实现雾效;域扭曲噪声与条纹;五次淡变与 Mach 条带;抖动包络;通过下游亮度表现方向;速度调节后仍能保持连续的积分相位时钟;针对折叠轮廓使用波浪倾斜法线和厚度补偿;相机相对的调节包络;通过轮廓相关性测量流动方向。
- references/multi-actor-physics.md:在一次烘焙模拟中处理多个演员——一个时钟上的 N 个状态向量、成对碰撞(先分开,再在相互接近时交换动量)、抓取时冻结整个世界、命中测试以确定手指选中了哪个演员、每个演员独有的接触列表,用于压缩效果和触觉反馈。
- references/hdr-environments.md:打造逼真的金属——卡通高光背后的 LDR 上限、通过文件 URL 加载真实的
.hdrHDRI(UIImage 截断陷阱)、使用wantsHDR并将泛光阈值设在 1.0 以上、曝光自适应与快照、因环境无法旋转而旋转摄像机装置、清漆层和深色表面效果说明、需要深色家具的浅色主题、为平面镜专门打造的环境(有限的高斯卡片,绝不使用全球环带)、为铬面上的三类线条分类、CC0 素材来源和打包。 - references/deformable-surfaces.md:让闭合曲面呼吸——在单位方向上应用 fbm 位移、区分液体与岩石的倍频预算、有限差分法线重建(没有它,镜面效果就会失效)、以 float4 uniform 槽位表示带行进波前的轻点涟漪、网格密度与波环波长、加大的不可见轻点碰撞体、可设置动画的振幅 KVC 调节项。
- references/rendering-contract.md:确定性舞台——一个可注入、以 dt 积分的时钟,负责着色器 uniforms、节点姿态和手势惯性;冻结启动钩子和回溯时间戳的事件;确定性检查清单(曝光自适应、墙上时钟、将 Reduce Motion 视为暂停);证明工具包(运动差异、逐位相同的冻结状态对、冻结时钟下的交互差异);合成 CGEvent 手势及其陷阱(过期的窗口帧、人为参与的环节)。
审核清单
发布阶段前,请确认:
- 在 SCNView 上设置
backgroundColor = .clear和isOpaque = false;舞台合成后不会出现可见的方框或接缝。 - 视图首帧不启动动画;运行
prepare,并延迟首次入场动画,或让场景保持挂载并预热。 - 阴影使用正向模式和
.shadowOnly接收器;不依赖延迟阴影、shadowBias或光源的categoryBitMask门控。 - 带 Alpha 纹理的平面(标签、贴花)设置
castsShadow = false,否则会投下方形阴影。 - 阴影可见性变化时,渐变调整
shadowColor的 Alpha;运行时绝不切换castsShadow。 - 如果场景必须忠实呈现纹理颜色,平面上的环境光加上方向光乘以入射角余弦后,亮度总和应为中性值(1000);不能出现剪切的浅灰色光照。
-
lightingEnvironment(或background)上的任何图像都必须使用可识别的立方体贴图布局——2:1 球形、6:1 或 1:6 条带,或六张图像;正方形图像会被静默忽略,反射材质也会呈现黑色。 - 每个异步续体(延迟入场、计划触觉反馈、串联过渡)都捕获一个代次令牌,并在执行前重新检查。
- 任何接管动画节点的代码,都必须先将
.presentation的值快照到模型中,再调用removeAllAnimations。 - 烘焙动画要在
addAnimation前将模型设为最终姿态;不能依赖fillMode = .forwards而不执行清理。 - 多阶段节拍的姿态函数上方必须有提示表注释,且与阶段边界结构体一致;阶段时间和提示时间只放在该结构体中,绝不作为字面量写入示例循环或计划触觉反馈中。每个接缝都必须分类:设计接缝为 C1,冲击接缝只出现在接触事件处,且每个接触事件都要触发提示。
- 滚动物体的旋转由距离除以半径驱动;不能发生滑动。
- 从真实物体复制的运动必须通过校准装置(直接姿态控制、保存的姿态、所有者的视角)制作成关键帧,绝不能通过照片推断运动机制。
- 手工制作的关键帧以姿态空间距离为参数,并使用跨度加权切线(或自然三次插值);烘焙前必须通过密集采样的数值最大加速度步长检查。
- 循环状态使用余弦函数驱动相位,覆盖状态的实际扫掠范围(起点等于终点,反向时速度为零),并由引擎重复播放;退出时从动画时钟读取当前相位,并沿相同路径离开,时长按剩余行程缩放。
- 进入或离开画面的路径,其缓动在可见窗口两端都必须保持非零弧长速率;追加到静止角色上的路径,起始切线必须与角色的滚动轴完全对齐(角色长度会放大首帧的朝向突变)。
- 任何可能与实时手指操作相遇的脚本节拍,都必须使用有界、受令牌保护的重试,绝不能只在静默守卫后尝试一次;每个手势入口点都必须先拒绝隐藏或未挂载的角色,再递增令牌。
- 报告运动故障时,先将路径绘制成约 4pt 宽的平面带状线(使用烘焙本身的点、不受光照、关闭深度测试),再调整任何参数;形状获批后移除带状线。
- 在多场景序列中:脚本时钟通过静态发布器按墙上时间增量推进;每次切换都必须等出场动画完全离开画面后进行;下一舞台须预先挂载在场外待命。
- 触觉反馈遵循应用的触觉偏好;运动驱动的额外效果(陀螺仪视差、抖动)遵循“减弱动态效果”设置。
- 视图离开窗口时停止 60 Hz 工作(运动回调、逐帧更新),重新挂载时再启动。
-
updateUIView对状态键进行差异比较;纯 SwiftUI 重新渲染不会重放任何内容。 - 舞台的 SwiftUI 标识必须稳定:绝不通过条件呈现来控制显示(用透明度隐藏),可表示视图上不带闭包参数,
.id仅用作有意重新挂载的手段。 - 舞台须支持 VoiceOver:作为一个带标签和值的无障碍元素;手势映射为可调节操作或命名操作;在“减弱动态效果”开启时,移除循环环境动态效果(而不是减慢它)。
- 纹理尺寸须远低于 GPU 的纹理尺寸上限(新款 iPhone 为 16384 px,较旧 GPU 为 8192 px);舞台永久退出时,释放任何纹理缓存。
- 生成的纹理以格式比例 1 渲染,尺寸与角色的屏幕投影相符;实时重新指定目标的纹理,每次更新通道最多合并为一次渲染。
- 首次入场动画须由
prepare的完成处理程序门控,不能只依赖固定延迟;粒子管线须在首次真实帧前预热(零不透明度爆发,标志与实际效果一致)。 - 冲击粒子使用 Alpha 混合、按距离排序、保持无光照并采用每个装置的纯色;其发射器形状绝不能与角色相交。
- 触觉生成器须按样式预先准备并重复使用,绝不能在计划闭包内临时创建。
- 任何仅由着色器时钟驱动动画的舞台都须设置
rendersContinuously;通过对两次截图进行像素差分来验证运动,通过逐帧轮廓相关性验证方向和速度,而不是凭肉眼判断。 - 每种由速度驱动的着色器图案都通过累积时钟(速度的积分)推进,绝不能使用
speed * absoluteTime;速度须渐变至目标值,时钟只向前推进。 - 变形薄片上的视角淡化须读取波浪倾斜后的法线(修改器更新的是
_geometry.normal,而不只是位置);折叠轮廓须通过厚度补偿处理,不能使用带下限的分段软阈值。 - 每个运动通道都拥有自己的包络:一个通道(波浪)的平稳段或渐弱段绝不能影响另一个通道(摆动弯曲)所依赖的渐变。
- 移植效果的控制参数须先根据新摄像机在屏幕空间中重新分组;调整细节前,必须根据新的可见行程重新推导所有与包络耦合的常量。
- 镜面角色须由作为文件 URL 传入的摄影级
.hdr环境照明;摄像机启用wantsHDR,泛光阈值高于 1.0;绝不能用手绘的 LDR 图像为铬色或金色材质提供环境光。 - 着色器变形几何体须根据变形重建
_geometry.normal(复合位移使用有限差分);点击命中测试须针对放大的隐形碰撞体,而不是基础网格。 - 每个运动源——着色器 uniform、节点姿态、手势惯性——都由一个基于 dt 积分、带冻结启动钩子的时钟驱动;关闭曝光适应;通过不同启动时冻结截图的逐位一致性证明确定性,并通过冻结时钟下限制在角色范围内的差分证明交互性。将运动交给引擎粒子模拟的舞台,须在注释中声明此豁免,并通过运动像素差分进行验证。
- 须在舞台机制可能产生的每种朝向下验证光照(切面、翻倒后的底面、翻滚中姿态),不能只验证设计的静止姿态。
- 生成纹理中的线条图案(条纹、色带、轮廓)须绘制为填充的矢量路径,绝不能堆叠逐行矩形;逐行堆叠在任何分辨率下都会产生阶梯边缘。
评论
支持 Markdown