当前位置: 首页 > news >正文

游戏引擎学习第314天:将精灵拆分成多个层

回顾并为今天的工作做准备

我们今天继续昨天开始的工作,现在我们要回到渲染中处理 Z 值的最终环节。我们目前已经有一个我们认为还算合理的排序方式,虽然可能还需要在接下来的过程中进行一些调整,但总体上已经有了一个明确的方向。

我们已经开始草拟出一些接下来的任务,这很可能会是我们下周的主要工作。不过今天也不能浪费时间,能推进多少就推进多少。昨天我们已经开始试着实现,现在可以开始真正深入推进,把自己放在一个更好的位置,为下周的完成打下基础。

接下来的过程其实没有什么特别神奇的东西。我们只是要根据现在对 Z 值处理的明确认知,把之前为了摸索而写下的内容进行整理和清理。之前的做法比较杂乱,是因为艺术风格、代码实现等多方面因素影响了我们对 Z 值如何工作的判断。但现在我们已经清楚地知道了 Z 值的职责是什么,也知道了我们希望它如何发挥作用。

因此,我们要做的是整理整个渲染流程中的 Z 值处理逻辑,确保它是清晰、统一且健壮的。否则,后续继续在这种不稳定的系统上构建,会出现很多潜在的问题,比如表现不一致或者难以调试的问题。我们希望最终能达到这样一种状态:对于 Z 值的使用,我们有清晰的理解和信心,不再出现“为什么会这样”的疑惑。

过去在开发过程中,我们多次调整 Z 值的处理方式,尝试不同的策略,遇到问题就修改,然后再试。这些过程是必要的学习阶段,是摸索方向的一部分。但现在我们已经走出了那个阶段,进入到需要把所有东西整理清楚、稳定下来的时候了。

我们要做的,就是清理原本那些零散、不一致的处理方式,让整个 Z 值系统在渲染层是干净、明确、可预期的。这样,在后续开发中我们就不必反复质疑其行为或逻辑,也不会被过去的临时实现困扰。我们需要的就是一个可靠的基础系统,然后就可以毫无顾虑地在其上构建更多功能。

运行游戏,查看当前进展

我们现在继续昨天的工作,主要是处理和楼梯相关的渲染问题。可以看到,在处理楼梯间时,通往上层的楼梯并没有被正确地包含在底层的渲染中。这说明我们在处理 Z 值的时候,整个流程还不够一致和清晰。

问题的根源在于我们没有系统地跟踪 Z 值。我们并没有从逻辑上根据所在区块来计算 Z 值。我们已经决定,Z 轴上的区块(chunk)应该对应于每一层楼——也就是说,同一层楼的所有内容应该处于同一个 Z chunk 中。这样渲染的时候才能将同一层的元素作为一个整体进行处理。

接下来要做的第一件事就是让每个 chunk 的 Z 值真正被用在渲染中。当前我们虽然有传递 chunk 的概念,但实际上渲染系统中并没有用这些 Z 值进行层级划分。我们需要修复这一点,让 chunk 的 Z 值真正参与渲染逻辑,使得每一层楼被当作一个独立的渲染切片(slice)处理。

此外,当前的渲染系统只是把所有东西混在一起,然后依赖排序规则进行排列。而我们的排序规则本身也并没有设计成处理这种多楼层的情况。这种混合处理是不对的,我们需要明确地区分不同层的数据,并作为单独的切片传入渲染流程中。

我们要做的第二件事就是,让渲染系统开始认识这些不同的切片,让它们分层传入并进行单独处理,而不是像现在这样全部混在一起后依赖排序算法“勉强”处理。

总体思路如下:

  1. 首先修正 chunk 的 Z 值处理,确保每一层楼被正确分配一个 chunk,且 chunk 的 Z 值能被渲染系统识别并利用。
  2. 然后让渲染系统以“切片”的形式,分别接收这些按层划分的数据。
  3. 最终要让排序系统明确知道这些层级是怎么工作的,避免不明确或错误的层间混合渲染。

通过这些步骤,我们就能让渲染流程在处理多楼层内容时更合理、更清晰、更可靠。现在我们开始动手。

修改 game_entity.cpp:停止 UpdateAndRenderEntities() 调用 ConvertToLayerRelative(),改为按昨天讨论的方法计算 RelativeLayer

我们现在在处理广义相对性的位置转换逻辑,也就是之前一直使用的 ConvertToLayerRelative。这个方法之前用于确定某个物体在 Z 轴上的位置。但现在我们不再想沿用这种基于相机视角差异来决定图层的方式,因为我们不再对图层做透视变换,所以这种依赖相机 Delta 的方式已经不适用了。

我们真正需要的是基于“chunk Z 值”的图层逻辑。我们已经开始在代码中明确了这一点,比如写下了注释说明:现在我们关注的是 chunk Z 值的差异,它决定了图层的高低,而不再关注相机的位置偏移。

问题在于,目前我们在执行重新打包(repack)之前,还无法准确知道某个实体的 chunk Z 值。也就是说,WorldPosition 这个值在当前阶段并不可信,因为我们还没确定目标位置在哪个 chunk。换句话说,我们移动了某个实体,但在进行重新打包之前,我们还不知道它的新 chunk Z 值应该是多少。

虽然在重新打包的阶段我们是可以计算出来的,比如在打包函数中,我们会根据实体的位置重新计算对应的 chunk,这时候我们确实能够知道 Z 的差异。但是我们现在还没进入这个阶段。

因此我们必须审视代码的执行顺序,尤其是在区域数据(region)内的行为。我们可以看到,当我们真正进行“打包实体进世界”这个操作时,比如调用 PackEntityIntoWorld 的时候,代码中确实传递了实体的 WorldPosition,也就是位置信息。

进一步查看,可以发现 PackEntityIntoWorld 是在 EndSim 的时候被调用的,意味着在模拟结束阶段我们会把所有实体重新打包。并且在 PackEntityIntoWorld 中,会根据实体的位置来计算它所在的 chunk,这个计算过程包括图块映射与坐标平移。

虽然这种做法会导致在某些地方重复计算 chunk 的代价略高,但当前阶段我们还没有更好的解决方案。当然,也可以考虑将计算出来的 chunk 信息缓存到实体中,以便后续重用,这部分我们可以留个 TODO,将来优化时考虑。

总的来说,当前任务核心内容如下:

  1. 不再使用基于相机 Delta 的 ConvertToLayerRelative 方法;
  2. 图层信息应当由 chunk Z 值来决定,这样才能与我们每层楼一个 chunk 的设计相匹配;
  3. 当前在重新打包实体前无法得知它准确的 chunk Z,需要等待进入打包流程才能得出;
  4. PackEntityIntoWorld 是我们当前依赖的处理过程,它在 EndSim 阶段执行,内部根据 WorldPosition 计算 chunk;
  5. 尽管存在一定的计算重复,但可以通过后续缓存优化;
  6. 当前阶段的目标是让逻辑流程保持清晰,让每个实体的图层归属可以通过 chunk Z 来可靠判断,为后续渲染处理打下基础。
    在这里插入图片描述

在这里插入图片描述

修改 game_entity.cpp:让 UpdateAndRenderEntities() 通过 MapIntoChunkSpace() 获取 WorldPos 来计算 RelativeLayer

我们现在考虑的问题是,如何在已知某个 chunk(例如 chunk C)的情况下,获得实体在该 chunk 中的相对位置(entity P)。实际上,我们并不一定需要保存这个转换的结果,仅仅在需要计算时进行一次转换就足够了。

举个例子,我们可以简单地调用一次 MapIntoChunkSpace,直接将实体的世界位置转换为 chunk 空间下的位置。虽然从逻辑上讲我们可以不保留中间结果,但实际上,我们很可能还是需要知道这个转换所产生的偏移量(offset),因为后续我们可能要利用这个偏移量做进一步处理,所以我们可能还是会保留这个值。

因此,我们可以这样处理:将世界位置 WorldPosition 传入 MapIntoChunkSpace,这时就能获得在 chunk 空间下的位置(例如 WorldPoster),从而得知实体当前所处 chunk 的 Z 值。在物理模拟步骤之后,实体已经被移动到新位置,此时再执行这一转换操作,就能知道它的新图层(relative layer)。

这样我们就可以获得准确的图层索引,这是非常重要的信息。

不过接下来还有其他问题需要考虑。我们还需要为这个实体设置用于渲染的“身份变换矩阵”(identity transform),用于对 Z 值进行合适的偏移处理,以确保实体在 Z 轴上被正确地渲染。因此,虽然我们现在已经可以正确计算出相对图层,但对于后续渲染的处理,还需要额外的步骤。

我们知道实体最终会被绘制在某个具体的位置,而这个位置会受到 chunk Z 值的影响,所以最终渲染的位置不仅取决于逻辑坐标,还取决于图层处理逻辑。我们目前还没有把这些都建立起来,因此这部分工作还得继续完善。

目前我们先暂时把这部分逻辑放一边,因为其他依赖的部分还没准备好,但这显然是我们接下来必须推进的方向。

此外还发现一个小问题,我们当前代码中似乎没有 World 的定义或引用,看起来像是漏掉了。可能是因为相关模块引用时没有传递 World 实例,或者有些代码根本没有用到它所以没人注意。我们推测可能是 WorldMode.World 之类的结构,只是没有在当前作用域里建立本地引用。

这个问题虽然不影响当前的主逻辑构思,但之后处理实体或渲染环境时还是需要修正,确保我们能正确访问世界状态信息。

总结:

  1. 可以通过 MapIntoChunkSpace 获取实体在 chunk 空间下的位置;
  2. 从中获得 chunk 的 Z 值,从而确定实体所在的图层;
  3. 物理模拟之后再执行该计算,确保使用的是实体的新位置;
  4. 后续还需要设置 identity transform,以处理 Z 偏移;
  5. 实体最终在画面中出现的位置依赖于 chunk Z 的正确处理;
  6. 当前 World 对象没有被本地引用,需要检查并修复;
  7. 接下来的重点工作是建立完整的图层渲染流程,包括位置变换和正确排序机制。
    在这里插入图片描述

运行游戏,确认状态明显改善

现在整体情况已经明显好转。楼梯间的处理已经完成,并且每一层楼的楼梯部分都已经准确地归类到了对应的楼层中,位置也符合预期。这是一个重要的进展,因为这意味着基础结构已经开始变得清晰和有序。

虽然仍然存在楼层切分的问题,目前还没有进行楼层的切片处理,因此相关的错误依然存在,但这只是下一步需要解决的问题。当前的主要成果是楼层中的元素已经被正确地分配到了各自应在的楼层,不再出现混乱或归类错误的情况。

这是第一次整理楼层内容的初步结果,虽然还不完整,但方向正确。整个结构开始表现出一定的连贯性,楼层之间的关系也更为合理,不再像之前那样杂乱无章。接下来需要进一步处理的是将每层楼进行实际的切分,从而彻底消除现有的切片错误。但总的来看,目前取得的进展令人满意,已经迈出了关键的一步。
在这里插入图片描述

考虑在渲染中引入图层 Z 和实际 Z 值的概念

接下来需要重点处理的是 Z 轴方向的转换部分,也就是 z transform 的另一部分,这是接下来工作中最复杂的部分之一。因此,我们必须开始收紧思路,对整个实现方案有一个非常清晰、明确的概念设计,特别是在渲染阶段的错误处理、图层管理和 Z 值之间的关系上。

我们需要处理图层(layer)和实际的 Z 值的关系。具体来说,目前的 chunk dim z(即每块在 Z 方向上的维度)需要作为一个绝对的排序标准,被渲染系统感知并使用。为了解决当前的问题,可以临时采用一种折中的方案:把 Z 层的信息直接嵌入到 sort key(排序键)中,让排序系统以此为依据来进行精确排序。

这种方式虽然是可行的权宜之计,但并不一定是最终推荐的做法,因为当前的 sort key 结构已经变得非常庞大,承载了过多逻辑。虽然目前我们可以继续在其中添加信息来满足开发需求,但这并不是一个长久之计,迟早我们需要面对复杂度上升所带来的系统开销和维护负担。

目前的重点是先把各部分如何组合的问题理清楚,让整个流程跑通。在实现阶段,可以允许排序逻辑暂时承担更多的责任,比如在 sort key 中嵌入 Z 层信息,让排序过程自动把不同层级的数据区分出来。

不过,等到最终版本的渲染阶段时,我们很可能需要更聪明、更高效的方案。可能会采用将不同图层的数据预先分配到独立的 buffer 中,从而避免后期再进行统一排序。这种方式可能在性能和逻辑清晰度上更具优势,因此在之后的设计中需要重点考虑是否转向这种结构优化的路径。

修改 game_render.h:在 sprite_bound 结构体中添加 ChunkZ

目前虽然还不确定最终方案应该怎么设计,但在现阶段,由于排序系统已经搭建完成,因此可以先提出一个临时性的处理思路。

具体来说,可以引入一个类似于 chunk z 的机制。这个 chunk z 会作为一个具有绝对优先级的排序因素存在,它的优先级甚至高于人工设定的排序顺序。这意味着,在排序过程中,只要涉及到 chunk z,它就会主导排序行为,把相关元素从通用排序逻辑中“抽离”出来,优先进行分组排序。

这个机制的核心思想是将 chunk z 作为一种强制的排序层级存在,把它看作是一种“排序组”的概念。凡是具有相同 chunk z 值的元素会被归为一组,然后在这组内部再根据其他标准进行细致排序。这种方式可以有效地将整个场景在 Z 轴维度上划分成结构清晰的分区,使得渲染或处理逻辑能够优先考虑 Z 轴上的分布,而不是完全依赖人工设定的顺序或其他混杂条件。

虽然这是一种暂时性的“吸附式”(hoover up)的解决方案,但在当前阶段非常实用,可以让系统在没有复杂 buffer 管理和最终结构优化之前,依然维持较高的逻辑清晰度和排序准确性。这样既能保证结构分层正确,也为后续的优化和系统演化打下良好基础。
在这里插入图片描述

修改 game_render.cpp:让 IsInFrontOf() 测试 ChunkZ

接下来在实现“是否在前方”的判定逻辑时,需要在已有逻辑的基础上引入一个额外的判断条件。这个处理的关键在于排序对比过程中,我们现在要处理的是一种更复杂的场景,而不仅仅是简单的几何对比。每当我们在进行对象之间的排序比较时,逻辑复杂度就会上升,因此这部分的设计也变得更加关键。

目前的做法是:在排序判断阶段,我们可以加入一个新的逻辑分支。这个分支基于 chunk z 的值来进行判断。具体操作是:如果参与比较的两个对象,其对应的 chunk z 值不相等,那么我们就可以直接根据这个值来判断前后关系。也就是说,chunk z 值较大的对象会被视为“在前方”,从而在渲染中获得更高的显示优先级。

这样一来,chunk z 就成为了一个天然的分层依据,相当于在所有其他排序逻辑之上,先进行一个大范围的分组。只要两个对象处于不同的 chunk z 区域,就不需要再进行更细致的空间对比,可以直接通过 chunk z 的大小关系来决定谁在前谁在后。

这种分层判断的方式虽然让排序逻辑变得更复杂,但它也是必须的。尤其是在对象数量增加、场景变得更加复杂的情况下,必须提前意识到哪些部分可能成为性能瓶颈或系统负担点。虽然目前系统中并不会出现成千上万个对象同时出现在屏幕上的情况,这在一定程度上给予我们一些“宽容度”,但从长远看,识别和标记这些潜在风险点仍然是必要的。

总之,这种基于 chunk z 的预分层策略能够有效简化部分排序判断,同时在逻辑上也有助于构建一个更清晰的图层结构。通过将其视作一种在其他所有排序标准之上的“层级规则”,我们就可以自由地将对象归入更宽泛的类别之中,从而提高整体系统的可维护性和可扩展性。
在这里插入图片描述

修改 game_render_group.h:在 object_transform 结构体中添加 ChunkZ

关于渲染分组的实现,其实在代码架构层面上并不复杂,这部分的逻辑设计非常直接,也容易实现。真正让人产生顾虑的并不是实现难度,而是更高层次上的系统整体性与性能代价问题。

当前采用的策略虽然可行,也能满足现阶段的需求,但在心里还是会有所担忧——不是说这条路径是错误的,而是担心在系统发展到更复杂或性能要求更高的阶段时,这种方案可能存在瓶颈,可能会拖慢整体的效率或者变得不易维护。

也就是说,现在的实现方案虽然暂时能跑通,也逻辑清晰,但未来在更高负载或者更大规模的渲染场景中,我们可能需要对这些结构重新审视,寻找更高效、更优雅的方式。不是说当前不能继续推进,而是要有一个心理准备:当系统进入后期阶段、功能趋于稳定或规模扩大之后,可能必须回头对某些设计做出优化和重构。

因此,这些担忧更像是一种预警机制,一种对未来可能问题的预留意识。当前阶段继续使用这些方法是可以接受的,不存在阻碍开发的风险。但同时,也需要在心中标注这些点,防止在后续版本中它们演化成更严重的系统问题,或对性能产生难以忽视的影响。这样可以避免到时候措手不及,而是能够有计划、有节奏地进行调整和优化。
在这里插入图片描述

修改 game_render_group.cpp:让 GetBoundFor() 设置 ChunkZ

现在我们所做的事情非常简单,主要是为了处理手动排序(manual sort)相关的逻辑。具体来说,我们要做的只是确认当前数据中是否存在某个元素,并将其关联的 chunk z 值提取出来,用于后续的排序判断。

操作上,我们所做的就是:当找到某个需要排序的对象时,直接从对象中提取其对应的 chunk z 值,并将这个值用于标记或记录,作为后续排序流程中的一部分。这一步确保了我们能够明确地知道这个对象在 Z 轴维度上属于哪个逻辑分层,为后续判断“是否在前方”以及具体渲染顺序提供了基础依据。

这种做法本身非常直接,目的也很明确,就是为后续排序打好基础,把对象在 chunk z 上的归属清晰地表达出来,避免后续处理时出现混淆或不一致。通过这个操作,我们就能确保在进行手动排序时,能够与 chunk z 的自动分层逻辑相兼容,不会发生矛盾或逻辑冲突。整体流程简单但关键,是排序系统和渲染系统保持一致性的必要步骤。
在这里插入图片描述

运行游戏,确认修改没有影响现有表现

理论上来说,这个改动不应该对现有的排序结果产生任何影响,因为我们并没有对任何值进行修改或重新设置。执行这部分代码时,排序逻辑依旧保持原样,所以在实际运行时,仍然会看到之前那种奇怪的排序现象。

通过观察,可以确认排序结果和之前完全一致,说明目前的改动只是做了数据的读取和确认,没有改变排序行为本身。这也意味着当前的处理流程正常运行,没有引入新的错误,同时保留了之前的排序状态,方便后续继续调试和优化。
在这里插入图片描述

修改 game_entity.cpp:让 UpdateAndRenderEntities() 设置 ChunkZ

现在的问题是,如果我们主动去修改渲染代码,使其真正设置排序键(sort key)和实体(entity),会发生什么情况。之前只是读取和确认数据,但没有实际更改排序键,这次打算尝试把手动排序部分真正生效,也就是说,让排序键被赋值并用于渲染排序。

通过这样做,可以直接观察手动排序对整体渲染顺序的影响,验证这种修改是否能够按预期改善或者改变排序结果。这个实验能让我们更直观地看到当前排序机制的实际表现,判断这种修改是否可行,以及是否需要进一步调整和优化排序逻辑。总之,这是一个动态测试,用来验证手动设置排序键后系统的反应和效果。

查看该修改如何影响排序

手动排序的部分,当我们将实体的变换信息(entity transform)中的 chunk z 和世界坐标(world pos)作为排序依据时,就形成了一种自动排序机制,这个机制会自动生效。经过这种处理后,可以直观地看到 Z 轴的排序效果几乎是“免费”实现的,整体画面变得更加合理、层次分明,排序看起来非常自然,物体之间的遮挡关系也更清晰。

不过,这里也出现了一个潜在的问题,具体影响还不太确定,后续版本还需要进一步观察和评估。问题主要出现在楼梯的场景里,比如当角色走上楼梯时,出现了渲染遮挡上的小瑕疵,比如角色应该显示在楼梯顶部某些部分的前面,但实际表现上可能没有达到预期。

这部分问题可能并不需要通过改变排序逻辑来解决,而更合理的做法可能是在角色移动到楼梯上某个区域后,主动将角色的 chunk z 调整到更高的层级。换句话说,当角色跨越楼梯的不同“块”时,可以将其归类到上一个 chunk,从而保证其在视觉上显示在正确的位置。这种策略比较合理,也容易实现,能够较好地解决角色与楼梯层级的遮挡关系问题。

总的来说,自动排序机制基本工作正常,带来了良好的层次效果,但对于角色与复杂几何体之间的细节遮挡,可能需要通过动态调整 chunk 归属来进一步完善。这个方案看起来是个比较合理且可行的解决思路。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

修改 game_entity.cpp:让 UpdateAndRenderEntities() 扩展 traversables 的边界,使其绘制时像真实的瓦片

现在我们尝试的目标是想扩展一下可通行区域的边界(bounds),以便观察会发生什么变化。说实话,对这部分的绘制细节已经没有太多印象了,甚至一开始都不确定是哪个部分负责渲染这些可通行区域。看了一下,确认是这段代码在处理这些 traversable 区域的绘制。

如果希望让这些区域在视觉上更接近实际游戏中的瓦片(tile)样式,那么我们可以尝试稍微扩展它们的显示方式。这样就能够在角色移动过程中,更清楚地看出每一个 tile 的作用和可行走性。

在实际运行中,也能更清晰地看到游戏中的可通行结构,以及角色在其中的移动轨迹。这个过程中,也开始逐渐呈现出一个更接近真实游戏的视觉效果。

但与此同时,也发现了一个新的问题:在某些地方出现了排序闪烁(sorting flicker),这可能意味着在渲染过程中出现了深度排序冲突。初步判断,这种问题极有可能是由循环依赖导致的排序错误,也就是多个对象之间的排序形成了闭环,系统无法稳定判断前后关系。

为了验证这一点,有必要重新引入调试工具,用于确认排序逻辑中是否真的存在循环冲突。同时,还需要将调试信息放入一个独立的 chunk 中,这样它们在渲染层级上可以总是处于最上层,方便观察和调试。

这不仅可以帮助修复当前的排序闪烁问题,同时也能为后续可能遇到的类似问题提供一个清晰可控的观察入口。总之,当前阶段的改动虽然小,但却暴露出系统中深层次的排序逻辑挑战,需要进一步验证并加以优化。
在这里插入图片描述

在这里插入图片描述

运行游戏,思考场景切换问题

现在可以明显看到出现了严重的排序循环问题(cycling),导致画面中出现非常糟糕的闪烁现象。这种问题很可能是由于当前的重叠检测逻辑过于宽泛,判定了许多实际并不真正重叠的对象之间存在遮挡关系。

虽然这些元素之间的重叠非常微弱,但在当前的系统中,哪怕是边缘上的极小接触,也会被判定为存在深度关系,从而被强行纳入排序比较中。这就导致了一种“伪重叠”的排序逻辑,最终形成大量不必要的图元之间的排序关系,进一步引发了闭环依赖和排序循环。

考虑到这一点,或许一个改进的方向是:在进行重叠检测之前,适当缩小物体的边界范围。也就是说,不使用实际的完整边界进行比较,而是对每个物体的包围盒略微进行收缩,从而避免那些仅仅在边缘轻微重叠的对象被误判为存在遮挡关系。

从直觉上来说,这应该是一个非常有效的优化策略,因为许多目前被系统判定为需要参与排序的对象,在视觉上其实并没有真正发生可感知的遮挡。如果能过滤掉这些“几乎不重叠”的情况,就可以大幅减少排序系统中的无效比较,从而降低循环发生的概率,提高整体排序的稳定性和效率。

另外,也注意到在某些角度,比如角色靠近一些树木或场景边缘的时候,会出现树木闪烁的问题,可能也是由于同样的循环排序导致的遮挡不确定性。这种现象一旦出现,就意味着当前排序图中存在大量的环路。实际上,令人惊讶的是在存在如此多的排序环时,系统仍然能在大多数情况下保持运行稳定,说明基础排序机制具有一定的鲁棒性,但这种状态显然是不可持续的。

因此,下一步应当重点考虑对重叠检测逻辑进行优化,从“边界缩小”入手,有望在不大幅更改排序结构的前提下,显著提升渲染表现和用户体验。
在这里插入图片描述

修改 game_render.cpp:在 BuildSpriteGraph() 中缩小传入 RectanglesIntersect() 的尺寸

现在我们开始关注排序过程中的重叠检测(overlap test),尤其是“矩形相交”的部分(rectangles intersect),思考它在逻辑层面上的实际行为,以及我们是否可以通过某种方式改进它,以减少排序错误或循环的出现。

具体来说,我们对当前的重叠判断逻辑产生了兴趣,想了解当我们在执行矩形相交检测时,若对矩形进行一些缩小操作(比如将其边界收缩4个像素),最终会产生怎样的效果。这是出于一种优化直觉:通过略微收紧物体边界,过滤掉那些边缘接触的伪重叠,从而减少不必要的排序干扰。

当前的矩形相交逻辑是集中在平台相关的谓词模块(platform-specific predicates)中定义的,所以得去那里查看或修改这部分的代码。

接下来尝试性的思路是:在进行碰撞或重叠检测之前,先人为地将两个待比较的矩形边界收缩,比如每边收缩4个像素。这相当于人为设置了一个“容差”或“安全缓冲区”,排除了那些几乎看不见的边缘接触情况。

这样做的潜在好处是显而易见的:

  • 可以显著减少那些在视觉上没有实际遮挡效果的对象之间的排序参与;
  • 减少排序比较数量,降低生成排序图时的复杂度;
  • 减小排序循环(cycle)的可能性,提升最终的排序结果稳定性;
  • 减少渲染过程中的闪烁现象。

这个方向的尝试并不需要大幅改动系统结构,仅仅是修改了相交检测的边界条件,却有可能带来非常直接且明显的排序行为改进,因此是一个性价比非常高的优化思路。

因此,当前我们着手的重点是将已有的矩形交叉检测逻辑,包装进一个带有可控“缩放”参数的版本,并实际应用到排序过程中的重叠判断中,观察是否能显著改善排序闪烁与环路问题。这个实验将对我们后续优化方向提供重要参考依据。
在这里插入图片描述

在这里插入图片描述

修改 game_debug.cpp:让 DEBUGStart() 设置调试元素的 ChunkZ

我们现在考虑的是是否可以彻底移除排序偏移量(sort bias)这一机制。

起因是我们目前已经引入了 chunk_z,它作为一个明确的、全局性的排序参考值,已经足以承担起排序中的层级判断功能。之前 sort bias 是一种临时手段,主要用于手动调整物体的前后关系,使其在渲染顺序中有所区别,但现在随着 chunk_z 的引入与正式使用,它已经具备更明确、更绝对的优先级判断意义。

因此我们着手进行了如下思考与操作:

  • 直接查看当前是否还有地方在使用 sort bias
  • 判断其是否还会对排序产生任何实际影响;
  • 初步结论是:有了 chunk_z 之后,再保留 sort bias 显得多余且冗赘;
  • 于是决定将 sort bias 从系统中完全移除;
  • 同时在调试系统中也进行相应修改,确保不会再引用这个字段;
  • 后续验证移除 sort bias 后系统的排序行为是否依旧正常,特别是可视化效果、物体遮挡关系等是否如预期一致。

总的来说,这是一次对系统历史遗留机制的清理优化,也是对当前排序逻辑简化和集中化的积极举措。通过移除多余的权重叠加方式,我们可以进一步减少系统中的潜在歧义来源,使排序系统更清晰、更统一、更易维护。未来如果发现仍有特殊情况需要精细微调排序,可以考虑以更通用的方式引入,而不是依赖旧的、命名模糊的机制。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

运行游戏,切换调试组显示

我们现在验证了调整后的调试覆盖显示确实变得更加清晰、美观了。

通过在重叠判断中排除那些只是边缘接触的元素(即只“轻微接触”的物体),我们成功避免了不必要的排序判断,也大幅减少了因排序循环产生的视觉闪烁问题。这种处理逻辑非常合理,因为那些几乎没有实质重叠的对象,本就不该强行介入排序关系的比较中。实际效果也印证了这个优化的价值 —— 排序更加稳定,调试信息也更易观察。

我们当前的系统状态令人满意,有几个积极的结果:

  • 排除边缘接触的重叠元素后,排序循环问题明显减轻;
  • 原本的视觉闪烁(尤其是在 tile 边界处)已经被有效解决;
  • 排序结构整体更加智能化,不再因为轻微接触而生成无意义的复杂比较图;
  • 测试中通过启用 debug group 显示,能清楚看到排序关系的调整结果,便于确认逻辑正确性;
  • 排序系统也更具鲁棒性,即使存在大量复杂结构,也仍能较好维持预期行为。

此外,我们也注意到未来仍需处理一项重要问题 —— X轴方向的倾斜(skew)。当前存在一些非法状态,即某些元素发生了不允许的 X 方向倾斜,这会导致排序逻辑失效,因为现有的排序规则并不能正确处理这种偏斜后的空间投影关系。因此,在继续推进系统前,还需要对这些异常状态进行清理和纠正,确保所有渲染实体都符合排序系统的假设前提。

最后,我们还观察了一些高度跳跃判断的逻辑,发现系统会自动选择不那么陡峭的落点,也就是说它会优先选择下落距离不太大的区域作为落点,这一点看起来也是符合预期的。

总之,当前我们对系统所处的状态感到满意,排序逻辑基本成立,调试工具正常工作,错误和异常现象显著减少。下一步将集中精力解决倾斜问题,并确保后续逻辑构建在一个更清晰、稳定的排序结构之上。

思考如何利用渲染器中的层数据

我们目前通过引入 chunk_z 实现了将所有渲染实体分组的能力,完成了对场景中各个对象的层级划分。这为后续的渲染排序奠定了基础。但是,到目前为止,渲染系统本身还没有真正利用这些分组信息,这才是目前的主要问题。

我们早前在渲染管线中实现了剪裁矩形(clip rects)机制,理论上这些剪裁矩形可以用来关联不同的渲染组,例如用来处理透明图层(alpha group)的叠加效果。但目前的结构中,各种逻辑交织在一起,导致渲染系统还无法真正基于这些剪裁区域或图层信息来合理地处理复杂的渲染层级,尤其是透明层。

理想情况下,我们希望能将透明层(alpha group)中的内容单独渲染到一个离屏缓冲区(offscreen buffer)中,最终再将该缓冲区合成回主画面。这就需要渲染系统具备更高层次的上下文感知能力,理解哪些对象处于透明图层、哪些不在,但目前的架构还不足以支撑这一点。

不过有一个方面我们可以先着手处理,那就是**雾效(fog)**的绘制逻辑。雾效本质上是对不同深度图层的透明度处理,而这部分逻辑其实可以从主渲染流程中剥离出来,单独处理。我们只需要在绘制时判断当前是否处于 alpha 渐隐层(alpha fade layer),以及当前层的透明度级别,而不必让整个渲染系统理解所有的分组细节(例如目前存在的六个 chunk_z 层级)。

也就是说,大部分这些分组和图层结构其实只是为雾效服务的,并非渲染必须要深入理解的内容。所以我们可以优化思路:

  • 把雾效处理从渲染器中抽离出来,由更高层负责提供 alpha 衰减信息;

  • 渲染器只需要关心两件事:

    1. 当前对象是否属于 alpha 衰减层;
    2. 当前 alpha 层的透明度等级是多少;
  • 这样一来,渲染系统只需处理“是否做渐隐”和“做多少”的简单问题,而不是陷入复杂图层结构的处理。

通过这种方式,渲染器逻辑可以保持简单、独立,同时我们仍然能够实现正确的层级显示、透明度控制和雾效渲染。这是目前较为理想的解耦路径,也有助于未来优化透明图层合成与性能。

修改 game_entity.cpp:简化 UpdateAndRenderEntities(),只关注 alpha 和雾层级别

我们现在开始处理的问题是简化并改进原先用于雾效(fog)和透明层(alpha layer)处理的机制,目标是去掉不必要的复杂结构,比如 clip_rects,并用更直接的方式控制图层效果。

首先我们决定统一使用一个叫做 fog_amount 的变量来表示当前图层的雾效程度。这个值决定了某个层中绘制元素的透明程度,fog_amount = 0 表示完全不透明,fog_amount = 1 表示完全透明。这种方式比使用复杂的剪裁矩形和分组结构要直观得多。

我们发现,原先的渲染流程中使用了多个剪裁矩形(clip rects),每个图层一个,但其实这些剪裁区域并没有真正被利用起来。事实上,渲染逻辑并没有按剪裁层级处理,而是根据图层的深度排序。因此我们决定彻底移除与 clip rect 相关的逻辑和数据结构。这些代码虽然存在,但并没有带来实质作用,反而增加了复杂性。

在实际修改过程中,我们进行了以下关键步骤:

  1. 去除 clip rects 和相关结构:包括清理 clip_rect_effects、剪裁层设置、相关计算和使用逻辑;
  2. 保留关键控制参数:我们保留了 fog_amounttest_alpha,但 test_alpha 也准备后续移除;
  3. 重构透明度计算:使用 clamp_map_to_range 函数简化透明度的处理逻辑,并将它直接映射到 fog_amount
  4. 统一处理流程:只使用一个默认剪裁区域,不再有复杂的分层裁剪;
  5. 保持雾效一致性:通过直接控制透明度值,雾效仍然得以实现,但实现方式更清晰、更易维护。

最终效果是,原本零散而复杂的图层处理系统被浓缩为一个由 fog_amount 控制的简单机制,渲染逻辑也变得更清晰。现在的系统只关注“当前图层的透明度是多少”,而不需要关心任何额外的图层裁剪信息。这不仅提升了代码可维护性,也为后续优化透明图层的渲染效率打下基础。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

修改 game_entity.cpp:关闭 Alpha

我们现在开始处理的目标是将实体的色彩处理(color blending 或称色盲渲染调整)整合到更系统化的流程中,而不是像之前那样手动进行操作。

当前我们已经可以设置每个实体的 colorblend 值,这个值会影响所有渲染出来的内容。也就是说,我们已经有了让实体根据某种设定改变其颜色的机制,不过这个机制目前还是手动控制的。

手动控制带来一些问题,比如:

  • 渲染逻辑不清晰:因为是手动指定的,有时不容易看清楚具体颜色是怎么被最终决定的;
  • 一致性差:不同部分可能采用不同的色彩处理方式,容易出错;
  • 维护困难:每次想要修改整体色彩表现,都需要手动找对应代码,效率低下。

为了解决这些问题,我们考虑将 colorblend 值直接作为变换(transform)的一部分属性处理。这样一来,在颜色校正(rectification)阶段,统一处理实体的颜色调整逻辑,就不需要在后续渲染流程中手动应用了。这种方式也和之前曾经试过的方案类似,但这次希望将其做得更加干净彻底。

我们开始尝试这个重构思路的初步验证:

  • 首先,将手动颜色调整部分(如 piece_colors 的相关逻辑)移除;
  • 颜色混合的逻辑被整合到了变换流程中,在颜色被“修正”或转换的时候统一应用;
  • 然后通过设置 colorblend 属性来观察最终效果,发现上方图块还没有变透明,说明 alpha 值并未降为 0,这与我们的预期一致,因为现在是从统一变换中推导出来的,而不再依赖额外的手动透明处理逻辑。

通过这一系列更改:

  1. 色彩控制逻辑被统一放入实体变换的属性中;
  2. 移除了原先混乱的手动颜色处理代码;
  3. 渲染结构更加清晰、可控,色彩的处理更加一致;
  4. 后续实现色盲模式、风格变换等视觉模式也更方便。

目前系统已经能够通过实体自身的属性统一控制其在渲染中的颜色变化,达成了更清晰和高内聚的设计目标。后续可继续拓展类似 fog、alpha 等效果进入这种机制,实现完全自动化和数据驱动的渲染控制体系。
在这里插入图片描述

在这里插入图片描述

修改 game_render_group.cpp:让 StoreColor() 接受 object_transform 指针,并修改其它相关函数以接受指针参数

目前我们正在对渲染系统中的颜色处理方式进行一次结构性优化,目的是将颜色混合(如色盲适配、透明度处理等)从全局状态转移到每个对象的局部变换(transform)中,以实现更清晰、模块化的渲染逻辑。

以下是详细的处理与重构思路:


整体目标

  • store_color(颜色存储与混合逻辑)绑定到每个对象的 transform 上,而非使用全局变量;
  • 优化 transform 的传递方式,改为以指针形式传递,避免结构体过大造成的低效;
  • 清理原有的全局状态和不再使用的片段,统一使用更简洁、更清晰的方式处理颜色;
  • 保证 debug 构建时性能仍保持较优,方便调试与跟踪。

具体操作与逻辑说明

  1. 将颜色混合逻辑从全局移入 transform

    • 原先使用全局状态来控制颜色(比如色盲值或 alpha 值),现在我们将其作为 object_transform 的一部分;
    • 每个对象的颜色混合状态将与其变换状态一起传递与生效,更加逻辑一致;
    • 实际渲染过程中,在调用 store_color 时,改为读取 object_transform 中的相关属性。
  2. 使用指针方式传递 transform 结构体

    • 鉴于 object_transform 较大,为避免频繁复制,改为指针传递;
    • 所有涉及 transform 的调用,如 render_layered_scene()reserve()default_flat_transform() 等函数中,均改为传递指针;
    • 这样也便于动态修改其状态或扩展更多临时渲染控制信息。
  3. 清除原本全局变量及杂乱逻辑

    • 统一去除旧有的全局混合状态变量;
    • 去掉在 store_color 或其他与颜色相关函数中曾依赖的外部设置逻辑;
    • 保持 transform 成为唯一权威的颜色变换数据源。
  4. 考虑编译器优化与 debug 构建表现

    • 虽然现代编译器可能足够聪明处理值传递,但为了避免 debug 模式下编译器不优化导致低效,手动选择更直接的方式;
    • 保持调试时可以准确查看 transform 状态,避免因结构内联或优化丢失调试信息;
    • 即便是在非 release 构建下,也追求良好的可维护性和执行效率。
  5. 代码推进与修改方式

    • 在具体推进时,采用系统性的替换策略,将原来传值的地方一律替换为地址传递;
    • 大量是“体力活”类型的重复劳动,但为了系统干净性,这一步是必要的;
    • 借此也表达出对现代开发工具的不满(调侃意义),因为这类操作本该由 IDE 或构建工具自动完成。

当前状态总结

  • 渲染中的颜色混合不再依赖全局状态,而是绑定到每个对象的变换信息;
  • 结构体传递方式更高效,也更适合进行渲染阶段的临时调整;
  • 调试流程变得更清晰,每个对象的颜色逻辑一目了然;
  • 下一步可基于此机制拓展其他类型的 per-object 渲染控制,如模糊、反射、特殊 shader 效果等。

整个过程虽繁琐,但极大提升了渲染系统的模块化与可维护性,为后续开发打下了坚实基础。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

修改 game_entity.cpp:让 UpdateAndRenderEntities() 设置顶层和远处雾的 Color 和 tColor

目前,我们完成了颜色信息从全局状态迁移到每个实体的 transform 中的改造,使得渲染流水线中的所有颜色处理,包括透明度、颜色混合等,都能在每个实体级别进行控制。接下来我们继续完善这套机制,使其能自动响应不同图层的雾效处理。


当前系统的状态

  • 每个实体的 transform 中已可设置颜色信息;
  • 渲染管线中,颜色计算已正确沿着 transform 信息传播;
  • 雾效处理现在也将在 transform 中控制,而非全局控制。

接下来的处理逻辑

1. 每个图层的处理方式根据其相对层级不同而不同:
  • 若为最顶层(即最大层级索引)

    • 该图层属于 alpha 渐隐层;
    • 此时只处理 alpha 混合,不改变颜色;
    • 因此设置 tcolor(临时颜色)时只影响 alpha 通道;
    • 不必指定具体颜色值。
  • 若为非最顶层(普通图层)

    • 该图层需要参与雾效混合;
    • 此时需要将背景颜色设置为混合基底;
    • 并根据该图层的雾效强度设置 tcolor 的 alpha 值;
    • 从而实现逐层雾效递增的视觉效果。
2. 实现逻辑中的变量准备:
  • fog_amount

    • 根据图层索引直接查表或计算获取;
    • 用作 tcolor.a(透明度)的输入;
    • 表示当前层级的雾化程度(0 为无雾,1 为完全雾化);
  • 背景颜色(blend to color)

    • 为雾效混合的目标色;
    • 应设置为全局统一背景色,以保证层叠一致性;

流程示意(伪代码形式)

if (relative_layer_index == max_layer_index) {// 顶层图层,只处理透明度渐隐transform->tcolor = vec4(1.0, 1.0, 1.0, computed_alpha);// 不设置背景混合色
} else {// 雾化层,混合背景色与实体色transform->blend_to_color = background_color;transform->tcolor = vec4(1.0, 1.0, 1.0, fog_amount_for_layer);
}

实现意义

  • 我们现在可对每个实体单独指定颜色与混合方式,无需依赖全局状态;
  • 渲染过程更清晰,逻辑分明,便于维护和调试;
  • 雾效与 alpha 混合统一由 transform 控制,避免状态污染和错乱;
  • 不同图层可以实现独立视觉效果,如逐渐远去的层次感或前景透明渲染。

整个重构过程确保了从数据结构到渲染逻辑的高一致性和高内聚性,为后续扩展更多视觉特效(如光照、遮罩、焦距模糊等)打下了稳定基础。
在这里插入图片描述

在这里插入图片描述

运行游戏,进入 UpdateAndRenderEntities() 并检查 EntityTransform 的值

目前,我们完成了颜色混合逻辑的接入和实体 transform 内部的颜色状态设置,理论上来说,渲染阶段应该可以正确处理这些颜色值。然而,实际运行时并未如预期生效,接下来我们针对这个问题进行排查和修正。


当前问题描述

  • 已经设置了 tcolor 值(代表当前层的颜色混合信息);
  • 渲染器应当在写入颜色时应用该值;
  • 但屏幕上并未看到预期的颜色变化效果;
  • 初步怀疑:可能是默认的 transform 初始化没有设置正确值,或颜色值未被正确传递。

排查过程与发现

1. 默认 transform 初始化问题
  • 默认的 transform 初始化可能将所有字段设为 0;
  • 包括 tcolorblend_to_color 等关键字段;
  • 如果未显式设置,可能导致颜色全为黑或 alpha 为 0,最终不渲染出任何内容;
  • 因此必须确保在创建 transform 时为这些字段赋合理初值。
2. 测试 alpha 值的问题
  • 当前用于控制图层渐隐的 test_alpha 值看起来出现了逻辑反转;
  • 原先的设计是 alpha 趋近于 0 时越透明,但现在呈现出相反效果;
  • 推测是在混合计算中进行了 1 - value 之类的变换,导致计算方向反了;
  • 需确认:tcolor.a = fog_amount 的方向是否与 shader 中 alpha 混合一致。

修正方向

1. transform 初始化中设置默认值:
transform->tcolor = vec4(1.0f, 1.0f, 1.0f, 1.0f);        // 默认完全不透明
transform->blend_to_color = vec4(0.0f, 0.0f, 0.0f, 0.0f);  // 默认无混合背景

确保所有创建的 transform 都拥有合理默认值,避免后续未显式赋值时出现渲染异常。

2. 修正 test_alpha 值方向:
  • 如果目标是从完全不透明 → 渐隐为透明;
  • 那么应确保 alpha = 1 - fade_value 或在 shader 中明确使用该关系;
  • 如果已在 CPU 端计算 tcolor.a,则避免重复反转。

例如,若当前计算为:

float fade = compute_fog_fade(layer_index);
transform->tcolor.a = 1.0f - fade;  // 若 fade 为 0.0 ~ 1.0,表示雾的强度

那就必须确认渲染端是否也是用相同方向的 alpha 混合。


进一步建议

  • 可在调试时设置断点,确认渲染前每个实体 transformtcolor 值;
  • 在 shader 内部打印或用特殊颜色验证是否 alpha 正常传入;
  • 添加日志机制或可视化辅助线,以便快速发现混合结果异常区域;
  • 若调试麻烦,可考虑先将雾效设置为固定值,验证整体渲染路径是否通畅。

综上所述,当前问题主要是由默认初始化和 alpha 值方向反转共同造成的。在修复这些细节后,颜色混合应可按预期工作,渲染效果也将回归正常。整个系统将更具可控性和可维护性,后续扩展更加灵活。

修改 game_entity.cpp:让 UpdateAndRenderEntities() 反转 TestAlpha 的计算

目前整体渲染流程已经趋于正常,颜色混合部分(尤其是 ti_color 的应用)已回归预期状态。我们在渲染阶段可以看到颜色值的正确生效,说明渲染管线中的颜色数据传递与应用逻辑是连通的。


当前工作状态总结

  1. test_alpha 逻辑回退至之前旧版本实现方式

    • 当前的 test_alpha 值处理方式已回归先前设计;
    • 这意味着它与旧逻辑保持一致性,并能在渲染输出中产生正确的 alpha 混合效果;
    • 渲染输出中可以观察到颜色与透明度的变化符合预期。
  2. 渲染阶段颜色混合逻辑正常运行

    • 通过检查 render 内部流程,可以看到 ti_color 的设置已经传递到对应的渲染路径;
    • 整体颜色混合效果如预期工作,说明颜色变换和混合已修复完成。
  3. 雾效部分已具备初步效果

    • 当前雾效(fog)已开始在画面中可见;
    • 可视区域中出现了因图层高度而造成的颜色混合与渐隐;
    • 雾效的起作用区域依赖于图层高度和 fog 混合逻辑,判断是否处于最大图层范围。

下一步操作建议

  1. 增加图层以测试更多雾效变化

    • 当前层数可能不足以完全观察雾效在远距离的混合过程;
    • 建议手动创建更多层级,使视野可以进一步穿透,验证雾效在不同层高下的效果。
  2. 观察并确认雾效是否随高度正确变化

    • 确保随着相机视角或对象高度提升,雾效的混合程度正确改变;
    • 检查 fog_amount 在每一层的计算是否平滑过渡;
    • 确认背景颜色是否正常混合进画面,未出现全黑或未初始化状态。
  3. 记录当前雾效效果基线

    • 当前实现状态已经可作为一个稳定基线;
    • 若后续需调整 fog 的参数(如浓度、渐变速率等),可基于当前结果进行微调;
    • 建议加注注释标记雾效使用的位置和关键点,方便日后维护与扩展。

总结

渲染通道已建立正确的颜色混合流程,test_alphati_color 的设置在各个 transform 上被成功传递与应用,雾效部分也开始在多图层结构中产生可视化表现。接下来可通过增加图层并进行视觉验证,确保整个雾效系统在纵深方向上具备平滑、合理的渐隐效果,至此整个渲染系统的雾效处理进入一个稳定的阶段。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

修改 game_world_mode.cpp:让 AddStandardRoom() 生成多层房间

我们目前将所有相关逻辑整合到了 world_nerd 模块中。接下来,通过调用 add_standard_room 接口,可以批量添加多个房间堆叠在一起,从而测试雾效、图层渲染、透明度混合等功能在多层空间结构中的表现是否正常。


当前状态详细总结如下:

  1. 逻辑已集成至 world_nerd 内部

    • 所有与图层、颜色混合、transform、透明度相关的逻辑现已内聚在 world_nerd 中;
    • 这样做简化了系统结构,使后续调试和扩展更清晰。
  2. 通过 add_standard_room 可连续添加多个房间

    • 可以一次性堆叠多个房间结构,形成多层地图或立体场景;
    • 每个房间都被放置于不同图层,从而形成高度差异,用于雾效等视觉效果的验证。
  3. 支持雾效深度渐变测试

    • 多层房间结构可以直接用来测试雾效随图层变动的渐隐与颜色融合;
    • 可验证 fog_amounttest_alpha 的混合是否随着高度合理过渡;
    • 同时可以确认颜色在不同层之间的混合是否与 ti_color 设置保持一致。
  4. 渲染路径完整运行

    • 添加房间后,相关实体 transform 颜色参数设置已完整接入渲染流程;
    • 渲染系统能够正确响应图层信息并绘制透明度、雾效、背景颜色等。

建议进一步操作:

  • 尝试构建超过三层以上的房间结构,观察不同高度下雾效变化;
  • 在每层房间内加入视觉锚点(如高对比度贴图),以便验证颜色混合是否平滑;
  • 可考虑对 add_standard_room 增加位置偏移参数,构造复杂场景布局;
  • 后续如需更复杂雾效(非线性渐变、基于视角等),可在此基础上继续扩展。

总结

当前我们已成功将雾效与图层透明度逻辑整合进主流程,并通过堆叠房间机制实现了对渲染管线的测试准备。add_standard_room 接口作为核心测试手段,可以灵活创建多层场景,为进一步优化视觉表现提供良好基础。下一阶段可专注于雾效表现细节的调整与性能测试。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

运行游戏,观察多层房间效果,并查看性能分析器

目前我们注意到在堆叠更多房间时,出现了一些有趣的现象,尤其是在与房间渲染和调试模式表现相关的部分。以下是详细情况整理:


当前观察到的关键现象和问题:

  1. close_traversablebuildsberry 表现差异明显

    • close_traversable 是目前少数未使用基本空间分区(basic partition)结构的系统;
    • 相比之下,buildsberry 已启用基本分区,并承担更多空间管理逻辑;
    • 两者在运行负载上出现明显差异。
  2. buildsberry 在 debug 模式下表现不佳

    • 当前 buildsberry 承担了较大的计算压力;
    • 在 debug 模式中性能下降尤为严重,可能存在较多断言检查、未优化的内存访问等问题;
    • 导致调试体验较差,尤其在堆叠多个房间后更明显。
  3. 需要验证优化编译下的表现差异

    • 初步判断,debug 模式的低性能并不能准确反映最终表现;
    • 因此计划在优化编译(optimized build)下进行快速测试,以确认系统是否能在实际使用中维持性能;
    • 拍照或快照操作也计划进行,以便对比渲染输出或用于后续验证。

其他补充:

  • 多房间堆叠测试中也可能揭示基础数据结构(如分区管理、实体变换、颜色混合)潜在的问题;
  • 如果 close_traversable 在未使用空间分区下表现更流畅,可能提示我们需要重新评估 buildsberry 的分区策略或其调试代码复杂度;
  • 图层管理、雾效处理等逻辑已能正确响应不同房间高度变化,进一步支持这类分析。

建议后续操作:

  • 在优化模式下运行 buildsberry 并记录性能指标;
  • 比较其与 close_traversable 的帧率、内存使用和响应速度;
  • 分析 debug 模式下哪些部分导致性能瓶颈,考虑是否能局部优化或加以屏蔽;
  • 若有必要,构建可视化工具追踪房间堆叠与渲染开销的关系。

总结

当前我们已观察到 buildsberry 在 debug 模式下因压力过大表现不佳,而 close_traversable 结构相对简单、无分区逻辑,运行更轻盈。这一对比有助于分析空间分区和调试模式的性能影响,接下来将在优化模式下进一步验证系统在真实运行时的表现,以确定是否需要调整相关模块的架构设计。

“我的天啊!为什么有这么多头在跟着我?”β

目前我们观察和测试了一些关键渲染特性,特别是与雾效、图层、Z轴偏移和渲染分层相关的内容。以下是当前工作的详细总结:


渲染和雾效状态确认:

  • 雾效在底层区域渲染结果正确,这意味着颜色混合、图层透明度、视距遮蔽等逻辑基本正常。
  • 当前我们主要关注的目标之一就是验证这些雾效在特定图层深度下是否呈现正确,目前结果令人满意。

屏幕内容混乱与设计目标偏离:

  • 当前画面中存在大量元素(例如多个“头部”实体)围绕玩家移动,视觉效果令人不适,且不是预期效果。
  • 虽然暂时不会优先处理,但这可能需要后续设计优化,例如减少视觉噪声、压缩敌人数量或行为频率。

图层与 Z 值的问题:

  • 我们现在拥有两个关于 Z 位置的概念:

    1. 图层(Layer)的位置索引;
    2. 实体在该图层内的实际 Z 偏移。
  • 为了实现正确的遮挡、渲染顺序和深度感,需要对 Z 值的处理逻辑进行整合与调整。

  • 目前尚未处理该问题,但已经明确必须要完成此步骤,才能确保系统稳定性和正确性。


图层渗透与可见性异常:

  • 目前观察到有一些图层渗透的现象,例如本应被遮挡的生命条(health rectangles)在不应出现的图层中露出;
  • 理论上完全不应透过主图层,但由于所有元素目前都被统一添加到了渲染图(graph)中,导致可能存在渲染周期或依赖冲突;
  • 如果图中出现循环依赖(cycles),可能导致渲染顺序错误或渲染结果不符合预期;
  • 当前做法违反了最初设计意图,因此将来必须将不同种类的元素(如HUD、实体、背景等)划分为不同的渲染阶段或通道(passes)。

后续改进思路:

  • 明确分离不同类型的渲染元素,例如:

    • 实体渲染 Pass;
    • HUD 渲染 Pass;
    • 后期处理 Pass;
  • 避免将所有元素直接插入单一渲染图结构,以免形成复杂依赖,导致渲染次序错乱;

  • 在分层渲染基础上增加独立的遮挡检测或剪裁策略。


时间限制与开发安排:

  • 当前仅剩大约 12 分钟开发时间;
  • 本阶段不打算深入实现 Z 处理或分层 Pass 管理;
  • 但上述两者已被确认为后续必做的重点开发项。

其他探索:

  • 未来计划尝试一些“技巧型”处理方式,简化图层渲染依赖或通过批次遮挡策略解决局部问题;
  • 这些技巧并不能取代系统级架构调整,但在短期开发周期内可暂时缓解部分问题。

小结:

目前雾效与图层透明处理已基本正确,但仍存在图层渗透与Z轴错位的问题未解决。图层与实体的渲染顺序及结构划分需进一步完善,并通过多 Pass 渲染架构重构,以彻底解决依赖错误和遮挡异常等潜在渲染问题。虽然当下开发时间有限,但整体方向已清晰,接下来的工作将集中在图层渲染逻辑与Z位置处理的完善上。

在这里插入图片描述

考虑始终用实体最初的 ChunkZ 来渲染

我们正在探索一种优化渲染顺序的策略,核心思想是:在进行渲染排序时,始终使用实体在本次模拟开始时所处的 chunk Z 值,而不是他们最终移动结束后的 chunk Z 值。


背后的动机与好处:

  • 实体是在按 chunk 分块的结构中流式加载的,因此它们天然已经是按照 chunk Z 值有序排列的;
  • 如果我们始终使用它们起始时的 chunk Z 值进行渲染排序,就能完全避免重新排序的开销
  • 对于大多数静止或微小移动的实体,这种排序方式不会造成任何视觉错误;
  • 整体上,这是一个性能友好型方案,能够极大减少排序逻辑的复杂度和运行时负担;
  • 从使用感受上来看,这种机制“感觉很好”,具有非常吸引人的简洁性和稳定性。

潜在问题:

  • 当实体正在跨越两个 chunk 边界时(例如从一个 chunk 移动到另一个 chunk),由于使用的是起始位置的 chunk Z 值,会造成某一帧的渲染顺序错误
  • 这意味着在极少数帧中,某些实体可能出现在错误的深度上,从而影响遮挡关系;
  • 不确定这种“错一帧”的视觉错误是否可接受,但初步判断问题可能比较小,尤其是在场景复杂度较高、动作频繁时用户感知不强。

屏幕空间排序的问题:

  • 屏幕空间排序的情况无法直接使用相同优化方式;
  • 屏幕空间错误一旦发生,其可见性和影响非常明显,会造成严重的错位或遮挡混乱;
  • 因此,屏幕空间排序仍然需要更加精确的实时更新与处理逻辑,不能使用类似的“起始状态缓存”方法。

当前结论:

  • 对于 chunk 层级的 Z 值排序,使用模拟开始时的 chunk Z 是一个“几乎白送的优化”,拥有显著优势;
  • 即使存在某一帧的错序风险,其实际影响也可能是可以接受的;
  • 暂时决定采用该方法,以换取性能与逻辑上的清晰性;
  • 屏幕空间排序方面仍需保持精确的实时处理。

后续可能探索:

  • 对“移动中实体”的特殊处理机制,比如在跨 chunk 时标记其状态,特殊处理渲染顺序;
  • 对于屏幕空间排序是否存在某种“近似”优化方式的深入测试与实验;
  • 可视化调试支持,帮助开发中观察是否出现明显的错帧渲染错误。

整体来说,这是一个兼具工程效率和运行效率的优化策略,适合当前架构在性能和复杂度之间找到平衡点。

黑板讨论:排序障碍物

我们正在提出一种改进渲染排序流程的方案,目标是在不影响排序逻辑正确性的前提下,大幅减少排序的开销并提升组织性。核心构想如下:


基本思路

当前我们已经将渲染条目(slices)以一定的顺序写入渲染缓冲区(render buffer)。现在我们设想通过在这些切片之间插入屏障标记(barrier token),来实现**局部排序(bucket sort)**的能力。每一组条目(每一个逻辑层级)在渲染缓冲区中由一个屏障标记分隔。排序时,只需要在两个屏障之间做本地排序即可。


渲染排序流程的改造

我们希望在构建渲染图(build spray graph)时,像这样处理:

  1. 渲染条目会按照逻辑层级(layer)顺序写入;
  2. 在每个层之间插入一个特殊标记,表示这一层的终止点
  3. 排序器(sort_entries)在处理时遇到此标记,立即对当前段内条目执行排序,然后开始新一段;
  4. 这个屏障标记可以使用特殊的 key 值(manual key)来实现,标记其为 barrier 类型。

这样,整个渲染排序就会像分段流水线一样处理,而不是整个一次性排序。


优势

  • 减少无效排序:不需要将全部渲染条目都丢到一个排序器中处理;
  • 提升可控性:每个逻辑层级内部排序彼此隔离,方便调试与分层渲染;
  • 提高性能:局部排序在数据量较小的范围内完成,排序开销更小;
  • 简化实现:逻辑上很容易实现,只需添加对特殊 key 的判断逻辑。

初步实现构想

sort_entriessort_right_bounds 这类排序函数中,实现如下逻辑:

for (int i = 0; i < entry_count; ++i) {if (is_barrier_key(entries[i].manual_key)) {sort_current_bucket(start, i);start = i + 1;}
}

其中,is_barrier_key() 判断是否为插入的特殊标记,sort_current_bucket() 对当前段执行排序。


实际收益场景

  • 渲染层级特别多时,减少全局排序代价;
  • 动态场景频繁变化时,提升帧稳定性;
  • 调试或设计工具中提供更清晰的结构化渲染输出。

下一步计划

这个机制暂时不会立即实施,而是作为后续的改进方向先行构思出来,待到下次继续开发(例如下周)时再投入实现。


总体来说,这是一个低成本、高回报的渲染排序架构改进方案,能带来更强的灵活性与性能表现。

修改 game_render.h:定义 SPRITE_BARRIER_OFFSET_VALUE

我们考虑在渲染排序流程中引入一个“精灵屏障值”(sprite barrier value)的机制,用于优化渲染条目的分段排序。这个屏障值将作为一个特殊标记,用来区分渲染缓冲区中不同逻辑分区的边界,从而实现分段式的排序处理。以下是详细构想:


精灵屏障值机制

设想创建一个特殊的屏障标识符,比如称作 sprite_barrier_value,其作用是:

  • 表示排序边界:一旦渲染队列遇到这个值,就意味着到达了当前排序块的末尾;
  • 不参与排序本身:这个值仅作为排序触发点,排序逻辑不会把它视为普通渲染项;
  • 使排序块分割清晰:不同层或不同逻辑区域被隔离开,每段单独排序。

实现层面考虑

在构建渲染节点时,我们将该值插入到指定位置,作为逻辑层之间的“锚点”:

  • 遍历输入节点时,记录 input_nodes_count
  • 在合适位置(如每个逻辑分层结束之后)插入 sprite_barrier_value
  • 在排序阶段判断当前条目是否是该特殊值,如果是则触发排序操作,并重置段的起始位置;
  • 这个特殊值可以是一个不会出现在实际实体中的保留数值,例如负值或特定 bit pattern。

示例逻辑

排序函数伪代码:

for (int i = 0; i < node_count; ++i) {if (entries[i].manual_key == SPRITE_BARRIER_VALUE) {sort_range(start, i - 1); // 排序当前段start = i + 1;            // 下一段开始}
}

系统集成逻辑

  • 在 build 阶段,处理 input_nodes 并在适当时机插入 barrier;
  • 在排序函数中根据 manual_key 判断是否为屏障;
  • 在渲染时跳过渲染此特殊条目,确保它不被绘制。

意义与优势

  • 排序粒度优化:更高效地控制排序块大小,避免全局排序;
  • 渲染结构清晰:渲染逻辑上更易组织与维护;
  • 性能提升:小范围排序速度远快于整体排序;
  • 支持扩展:后续可扩展为图层隔离、特效区分等更多用途。

总之,这一机制将显著提升渲染排序流程的清晰度与效率,是针对当前层级排序瓶颈所设计的切实可行的解决方案。
在这里插入图片描述

修改 game_render.cpp:让 BuildSpriteGraph() 接收并返回 NodeIndexA,由 SortEntries() 传递

我们现在需要做的事情是,在构建渲染排序系统时引入一个控制排序分段起点的机制,避免全局排序带来的开销。目标是更灵活地控制每段排序的开始与结束位置。以下是详细内容:


核心目标

不再从 0 开始遍历所有输入节点 (input_node_count),而是:

  • 接收一个明确指定的起始值;
  • 通过该值控制每次排序从哪里开始;
  • 实现排序逻辑的分段式执行。

实现步骤

  1. 设定起始值
    在外部控制逻辑(如构建精灵图的函数)中设定一个初始的 first_index 值,初始为 0;

    int first_index = 0;
    while (first_index < total_count) {first_index = build_sprite_graph(..., first_index);
    }
    
  2. build_sprite_graph 中处理 barrier
    在遍历过程中,判断当前节点是否是“排序屏障”,若是,则提前返回当前索引,表示本轮排序终止点:

    for (int i = start; i < count; ++i) {if (offsets[i] == SPRITE_BARRIER_OFFSET_VALUE) {return i + 1; // 下次从这里继续}// 处理正常节点
    }
    
  3. 将屏障值放入 offsets
    为避免污染 manual_key 字段,使用 offsets 来存储特殊屏障值(如 -1 或其他非法偏移):

    if (offsets[i] == SPRITE_BARRIER_OFFSET_VALUE) {// 不进行排序处理
    }
    
  4. 测试机制
    在测试阶段可以插入伪 barrier 值用于验证分段是否正确:

    if (offsets[i] == SPRITE_BARRIER_OFFSET_VALUE) {// 停止本轮排序,测试分段是否有效
    }
    

效果和意义

  • 支持多轮分段构建排序图:每次从上次 barrier 后开始处理;
  • 显著提高排序效率:避免无意义的全量排序;
  • 逻辑清晰可控:通过 barrier 分隔各个图层或区域;
  • 屏障信息存储更合理:利用 offsets 避免干扰 manual_key 等核心字段。

这个方案使我们可以灵活地构建图层隔离、优化渲染处理流程,同时也方便后续扩展,例如支持动态图层、区域遮挡裁剪等。通过精确控制每段渲染数据的边界,进一步优化性能并降低维护复杂度。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

运行游戏,确认没有变化

现在运行的话,基本上处于稳定阶段,暂时不需要做任何改动,系统应该一切正常。但接下来,我们可以回头对之前的方案做出调整和完善,这部分工作计划在周一进行。到时候会根据目前的情况,把之前的改动落实并优化,使整体流程更加合理和高效。
在这里插入图片描述

问答环节

那些召唤兽都从哪儿来的?!

突然出现了很多随从,我们完全不清楚它们是从哪里来的。每个层级都会生成一个随从,具体原因不明。观察了随从的行为,它们似乎只是简单地沿直线移动,代码里它们只检查目标位置是否被阻挡,其他情况似乎没有判断。这些随从行为怪异,让人觉得有点诡异。既然没有更多疑问,我们可以提前结束了。

蛇看起来坏掉了?

蛇的表现很奇怪,只有一节身体出现,像是爬上了楼梯到更高的层级,但其他蛇都正常,没有出现这种情况,这个bug挺诡异的。之前好像没见过这种情况。虽然蛇爬楼梯本身没问题,只要它能及时避开其他物体,但这只蛇跳到了顶层,我们根本不知道它是怎么做到的。估计是代码里关于z轴跳跃高度的逻辑有问题,没对爬升做限制,导致它能直接选择高楼层。场景里还有些断头随从漂浮,画面显得怪异,虽然这些代码不是我们主要关注的,但整体行为怪异,让人感觉诡异和不安。

http://www.xdnf.cn/news/667261.html

相关文章:

  • U 盘数据恢复全攻略
  • 说说 Kotlin 中的 Any 与 Java 中的 Object 有何异同?
  • Go 应用中的 Redis 连接与操作
  • NLua性能对比:C#注册函数 vs 纯Lua实现
  • Nginx--手写脚本压缩和切分日志(也适用于docker)
  • 【Linux】进程状态优先级
  • 【QT】在QT6中读取文件的方法
  • 私服 nexus 之间迁移 npm 仓库
  • Debian 11之解决daemon.log与syslog文件占用空间过大问题
  • pyspark实践
  • [yolov11改进系列]基于yolov11引入感受野注意力卷积RFAConv的python源码+训练源码
  • 手机收不到WiFi,手动输入WiFi名称进行连接不不行,可能是WiFi频道设置不对
  • Matlab实现LSTM-SVM时间序列预测,作者:机器学习之心
  • 链表:数据结构的灵动舞者
  • Linux系统-基本指令(3)
  • 智能体赋能效率,企业知识库沉淀价值:UMI企业智脑的双轮驱动!
  • 【Quest开发】空间音频的使用
  • [AI]大模型MCP快速入门及智能体执行模式介绍
  • HJ25 数据分类处理【牛客网】
  • 小白成长之路-Linux程序与进程(一)
  • Linux 使用 Docker 安装 Milvus的两种方式
  • 记忆术-汉字部首编码记忆
  • APP广告变现,开发者如何判断对接的广告SDK安全合规?
  • CQF预备知识:一、微积分 -- 1.6.1 不定积分详解
  • ctf.show pwn入门 堆利用-前置基础 pwn142
  • 【刷题】数组拼接(超聚变暑期实习笔试)
  • MATLAB实现音频参数均衡器(PEQ)
  • 鸿蒙OSUniApp 实现的数字键盘与密码输入框组件#三方框架 #Uniapp
  • 用AxumStatusCode细化Rust Web标准格式响应
  • 动态防御新纪元:AI如何重构DDoS攻防成本格局