Ray Marching + SDF实践
[{“source”:{“position”:0,“lines”:[”
添加图片注释,不超过 140 字(可选)
省略启发式AABB构建过程…
SDF形状实现
基类:
[ExecuteInEditMode]"]},"target":{"position":0,"lines":["添加图片注释,不超过 140 字(可选)省略启发式AABB构建过程....SDF形状实现基类:[ExecuteInEditMode]"]},"type":"CHANGE"},{"source":{"position":22,"lines":[" }s_shapeComponents:存放场景中所有启用的 RayMarchedShape 实例。s_tree:对应的 AABB 树结构,用于快速做空间剔除和射线相交测试。s_sdfShapes:每帧根据组件列表生成的 SdfShape 数组,降低运行时的内存分配和 GC 频率,逻辑上不是必须。private int m_shapeIndex = -1;"]},"target":{"position":22,"lines":[" }s_shapeComponents:存放场景中所有启用的 RayMarchedShape 实例。s_tree:对应的 AABB 树结构,用于快速做空间剔除和射线相交测试。s_sdfShapes:每帧根据组件列表生成的 SdfShape 数组,降低运行时的内存分配和 GC 频率,逻辑上不是必须。private int m_shapeIndex = -1;"]},"type":"CHANGE"},{"source":{"position":46,"lines":["}OnEnable:每当一个带 RayMarchedShape 的 GameObject 激活时,就把它加入 s_shapeComponents,并在 s_tree 中开辟一个 proxy,用它来跟踪该物体的包围盒(Bounds)。OnDisable:移除列表和 BVH proxy。同样保证列表紧凑,无“空洞”。public static int FillAabbTree(ComputeBuffer buffer, float aabbTightenRadius = 0.0f)"]},"target":{"position":46,"lines":["}OnEnable:每当一个带 RayMarchedShape 的 GameObject 激活时,就把它加入 s_shapeComponents,并在 s_tree 中开辟一个 proxy,用它来跟踪该物体的包围盒(Bounds)。OnDisable:移除列表和 BVH proxy。同样保证列表紧凑,无“空洞”。public static int FillAabbTree(ComputeBuffer buffer, float aabbTightenRadius = 0.0f)"]},"type":"CHANGE"},{"source":{"position":57,"lines":["}SyncBounds:把每个组件最新的 Bounds(由子类 override)写入对应的 proxy。Fill:让 AABB 树重建内部节点,并把树的数据打平写入到 GPU 用的 ComputeBuffer。返回值 root 是根节点在数组中的下标,也会传给 GPU Shader,用来做递归遍历。SDF形状:[StructLayout(LayoutKind.Sequential, Pack = 0)]"]},"target":{"position":57,"lines":["}SyncBounds:把每个组件最新的 Bounds(由子类 override)写入对应的 proxy。Fill:让 AABB 树重建内部节点,并把树的数据打平写入到 GPU 用的 ComputeBuffer。返回值 root 是根节点在数组中的下标,也会传给 GPU Shader,用来做递归遍历。球类(继承类):[ExecuteInEditMode]","public class RayMarchedSphere : RayMarchedShape","{"," public float Radius = 0.5f;",""," protected override SdfShape Shape"," {"," get"," {"," return SdfShape.Sphere(transform.position, Radius);"," }"," }",""," public override Aabb Bounds"," {"," get"," {"," return "," new Aabb"," ("," transform.position - Radius * Vector3.one, "," transform.position + Radius * Vector3.one"," );"," }"," }",""," protected override void OnValidate()"," {"," base.OnValidate();",""," Radius = Mathf.Max(0.0f, Radius);"," }","}球的AABB包围盒的求法很简单,符合直觉SDF形状:[StructLayout(LayoutKind.Sequential, Pack = 0)]"]},"type":"CHANGE"},{"source":{"position":152,"lines":["}球类(继承类):[ExecuteInEditMode]","public class RayMarchedSphere : RayMarchedShape","{"," public float Radius = 0.5f;",""," protected override SdfShape Shape"," {"," get"," {"," return SdfShape.Sphere(transform.position, Radius);"," }"," }",""," public override Aabb Bounds"," {"," get"," {"," return "," new Aabb"," ("," transform.position - Radius * Vector3.one, "," transform.position + Radius * Vector3.one"," );"," }"," }",""," protected override void OnValidate()"," {"," base.OnValidate();",""," Radius = Mathf.Max(0.0f, Radius);"," }","}球的AABB包围盒的求法很简单,符合直觉RayMarching 后处理 ComputeShader// cs"]},"target":{"position":184,"lines":["}RayMarching 后处理 ComputeShader// cs"]},"type":"CHANGE"},{"source":{"position":195,"lines":["var camera = window ? window.camera : null;可以获取Scene视图的相机,还真是万物皆可组件,Scene上也有Camera组件求交 // gather shapes around ray by casting it against AABB tree"]},"target":{"position":195,"lines":["var camera = window ? window.camera : null;可以获取Scene视图的相机,还真是万物皆可组件,Scene上也有Camera组件求交 // gather shapes around ray by casting it against AABB tree"]},"type":"CHANGE"},{"source":{"position":201,"lines":[" );数量:numNearShapes 这是一个计数器,初始为 0。 索引列表:数组 aiNearShape(长度上限 kMaxShapesPerRay)用来存放「这条射线可能会命中的」那些 SDF 形状在全局 aSdfShape 数组里的 索引(shapeIndex)。数组下标范围是 0~numNearShapes-1,每次发现一个叶子 AABB 被射线击中,就把它对应的 shapeIndex push 进去。March Ray//对 numNearShapes 个候选形状做距离评估"]},"target":{"position":201,"lines":[" );数量:numNearShapes 这是一个计数器,初始为 0。 索引列表:数组 aiNearShape(长度上限 kMaxShapesPerRay)用来存放「这条射线可能会命中的」那些 SDF 形状在全局 aSdfShape 数组里的 索引(shapeIndex)。数组下标范围是 0~numNearShapes-1,每次发现一个叶子 AABB 被射线击中,就把它对应的 shapeIndex push 进去。March Ray//对 numNearShapes 个候选形状做距离评估"]},"type":"CHANGE"},{"source":{"position":285,"lines":["}这段代码没有严谨地考虑穿过的情况,也就是两个Step之间距离都小于hitDst的情况,但场景里都是球体,并没有厚度小于hitDst的平面。看完这个代码我才知道原来SDF在渲染中的作用就是计算法线。用立方体的四个对顶角计算,4 个点正好能提供三个自由度的梯度信息(∂x,∂y,∂z)加上一个整体偏移。添加图片注释,不超过 140 字(可选)Lerp 平滑// 平滑并集(smooth union)"]},"target":{"position":285,"lines":["}这段代码没有严谨地考虑穿过的情况,也就是两个Step之间距离都小于hitDst的情况,但场景里都是球体,并没有厚度小于hitDst的平面。看完这个代码我才知道原来SDF在渲染中的作用就是计算法线。用立方体的四个对顶角计算,4 个点正好能提供三个自由度的梯度信息(∂x,∂y,∂z)加上一个整体偏移。添加图片注释,不超过 140 字(可选)Lerp 平滑// 平滑并集(smooth union)"]},"type":"CHANGE"}]