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

游戏引擎学习第298天:改进排序键 - 第1部分

关于向玩家展示多个房间层所需的两种 Z 值

我们在前一天基本完成了为渲染系统引入分层 Z 值的工作,但还没有完全完成所有细节。我们开始引入图形渲染中的分层概念,即在 Z 轴方向上拥有多个独立图层,每个图层内部再使用一个单独的 Z 值来实现 Y 方向上的位移。这种做法在游戏中非常关键,原因在于本项目虽然是二维游戏,但由于其“2.5D”或“2.2D”的设计特性,房间可以垂直叠放,玩家可以通过地板的孔洞等看到上下层的空间。

为了合理地将这种设计呈现给玩家,我们发现需要两种不同类型的 Z 值:一种是用于图形缩放的 Z 值,主要用于体现上下楼层之间的视觉深度感;另一种是标准的正交 Z 值,用于在屏幕上沿 Y 轴移动图像,以表现相对高度关系。之前我们还没有彻底整理清楚这两种 Z 值的使用方式,但现在正处于项目开发向游戏内容过渡的阶段,这个系统需要被规范化和最终确定。

因此,我们当前的目标就是将这两种 Z 值的处理逻辑进一步明确并最终实现到渲染系统中,让它们能够同时协同工作,既提供视觉上的深度感,也能保证场景中物体的绘制顺序正确

回顾内容并为今天的工作定下方向

我们昨天在最后已经完成了将实体划分为不同图层的核心代码,但尚未处理每层图层内的 Z 值使用方式。当前的显示结果看起来有些混乱,比如每个房间本应只有一个蛇和一个怪物,但屏幕上却显示出了两条蛇和两个怪物,还有一个明显被分裂的楼梯。这是因为我们虽然实现了将图块和实体划分到不同图层,但还没有让渲染系统识别和使用这些图层信息来区分不同楼层。

当前的情况是,多个图层的内容全部被“压扁”到同一个平面上来渲染了,因为我们没有将图层层级的 Z 值单独处理并传递给渲染器,而只是统一用了单一的 Z 值,这个值本应该是用来做 Y 轴方向的位移的。现在我们看到的正是第一个楼层和第二个楼层的内容被堆叠渲染到了同一个平面上,才会看到两个蛇和两个怪物重叠在一起。

我们还观察到一个细节:一组蛇和怪物所在的图块会被高亮,而另一组则没有。这是因为底下那一层的蛇和怪物所站立的图块也被处理了高亮逻辑,但因为它们在下层,被上层完全遮挡,看不到。这正说明了图层处理逻辑没有完全实现,导致所有图层被当作一个平面处理了。

接下来的任务就是继续推进这一部分,把每一层的图层 Z 值和实体内部的 Y 位移 Z 值区分开来,并在渲染路径中正确使用。我们需要让渲染系统清晰知道,哪个 Z 是代表图层层级(用于透视缩放、远近遮挡等),哪个 Z 是用来模拟上下位移的,这样才能真正呈现出“上下楼层”的效果。

这部分工作会比想象中复杂,因为它不是一个标准图形系统里的通用流程,没有现成的数学模型或图形标准可以照搬。我们不能单纯地参考现成的 3D 渲染逻辑去解决问题,而是需要根据我们的游戏设计需求,自行定义出一个合理的规则体系,将必要的信息尽量完整地传递下去,同时又避免保存冗余或不必要的信息。这就需要我们不断尝试、打磨流程,直到形成一个既稳定又清晰的渲染机制。
在这里插入图片描述

查看 game_render_group.cpp 中目前透视变换的实现方式

我们进入 game_render_group 模块,查看当前 Z 值的处理逻辑。在这个渲染流程中,我们设置了透视或正交变换,并调用了 GetRenderEntityBasisP 函数,这个函数就是目前进行 Z 值处理的地方。

我们可以明显看出当前的变换流程存在很多问题。在函数内部,X 和 Y 是按照一种特定方式传递和处理的,而 Z 的处理方式却并不一致,显得很不统一。其中 OffsetP 是从自动变换中提取出来的,用于做位置的偏移,它会以一种方式保留 Z 的值。但整体上,这些数据如何组合,以及最终如何计算得到的渲染位置 P,流程显得非常凌乱,存在很多特殊逻辑,缺乏统一结构。

我们现在的任务就是尽可能清晰并统一这套流程,明确 Z 值在系统中的作用和流向。我们希望 Z 的处理方式在整个渲染流程中是连贯一致的,但从当前的代码来看,它不是一个标准的、直通的路径。比如我们并没有在某个固定位置将 Z 值手动地应用到 X 或 Y 坐标上,而是依赖透视变换的某些特性来“自动”地引起 X 和 Y 的变化。这种方式缺乏显式控制,在我们需要细致地手动指定图层深度和垂直位移时就显得非常不足。

所以接下来,我们的目标是:提供一种机制,可以明确地把 Z 值作为渲染控制参数,在变换和排序中手动使用它。我们想要的是一个具备 XYZ 控制逻辑的渲染流程,使得图层 Z 值(用于表示楼层关系或透视深度)和实体自身的 Z 值(用于模拟垂直位移、遮挡关系)都能正确地传递并参与计算,而不是依赖模糊的透视规则或者隐式偏移。

我们需要从底层整理渲染流程,确保渲染系统明确知道 Z 的每个用途,并且能在不同阶段进行恰当的处理。总之,我们的任务就是把这一套混乱的处理方式梳理清楚,构建一个可靠、可控、符合我们渲染需求的 Z 体系结构。

黑板讲解:处理不同楼层之间的“转换点”问题

我们现在面临的一个关键问题,是关于在渲染中如何处理两种不同类型的 Z 值之间的转换,尤其是在“转换区域”(Changeover Point)这块最难处理。

设想一下,我们的游戏中有多个楼层或切片,使用的是一种伪 3D 的视角,允许玩家透过楼层看到下面的内容。这样就会出现一种情况:同一个实体可以处于不同的高度或楼层,然而由于不同楼层的内容会有不同的缩放比例,我们必须解决一个技术难题:当一个实体从一个楼层跳到另一个楼层时,它的图像大小不能瞬间发生变化,否则视觉上会非常突兀。所以我们必须做出一种视觉上的过渡处理。

这个问题的关键点在于:我们不能让缩放的变化发生得太突然。我们希望实体从上层跳到底层(或者反之)时,不是立刻改变大小,而是有一个渐变的缩放过程,带来视觉上的平滑过渡。

为了实现这个,我们需要将整个楼层高度(Z 高度)划分为两个区域:

  1. 缩放区(Scaling Region):在这个区域中,实体的 Z 值将被用于计算缩放比例,同时位置也会随着缩放逐渐改变。
  2. 位移区(Displacement Region):在这个区域中,实体的 Z 不再引起缩放,而是仅仅用于向上或向下的 Y 方向位移(模拟浮动或跳跃等效果)。

举个例子,假设我们当前有两个楼层,上面是第一层,下面是第二层。我们希望实体从第一层跳落到第二层时,逐渐缩小,而不是瞬间缩小。我们会定义一个楼层高度参数(当前叫 typical floor height,之后可能会重命名为 floor height),用这个高度来划分缩放区和位移区的位置。

具体的做法是这样的:

  • 整个楼层高度是一个固定的值,我们在其中划出一个上部区域,称为缩放区;
  • 剩下的部分则是位移区;
  • 当一个实体处于缩放区时,我们根据它的 Z 值去计算缩放比例,并平滑地调整它的位置;
  • 当实体进入位移区后,它的 Z 值不再影响缩放,而是仅仅控制其在 Y 轴上的偏移,从而实现视觉上的高度感,但不会改变图像大小。

这其实相当于我们人为规定了一个“非线性变换区域”,在这个区域中对 Z 值的处理方式发生了变化,从普通的“Z 控制缩放 + 位置”变成了“Z 只控制 Y 位移”。

这个机制必须非常明确地实现,否则视觉结果会很混乱。我们需要决定清楚是由哪个系统(实体系统、渲染系统、还是某个中间层)来负责判断当前实体的 Z 值属于哪个区域,以及什么时候开始或停止进行缩放。换句话说,这里不仅是一个技术实现问题,更涉及到系统设计层面的责任划分。

这个方案并不复杂,但需要小心实现,以确保画面中实体跳跃或跨层时的视觉效果平滑自然。这是目前我们需要重点解决的难点。

黑板讲解:使用“热点规则”在 Y 和 Z 方向上对实体进行排序

我们需要注意并控制的另一个重要问题,是场景中“块状地形”(block)与角色之间在视觉上的遮挡排序问题。虽然目前我们还没有完全实现这个系统,可能会等到有完整的美术素材包之后再展开,但现在已经可以看出一些潜在的复杂性。

在我们的场景中,地面不是完全平的,而是由多个可跳跃的“块”构成,这些块在 Z 轴上可以有偏移。因此即使我们解决了 Z 切片(Z slice)的绘制问题,依然存在一个问题:角色与地面(或障碍物)在屏幕上的遮挡顺序并非总是正确的

作为人类观察者,我们很容易理解遮挡关系,比如一个人在一个柱子后面,那么柱子应该绘制在前面。但计算机并不具备这种直觉,必须通过规则来严格排序。

当前我们使用的是基于 Y 值的排序方法,即对象的 Y 坐标越小(越靠上),越晚绘制,从而“在后面”;Y 越大(越靠下),越早绘制,“在前面”。这个方法在多数情况下是有效的,但当角色站在某些块上,或者和某些立体物体交错的时候,就会出现排序错误。

举例来说:

  • 如果角色站在一个高台上,我们希望他被画在高台的上方;
  • 如果在他前方有一个柱子,即使该柱子与他高度相同,但因为柱子更靠近观察者,它应该被画在角色前面;
  • 如果只是简单地根据 Y 值排序,这种情况容易出错,角色可能会错误地出现在柱子前面或后面。

这个问题进一步复杂化的原因在于当前我们使用的排序点(sort point)是 tile(瓦片或图块)的中心点。如果角色刚好位于这个排序点附近,就会发生错误排序。例如:

  • 当角色刚好站在 tile 的 Y 中心点前方一点,就被当作在它“后面”;
  • 当角色稍微移过中心点一点,就被当作在它“前面”;
  • 实际上这两种情况下视觉遮挡关系是一样的,但计算上发生了断裂。

为了解决这个问题,我们提出了一种思路:

  1. 引入独立的排序点(sort anchor):每个图块或对象可以拥有一个独立设置的排序锚点,而不是固定使用几何中心。例如可以把排序点放在 tile 的底部后方,这样不论角色在 tile 前面还是上面,排序结果都更加符合直觉。

  2. 避免排序点与角色重叠:将 tile 的排序锚点设置在角色无法站立的后方区域,这样可以防止角色与 tile 的排序位置发生交错重叠,确保遮挡正确。

  3. 进一步调整角色的排序点:有时候我们也需要调整角色的排序锚点,使得其遮挡逻辑更合理,例如放在角色脚下或阴影下方的位置。但这样做会引入新的问题,比如当角色站在 tile 前面却排序点落在 tile 后方,就可能会被 tile 遮挡。

因此,这个问题并不是仅靠移动一个点就能彻底解决的。特别是在阴影投射、脚下阴影贴图等细节处理上,更容易暴露问题。

可能的解决方向还有:

  • 使用真正的 3D 空间数据进行遮挡判断:我们本质上是在 2D 中模拟 3D 视图。如果利用已有的 3D 结构信息,在排序时引入视角方向和相对深度信息,可能能解决一部分遮挡问题。

  • 使用 Z-bias 或图层偏移处理特殊情况:比如我们可以对一些 tile 使用 Z 偏移,让它们在排序时更靠前或更靠后,从而调整结果。但这可能只是一个权宜之计,不能完全解决所有情况。

这个问题的本质是:我们希望实现一个理想的视觉遮挡逻辑,而不仅仅是通过坐标排序来完成它。但这种“概念上的排序逻辑”不是传统的 2D 渲染系统能直接支持的,因此必须通过调整排序锚点、引入额外规则、甚至综合使用伪 3D 信息等多种手段配合实现。

这确实是一个非常棘手的问题,目前我们还没有完全确定的解决方案,但可以确定的是,这个问题必须在渲染系统中被考虑进去,并且我们需要设计出稳定的机制来应对各种排序场景。最终不论这个逻辑放在渲染器内部还是外部逻辑层,排序的正确性都是必须要解决的核心问题。

黑板讲解:考虑各种情况以获得更多启发

我们正在思考渲染排序的问题,试图找出一种更合理的方式来决定场景中物体的绘制顺序。场景中存在两种排序因素:一个是Y轴(屏幕空间中的纵向位置),另一个是Z值(表示深度或高低)。而我们的问题主要来自于这些排序逻辑在一些特殊情况下会互相冲突,导致不符合直觉的渲染结果。

我们首先意识到一个现象:当一个对象的Z值低于另一个对象时,我们希望它被画在另一个对象的“上方”——这主要影响的是像阴影这样的贴地元素。当阴影所在的Z层低于角色所在的Z层,我们希望阴影能够正常绘制在地面上。但如果Z层更高,就不希望阴影覆盖角色。也就是说,阴影的绘制主要受Z值控制,而不是Y值。

相对的,对于角色和柱子这种“站立”的物体,它们之间的遮挡关系更多依赖Y轴。比如角色站在较后的地面上,柱子在前面,虽然两者Z值可能相似,但我们希望根据Y坐标判断前后关系。这说明,对于这类“直立”的位图,Y轴排序更加重要。

我们又分析了一些具体情况,比如:

  • 角色站在后方平台上,前方有一个高柱子。这个时候柱子应当遮挡角色,Z值可以帮助我们做出正确排序。
  • 若将柱子下移(Z值变小),它变成了前方的地面,此时我们希望角色能被正确地画在柱子“上方”,以显示角色站在地面前方。这就必须依赖Y排序。

通过这些例子我们得出一个结论:Z值排序在处理“同一个平面上的不同高度层次”时是必要的,而Y值排序在处理“不同前后位置的对象遮挡”时是必要的。问题是,如何将这两种排序方式合理结合。

我们考虑过以下几种排序方式:

  1. 单独用Z值排序:在某些情况下能正常工作,比如绘制阴影。但会出现在Y轴前后遮挡关系错误的情况。
  2. 单独用Y值排序:在大多数情况下可行,尤其是处理前后关系遮挡。但无法处理Z值造成的遮挡和阴影层次。
  3. 先按Y排序,再按Z排序:似乎是最可行的方式,在大多数情况下能得到正确的效果。但在阴影或极端交错层次下可能仍然出错。

进一步思考后我们发现,阴影这种半透明的特殊对象可能需要区别处理。一个可能的解决方案是:将阴影分离为每个地砖上的独立贴图,而不是一个统一的阴影图层。这样可以避免阴影与角色或柱子等物体发生错误排序。通过针对每块地砖绘制阴影,就可以更容易地控制其排序层级。

类似地,对于角色和地面之间的关系,我们也可以通过将角色的排序锚点(sort point)设置在角色脚后的位置来控制其在场景中的绘制顺序。这样即使角色部分身体位于地砖前方,只要锚点正确地放在后面,它就能与地砖正确地排序。

最终,我们认识到要实现正确的视觉效果,必须:

  • 区分处理阴影类和实心类对象;
  • 制定统一的排序锚点规范,使每种对象在排序时能产生正确的结果;
  • 或者进一步引入3D视角信息(比如相机视线方向与对象空间信息),借助更完整的几何结构进行排序判断,尽管这可能会使系统复杂度上升;
  • 排序方式可能需要在“Y值主导”加上“Z值辅助”的基础上做特殊情况处理或修正。

我们还需继续验证这些排序规则在更复杂场景中的稳定性和通用性,并最终将其纳入渲染系统的排序逻辑中。这个问题确实很棘手,是一个不可回避且必须解决的核心问题之一。

黑板讲解:根据实体是否直立决定排序点的位置

我们进一步深入分析了如何设置渲染排序点(sort point)的问题,并尝试提出一种通用且稳定的排序机制。

首先我们考虑到:
对于“平放”的物体,比如地砖、平台等,我们应该将排序点(sort point)设置在它的后缘,也就是靠近玩家的相机方向一侧;
而对于“直立”的物体,比如角色、柱子等,我们应该将排序点设置在物体底部,也就是它与地面的接触点。

这样做的好处是:

  • 如果一个物体是直立的,它的排序点在底部,那么它就会与周围物体在视觉上正确地进行前后遮挡判断;
  • 如果一个物体是平铺的,它的排序点在后方边缘,当角色站在上面时角色的排序点会在更前方(Y值更大),从而角色会被正确地绘制在地面之上;
  • 一旦角色走下地块,Y值发生变化,也能自然地导致角色绘制在地块之后,实现正确遮挡。

在去除阴影这一复杂因素后,这种排序机制基本上可以正确应对各种场景。阴影由于其特殊性(通常为半透明),可通过单独机制处理,因此不纳入这一通用排序逻辑中。

我们得出如下的排序规则流程:

  1. 为每个渲染对象定义一个“排序点”(sort point)

    • 这个点与物体的“锚点”或“放置点”是独立的;
    • 排序点负责决定该物体在整个渲染中的层级位置;
    • 排序点的位置是人为确定的,依据物体是“平躺”还是“直立”来设定。
  2. 排序逻辑

    • 首先按排序点的 Y 值 升序排序(Y 值越小越靠上,越早绘制);
    • 然后按 Z 值 升序排序,作为次要参考,用于处理同一Y值下的高度差;
    • 若还有需要,可以加一个人工的偏移值或“fudge factor”来进一步控制排序细节,例如角色身上堆叠的装备等。

这种机制的优点是:

  • 排序逻辑清晰、统一,便于维护;
  • 可以避免依赖“物体是否直立”的额外标记,完全靠排序点的设定决定其排序行为;
  • 减少了不必要的复杂分支和判断;
  • 更加贴合实际的视觉预期,不易出错。

此外,我们也确认了,这样的排序逻辑将“排序点”的概念与其他如“物体中心”、“碰撞盒”等概念彻底分离开,使其成为一个独立的、专门用于渲染排序的概念。这种独立性有助于在处理复杂场景(例如嵌套对象、复合动画、特效层叠等)时更加灵活、清晰。

最后我们也思考了一些可能的问题,但目前看起来这种方式是可行的,不会引入新的错误逻辑,且简化了整体架构。我们准备继续往这个方向推进,在代码中实际实现这种排序点和排序机制。

查看 game_render_group.cpp 中 PushBitmap 函数的排序逻辑

我们刚才在尝试解决排序问题时引申出另一个与渲染排序密切相关的技术点,因此我们又转向了对现有渲染系统中 push_bitmap 调用及其排序关键字(sort key)机制的分析与优化。

在当前的渲染流程中,当调用 push_bitmap 函数时,我们实际上是通过渲染基础结构(render basis)来计算对应的排序关键字。也就是说,虽然函数可以传入一个排序关键字,但真正使用的排序关键字通常是通过 push_render_element 这一步计算得出。

这意味着,如果我们想根据刚才讨论的“排序点”规则来准确地指定每个图像的排序方式,我们可能需要在 push_bitmap 调用的过程中就自行计算并传递排序关键字。这就要求我们对排序关键字的组成有明确的控制权,而不能完全依赖当前自动生成的方式。

    Result.SoftKey = ObjectTransform.SortBias +4096.0f * (2.0f * P.z + OriginalP.z + 1.0f * (real32)ObjectTransform.Upright) -P.y;

接着我们分析了代码中的 CAlign 值,这个值主要是用于调试场景中的。它的作用是让某些特定的图像在调试时不被其他渲染对象遮挡,属于一个专门为调试需求添加的临时机制。在实际运行中它并不影响渲染排序的逻辑,因此并不需要纳入我们目前排序系统的调整范围。

回到排序关键字本身,我们注意到 basis_sort_key 的生成逻辑是基于 camera_transform 等信息的,其中最关键的是单位换算的 meters_to_pixels 值。我们意识到这可能会对排序精度或一致性造成影响,特别是在不同尺度转换或透视变化下。然而,如果场景中所有对象都统一乘以相同的换算比例,那么这部分理论上不会影响相对排序,只需保持整体一致性即可。

因此,我们得出如下思路与计划:

  1. 排序关键字需要手动生成并传入
    为了实现我们前面讨论的基于“排序点”的渲染顺序控制,我们打算在 push_bitmap 调用中,显式地生成并传入排序关键字,而不是依赖渲染系统自动生成。排序关键字由排序点的 Y 值与 Z 值组合而成。

  2. 保持现有摄像机变换的统一性
    即便 meters_to_pixels 会影响最终值,只要所有物体的换算一致,排序仍是正确的。因此我们不打算修改换算机制,而是在生成排序关键字时,确保使用一致的单位处理。

  3. 需要建立统一的排序关键字生成函数
    我们需要在渲染接口层抽出一个统一的逻辑函数,用于从“世界坐标下的排序点”生成标准的 sort_key,并封装到 push 调用中,以统一各处用法,避免每次都重复计算。

  4. 暂时忽略调试相关的 CAlign
    这部分不影响正式渲染流程,我们不计划在此阶段优化它。

总之,我们发现,要将“排序点”机制成功引入现有渲染框架,必须对排序关键字的生成过程进行改造,使其可控、透明、与视觉逻辑一致。这是一个关键步骤,也是我们正在着手解决的问题。接下来会进入实际编码阶段,尝试将这些机制嵌入渲染管线。
在这里插入图片描述

修改 GetRenderEntityBasisP,使其根据 PerspectiveZ、DisplacementZ、PerspectiveSortTerm、YSortTerm 和 ZSortTerm 计算 SortKey

我们在进一步推导渲染排序系统的细节时,确立了一种新的方式来处理排序关键字的生成与位图的排序偏移问题。整个思路是围绕“排序点”与实际图像渲染位置之间的差异展开的,目标是实现更灵活、更精准的图层排序逻辑,特别是在涉及图像中心与排序基准点不一致时。


引入“排序点”的第二输入

我们决定在渲染系统中引入一个第二个点用于排序。这个排序点可能是与图像实际中心不同的位置,目的是允许我们为不同的图像类型(比如平放和直立)指定不同的排序基准。

我们通过以下方式来处理这个排序点:

  • 将其视为相对于实际渲染位置的一个位移(delta)
  • 在变换完毕之后,再将这个位移值应用到已变换的位置上。
  • 位移值会受到缩放(scale)的影响,因此需要将位移乘以相应的缩放因子。

将排序偏移视为“排序偏差”(Sort Bias)

我们统一将这种相对位移处理为一种“排序偏差”,主要表现为:

  • X 方向上的位移对排序无影响,可以忽略;
  • Y 方向上的位移影响视觉遮挡,排序需考虑;
  • Z 方向上的位移用于微调相对高度(用于叠加逻辑,例如角色头部、物品堆叠);

这意味着排序关键字其实是由三个维度组成:

  1. 层级 Z(Layer Z):用于区分场景层级,比如背景、前景、UI 等;
  2. Y 排序值(Y Sort Term):代表对象在平面中的前后位置;
  3. Z 排序值(Z Sort Term):代表在同一 Y 值下的精细叠加顺序;

我们提出的排序公式为:

最终排序值 = Layer Z(最优先) + Y(次优先) + Z(最低优先)

调整向量结构,显式表达排序维度

为避免混淆,我们打算将传入渲染系统的坐标向量从原本的 v3 扩展为 v4,具体含义为:

(x, y, displacement_z, layer_z)
  • x, y:用于实际位置变换;
  • displacement_z:微调用于 Z 排序的偏移;
  • layer_z:作为最重要的排序维度,用于强制层次隔离;

我们将 displacement_z 放入 affine(仿射)部分,是因为它代表偏移;而将 layer_z 放入 perspective(透视)部分,是因为它决定整个排序层。


排序关键字的生成逻辑清晰划分

整个排序关键字的生成被拆解为三个明确的部分:

  1. 透视排序项(Perspective Sort Term):基于 layer_z 产生,是最优先的排序指标;
  2. Y 排序项:用 y + sort_bias_y 表达,是主排序手段;
  3. Z 排序项:使用缩放后的 displacement_z * scale 得到,是补充层内排序的依据;

这种方式带来的好处是排序逻辑清晰、结构统一、计算可控,也不会因渲染偏移中心不同而产生视觉错乱。


排序点的使用策略与接口设计建议

我们目前在接口层的设计中有两种方式可以选:

  1. 显式传入排序偏移(sort bias)
    保留位图居中机制,由调用者传入 Y 排序偏移值。

  2. 直接将图像锚点放在排序点
    所有图像都在排序点进行放置,bitmap 居中逻辑内置处理。

我们暂时没有明确倾向于哪种方式,两种方式都可行,主要依据是否需要灵活控制位图的排序锚点。如果选择第二种方式,我们可以省去很多偏移计算,让排序逻辑更简单。


小结

当前渲染排序系统的更新主要集中在以下几个核心改进点上:

  • 引入排序点(与实际渲染点分离);
  • 排序关键字三层划分:Layer Z、Y、Z;
  • 使用 v4 向量明确传递排序相关维度;
  • 排序偏差(sort bias)作为独立概念处理;
  • 接口可以进一步抽象,统一处理缩放与位移逻辑。

接下来我们将逐步修改现有的渲染代码路径,确保排序关键字的生成与上述逻辑保持一致,确保各种场景下排序行为稳定、正确。

在这里插入图片描述

考虑位图中存在两个重要点时的风险,以及它们对齐的程度

我们目前遇到的问题在于,一个位图上存在两个“关键点”的概念,而这两个关键点在功能上是不同的:


两类关键点的定义与用途

  1. 对齐关键点(Alignment Point)

    • 这是用于确定位图在空间中旋转和缩放的参考点。
    • 例如当进行旋转或缩放操作时,该点保持不动,其余部分围绕它变化。
    • 类似于“锚点”或“中心点”,影响图像的视觉变换方式。
  2. 排序关键点(Sorting Point)

    • 这是用于在渲染队列中确定图像前后关系的点。
    • 一般反映图像“落在地上的位置”或“遮挡基准”,用于决定绘制顺序。
    • 该点对渲染顺序敏感,但对几何变换(如旋转)没有直接影响。

二者之间的一致性问题

我们在思考这两个关键点在实际使用中是否一致,即它们是否经常是同一个点。

目前观察来看:

  • 在某些情况下,这两个点可以是同一个,例如:顶部对齐的立式人物贴图,旋转和排序都以脚底为基准。

  • 但在其他场景中,这两个点可能不重合

    • 比如一个从顶部对齐缩放的物体,但它的排序是根据底部计算的;
    • 又比如一个横向漂浮的UI元素,其中心旋转,但排序参考左下角。

因此,我们不能默认这两个点相同,否则会导致排序与变换出现冲突或不一致。


接下来的思考方向

我们需要进一步考虑以下问题,以完善系统设计:

  • 是否需要显式分离对齐点与排序点?

    • 可以在系统中明确两个参数,一个用于变换,一个用于排序;
    • 可以统一接口格式,但在内部处理逻辑上保持二者独立。
  • 在实际使用中,对齐点和排序点之间的默认关系是否应保持一致?

    • 如果默认一致,是否提供“解耦”开关供特殊情况使用?
    • 如果默认不同,是否会增加使用负担?
  • 是否存在第三类关键点,例如碰撞检测点?

    • 如果存在,是否也应当设计为独立参数处理?

总结

我们目前识别出位图存在两个独立功能的“关键点”:

  1. 用于旋转/缩放的对齐点;
  2. 用于渲染排序的排序点;

它们在部分场景中重合,但不能默认相同。接下来需要在系统中显式支持这两个点的独立性,以避免排序与变换逻辑之间的混淆,从而确保渲染行为在各种使用情境下都能正确工作。

黑板讲解:对多段式身体结构的排序问题

我们考虑一个多段式的身体结构,例如一个关节连接的人物角色,这类角色已经存在并且未来会越来越多。这种结构中,每一部分通常是独立的位图,比如躯干、手臂、腿等,这些部分通过关节相连,并作为整体进行旋转或移动。


位图对齐点的意义

当我们要围绕某个点进行旋转时,这个点显然应当是该位图的对齐点(alignment point)——通常是关节或连接点:

  • 例如旋转上臂时,其对齐点应在肩部;
  • 旋转小臂时,对齐点应在肘部。

这部分逻辑是明确的,也非常合理,当前系统对此支持良好。


位图排序的问题:组合体作为一个整体排序

但问题在于这些位图的排序方式:

  • 这些位图应该作为一个整体进行排序
  • 排序应该基于整个角色的某个公共参考点(例如脚底中心、角色根节点等);
  • 不是每个部位按自己的排序点单独参与全局排序

换句话说:

  • 排序基准只需要一个;
  • 内部结构的排序仅依据预设的绘制顺序或层级,而不依赖各自的空间位置。

例如:

  • 一条腿可能由大腿、小腿、脚三部分组成;
  • 虽然它们在空间中有不同的Y坐标,但它们在渲染时应始终依照一个整体排序规则
  • 是否遮挡别的对象,只看整个腿的“参考点”在哪,而不是单独考虑脚或大腿的位置。

内部排序由结构定义,而非位置推导

对于这种组合体结构:

  • 各子部分之间的渲染顺序,应由固定的结构顺序或“艺术家意图”定义;
  • 而非根据每个位图的世界坐标Y值或Z偏移自动计算。

这样可以避免由于肢体的空间变化造成不合理的遮挡错误,例如:

  • 肘部比肩膀略低,不应该因此被提前渲染;
  • 脚掌向上踢起,也不应因为Y值更高而被遮挡掉其他身体部分。

总结与结论

我们需要区分以下两层排序逻辑:

  1. 整体排序

    • 多段组合体整体只使用一个排序关键点;
    • 决定其与其他对象的前后关系。
  2. 局部排序

    • 组合体内部的各部分遵循固定绘制顺序;
    • 不依赖位置坐标排序;
    • 与位图的对齐点(用于旋转)分离。

因此,我们系统应支持:

  • 设定“组合体排序基准点”,所有子元素共享它;
  • 子元素按指定顺序绘制,无需单独参与全局排序;
  • 对齐点独立用于每个子元素的局部变换(旋转、缩放等)。

这样才能在多关节角色中实现既准确的空间遮挡逻辑,又灵活的动画变换效果。

在 game_render_group.cpp 中引入 ComputeSortKey 以支持正确排序关节式人物

我们目前的结论是:排序点(Sort Point)应该是一个完全独立于位图变换流程之外的概念,并不属于单个位图或其局部变换参数中的一部分。


排序点的独立性和来源

我们在做投影、位移 Z(Displacement Z)等操作时,实际参与排序的值,应该来自独立计算得到的排序键(Sort Key)

  • 排序值不应该由每个位图单独计算;
  • 而是由上层逻辑(例如角色、组合体、实体等)统一设定;
  • 这个排序键在构建角色或组合体时一次性生成,然后被传递给所有子部分共享使用。

举例:

  • 一个多关节人物由多个子位图组成,如头、躯干、手臂、腿等;
  • 排序值应在角色创建阶段就被确定,例如设定为“角色根部位置 Y”;
  • 所有子位图在绘制时,都使用这一共同的排序键;
  • 子位图之间仅通过局部绘制顺序控制彼此的显示层级,而不参与全局排序运算。

排序键的传递与使用

当我们调用渲染逻辑并执行相关变换时:

  • 排序键可以作为一种对象变换的一部分被传递下来;
  • 或者以显式参数的形式传入,独立于其他空间变换参数;
  • 这个排序键由调用者提前准备好,并统一用于组合体内的所有元素。

换句话说:

  • 位图变换继续用于控制位图的旋转、缩放、位置等;
  • 排序值不再依赖于单个位图的计算;
  • 排序键是更高层抽象所提供的内容,例如角色系统、层级系统等。

组合体内部的排序:相对结构,而非计算推导

组合体内部的排序应依赖于预设的结构性信息,如:

  • 层级定义;
  • 局部顺序;
  • 美术数据文件中的绘制顺序。

而不应依赖各个组件的实际空间坐标。


总结

我们的渲染排序逻辑应当遵循以下结构:

  1. 排序键应由外部系统提前设定
    排序点(Sort Point)在角色或组合体初始化时就确定,并在所有子元素中复用。

  2. 排序键从对象变换中解耦
    位图变换(Transform)专注于变换,排序键专注于渲染层级判断。

  3. 组合体内部排序基于结构性定义
    子元素不再参与全局排序值的计算,而是根据结构或手工设定顺序绘制。

这种方式能带来更清晰、更可控的渲染排序逻辑,尤其在处理复杂的关节角色、UI组件集合、动态组对象等场景时,能有效避免遮挡错误与逻辑混乱。

暂时撤销 GetRenderEntityBasisP 中的修改

目前我们在一天的开发尾声,对渲染排序逻辑的一些实现细节进行了反思和回退,同时进一步明确了对投影坐标中 Z 值的处理策略。


回退当前修改的原因

我们决定临时撤销部分代码更改,原因在于:

  • 排序逻辑目前仍存在一些不确定性,特别是在缩放因子与排序值之间的关系上;
  • 投影坐标中的 Z 值(即 Perspective Z)是否应当和其他值一起缩放目前尚未完全确定;
  • 为了保持系统稳定性和后续调试清晰性,暂时选择回退更改,保留已有的处理路径。

投影 Z 值(Perspective Z)的缩放疑问

我们目前面对的问题是:

  • 是否应该对透视 Z 值应用缩放(scale)操作?
  • 投影后的 Z 值参与排序时,是否应该和 XY 变换中使用的 scale 保持一致?

目前的想法如下:

  • XY 投影值在渲染系统中经过了统一的单位转换(如米转像素),Z 值作为排序参与者,是否也应该被一致地转换,目前不确定;
  • 如果系统中所有值都统一应用了缩放(包括 bitmap 位置、尺寸、偏移等),那么 Z 值也跟着缩放会更一致;
  • 但另一方面,Z 是用于排序的逻辑值,是否需要和屏幕空间保持一致,取决于排序设计本身。

小结

我们现在的决定:

  1. 暂时回退排序处理相关的代码改动
  2. 保留当前投影 Z 值的处理方式,避免对其缩放,直到理清是否需要一致处理
  3. 后续在统一的排序策略确定后,再决定是否对 Z 值进行缩放处理

这是一个稳妥的做法,既保留了系统现有的稳定性,又为后续更清晰的排序逻辑设计预留了空间。
在这里插入图片描述

运行游戏并观察混乱效果

目前我们正在为接下来的开发周期做出规划,基于当前渲染排序系统的改进方向,决定从下周开始进入“世界模式”的构建阶段。在这一阶段,我们的重点是将已有的基础机制整合成更完整、统一的排序和渲染系统。


当前状态总结

  • 基础排序机制已初步建立,部分位图对象的排序和渲染方式已经可用;
  • 位图组排序的基本逻辑已被清晰描述,接下来需要将其以更系统的方式实现;
  • 部分临时性调试代码(如错误注入、临时标记逻辑)已被移除,以保持代码清洁;
  • 整体系统进入了可以进一步测试和稳定化的阶段。

下一阶段目标:“世界模式”构建

计划从下周开始:

  1. 进入“世界模式”工作流
    将当前“位图片段”和“渲染排序”机制,提升为处理完整“世界对象”集合的框架;

  2. 实现完整的排序组逻辑

    • 构造排序组(sort key group)机制;
    • 允许一个对象的多个组件共享一个主排序键;
    • 在构建排序组时指定排序锚点,确保组内排序有统一参考点;
    • 实现“整体对象排序 + 内部固定结构”的组合渲染。
  3. 定义并完善排序流程

    • 明确“开始一个 sort key”的入口;
    • 实现“排序组起始 + 自动传播 + 排序锚定”的自动化逻辑;
    • 提供必要的接口或宏,将其集成到渲染调度流程中。

小结

我们已为世界对象的统一渲染和排序打下基础,接下来将:

  • 构建以排序键为核心的统一分组系统;
  • 将片段渲染逻辑提升至完整对象渲染逻辑;
  • 明确各个片段之间如何协作排序,以实现正确、稳定的可视输出。

这将使整个渲染系统从“逐个位图拼接”提升到“整体世界分层排序”的完整阶段。
在这里插入图片描述

制定在 game_entity.cpp 中处理关节式人物的计划,留待下周

我们为下周的任务制定了明确的计划,目标是在几天内彻底理顺当前的渲染与排序机制,重点集中在**支持带骨架结构的可动角色(articulated figures)**的渲染提交流程上。具体工作内容与目标如下:


渲染提交逻辑的重构目标

  1. 以骨架整体创建排序键(sort key)
    在渲染提交流程中,先为整组可动结构创建一个统一的排序键;

  2. 分片提交顺序保持稳定
    所有从属于该骨架的子组件(如身体各部位)在被统一插入渲染序列后,仍然能根据它们被提交的先后顺序稳定渲染;

  3. 剔除多余机制,简化接口

    • 当前使用的“排序偏移(sort bias)”和“位移量(displacement)”机制可被移除;
    • 位图的对齐点(pivot point)已通过正确机制定义,所有定位与旋转基准可通过对齐点完成,无需额外偏移;
    • 用户现在可以直接控制排序值,避免在渲染组内部进行复杂调整;

排序键生成与传递机制优化

  1. 实现排序键变换函数(sort key transform)

    • 实现一个可以将实体基础信息变换为排序键的函数;
    • 此函数将在渲染准备阶段调用,负责返回最终用于排序的键值;
  2. 排序键类型调整为 u32 二元组u32 x, u32 y):

    • 将浮点型排序值转换为整型,便于之后进行高效比较与组织;
    • 利用低位保留原始提交顺序(或用于手动设置局部排序权重);
    • 或者使用稳定排序算法(如 stable sort)来自动保留相对顺序;

总结

通过上述改动,我们将实现:

  • 可动角色的各个部位以统一的排序锚点进行整体渲染;
  • 渲染顺序既受全局排序控制,又能保持本地一致性;
  • 彻底清除冗余的偏移量与排序偏置参数,接口更清晰;
  • 排序键结构简洁高效,适配当前渲染系统架构。

这将极大提高排序系统的灵活性与可控性,为接下来的更复杂角色和分层世界的支持打下基础。

问答环节

从邮件中没太明白——你还打算休息一下并做一个编程入门系列吗?

我们讨论过程中明确表示,不打算进行所谓的“利率编程”相关工作,也不记得曾经承诺过要做这件事。这部分内容明确了目前的工作重点不会涉及相关方向,也不会中断当前的开发任务去处理这些不相关的内容。

在澄清这一点之后,我们继续推进当前的技术探讨,提出了另一个关于 YZ 方向的使用场景值得考虑。这说明尽管我们否定了与利率编程相关的内容,但仍然专注于渲染系统中与空间变换和排序有关的问题,尤其是涉及到 YZ 平面或坐标轴方向的场景处理,可能关系到视角、深度或图层分离的精度处理需求。我们将继续围绕这类问题进行探索和优化。

一个关于 Y/Z 排序的额外用例:“平面”物体叠放在其他“平面”物体上(比如地毯在地板上),如果上面的物体比下面的还大,这种情况该怎么设置 Y 排序位置?

我们探讨了一个关于排序的特殊场景:在一个平面物体上方叠放另一个平面物体,比如一张地毯铺在地板上。在这种情况下,如何设置 ZY 方向的排序位置尤其复杂,特别是当上层物体的尺寸大于下层物体时,会造成覆盖范围广、可能涉及多个底层图块的问题。

我们倾向于将这种情况视作与关节式结构类似的情形来处理,即作为一个整体来排序。初步思路是:只要这些物体在“地板块”层面上是分割好的,那么排序可以通过 Z 值来简单管理。也就是说,只需要确保上方物体的 Z 值略高于下方物体,就能保证正确的渲染顺序。

不过我们也认识到,如果上方的物体尺寸覆盖较广,横跨多个底层图块,那这种简单的 Z 值方式可能并不完美,很难在所有情况下都确保视觉上严格正确的排序。这方面缺乏一个绝对可靠的处理方案,目前还不确定如何在所有复杂情境下实现理想的视觉效果。

这个问题提示我们,在设计排序系统时,可能需要引入更复杂的处理机制,比如明确的遮挡优先级、区域划分、或者特殊规则来处理类似地毯铺地板的情形。我们会在之后的实现中持续探索更健壮的解决方式。

你最初想比 C 语言入门更深入一些,后来有没有改变想法?

我们提到了一些未来想要实现的更复杂或更严肃的功能或系统,虽然早期曾经有过相关的设想或者讨论,但明确指出这些内容不会在当前阶段实现。当前的重心仍然是集中在完成现有的核心项目,也就是说,只有在这个主要项目(例如我们正在推进的)完成之后,才会考虑进入这些更深入、系统性更强的方向。

这表明我们目前的开发节奏是阶段性的,优先级清晰:先保证当前项目的完整与质量,之后才是探索更加复杂的新领域。此外,这也强调了一个现实的开发策略——并非所有愿景都能立刻实现,而需要根据项目周期和资源安排逐步推进。

因此,尽管我们对某些更高级的系统或实验仍然感兴趣并保持愿景,但这些工作被明确推迟到未来某个较为自由或后续的阶段来进行,而不会干扰当前项目的推进。我们保持计划性,同时也为未来预留了可能性。

你怎么看把实体表示为简单的 3D 物体,比如用圆柱表示人物,用立方体表示瓷砖和房间?

我们探讨了在渲染中使用简化的三维几何体(比如用圆柱体代表角色、用立方体表示瓦片和房间)来简化处理流程的可能性。这种做法的一个主要优点是可以规避使用纯二维位图在排序上容易出现的问题,尤其是在依赖Y轴进行深度排序时,排序的稳定性和可预测性很差。

我们认为,可能使用带有Z值的“板子”(plates)来承载每一个精灵图,是一种合理的折中方法。这在现代游戏开发中其实已经比较常见:每个精灵都会映射到一个扁平的3D平面上,而这个平面可以拥有一个Z值,从而借助Z缓冲(Z-buffer)或其他深度测试机制来进行排序。这样做可以大大减少我们在排序算法上遇到的问题,并提高图形渲染的一致性与可控性。

不过,我们同时也表达了顾虑。当前项目的初衷是尽量避免引入复杂的三维系统,因为那可能会让项目开发的门槛变高、学习曲线变陡。而这正是我们希望通过保持“纯2D”来规避的风险。因此,现在还不能确定我们是否最终会选择引入3D“板子”的方式。或许目前的方案已经足够用了,也可能随着项目深入,我们不得不引入一定程度的3D支持来解决无法绕开的问题。

整体来看,我们保持开放态度:倾向于先以简单方案推进,观察其效果,再决定是否引入更复杂的3D机制。这样既控制了复杂度,也保留了升级空间。

黑板讲解:地毯与地板的排序方式

关于地毯覆盖在瓷砖上的排序问题,确实存在比较棘手的情况。这个问题的核心在于如果我们只是单纯按照Y轴进行排序,会出现矛盾:地毯应该覆盖在瓷砖之上,所以瓷砖不应该被排序到地毯前面;但如果给地毯设定一个排序点,使其在所有瓷砖之后,那么一个站在地毯上的角色可能会被地毯“遮挡”,排序反而出现了错误。

这个排序矛盾的根本难题是排序点的位置无法兼顾这两种关系:既让地毯在瓷砖前面,又不让地毯遮挡站在它上的角色。换句话说,排序点不能简单地用一个固定的Y值来表示,因为不同元素的空间关系更加复杂。

因此,我们认为要真正解决这个问题,需要从3D空间的角度来重新思考和处理。在3D中,排序会考虑物体的实际深度和空间位置变化,可能需要对排序值进行插值处理。比如,地毯是平面的,深度值在它的范围内基本不变,而瓷砖的位置深度是连续变化的,这样才能更准确地反映视觉关系。

然而,即使是这样,我们也没有碰到过真正交叉重叠的情况,理应不存在深度值交错的问题,所以其实不需要处理物体深度“变化”的复杂情况。问题在于如何找到一种合理的表达方式来准确表示这些物体间的层级关系和空间关系。

总的来说,现阶段这个排序难题还需要更详细地在三维空间里做推敲和验证,只有理解了3D中物体的空间关系和深度排序机制,才能设计出在二维排序中也能合理复现的解决方案。

黑板讲解:将其放入 3D 空间中考虑

我们在观察一个场景时,如果有类似这样的物体排列,我们可以知道某个物体是在另一个物体的前面,因为从相机视角出发,射线在穿过第一个物体的时间点会比穿过第二个物体的时间点更早。换句话说,当射线从相机发出时,哪个物体先被“遇到”,那个物体就排在前面。

具体来说,这里的关键是射线与物体平面的交点以及交点在相机平面上的投影位置。不同的物体平面在相机视角下形成的交点路径不同,比如一个物体的交点沿着某条线移动,而另一个物体则是沿另一条线移动,这种差异决定了它们在视线上的遮挡关系。

此外,即使两个物体整体看上去一个在另一个前面,但它们的不同部分可能会有不同的排序关系:物体的一部分可能在前,而另一部分则不在前。这说明排序不能只考虑整体,而要关注具体平面上的具体部分。

目前,我们对这个问题的理解还不够完整,这涉及到空间中物体的深度关系和它们在相机视角下的表现,排序的复杂性超出了简单的规则。还有更多细节和原理需要进一步研究,才能找到更准确的排序方法,确保在实际渲染中各个部分能够正确表现前后关系。总结来说,这个问题需要更深入地分析和探索,我们的理解还有待完善。

或许你需要在 Y 和 Z 方向分别对物体排序,然后在 A 在 Y 排序中排在 B 前而 Z 排序中排在 B 后时解决冲突?

考虑到排序问题时,有一个想法是对物体分别按Y轴和Z轴进行排序,然后再解决两者排序冲突。具体来说,如果物体A在Y轴排序中排在物体B前面,但在Z轴排序中却在物体B后面,那么这种冲突需要特别处理。理论上,如果一个物体在X、Y、Z三个维度上都排在另一个物体前面,那么排序应该是无误的。

不过,这个方法虽然有趣,但仍然存在困难。一个主要问题是,物体并不是只有单一的Y值,它们可能在空间中占据范围,这使得单纯按Y值排序变得复杂,容易出现模糊或矛盾的情况。为了避免这些问题,不应简单“作弊”地把物体放在不同位置来适应排序。

同时,也有疑问是否过于复杂化问题,或许真正关键的只是Z值(深度值)。也就是说,或许Z轴排序才是我们最应该关注和依赖的。

总的来说,这个问题还没有完全搞明白,需要更多时间和专门的探讨,甚至可以专门开一个完整的讨论环节来彻底理清这些排序冲突和逻辑。现在还没有找到最终的解决方案,理解还不够全面。

相关文章:

  • Java 单元测试框架比较:JUnit、TestNG 哪个更适合你?
  • 宿州金博学校开展防震演练:夯实安全根基,守护校园平安
  • 机器学习算法-聚类K-Means
  • 凸优化系列——First-order method
  • RestFul操作ElasticSearch:索引与文档全攻略
  • DeepSpeed简介及加速模型训练
  • Spring Boot中如何使用RabbitMQ?
  • 【Go-2】基本语法与数据类型
  • Qt动态生成 UI
  • 零基础深入解析 ngx_http_session_log_module
  • 系统架构设计师软考要点分析及知识学习指南
  • 【Python装饰器深潜】从语法糖到元编程的艺术
  • 人工智能如何做主题班会PPT?
  • 量子计算的曙光:从理论奇点到 IT 世界的颠覆力量
  • 鸿蒙HarmonyOS多设备流转:分布式的智能协同技术介绍
  • 【华为鸿蒙电脑】首款鸿蒙电脑发布:MateBook Fold 非凡大师 MateBook Pro,擎云星河计划启动
  • BRIGHTONE : 520-On-Chain WOHOO Carnival
  • TYUT-企业级开发教程-第8章
  • 基于规则引擎与机器学习的智能Web应用防火墙设计与实现
  • 【数据库课程设计】网上投票管理系统
  • 翻越高山,成为高山!浙江广厦成CBA历史第八支夺冠球队
  • 兴业证券:下半年A股指数稳、结构牛,中国资产重估刚刚开始
  • 越秀地产约41.49亿元出售北京海淀功德寺项目公司65%股权,此前已质押给华润置地
  • 多名幼师殴打女童被行拘后续,盘锦教育局工作人员:该局将专项整治全市幼儿园
  • 文化厚度与市场温度兼具,七猫文学为现实题材作品提供土壤
  • 首届巴塞尔艺术奖公布:大卫·哈蒙斯、曹斐等36人获奖