游戏引擎学习第204天
回顾并为今天的内容做铺垫
好,现在开始这一集。今天我们将进行一些用户界面编程,觉得这是一个展示如何编写这类代码的好时机。很多人对如何做用户界面代码都很好奇,所以展示一下如何编写是非常有意义的。
我之所以在现在的这个地方做这些工作,是因为对于目前的游戏来说,并没有太多理由去做复杂的用户界面代码。毕竟,这不是一个需要复杂界面的游戏,没有很多工具栏、对话框或者其他类似的东西,游戏的类型是动作游戏。因此,我利用这个机会,编写一些用户界面代码。
我们目前正在做的是一个调试查看器,基本上就是一个查看调试信息的工具。我们在这个功能上已经取得了相当大的进展,能够做一些很酷的事情。但仍然有更多的工作要做,所以今天我想继续在这个方面进行编码。
运行游戏,看看当前进展
好,现在来看一下目前的进展。这里是我们的键盘系统,正在运行的状态。上次我们离开时,做了一些小的测试,我们最近做的事情是添加了能够管理多个层级版本并分别跟踪它们状态的功能。这意味着我们可以有多个层级的状态,并且可以分别处理每个层级的状态。
这样,如果我们希望有一个渲染部分在某些地方显示,而在其他地方隐藏,系统是完全支持的,完全不会造成问题。我们通过这个系统解耦了层级的状态与实际层级本身的关系。这样,我们就不需要担心层级的状态与正在交互的对象之间的耦合问题。
这是我认为应该做的事情,因此我们也实现了它。接下来的目标是,我希望今天开始,因我们已经接近完成,能够继续推进我们的调试层级视图的工作。
考虑是否要摆脱显式构建调试层次结构
我想做的事情是开始查看调试变量的实际表现形式。我的目标是将调试基础设施向前推进一步,达到一个比较重要的地方。目前我们所做的是,调试层级的底层数据是显式构建的,并且一直存在在内存中。
具体来说,如果查看代码的话,你会看到我们在这里显式地构建了这个层级的实际版本,并且它会一直存在于内存中。这意味着任何想要添加调试变量的地方,都必须实际创建一个真实的调试层级,并将其存储在内存中。这样做的一个问题是,它可能会让一些代码变得麻烦,尤其是当其他代码想要快速暴露调试变量时。
因为现在这是一个保留模式的东西,任何想要暴露调试变量的代码都必须经历创建它并将其放到某个地方的麻烦。对于全局变量来说,这并不算什么大问题,因为它们像是开关,开了就能用,关了就没有影响。对于像是否使用大地震的轮廓这样的全局变量,开关的存在和关闭没有太大影响,我们只需要创建它然后忘记它,调试系统就可以始终使用它。
然而,我更感兴趣的是处理更复杂的问题,特别是当游戏中的某些内容想要暴露调试信息时,它们不一定想花费额外的精力去创建一个实际的调试树。这种情况下,我们可能希望避免让这些内容强制管理或存储一些东西,而是希望它能够直接工作。
所以,我想做的是探索一下如何使这个过程变得更顺利,尤其是让我们能够检查实体的状态。比如,能够查看一个实体的值,因为这是调试过程中可能比较困难的一部分。我们可能并不总是知道哪个实体出了问题,想要了解它的状态很困难。能够查看某个实体的状态,可能会是一个我们系统中非常有用的功能。
今天,我希望能够从这个方向开始,着手解决这个问题。
讨论编写使用代码并统一系统
决定完全忽略已经写好的调试代码,先去修改实体代码,使得能够检查那些变量。接下来,回到调试系统,并将这两个部分统一起来。
这种做法是常见的编程技巧。如果知道某个操作或任务需要系统完成,通常会先编写该任务的使用代码,即先实现如何使用该系统,然后让这个使用代码决定系统应该如何工作。在这种情况下,虽然之前已经做了一些初步的工作,例如为预处理定义了一些常量,但对其他用例并没有真正考虑过。所以,计划清除当前的用例,并让新的需求驱动系统的更新,而不是先改变系统结构,期望它能支持新需求,再去使用时发现不合适。通过先实现目标,然后根据实际需求来完善系统,是一种更加高效的工作方式。
game_sim_region.cpp:查看当前实体的处理方式
如果我们要做类似的事情,可以先看看game_sim_region代码,这是我们处理敌人的地方。你可以看到我们在这里是怎么做的。
当我们进行初始化时,我们会循环遍历所有在当前区域中的实体,并将它们添加到模拟sim系统中。这部分代码在这里有体现。所以,如果我们在这里做类似的操作,去循环并将实体添加到模拟系统中,那么就会出现一个有趣的问题:是否应该在这个地方宣布实体呢?这个问题并不好回答。
但是我知道的是,在将所有这些实体添加到模拟系统之后,我们现在可能有一种比较简便的方式来选择它们。所以我想做的是,能够做一些操作,让我们能够很容易地选择并检查这些实体。
实现点击实体进行检查的功能
我们希望能够通过鼠标点击或者其他方式来选择实体。具体来说,就是能点击到某个实体,并选中它。这样,我们就能知道这个实体在游戏中的具体情况,或者它的属性和状态,基本上就是可以通过这种方式来“查看”该实体。
在初始化过程中,我们会添加实体到模拟系统中,可以看到在这段代码里,我们将它们加入到模拟中,并且有一个实体碰撞体积组,这个组告诉我们实体的边界范围。基本上,我们目前对实体边界的了解就是这些。
我记得,当我们进行“移动实体”时,可能会使用这些碰撞体积来处理,或者使用其位置来插入数据到集合中。我认为插入时不太关心实体的边界,而是直接用位置将其放入集合。虽然碰撞体积是我们大部分实体的一个属性,但其实我们也可以仅使用实体的位置来进行选择。
目前来说,不确定哪种方法更适合,但是无论哪种方式,都有一些选择和可能性。
运行游戏并演示我们想要实现的效果
首先,想要处理的第一个问题是如何绘制实体的碰撞体积,以便我们可以看到它们。到目前为止,如果观察当前显示的内容,我们可以看到实体和它们的碰撞体积,但是这些碰撞体积并没有在屏幕上显示出来。实体可能是不可穿透的,但它的碰撞体积并没有直观呈现给我们。
为了能够看到这些碰撞体积,我打算引入一种方法,在屏幕上绘制这些碰撞体积。这样,我们就能直观地看到这些碰撞区域。为此,我们可能需要引入像线条这种基本图形,或者考虑使用矩形来绘制碰撞体积,尽管这有些复杂,因为矩形的两个点在三维空间中的投影等问题需要考虑。
碰撞体积的定义中包含一个位置偏移和一个尺寸(dim)。这意味着每个碰撞体积都有一个位置坐标,相对于实体的中心位置,以及一个表示其大小的尺寸。在渲染系统中,我们没有直接用来渲染碰撞体积的东西。现有的矩形渲染功能只支持二维坐标的尺寸,而碰撞体积的尺寸实际上是三维的,这样就无法直接渲染出一个三维矩形。
因此,考虑到这一点,我们可能需要设计一种新的方法来绘制这些碰撞体积。例如,我们可以创建一个函数,接受两个三维点来构建一个矩形,围绕这两个点绘制出碰撞体积的区域。或者,我们也可以使用“绘制矩形轮廓”这样的功能,因为我们可能不想填充碰撞体积,而是仅仅绘制出其边界,这样可能会更有意义。
game.cpp:引入DEBUGUI_DrawEntityOutlines
首先,开始时的目标是实现一个简单的功能。当处理所有实体和渲染循环时,可以在渲染的最后阶段,遍历所有实体的碰撞体积,并为每个实体绘制轮廓。这是一个简单的过程,可以在渲染流程的末尾完成。
在渲染过程中,已经有了处理“推送矩形”的功能,而现在的目标是将这种功能扩展到所有实体的碰撞体积。可以通过遍历所有实体并为每个实体的碰撞体积绘制轮廓,这样无论是什么类型的实体,都能绘制出它们的碰撞体积边界。
接着,需要在配置文件中定义一个新的选项,比如 "DrawEntityOutline "。这个选项的作用是启用绘制实体轮廓的功能。通过将这个设置添加到配置文件中,就能控制是否绘制所有实体的碰撞轮廓。
在实现时,会进行一些变量和代码的定义工作,比如在 RGB 变量文件中添加对应的定义,并确保功能能够编译通过。这样,系统就可以根据需要绘制所有实体的轮廓,帮助调试和检测问题。
运行游戏,查看新的轮廓
在实现了实体轮廓绘制功能后,得到了大致正确的效果,可以看到不同的实体轮廓已经被绘制出来。我们使用了与之前相同的代码来绘制这些轮廓,效果是可以接受的。绘制的轮廓有些许厚重,但整体上效果基本正确。
对于碰撞体积的绘制,虽然只考虑了二维的 X 和 Y 维度,而忽略了 Z 维度的范围,这种做法虽然简化了问题,但对于当前的需求来说,这种处理方式是可以接受的。毕竟,这样的绘制方式对于调试和检测是足够的,尤其是在当前的系统中,可能不需要过于复杂的三维碰撞体绘制。
game_render_group.cpp:使PushRectOutline函数可以额外接受Thickness参数
目前绘制的轮廓看起来稍微有些厚,可能需要调整一下。在考虑这个问题时,怀疑目前使用的 PushRectOutline
方法是否支持调整线条的粗细。似乎它并不直接支持这种功能,因此需要考虑对这个方法进行修改或扩展,允许设置轮廓的线条厚度。
理想情况下,可以在调用 PushRectOutline
时,传入一个可调整的厚度值,这样在绘制实体的轮廓时,可以根据需求调整线条的粗细,使得轮廓显示更加精细或符合调试需求。
运行游戏,查看更细的品红色轮廓
现在,已经能够在屏幕上看到实体的边界,边界使用了鲜明的品红色轮廓,效果不错。接下来,目标是实现鼠标选择这些边界中的一个。当鼠标在屏幕上移动时,可以点击某个实体的边界,并将其选中,突出显示该实体。
为实现这个目标,只需要判断鼠标指针是否位于某个矩形区域内,就像之前所做的那样。通过这种方式,可以轻松地实现点击选择某个实体的功能。
考虑视角变换的问题
问题在于,当前的视图使用了一个变换(transform)。例如,使用调试摄像机时,整个场景会缩小,所以显然在渲染时并没有一个标准的坐标系统。实际的过程是,世界坐标和摄像机的位置通过投影转换为屏幕上的实体位置。
为了让鼠标能够准确地与实体进行交互,需要将鼠标位置从屏幕坐标反转回游戏世界坐标。这是因为,鼠标的位置是基于屏幕坐标的,而实体的位置是通过摄像机变换投影到屏幕上的。因此,需要逆转这个渲染过程,将鼠标位置从屏幕坐标转换回世界坐标,才能进行正确的碰撞检测和选择。
在调试界面中,不需要做这种转换,因为调试界面是直接在屏幕上的像素空间中进行的,鼠标位置和界面元素的像素位置是一致的,所以没有这种复杂的变换过程。而在处理游戏实体时,由于这些实体是经过摄像机变换的,需要找到一种方法来逆转这个过程,才能确保鼠标点击能够正确地选中对应的游戏实体。
game_render_group.cpp:查看Unproject函数
在代码中,存在一个名为 Unproject
的函数,它用于将屏幕上的坐标逆投影回世界坐标。这个过程通过获取显示器的维度,进行逆投影,从而了解摄像机视野的范围。事实上,这个逆投影过程本来就是有用的,且已经存在了。
函数的使用是基于某种矩形区域,目的是从显示器的维度推算出摄像机能够看到的世界坐标。因此,理论上,如果将鼠标坐标传入这个 Unproject
函数,它可以返回鼠标在世界坐标中的位置。
这个过程的关键在于能够将屏幕上的像素坐标转换回世界中的相应位置,这样鼠标点击事件就可以正确地与世界中的实体进行交互。
黑板:为什么我们需要相机的距离
在讨论屏幕上鼠标的位置时,假设屏幕、摄像机和世界中的某个点(如树)之间的关系,可以通过投影来理解。首先,摄像机会将树上的某个点投影到屏幕上,屏幕上的每个点都对应着世界中一条从焦点到该点的线上的所有点。换句话说,在屏幕上的一个点对应着一条线,而这条线上的每个点都会投影到该屏幕上的同一位置。
但是,当我们进行逆投影时,问题就变得复杂了。如果已经知道了屏幕上的某个点,想要找出对应世界中的位置,实际上并不存在一个唯一的世界坐标与屏幕上的单一点一一对应。因为世界中的每个点都会映射到这条线上的某一个位置,所以我们无法直接得到一个明确的坐标。
为了克服这个问题,需要引入一个额外的参数——摄像机到屏幕上的某个平面的距离。这个距离表示的是沿着投影线的距离,用来确定在该投影线上应该选择哪个点。通过这个距离,可以找到相应的世界坐标,因为在知道这个距离后,我们就能确定对应的世界位置。
黑板:概念化实体拾取的过程
在进行选取操作时,问题变得更加复杂,因为我们并不总是知道要选取的是哪个平面。假设在场景中有多个物体重叠在一起,例如远处有更多的树木,这时我们需要能够在多个实体之间进行选择,并且可能有多个实体与鼠标光标重合。因此,当我们尝试进行选择时,不仅仅是在寻找与光标位置对应的单一实体,而是要确定哪些实体与光标所在的射线(即从摄像机到鼠标位置的这条线)相交。
这就涉及到更复杂的操作,因为我们需要考虑每个实体在摄像机视野中的位置,并计算它们是否与这条射线相交。接下来还需要判断,哪些交点离摄像机更近,从而决定最终选中的实体。虽然这个过程稍微复杂,但实现起来并不困难,因此可以通过这个方法来进行选择操作。
我们的目标是当鼠标移动时,能够实时地改变实体的颜色,比如将颜色改为黄色,这样就能直观地看到鼠标当前选中的实体,从而确保选择功能正常工作。
game.cpp:对MouseP进行Unproject处理
为了实现这个功能,可以通过比较简单的步骤来进行。首先,可以设定一个标志来检查是否高亮显示某个实体,或者直接在绘制轮廓时修改轮廓的颜色。例如,初始时轮廓颜色是品红色,然后在某些条件成立时将颜色改变为黄色。这样,就能够直观地看到哪些实体是当前选中的。
具体的做法是,首先需要反向投影鼠标的位置。投影的目的是将鼠标在屏幕上的位置转换成世界空间中的位置,这样才能确定鼠标点击的是哪个实体。在进行这个操作时,还需要确保坐标系是合理的,并且会处理鼠标位置在场景中的转换。
接下来,我们需要计算实体与鼠标光标之间的距离。在这里,计算的是实体在其所在平面上的位置,通常这个位置是相对于摄像机的。计算这个距离时,首先要确定摄像机的位置,并确保摄像机设置正确。通过这种方式,可以得到鼠标点击的实体是否位于指定的平面上。
然后,根据这个信息,我们可以更新鼠标的选择状态。当鼠标点击某个实体时,会根据计算的距离和投影结果来确定实体是否被选中。最终,根据鼠标位置所对应的实体,修改其轮廓颜色,使得选中的实体变为黄色,其他未选中的实体保持原来的颜色。
game.cpp:将LocalMouseP与2D实体Volume->Dim进行比较
我们讨论的是如何实现碰撞检测,特别是在一个二维空间中判断一个点(比如鼠标位置)是否与某个矩形碰撞。首先,我们假设有一个碰撞体积的矩形,该矩形的维度和偏移量都已知。我们的目标是基于这些信息,判断一个给定的点(如鼠标的位置)是否位于该矩形内部。
-
理解碰撞体积的表示:每个碰撞体积有一个矩形维度(宽度和高度)以及一个偏移量(相对于某个参考点的位置)。我们假设碰撞体积的矩形是以某个中心点为基准,定义了一个矩形区域。
-
鼠标位置转换:为了判断鼠标是否在矩形内,首先需要计算出鼠标相对于碰撞体积中心的局部坐标。也就是说,要将鼠标的全局坐标转换为相对于碰撞体积中心的坐标。这可以通过减去碰撞体积的中心位置来实现。
-
判断是否碰撞:当我们得到了鼠标的局部坐标后,可以比较这些坐标是否在碰撞体积矩形的范围内。具体来说:
- 如果鼠标的x坐标在矩形的左右边界之间,并且y坐标在矩形的上下边界之间,那么可以认为鼠标与该碰撞体积发生了碰撞。
- 由于碰撞体积是以中心点为基准的矩形,我们需要检查鼠标坐标是否在矩形宽度的一半和高度的一半的范围内。
-
局部坐标转换和碰撞测试:通过将坐标转换为局部坐标后,判断条件会变得非常简单:
- 检查局部坐标x是否大于负的矩形宽度的一半且小于矩形宽度的一半。
- 同理,检查局部坐标y是否大于负的矩形高度的一半且小于矩形高度的一半。
-
简化处理:这种碰撞测试方式仅适用于二维空间。虽然可以使用更复杂的三维射线检测来进行更精确的碰撞检测,但在调试过程中,二维检测已经足够使用,不必复杂化。
通过这种方式,可以高效地检测鼠标是否在碰撞体积内,而不需要进行复杂的三维碰撞测试。
game.cpp:对鼠标位置进行PushRect处理
我们正在讨论如何处理鼠标输入,并记录鼠标位置以便进行调试。首先,系统会给出鼠标的x和y坐标,也就是我们可以通过这些坐标知道鼠标的位置。但问题是,系统提供的这些坐标并不总是准确的,因此我们希望能够记录下鼠标的位置,以便查看系统认为的鼠标位置与实际的鼠标位置之间的差异。
为此,我们决定使用一种方法来“推送”记录鼠标的位置。具体来说,不需要特别复杂的操作,可以通过临时记录鼠标的坐标来实现。这样,在渲染操作之后,也就是在控制器逻辑执行完毕之后,我们就能看到鼠标的真实位置。
这个过程涉及到在渲染时记录鼠标坐标,而不是依赖于每次都重新计算或记住系统认为的鼠标位置。通过这样的方法,我们可以轻松地调试和检查鼠标坐标的准确性,而不需要做复杂的处理。
game.cpp:做一个正交(Orthographic)调用绘制MouseP
在进行鼠标位置调试时,我们希望能够在投影之前显示鼠标的位置。为此,需要先进行一个正交投影(orthographic projection),这其实不算复杂的操作。正交投影可以帮助我们在渲染时准确地显示鼠标位置。
首先,我们要在执行控制器逻辑之后(也就是做其他事情之前),通过正交投影来绘制鼠标的当前位置。这可以通过简单地将鼠标坐标用 ASCII 图形或其他简单方式显示在屏幕上,作为额外的调试输入。
为了做到这一点,我们需要确保在开始绘制之前清理屏幕,避免任何残留的图形干扰当前的显示。清理操作通常会清空整个表面,但具体行为需要确认。然后,清理之后,我们可以绘制一个矩形框来表示鼠标的当前位置。这个矩形框的尺寸可以简单设置为固定的值,比如 1x2 或者 2x2,这样就能在屏幕上看到鼠标的准确位置。
完成这些步骤后,我们就可以在进行更复杂的操作之前,确认鼠标的位置是否正确。需要注意的是,绘制鼠标位置的操作应该在所有其他渲染内容之后进行,以确保鼠标位置能够正确显示在其他图形之上。
最后,应该确保这些绘制操作按顺序进行,尤其是在进行更复杂的渲染之前,这样才能有效地进行调试,确保鼠标位置的准确性。
game_render_group.cpp:向Orthographic函数中添加OffsetP和Scale参数
在渲染过程中,另一个想法是重置变换矩阵,特别是重置渲染偏移量(OffsetP)。我们需要检查是否有办法将渲染变换矩阵设置为单位矩阵(identity)。这一步非常重要,因为单位矩阵代表没有任何变换,所有坐标都会按原始状态进行处理。这样可以确保在渲染其他内容时,不会受到之前的变换影响。
查看当前的变换矩阵,发现偏移量(OffsetP)并没有被设置,而变换矩阵包含了偏移量和缩放(scale)。这些值没有在当前的渲染过程中得到适当的重置,这可能会影响后续的渲染操作。因此,可能需要手动清理这些变换,确保它们被正确重置为初始状态。
如果不清理这些变换,可能会导致后续渲染结果不正确,因为之前的偏移和缩放操作会影响新的渲染内容。所以,觉得在渲染之前重置这些变换矩阵是必要的,这样可以确保渲染的每一步都是基于正确的坐标和状态进行的。
两行要移动到函数结尾不然会有问题
运行游戏,查看鼠标位置
现在可以看到鼠标位置已经正确显示在屏幕上,这表明我们已经验证了鼠标位置的准确性,这是我们想要的结果。然而,仍然存在一个问题,就是在正交模式下,虽然我们能够看到鼠标的位置,但我们并不确定这个鼠标位置是否已经正确地投影到屏幕上。具体来说,我们不清楚鼠标的坐标是否已经按照正确的投影方式显示在屏幕上。
因此,我们需要查看鼠标位置的实际值,尤其是当鼠标位于屏幕的不同位置时(例如,当鼠标位于屏幕的顶部)。通过查看这些实际值,我们可以确定鼠标坐标是否正确地被投影到了屏幕上,并确保坐标转换过程没有出错。这样可以帮助确认渲染和投影过程的准确性,确保鼠标位置在不同情况下的正确显示。
调试器:进入GameUpdateAndRender并检查MouseP
目前,想要检查鼠标坐标的实际值,因此决定在代码中随便一个地方设置断点,不管是在游戏更新(game update)还是渲染(render)过程中的任何位置。目标是查看鼠标位置的实际值,即 MouseP
被设置成什么样,特别是当鼠标坐标最终被计算出来时。
为了更方便地查找,使用了快速查找功能,搜索了 mouse
相关的代码。在查看了 MouseP
的值后,发现当前的鼠标 x 坐标是 -934,y 坐标是 511。这表明,鼠标坐标实际上是使用屏幕坐标系来表示的。这个坐标值看起来是完全合理的,符合预期的屏幕坐标系统。
通过这种方式,能够确认鼠标的坐标系统是基于屏幕的,确保了坐标转换和渲染过程中的准确性。
game.cpp:尝试绘制LocalMouseP
接下来,想要将调试过程提升到一个新的层次。在绘制调试轮廓时,除了绘制之前的内容,还希望能够绘制局部坐标的鼠标位置(local mouse position)。这样可以帮助更清楚地查看鼠标在本地坐标系中的位置。
具体来说,当前的操作是遍历这些碰撞体积,并计算出局部鼠标位置(local mouse p)。然后将这个局部位置投影回世界坐标系。接下来,目的是在绘制时能够显示出这个局部鼠标坐标,也就是说,先将局部鼠标位置计算出来,然后通过投影再绘制出来。
为了实现这一目标,需要首先计算出局部坐标对应的z值,然后使用这个值来帮助计算鼠标位置在世界坐标中的实际显示位置。通过这些计算,最终可以决定如何绘制这个位置,可能会用更大的标记或者某种颜色(比如青色),让它在调试过程中更容易辨识。
另外,还可以考虑用一种方式将这个点与其他参考点(如原点)连接起来,形成一个从零到鼠标位置的向量,进一步帮助理解鼠标的准确位置。
运行游戏,未能显示LocalMouseP
接下来,想要检查是否能够看到绘制出来的局部鼠标坐标。如果看不到,可能是因为计算有问题。理应每个实体的局部鼠标坐标都能显示出来,但目前并没有看到预期的结果。这表明可能存在一些错误,导致鼠标位置没有正确地显示。
考虑到这一点,意识到可能自己在思考这个问题时有所偏差,没有正确地执行所需的操作。当前的做法显然并没有按照预期产生效果,因此需要重新审视代码,确保计算和绘制过程是正确的。
game.render_group.cpp:调查为什么看不到LocalMouseP
目前出现了问题,无法看到预期中的鼠标位置。虽然已经遍历了所有体积(volumes),并且根据每个体积的z值进行了逆投影(reverse projection),并且将x和y坐标与体积进行差值运算,理论上这些应该是局部坐标(local to the entities)。虽然这个计算有一点偏差,因为体积是球形的,但应该还是比较接近预期的局部坐标。
当鼠标悬停在实体上时,应该能够看到鼠标位置附近的某些标记或图形,但实际情况是并没有看到任何预期的结果。这意味着当前的计算或者操作有误,需要进一步分析为什么没有得到正确的结果。
仔细回顾了一下代码,当前的计算方式是:根据相机距离、焦距(focal length)和投影的x、y坐标,进行一些计算来得到鼠标位置的投影。然而,这个计算似乎没有正确地转换为像素坐标。问题出现在投影到世界坐标后,进行从“米”(meters)到“像素”(pixels)的转换时。
具体来说,投影后的x、y坐标经过了“米到像素”的转换,这个转换可能是问题所在。如果我们查看代码,可以看到在进行投影后,计算的是从米到像素的转换。因此,在未进行“米到像素”转换之前,应该先应用这个转换,才能得到正确的像素坐标。
game.cpp:将鼠标的Transform乘以PixelsToMeters
目前,存在一个问题,鼠标位置的转换没有正确完成。鼠标坐标(MouseP)的局部z值已经是以米为单位的,而鼠标位置本身还没有进行单位转换。因此,需要将“米”转换为“像素”才能得到正确的屏幕坐标。
目前有一个“米到像素”(MetersToPixels)的转换方法,但似乎没有实现反向转换(像素到米)。为了解决这个问题,可以通过将“米到像素”的转换反转,即将像素坐标乘以转换的逆值,来进行正确的转换。
此外,发现实际的转换并没有如预期那样进行,因为转换过程应该基于“像素到米”的反向操作,而不是仅仅进行“米到像素”的操作。为了修复这个问题,可以在处理过程中将米转换为像素,然后按照反向转换的方式来进行操作,这样就可以得到正确的鼠标位置。
还需要注意的是,当前计算出来的坐标是在“米”单位下的,这会导致坐标的值过大。因此,需要将计算结果缩小,使得它适应正确的屏幕坐标系统。
运行游戏,发现矩形位置不正确
目前整体进展逐渐接近目标,虽然还有一些细节没有完全到位,但整体逻辑开始趋于正确。从当前表现来看,鼠标的投影结果已经可以显示出来,但存在一个明显问题:所有的绘制元素似乎都共享了同一个偏移量,而不是根据每个碰撞体(bounding element)分别计算的。
理想情况下,每一个碰撞体的偏移应该是相对于其各自的局部坐标系进行的,而不是统一使用一个通用的偏移量。当前的状态下,虽然鼠标移动可以触发绘制,但由于缺乏个体差异化处理,所有绘制内容的相对位置是一样的,导致逻辑上的偏差。
不过从整体来看,系统已经具备了鼠标位置获取、反投影、单位换算以及初步的绘制能力,说明核心架构已经具备,只是细节尚未处理完全。例如需要为每个碰撞体计算独立的局部坐标偏移,使得最终绘制的位置能够正确反映实体之间的位置差异。
尽管目前效果有些抖动、不稳定,表现略显粗糙,但可以确认各项逻辑正逐步接近预期功能。接下来,只需理清每个体积对应的局部偏移计算逻辑,并确保单位换算在每个实体下都进行独立处理,就可以得到准确的调试绘制结果。整体思路是正确的,剩下的主要是实现细节上的完善与调整。
game.cpp:调查为什么矩形位置不正确
现在在完成了像素与米之间的单位换算后,我们在反投影鼠标位置并将其围绕特定碰撞体进行居中时,又发现了另一个问题。
我们最初在进行偏移量计算时,使用了 OffsetP.x
、OffsetP.y
和 OffsetP.z
,但实际上这些值并不是碰撞体在世界空间中的真实位置。它们仅仅是相对于某个实体的局部偏移。因此,我们必须先将鼠标的位置转换为相对于该实体的位置,才能继续后续的操作。
为此,我们引入了一个新的变量 MetersMouseP
,表示以米为单位的鼠标坐标,然后需要将它转换为以该实体为基准的相对坐标。只有在正确地获取这个相对位置后,后续的投影和碰撞检测等操作才能基于正确的空间坐标系进行。
此时,问题还出在 LocalZ
的使用上。原本的 LocalZ
实际上并没有包含该实体的空间位置信息,因此它不是一个正确的局部深度值。要解决这个问题,需要将实体自身的位置(比如实体的位置 entity_p
)加入到 MetersMouseP
的计算之中,从而保证所得到的局部坐标是真正相对于该实体的。
另一方面,unproject
操作在内部使用了 transform
,而这个 transform
已经是基于当前实体配置过的,因此投影操作本身是没有问题的。然而,进行 MetersMouseP
和 OffsetP
的减法操作时,没有经过 transform
,就没有包含实体的位移信息,导致计算出来的相对位置不准确。
这一点非常关键:所有发送给渲染系统用于绘制的坐标,已经在 transform 设置后自动变成了相对于当前实体的位置,但我们自己手动做减法计算位置偏移时,必须也要做相应处理,否则就会造成局部坐标的错误。
总结如下:
- 鼠标坐标必须先转换为米为单位,并基于实体的位置做相对化处理;
LocalZ
的值需要包含实体位置的信息,才能代表正确的深度;unproject
自动使用已配置的 transform,因此不会出错;- 手动计算偏移时,需要手动包含实体的位移,才能与
unproject
之后的结果对应; - 渲染系统在 transform 设置之后,所有坐标都是基于当前实体的,因此后续绘制、测试等都必须使用与之匹配的坐标系。
这些调整是确保调试绘制精确对齐实体和鼠标交互判断的关键步骤。经过这些校正后,鼠标位置的绘制应该可以正确地反映实体之间的空间关系,真正实现准确的调试与交互分析。
game.cpp:将实体的Transform传递给Unproject调用
当前这部分数学计算存在问题,因为它并没有真正做到“相对”的处理。现在的实现仅仅使用了碰撞体(volume)的 OffsetP
,但实际上它还应当考虑实体(entity)本身的位置 p
,因为最终的空间位置不仅取决于碰撞体的偏移,还取决于实体在世界空间中的位置。
换句话说,如果只使用 OffsetP
,那计算出来的局部坐标就只是相对于碰撞体的原点,而不是相对于整个实体的位置,因此得出的结果是错误的。
正确的做法应该是:需要同时考虑两部分信息:
- 碰撞体自身的偏移量
OffsetP
; - 实体的空间位置
p
(也就是 transform 中的基础位置);
只有将这两部分组合在一起,才能得到真正的本地相对坐标,也才能确保和渲染系统的 transform 逻辑一致。
这里其实也进一步说明了一个核心问题:这个错误可能就是之前调试中鼠标位置始终偏离实体、对不上实际位置的根源。因为没有把实体的位置包含进来,所以无论怎么调试,鼠标与碰撞体之间的对齐始终存在偏差。
通过补全这部分逻辑,现在的数学模型才能真正准确反映实体和鼠标在世界空间中各自的位置和偏移关系,进一步保证调试可视化时能够准确显示鼠标所指向的对象和区域。
总结如下:
- 原来的计算只使用了
OffsetP
,缺失了实体的p
; - 实际上需要把两者组合起来,才能表示完整的空间位置;
- 渲染系统会基于 transform 来处理坐标,因此手动逻辑也必须与之保持一致;
- 这可能就是之前鼠标调试逻辑不准确的根本原因;
- 修复这部分后,坐标的局部化转换才能正确,调试也才真正有效。
运行游戏,看到接近正确的位置
我们现在离目标更近了一步,但目前绘制出来的调试图形仍然显得过于保守,偏小,表明计算中仍存在偏差。
当前的处理流程如下:
- 首先将鼠标位置
MouseP
从像素坐标转换为米单位,这是通过乘以pixels_to_meters
实现的,这是正确的; - 接着使用这个转换后的坐标,结合深度值
z
进行透视反投影unproject
,这也是我们想要做的; - 然后从反投影得到的位置中减去实体变换的偏移量
transform.offset
加上碰撞体的偏移量volume.offset
; - 最后将这个结果用于绘制调试图形;
从逻辑上看,这一系列操作是合理的,特别是在反投影时,已经考虑了当前实体的变换变换矩阵,所以理论上变换后的坐标应该就是鼠标在当前实体坐标空间下的位置。
然而实际表现上,并没有达到预期。鼠标移动时,各个实体上的图形没有正确对齐在光标之下,说明推导的相对位置仍有偏差。
这是为什么?
因为在透视投影下,空间坐标的映射并不是线性关系。我们在执行反投影后,得到的是世界坐标中的点,接着将其视为相对于某个实体的局部坐标时,仍需要精确考虑该实体的位姿变换,这里可能出现两个问题:
- 反投影之后没有再进行一次变换:虽然
unproject
使用的是 transform,但在绘制前我们手动进行了OffsetP
的减法,而这个过程可能是冗余或不一致的; - 绘制逻辑与 transform 的使用不一致:如果 render 系统已经考虑了 transform 的偏移量,我们手动再减一次
OffsetP + volume.offset
,就可能导致重复变换,使得绘制点位置不准确;
也就是说,当前的做法很可能存在“减多了”的问题,导致绘制位置偏移。
此外,当前结果显示“所有实体的偏移一致”,也说明我们并没有正确对每个实体分别计算对应的本地位置,而是使用了某种全局统一的参考,这进一步表明计算逻辑中仍有瑕疵。
我们判断是否正确的一个核心依据是:调试图形应始终与鼠标光标保持一致。如果不一致,就说明反投影、偏移或绘制中的某个步骤不对。
接下来可以尝试的方向:
- 检查反投影后的点是否直接就可以被绘制,无需再手动减
OffsetP
; - 或者确保
transform
设置与绘制逻辑完全一致,不产生重复变换; - 可以尝试仅减去实体的位置(非 transform),而保留其他不动;
- 打印实际绘制坐标与鼠标位置进行对比,确认误差来源;
目前调试的方式、流程和思路已经非常接近问题核心,只需再细化变换顺序即可精准对齐。现在的结果已经逐步逼近正确状态。
game.cpp:选择一个实体进行测试
这里我们已经进入了一个新的阶段,即希望针对某一个实体进行操作或调试。由于我们手上有实体的索引 entity_index
,我们完全可以从中挑选出某一个实体来处理或绘制。
不过问题在于,我们当前并不清楚到底该选择哪一个实体,也就是说我们缺少一个明确的判断逻辑来决定“选中”的应该是哪一个实体。这个问题暂时可以搁置,因为接下来我们会进一步探索和试验,很快就能搞清楚。
重点在于:现在已经具备条件,可以对某个单独实体进行调试,只需要确认要处理的是哪一个。选中实体后,就可以更准确地观察当前反投影、坐标变换和绘制逻辑是否在该实体上表现正确,从而进一步缩小调试范围,推进整体调试进度。
运行游戏,发现鼠标矩形位置和缩放不正确
现在我们运行了调试代码,并选取了一个实体进行分析,比如一个树。接着,我们尝试在该实体的局部空间中绘制鼠标的位置,目的是验证鼠标在该实体坐标系下的位置是否正确。
然而,结果显示,绘制出来的鼠标位置完全不正确,不仅位置不对,连缩放比例都显然是错误的。这说明目前的反投影或坐标变换逻辑仍然存在问题,至少在处理坐标系统之间的转换过程中,某些步骤还未正确实施。
虽然系统已经考虑了从屏幕坐标到世界坐标的转换、从像素到米的单位换算等多个环节,但实际结果仍不合理,说明这些变换之间的关系还没被完全理清。
因此,为了更有效地排查问题,我们采用了经典的调试手段:将调试范围限制到单个实体。这么做的好处在于可以屏蔽掉其他干扰因素,只聚焦在一个对象上,从而更清楚地观察实际绘制与预期之间的偏差,更快速地定位问题源头。通过简化可视信息,有助于我们理清当前坐标偏差的根本原因。
game.cpp:尝试去掉减法操作进行Unproject
现在我们已经得到了 MetersMouseP
(转换为米单位的鼠标位置),并完成了反投影操作(unproject)。接下来,我们出于“学习目的”,想看看在不进行坐标修正的情况下,单纯反投影后的结果是什么样子。
具体来说,我们暂时移除了原本用于将反投影结果转换到局部空间的坐标差(subtract)操作。也就是说,我们不再把鼠标位置从世界空间转换到相对于实体的局部空间。这样可以清晰地看到:仅做反投影而不对其做局部空间转换时,坐标会落在哪个位置,以及其视觉效果如何。
这个过程的目的是为了深入理解:反投影操作本身输出的是一个怎样的空间位置,它是否已经靠近我们所需的结果,或者是否仍需进一步处理来变换到我们最终希望可视化的位置。
简而言之,这是一种通过故意“省略”某步处理来帮助理解系统行为的调试策略,有助于掌握每一阶段变换对最终渲染的影响。
运行游戏,发现结果意外地在正确的位置
在不进行坐标修正的情况下,可以看到反投影后的结果居然大致出现在了正确的位置。这一点出乎预料,因此需要进一步思考其原因。
反投影操作(unproject)本身并不会考虑实体的位置,也就是说,它只将某个屏幕坐标(如鼠标位置)转换回三维空间的某个点,而不会根据当前实体的偏移或坐标系进行调整。所以按理说,反投影之后应该还需要做一次减法(subtract),也就是把实体的偏移从中减去,才能变换到相对于该实体的局部坐标中。
但奇怪的是,在没有进行这一步减法的情况下,绘制出来的位置似乎已经是正确的。这个现象起初看起来非常奇怪,不过仔细想想也说得通:绘图系统在渲染时是以实体为中心进行绘制的,即绘制的所有东西默认都相对于当前实体的变换进行转换。因此,即使没有手动减去偏移,绘图的结果也会自动加上了实体的变换,最终仍然落在了相对合适的位置上。
所以这种情况下,反而不需要手动执行那一步坐标减法,否则就会多减了一次偏移,导致位置错误。
在进一步观察中,发现图形围绕屏幕中心旋转时,并没有正确地向鼠标指针移动。这说明当前虽然坐标位置看起来是合理的,但在动态变化上并未完全表现出期望的行为,因此仍存在逻辑问题。这就指出了接下来的目标:我们希望看到的是,鼠标点相对于实体在空间中准确地移动和对齐,这才意味着坐标转换真正是正确的。
总结:
- 反投影不会考虑实体偏移;
- 渲染系统自动将坐标转换为实体相对位置;
- 手动减去偏移可能导致坐标错误;
- 当前看似正确的结果实际在动态表现上仍有偏差;
- 接下来应进一步验证动态交互下的坐标行为是否正确匹配。
game.cpp:考虑Unproject函数可能存在根本问题
在执行反投影(unproject)操作时,使用了当前的 localZ
值来从屏幕坐标转换回世界坐标。然而在观察整个过程后,发现反投影过程似乎存在根本性的问题。
仔细检查 unproject
的实现逻辑,会发现它从未考虑过偏移量 OffsetP。也就是说,当前的反投影实现完全忽略了实体本身的位置偏移,就好像这个偏移量根本不存在一样。这意味着反投影的结果仅仅是基于摄像机视角下的原始计算,未包含任何对象自身的变换信息。
这种处理方式会导致一个关键问题:反投影得到的位置不具备任何“局部性”,无法准确反映鼠标在某个特定实体或局部空间中的位置。理论上,若要实现精确的坐标转换,反投影逻辑就应该结合当前的实体变换信息(例如偏移、缩放等),否则投影和反投影之间就不对称,最终导致渲染和交互行为偏离预期。
总结:
- 当前的
unproject
实现未考虑偏移量OffsetP
; - 导致反投影结果总是相对于摄像机中心,而不是具体实体;
- 这让后续逻辑如坐标对齐、局部定位等功能失效;
- 如果要修复,应该在反投影过程中加入实体偏移信息,使其结果真正反映世界空间或实体局部空间下的位置;
- 当前表现表明
unproject
逻辑在设计上是不完整甚至有缺陷的,需要系统性地修正。
game.cpp:将OffsetP.z添加到LocalZ
确实,经过对坐标转换流程的深入分析后,发现当前逻辑存在设计上的不足。
在进行反投影操作时,LocalZ
理应包含完整的空间变换信息,也就是说,它应该包括整个 transform(变换矩阵或结构)所代表的全部位置、偏移、旋转等内容。然而当前的做法只是使用了一个 LocalZ
值,完全忽略了 transform 中其他的关键部分,导致反投影的结果并不准确。
换句话说,真正准确的 LocalZ
应该是带有完整 transform 上下文的世界空间深度信息,否则反投影得到的位置将失真,不再反映实际鼠标在实体局部空间中的位置。
因此也得出一个重要结论:目前的 project
和 unproject
两个函数实现方式存在问题,它们都没有真正处理 transform 中的重要数据,尤其是在涉及多个实体、偏移和局部空间时,这种简化处理方式会直接造成坐标错位、行为不符等问题。
最终总结如下:
- 当前的
LocalZ
不完整,应包含整个 transform 的影响; - 反投影计算过程中没有考虑 transform 的实际变换内容;
project
和unproject
两个函数在设计上缺乏对 transform 的支持,导致整个坐标转换链条不闭环;- 为了保证空间逻辑的对称性与准确性,应当对这两个函数进行重构,使其自动处理 transform;
- 否则所有以鼠标或世界坐标为基础的判断与绘制都可能存在系统性偏差。
这个问题虽然看似细节,但从底层影响了所有依赖空间转换的系统逻辑,是一个必须要修正的核心问题。
运行游戏,发现这更接近正确
经过分析,当前的处理流程已经朝着正确的方向发展,但仍然存在一些细节问题。
首先,LocalZ
确实需要包含完整的变换信息,包括目标实体的所有偏移和缩放等。对于投影和反投影的操作,应该考虑到整个 transform 的影响,而不仅仅是 LocalZ
本身的值。这样,计算出的空间位置才能更加准确,避免了之前提到的变换信息缺失的问题。
然而,尽管变换部分已经得到改进,当前的主要问题仍然出现在坐标缩放部分。特别是在与鼠标位置相关的坐标计算时,坐标的缩放依然不正确。虽然我们进行了正确的变换,但最终鼠标位置在屏幕上的显示没有随着鼠标的移动而正确调整。
具体问题在于:当前的 unproject
和 project
过程中,虽然涉及到局部坐标和变换矩阵,但**LocalZ
在投影时没有得到适当的处理**,导致反投影后的坐标没有按照预期进行缩放。
此外,在将“米制坐标”转换为“像素坐标”时,可能没有正确执行反向转换。具体来说,当前的 MetersToPixels
变换可能在某些情况下没有正确应用,导致鼠标位置无法与屏幕坐标正确对齐。
为了确保最终的坐标转换无误,应该对以下几个方面进行进一步检查和修正:
- 确保每次反投影时都使用完整的变换信息,包括目标实体的所有相关变换。
- 在将“米制”坐标转换为“像素坐标”时,确保所有的尺度因子都被正确应用,特别是在涉及鼠标坐标时。
- 验证
unproject
和project
是否在计算过程中正确地应用了与屏幕坐标相关的所有因素,包括坐标缩放。
总的来说,虽然已经取得了一定进展,但需要对坐标缩放的部分进行更细致的调整,确保所有的计算在空间转换时能够准确地反映屏幕坐标系统中的变化。
game_render_group.cpp:研究我们的常规处理函数
经过一番推理与计算,目标是通过正确的数学操作来获取鼠标位置的相对坐标。
首先,我们尝试通过将米制坐标转换为像素坐标,来得到鼠标位置在屏幕上的投影坐标。具体来说,我们将 米制坐标 乘以 像素转换因子,再乘以 投影后的 x、y 坐标 来计算鼠标的位置。这是我们希望达到的目标——得到一个正确的、以像素为单位的鼠标坐标。
然而,出现了一个问题:即使做了这些计算,投影的结果看起来并不完全正确。特别是,投影的坐标似乎没有按照预期正确缩放,导致鼠标在屏幕上的位置与预期不一致。
之后,分析表明,可能是 米制坐标到像素坐标的转换 过程中存在一些问题。通过对比计算过程,可以发现我们遗漏了对 米制到像素的反转转换。虽然在操作过程中,我们已经进行了像素到米制坐标的转换,但由于未正确处理反向转换,导致结果无法准确反映鼠标的实际位置。
在检查了所有操作后,决定通过对投影坐标进行逆运算来进一步解决问题。换句话说,我们尝试通过反向操作,恢复原始的坐标,以便在正确的坐标系下进行显示。为此,我们将 投影后的坐标 除以一个变换因子,再通过焦距调整这些坐标,理论上这应该能够让结果正确。
尽管反向操作的数学公式看起来合理,但最终的结果依然没有完全符合预期。分析后发现,尽管投影坐标计算的顺序和方法是正确的,但在处理 焦距 和 米制到像素的转换 时,似乎还有一些遗漏。特别是 焦距 的处理方式,虽然我们将其应用到了计算中,但似乎没有正确地融入到最终的转换过程中。
总结来看,问题的根本在于如何精确地控制和调整 米制坐标与像素坐标之间的转换关系,以及如何确保 反向投影操作 中所有因素都能正确地影响最终结果。
game.cpp:尝试将减法操作重新添加回Unproject
目前问题还没有完全解决,因为即使进行了多次计算和调整,预期的效果依然没有实现。虽然表面上看似问题应该已经解决,但实际情况却并非如此,这让人感到困惑。希望能够继续调试,仔细分析每一步,找出真正的原因,但由于问题太过复杂,已经进入了反复调试的循环。
从目前的调试情况来看,问题可能出在 逆向转换 的某些环节,尤其是 米制坐标到像素坐标 的转换可能没有完全按预期工作。这类问题尤其难以解决,因为它涉及到 投影 和 坐标变换,而且这些操作不是那么直观,通常很难通过视觉或简单的调试工具捕捉到错误。
目前的方案是通过逐步检查代码中的每个细节,确保每一步的数学操作都准确无误。可以看出,在进行一些具体的变换和投影时,局部 Z 坐标 和 焦距 等因素可能还没有得到正确处理。虽然调整了坐标转换和投影操作,但 缩放 方面的处理仍然存在疑问。也有可能是在计算时出现了缩放的偏差,但目前还无法确定具体原因。
总的来说,虽然大部分步骤似乎是合理的,但依然无法达到预期效果。问题可能出在某些细节上,例如 相机的距离(是否正负),或者 局部坐标 和 缩放转换 之间的相互关系。需要进一步的调试和实验来找出具体问题。
运行游戏,尝试确定差距
目前的问题是 缩放 错误,首先是没有围绕正确的中心进行缩放,这导致了预期效果与实际效果之间的偏差。虽然一开始没有发现是缩放问题,但现在意识到 缩放的错误 比较有趣,需要进一步的调整。首先,问题可能出现在 缩放计算 和 坐标中心 上。中心位置没有正确对齐,而 缩放 本应根据目标的 位置和距离 来调整。
为了进行调试,打算尝试一些比较激进的方案,进行不同的实验来进一步检查缩放和位置的关系,尝试通过不同的操作来找出 问题的根源。
game.cpp:尝试硬编码一些值
目前的想法是,如果之前我们总是围绕地面和某个点进行偏移,那么接下来可以尝试改变这种方式,看看会发生什么。如果把相对变换的偏移量设置为零,那么所有的绘制操作就会相对于普通的世界坐标进行。这意味着不再考虑任何相对位置的偏移,而是直接将鼠标的位置相对于世界坐标进行处理。
接下来,可以尝试用 10米 这个值来表示鼠标和物体的距离。假设鼠标距离物体 10米,就可以绘制出物体在该位置的世界坐标。这时,通过使用 相同的局部 z 值 进行投影,期望看到物体正确地显示在鼠标光标的正上方。
这个过程的核心是去掉变换的偏移量,使得绘制操作直接发生在标准世界坐标系中,利用投影将其放到正确的位置,期望通过这种方式验证缩放和位置是否正确。
运行游戏,结果尚可,但仍不完美
目前的情况是,虽然画面显示出来了,但效果并不理想。物体的位置接近鼠标的位置,但并没有完全跟踪鼠标,说明存在一些问题。这个问题可能出现在坐标转换或投影的计算上,导致物体并未准确地与鼠标位置同步。
game.cpp:将LocalZ设置为1.0f
问题出在坐标转换的过程中,当使用LocalZ
时,应该是尺度不变的,无论我们传入什么z
值,unprotected
和project
都应该返回相同的位置。然而,实际情况并没有这样发生,显示的位置并没有按预期的方式改变。这意味着有部分计算或者数据处理是错误的。
在分析过程中发现,主要的问题在于我们没有正确考虑pz
值,即物体距离目标的距离。在unprotected
过程中,未正确处理这个z
值,而是直接用了distance from camera
,这导致了错误的结果。实际上,z
值应当是物体距离目标的实际值,并且在计算时,应该从DistanceAboveTarget
减去dz
来获得正确的z
值。
game.cpp:将RenderGroup->Transform.DistanceAboveTarget添加到PushRect
问题出在unprotected
过程中,未正确处理z
值的逆向变换。当前,LocalZ
值并没有正确地反映出物体在空间中的位置。解决办法是,在unprotected
步骤后,需要将返回的值与LocalZ
进行处理,使得LocalZ
值能够正确反映物体的位置。
具体来说,需要进行如下处理:我们首先获取Transform.DistanceAboveTarget
,然后将这个值从LocalZ
中减去。通过这种方式,最终的结果应该与LocalZ
一致,这样才能确保坐标的转换和缩放是正确的。
在处理过程中,需要确保两者的变换是对称的,也就是说,我们需要在进行坐标变换时,确保最终的计算能够回到正确的LocalZ
值,而不是错误的计算结果。
这意味着我们需要通过正确的方程来进行计算,使得最终的结果符合预期,并且能够精确还原到物体的原始位置。虽然当前实现还没有完全正确,但这是导致问题的一个重要部分。
焦距会如何影响?会有FOV的设置吗?
焦距(focal length)在投影和反投影过程中起到了至关重要的作用。焦距决定了视场的缩放程度,也就是说,它影响了如何将三维空间中的物体映射到二维屏幕上,尤其是物体与相机的距离对其在屏幕上的表现有多大影响。焦距影响的是物体在z轴上的压缩程度,即物体越远,投影会变得越小,而焦距越长,这种压缩效应越明显。
在这个过程里,焦距帮助决定了物体在屏幕上的位置和大小。例如,在进行投影时,物体的Z值(与相机的距离)将会被焦距所影响,焦距越长,物体越远离相机时,它的投影就越小。这个比例关系也影响着反向投影的计算,因此需要在将物体的坐标从屏幕空间转换回世界空间时,考虑到焦距的影响。
尽管焦距的作用相对简单,但在调试时,问题可能出在处理细节上。焦距的处理可能没有完全按照预期工作,导致投影和反投影操作的结果不如预期。因此,调试过程中,需要确保焦距的影响在每一步都被正确地纳入计算,特别是在物体与相机之间的距离变化时,焦距的影响应该被适当地反映出来。
半相关问题:你怎么看待使用不同类型的点和向量?
关于使用不同类型的点(Point)和向量(Vector),认为这种区分并不是特别有用,反而有时会带来不必要的麻烦。点和向量的区别并不在于存储方式,而在于它们的使用方式和概念化。在很多情况下,可能需要根据不同的情境,将同样的事物概念化为两种不同的东西,而这时候,如果类型被严格区分,就会需要进行类型转换或强制转换,导致额外的工作量和麻烦。
从使用的角度来看,将点和向量视为不同的类型可能会限制灵活性,因为在实际开发过程中,可能需要在某些情况下把一个点当作向量使用,或者反过来。强行区分它们的类型不仅增加了代码的复杂度,也可能导致一些不必要的转换,增加了出错的机会。因此,认为在很多情况下,这种区分并没有太大必要,反而可能让事情变得更加繁琐。
问题的一部分是不是因为代码没有规范化鼠标和实体之间的运动关系,尤其是在不同层次的世界坐标下?
问题的核心在于代码没有正常化鼠标运动与屏幕尺寸之间的关系,尤其是在世界坐标系中的不同层级。这个变换和未保护的部分实际上是完全线性的,仅仅是多个乘法操作的组合。因此,理论上,当将鼠标从像素转换为米时,不论是在变换之前还是之后进行转换,最终的结果应该是相同的,不应该有任何变化。
game.cpp:尝试在事后进行MetersToPixels处理
在调试过程中,验证了当前的变换方法,无论是先进行变换再转换为像素,还是先转换为像素后再进行变换,最终结果是相同的,说明这种方法仍然有效。然而,尽管方法本身没有问题,仍然存在问题,主要体现在如何处理变换的细节上。还没有仔细检查是否涉及到“PushRect”部分,这可能是问题的关键所在。
game_render_group.cpp:检查PushRect是否没有其他变换操作
在渲染过程中,使用了渲染基准点 p
,并且直接应用了它。在绘制时,使用了 p
作为坐标,进行矩形的绘制操作。可以确认,p
被直接使用,没有发生任何其他的变换。这表明当前渲染过程中的坐标没有经过额外的变换处理。
Unproject出现了什么问题?
遇到的问题是关于这个项目的具体原因,自己并不清楚具体的原因是什么。这是一个很好的问题,自己也很想知道问题出在哪里,但目前还没有找到确切的答案。
game_render_group.cpp:调查问题
问题出在计算过程中,特别是在涉及到目标距离和 z 值时,最后的结果应该是基于本地 z 值的,并且涉及到焦距的运算。现在遇到的问题是无法准确理解和解决这个问题,导致情况变得复杂且没有明确的答案。因此,计划明天继续排查问题,逐步分析代码,确保每个步骤都被仔细检查。这是一个没有简单解决方法的复杂问题,可能需要更细致的调试和逐步验证。
问题:讨论开发工具的潜在功能,这些功能有助于解决此类问题
这个问题的核心在于某些程序错误往往浪费大量时间,特别是像这样的 bug。通常在编程时,我并不会遇到很多因为忘记释放内存之类的错误。相反,常常是一些计算逻辑上的 bug,尤其是在游戏编程中,涉及到转换和计算时,难以找出问题的根源。很多时候,尽管工具和语言提供了各种优化,比如垃圾回收,虽然可能偶尔会节省时间,但这种节省并不是最重要的,因为很多开发者都会解决内存管理的问题,像链表这种结构自己写就好。
然而,像当前的问题,涉及到复杂的数学计算和向量转换,这类问题才是影响编程效率的根源。理想的开发工具应该能帮助可视化这些计算步骤,帮助开发者清晰地理解数据在每个步骤中的变化,而不是只看着一堆数字。这种开发工具可以让我们立刻看到问题出在哪里,从而减少查找 bug 的时间。而现在的问题是,虽然开发工具在优化某些简单任务方面做得很好,但像这种复杂的数学推导和逻辑错误往往没有好的工具来帮助解决。因此,在这种情况下,开发者只能依靠手动调试,逐步分析问题所在,这让调试过程变得异常困难。
总的来说,理想的工具应该专注于帮助解决那些隐藏得更深、难以调试的复杂问题,而不是专注于那些简单且很容易解决的问题。
调试器:检查一些值并将其复制到临时缓冲区
首先,我们来看一下我们的原始坐标p,并进行处理。首先,原始的鼠标坐标被转换成了更小的值,单位是米。接下来,我们知道局部坐标系(local xie)的固定值是1,我们在进行投影计算时,使用的是焦距(focal length)进行的计算,结果得到了一个投影的xy坐标。
然后,我们将这个计算结果传递到下一个步骤。在进行这一系列操作时,我们在调试过程中发现,当我们调用push wrecked
函数时,会进行一个偏移量的计算,这个偏移量是基于xy值的,这个计算与绘制矩形有关。为了简化计算,原本应该在绘制矩形时,将维度设置为零,从而使得计算变得更简单。在这种情况下,我们可以直接修改这些值,确保我们传入的p值保持不变。
通过这种方式,最终输入值保持一致,并且我们不再绘制矩形,而仅仅进行计算。我们接下来传递的是目标与当前z坐标之间的距离。通过查看变换(transform)的情况,我们知道目标距离为8,z坐标是1。按照公式计算,目标与z坐标的差值应该为8,符合预期。
最后,我们进行一步调试,查看是否得到了正确的变换。经过验证,最终的结果与预期一致,坐标并没有发生变化。
整体而言,以上步骤的操作确保了在进行坐标变换时,相关的数值都能按照预期正确计算和转换。
我们把相对于基础P的Z轴运动去掉了
在这个过程中,我们忘记了一些关键点。首先,我们没有进行正交变换(orthographic transform),而是使用了距离目标的9个单位。在计算过程中,我们将pz设置为了某个值,然而在这个过程中,实际上我们曾经移除了相对于基础b的z轴移动。
记得之前做了一个决定,即我们不再考虑z轴的移动,将其移除了。这实际上意味着,这整个过程并没有像我们之前以为的那样存在bug,可能从一开始就没有问题。
game.cpp:正确设置Transform.OffsetP
所以基本上,我们真正需要做的就是忽略一些不必要的部分,重新审视整个流程。通过去除不需要的z轴移动,实际情况变得清晰了许多。原本以为的问题,其实并不存在,只是我们忽略了一些细节,导致了误解。
运行游戏,发现位置正确
问题最终出现在一个没预料到的地方。原本以为问题复杂,结果发现只是因为在设计时忽略了一些细节。最初的设想是我们并不想使用z轴的偏移量,然而后来发现我们并没有明确注意到这一点,导致了bug的产生。虽然我们原本的设计是正确的,但由于忘记了实现的方式,导致出现了问题。
这个bug本质上是因为我们在处理时没有考虑到一些已经实现的特性,而这些特性本应被考虑到。最终,经过反复检查和回顾,才发现问题出在了这部分。可以说,这是一个完全可以避免的问题,如果当时更仔细地查看代码,可能就能很早发现。
这整个过程其实也说明了一个点:即便是看似简单的设计和代码实现,也容易被细节忽视。
相关文章:
游戏引擎学习第204天
回顾并为今天的内容做铺垫 好,现在开始这一集。今天我们将进行一些用户界面编程,觉得这是一个展示如何编写这类代码的好时机。很多人对如何做用户界面代码都很好奇,所以展示一下如何编写是非常有意义的。 我之所以在现在的这个地方做这些工…...
蓝桥杯2024年第十五届省赛真题-R 格式
题目链接: 思路: 通过数组模拟d的每一位,逐位进行计算,从而实现对d的精确处理。 代码: #include<bits/stdc.h> #define int long long using namespace std; const int N 2020;int n; string s; vector<i…...
Haskell语言的区块链安全
Haskell语言在区块链安全中的应用 引言 随着区块链技术的发展,它已经成为金融、供应链管理、身份认证等多个领域的重要基础设施。然而,区块链的安全性问题一直是行业关注的焦点。为了确保区块链的安全性,开发者需要选择合适的编程语言来编写…...
BUUCTF-web刷题篇(11)
20.admin 这道题很可能用admin或者伪造admin进行登录,用admin进行登录,随便填写密码进不去,发现页面有register、login,用admin注册提示已经被注册。 方法一:(burp爆破) 进入登陆界面&#x…...
TensorFlow
TensorFlow 是一个由 Google 开发并开源的机器学习和深度学习库,被广泛应用于各类机器学习项目。以下为你详细介绍: 概述 TensorFlow 最初是为了满足 Google 内部大规模机器学习需求而研发,后于 2015 年开源。它提供了一个强大且灵活的生态…...
分子生成的深层次层次变分自编码器 - DrugHIVE 测评
一、背景介绍 DrugHIVE 来源于南加州大学定量与计算生物学系的 Remo Rohs 为通讯作者的文章:《Structure-Based Drug Design with a Deep Hierarchical Generative Model》。文章链接:https://pubs.acs.org/doi/10.1021/acs.jcim.4c01193。该文章在 202…...
54.大学生心理健康管理系统(基于springboot项目)
目录 1.系统的受众说明 2.相关技术 2.1 B/S结构 2.2 MySQL数据库 3.系统分析 3.1可行性分析 3.1.1时间可行性 3.1.2 经济可行性 3.1.3 操作可行性 3.1.4 技术可行性 3.1.5 法律可行性 3.2系统流程分析 3.3系统功能需求分析 3.4 系统非功能需求分析 4.系统设计…...
Linux文件特殊权限管理及进程和线程
acl 权限优先级 拥有者 > 特殊指定用户 > 权限多的组 >权限少的组 > 其他 mask阈值 mask是能够赋予指定用户权限的最大阀值 当设定完毕文件的acl列表之后用chmod缩小了文件拥有组的权力 mask会发生变化 恢复: setfacl -m m: 权限 :rwx 文件/…...
Vue2+Vue3 45-90集学习笔记
Vue2Vue3 45-90集学习笔记 小兔鲜首页 页面开发思路: 分析页面,按模块拆分组件,搭架子(局部注册或全局注册) 局部注册:App.js中 导入(import),注册(compon…...
【Web 服务器】的工作原理
🌐 Web 服务器的工作原理 Web 服务器的主要作用是 接收客户端请求(通常是浏览器发出的 HTTP/HTTPS 请求),处理请求,并返回相应的数据(如网页、图片、API 响应等)。 📌 工作流程 1️…...
LeetCode 5 -- 区间DP | 中心拓展算法
题目描述 最长回文子串 数据规模为 5e5,必须 manacher 算法 1. DP 由于 r e v e r s e ( ) reverse() reverse() 的时间复杂度是 O ( N ) O(N) O(N),因此暴力肯定是不行的。 d p dp dp 的思路:如果 s [ l . . r ] s[l..r] s[l..r] 是一个…...
IntelliJ IDEA中Spring Boot 3.4.x+集成Redis 7.x:最新配置与实战指南
前言 Spring Boot 3.4.x作为当前最新稳定版本,全面支持Java 17与Jakarta EE 10规范。本文以Spring Boot 3.4.1和Redis 7.x为例,详解如何在IDEA中快速接入Redis,涵盖最新依赖配置、数据序列化优化、缓存注解及高…...
数仓建模中计算累计销量
在数仓建模中计算累计销量,通常需要结合时间维度和业务逻辑设计合理的模型与计算逻辑。以下是分步骤的实现思路和示例: 1. 模型设计 累计销量的计算通常基于星型模型或雪花模型,核心结构包括: 事实表:记录每一笔销售…...
(多看) CExercise_05_1函数_1.2计算base的exponent次幂
题目: 键盘录入两个整数:底(base)和幂指数(exponent),计算base的exponent次幂,并打印输出对应的结果。(注意底和幂指数都可能是负数) 提示:求幂运算时,基础的思路就是先无脑把指数转…...
Pollard‘s Rho 算法
Pollard’s Rho 算法:一场数学与计算机科学的巧妙结合 在现代计算机科学中,素数分解、整数因子化问题有着广泛的应用,尤其是在密码学领域。然而,当面对一个大合数时,寻找其因子仍然是一个非常复杂的问题。我们常常依赖…...
8款分形长虹玻璃科幻渐变海报设计JPG背景素材 The Gradient Backgrounds Pack
天空从未如此美好 — 直到有人将日落洒在您的屏幕上。这些渐变是带有心跳的液体颜色,从熔化的金色转变为深紫色,就像地平线一样。 8 个背景中的每一个都以 45003000 像素和 300dpi 的速度脉冲,清晰到足以让您感觉自己可以直接踏入光芒中。但这…...
AIGC9——AIGC时代的用户体验革命:智能交互与隐私保护的平衡术
引言:当AI成为交互主角 2024年,淘宝AI客服"阿里小蜜"日均处理20亿次咨询,日本虚拟偶像"初音未来"演唱会门票3秒售罄——这些现象标志着AIGC已深度融入人机交互场景。但与此同时,过度个性化的推荐引发"信…...
vm虚拟机虚拟出网卡并ping通外网
在 Linux 和 Windows 系统中,即使不使用网络命名空间(namespace),也能实现虚拟网卡上网。以下是不同场景下的实现方法: 一、Linux 系统(不使用网络命名空间) 1. 直接创建虚拟网卡对(…...
基于时间卷积网络TCN实现电力负荷多变量时序预测(PyTorch版)
前言 系列专栏:【深度学习:算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域,讨论了各种复杂的深度神经网络思想,如卷积神经网络、循环神经网络、生成对…...
ESXi8的部署过程
目录 一、系统安装 二、ESXI8的序列号 三、挂载硬盘和新建VMFS数据分区 四、通过数据存储浏览器上传下载文件 五、Windows远程桌面端口隐射 六、导出虚机 一、系统安装 1、使用UtrIOS系统制作ESXI8的启动盘; 2、服务器启动F8按键进入Popup启动选项,选择U盘启动; 3、安…...
IntelliJ IDEA 2020~2024 创建SpringBoot项目编辑报错: 程序包org.springframework.boot不存在
目录 前奏解决结尾 前奏 哈!今天在处理我的SpringBoot项目时,突然遇到了一些让人摸不着头脑的错误提示: java: 程序包org.junit不存在 java: 程序包org.junit.runner不存在 java: 程序包org.springframework.boot.test.context不存在 java:…...
Windows 权限配置文件解析与安全分析(GPP,GPO,LSA)
在 Windows 网络环境中,权限配置文件用于管理用户权限、密码策略和访问控制,涵盖组策略首选项(GPP)、本地安全策略(LSA)、注册表以及 Active Directory 组策略(GPO) 等。这些配置文件…...
【微服务】基础概念
1.什么是微服务 微服务其实就是一种架构风格,他提倡我们在开发的时候,一个应用应该是一组小型服务而组成的,每一个服务都运行在自己的进程中,每一个小服务都通过HTTP的方式进行互通。他更加强调服务的彻底拆分。他并不是仅局限于…...
MYOJ_4342:(洛谷P1087)[NOIP 2004 普及组] FBI 树(二叉树实操,递归提高)
题目描述 我们可以把由 “0” 和 “1” 组成的字符串分为三类:全 “0” 串称为 B 串,全 “1” 串称为 I 串,既含 “0” 又含 “1” 的串则称为 F 串。 FBI 树是一种二叉树,它的结点类型也包括 F 结点,B 结点和 I 结点三…...
LLM(13):词编码后的位置
原则上,token 嵌入是大型语言模型(LLM)的合适输入。然而,LLM 的一个小缺点是它们的自注意力机制无法指导序列中 token 的位置或顺序。在前面介绍的嵌入层的工作方式中,无论 token ID 在输入序列中的位置如何࿰…...
MINIQMT学习课程Day4
聚宽的模拟/实盘跟单系统,已经全部介绍完毕,上传完毕了,相信大家已经可以进行聚宽的miniqmt的交易了。如果还有疑问,私信我进行沟通。 现在开始进入新的课题,如何学习python,我不教那些乱七八糟的ÿ…...
AWS云服务:大数据公司实现技术突破与商业价值的核心引擎
在数据驱动决策的时代,大数据公司面临着海量数据存储、实时计算、复杂分析及安全合规等核心挑战。如何高效构建弹性、可扩展且低成本的技术架构,成为企业能否在竞争中胜出的关键。亚马逊云科技(AWS)作为全球云计算领域的领导者&am…...
Openpyxl使用教程(包含处理大数据量案例)
文章目录 一、简介功能特性应用场景使用优势 二、常用方法1、工作簿wb2、工作表ws 三、案例1、创建新工作簿2、将Excel数据存入list中3、按行读取文件(适合大文件)4、按指定行读取文件(适合大文件) 一、简介 在 Python 数据处理领域,openpyxl 凭借其卓越的功能与易…...
蓝桥杯15届 宝石组合
问题描述 在一个神秘的森林里,住着一个小精灵名叫小蓝。有一天,他偶然发现了一个隐藏在树洞里的宝藏,里面装满了闪烁着美丽光芒的宝石。这些宝石都有着不同的颜色和形状,但最引人注目的是它们各自独特的 “闪亮度” 属性。每颗宝…...
THE UNIVERSITY OF MANCHESTER-NUMERICAL ANALYSIS 1-3.4数值积分-复合积分公式
3.4.1 复合梯形法则 梯形法则仅使用两个点来近似积分,显然对于大多数应用来说,这不足够。为了提高精度,有多种方法可以利用更多的点和函数值。正如我们刚才在Newton-Cotes方法和辛普森法则中所看到的,一种方法是使用更高阶的插值函数。另一种方法是将区间划分为更小的区间…...
嵌入式系统应用-拓展-相关开发软件说明
这里以STM32的系列产品为例子,利用MDK的集成开发平台进行开发过程中,所有相关软件安装说明。 1 集成开发环境安装 1.1 MDK 下载 1.1.1 官网下载 官方下载地址: https://www.keil.com/download/product/ 选择MDK-ARM ,填写一些…...
react实现上传图片到阿里云OSS以及问题解决(保姆级)
一、优势 提高上传速度:前端直传利用了浏览器与 OSS 之间的直接连接,能够充分利用用户的网络带宽。相比之下,后端传递文件时,文件需要经过后端服务器的中转,可能会受到后端服务器网络环境和处理能力的限制,…...
嵌入式学习笔记——ARM-中断与异常
文章目录 中断与异常的区别中断与 DMA 的区别中断能否睡眠?下半部能否睡眠?1. 中断处理程序不能睡眠2. 下半部(SoftIRQ、Tasklet、Workqueue) 中断处理注意点1. 快进快出2. 避免阻塞3. 正确返回值4. 如何处理大量任务5. 避免竞态问…...
OpenHarmony子系统开发 - 安全(十二)
OpenHarmony SELinux开发指导(五) 一、OpenHarmony SELinux常见问题 neverallow编译报错处理 现象描述 编译SELinux时会进行neverallow检查,当配置的策略不合理时,可能出现违反neverallow编译报错。 neverallow check failed…...
深入解析ARM与RISC-V架构的Bring-up核心流程
深入解析ARM与RISC-V架构的Bring-up核心流程 作者:嵌入式架构探索者 | 2023年10月 引言 在嵌入式开发中,处理器的Bring-up(启动初始化)是系统运行的第一道门槛。ARM和RISC-V作为两大主流架构,其Bring-up流程既有共性…...
【力扣hot100题】(054)全排列
挺经典的回溯题的。 class Solution { public:vector<vector<int>> result;void recursion(vector<int>& nums,vector<int>& now){if(nums.size()0){result.push_back(now);return ;}for(int i0;i<nums.size();i){now.push_back(nums[i]);…...
vue中如何动态的绑定图片
在项目中遇到需要动态的改变图片路径,图片路径并非是从后台获取过来的数据。 因此在data中必须用require加载,否则会当成字符串来处理。...
湖北师范大学计信学院研究生课程《工程伦理》12.6章节练习
1【单选题】下列哪个不是数字身份的特点? A. 多样性 B. 唯一性 C. 可变性 D. 允许匿名和假名 2【单选题】下列哪项不是现代国家的基本职能。 A. 保护政权统一 B. 保护本国面对其他国家侵犯 C. 保护国内每个人免受他人侵犯 D. 承担发展国民经济 3【单选题】哪个国家在全球率先发…...
prism WPF 登录对话框登录成功后显示主界面
prism WPF 登录对话框登录成功后显示主界面 项目结构 LoginUC.xaml <UserControl x:Class"PrismWpfApp.Views.LoginUC"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml…...
MySQL统计信息
1. 什么是统计信息? 统计信息就像是数据库的"地图",它告诉优化器: 每个表有多大(有多少行数据) 每个索引的"区分度"(有多少不同的值) 数据分布情况(哪些值出…...
Spark,配置hadoop集群2
编写Hadoop集群启停脚本 1.建立新文件,编写脚本程序 在hadoop101中操作,在/root/bin下新建文件:myhadoop,输入如下内容: 2.分发执行权限 保存后退出,然后赋予脚本执行权限 [roothadoop101 ~]$ chmod x /r…...
⭐算法OJ⭐重建行程【哈密尔顿路径】(C++ 实现)Reconstruct Itinerary
You are given a list of airline tickets where tickets[i] [from_i, to_i] represent the departure and the arrival airports of one flight. Reconstruct the itinerary in order and return it. All of the tickets belong to a man who departs from “JFK”, thus, t…...
大模型如何优化数字人的实时交互与情感表达
标题:大模型如何优化数字人的实时交互与情感表达 内容:1.摘要 随着人工智能技术的飞速发展,数字人在多个领域的应用愈发广泛,其实时交互与情感表达能力成为提升用户体验的关键因素。本文旨在探讨大模型如何优化数字人的实时交互与情感表达。通过分析大模…...
【含文档+PPT+源码】基于SpringBoot+Vue旅游管理网站
项目介绍 本课程演示的是一款 基于SpringBootVue旅游管理网站,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含:项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 3.该项目附…...
理解OSPF Stub区域和各类LSA特点
之前学习到OSPF特殊区域和各类类型LSA的分析后,一直很混乱,在网上也难找到详细的解释,在看了 HCNP书本内容后,对这块类容理解更加清晰,本次内容,我们使用实验示例,来对OSPF特殊区域和各 类型LSA…...
AI智算-K8s如何利用GPFS分布式并行文件存储加速训练or推理
文章目录 GPFS简介核心特性存储环境介绍存储软件版本客户端存储RoCEGPFS 管理(GUI)1. 创建 CSI 用户2. 检查GUI与k8s通信文件系统配置1. 开启配额2. 启用filesetdf文件系统3. 验证文件系统配置4. 启用自动inode扩展存储集群配置1. 启用对根文件集(root fileset)配额2. igno…...
Linux如何设置bash为默认shell
大部分情况下,Linux的默认shell是bash,但某些Linux发行版,例如Kali,默认的终端是zsh,本文以Kali为例,将Kali的默认shell从zsh改为bash。 其实Kali早期的shell也是bash,2020 版本之后:…...
leetcode-代码随想录-链表-翻转链表
题目 链接:206. 反转链表 - 力扣(LeetCode) 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1]class Solution { public:ListNode* rev…...
CSS快速上手
第一章 CSS基础 首先来回答2个问题。 1.CSS是什么? CSS是用来控制网页外观的一门技术。 2.前端最核心的技术是什么?他们分别是用来干吗的? 前端最核心的技术有:HTML、CSS、JavaScript。 HTML用于控制网页的结构,CSS…...
虚拟现实 UI 设计:打造沉浸式用户体验
VR UI 设计基础与特点 虚拟现实技术近年来发展迅猛,其独特的沉浸式体验吸引了众多领域的关注与应用。在 VR 环境中,UI 设计扮演着至关重要的角色,它是用户与虚拟世界交互的桥梁。与传统 UI 设计相比,VR UI 设计具有显著的特点。传…...