游戏引擎学习第171天

回顾并计划今天的内容

昨天,我们在处理一项任务时暂停了,当时的目标非常清晰,但由于时间限制,我们将其分成了两个部分。我们首先完成了运行时部分,而今天要处理的是资产打包部分。这项任务涉及改进字体系统,使其能够在运行时提供比以往更多的信息。此前,我们为了让字体系统正常工作,使用了一些临时值,而现在,我们已经将这些临时值迁移到了每种字体的专用数据表中,以便游戏可以加载并支持多种字体,同时提供更丰富的信息。

但这些字体数据表必须有来源。尽管我们已经在运行时编写了代码来使用这些表,但目前仍未建立从资产打包阶段到游戏运行时的数据流。为了解决这个问题,需要完成两个关键步骤:

  1. 从Windows提取字体数据表

    目前,资产打包器并没有提取这些表的数据。因此,我们需要修改资产打包器,使其能够从Windows获取字体数据,并将其写入资产包文件中。

  2. 在游戏运行时加载这些数据表

    运行游戏时,我们需要从资产包文件中提取字体数据表,并进行适当的映射。由于游戏允许多个资产包合并,因此某些ID(如位图ID)在加载时可能会发生变化。因此,我们需要确保在加载字体数据时正确地重新映射相关的ID,例如字体表中包含的位图ID表等。

整体来看,这项任务并不复杂,但涉及的工作量不小。编写代码和调试的过程主要是基础性的实现,而不是复杂的逻辑推理。尽管如此,仍然需要细心处理,以避免低级错误和潜在的Bug。

在上次的进度中,游戏已经能够正常运行,但由于字体数据尚未加载,屏幕上的字体未能显示。现在,我们的目标是让字体数据表正确工作,从而恢复字体显示,并进一步优化字体功能,例如正确的字距调整等。

首先,我们需要修改资产打包器(Asset Packer)。当前的测试资产构建器仍需大量工作,因为之前的字体处理是临时实现的。此前的方式是每次加载字体字符时,直接调用 LoadGlyphBitmap 来提取字形位图。但现在,由于我们已经正式定义了字体的概念,因此字体需要在资产表中得到明确的表示,并进行专门的处理。

我们会按照一贯的方法,一步步地进行改进,逐步将字体的概念整合进资产管理系统。在这一过程中,我们会先保持代码的整体架构不变,并在保证现有逻辑正常运行的基础上,逐步添加新的功能,以确保字体的正确加载和管理。

我们希望能够加载多个字体

当前的字体加载方式需要改进,使其能够支持多个字体,并在资产系统中正式引入"字体"这一概念。此前的实现方式较为简陋,存在一些静态变量,使得系统实际上只能使用一个字体。如果需要支持多个字体,必须对其进行重构,而现在正是一个合适的时机。

通过这次改进,字体将成为资产文件中的一等公民。过去的资产打包器并未真正区分不同的字体,而现在需要在其中正式引入"字体"这一概念,使其能够更好地管理和存储多个字体。这一调整对于整体资产管理来说至关重要。

在当前的 GameAssets 代码中,可以看到 AssetSource 负责告诉系统如何打包某个资源。在 AssetSource 结构体中,有 FirstSampleIndexCodePoint 这些字段,而现在需要让 AssetSource 变得更加完善,以支持更复杂的字体管理。

此外,关于代码中之前随意设定的数值,像是 4096 这样被认为是"非常大的数",其实是没有具体依据的。这类随意定义的数值需要在优化过程中被替换为更合理的设计,确保系统的可扩展性和健壮性。

有没有比4096更大的数字...?α

当前的代码中曾设定 4096 作为某个值的上限,定义 VERY_LARGE_NUMBER,

为了确保系统的灵活性和扩展性,需要重新评估 4096 这一限制是否合理。如果系统在未来需要支持更大规模的数据,当前的设定可能会成为瓶颈。虽然这一数值在当下可能足够,但硬编码的限制可能会在未来造成不必要的约束,因此有必要考虑更具弹性的方案,以避免人为设定的上限影响系统的发展。

在资产打包器中使用更明确的资源结构

当前的代码对字体资产的管理方式进行了重构,主要目标是让字体的加载和处理更加清晰,并支持多个字体,而不是之前的单一静态字体实现。此前,"资产类型字体"(asset_source_font)实际上是指"字体字形资产"(asset_source_font_glyph),这两者并不完全相同。为了更好地管理字体资产,需要区分"字体"与"字体字形"这两个概念。

主要改动点:

  1. 重构资产类型管理

    • 过去的实现方式较为简陋,字体只是作为位图处理,在资产打包器(asset packer)中并没有真正的"字体"概念,而只是处理了单个字形。
    • 现在的改动让字体成为一级概念,与单独的字体字形区分开来,使资产打包器可以明确管理不同类型的资源,例如"字体"、"字体字形"、"声音"等。
    • 具体做法是增加 asset_source_font,用于表示字体资源,而 asset_source_font_glyph 则用于表示单个字形。
  2. 优化资产来源结构

    • 通过 asset source 结构,统一不同类型的资产来源,使其更具条理性。
    • 例如,新增 asset_source_font 来表示字体,asset_source_sound 处理音频,而 asset_source_bitmap 则用于位图。
    • 这使得资产打包器更加模块化,可以方便地扩展支持不同类型的资产,而不需要修改大量代码。
  3. 引入"加载字体"概念(loaded_font

    • 过去的代码并没有真正管理字体数据,而只是简单地加载字形。这种方式导致字体信息分散且难以维护。
    • 现在增加 loaded_font 结构,作为字体的统一管理单元,用于存储所有相关的数据,如字体文件路径、字体信息等。
    • 这样,每个字体可以在 loaded_font 结构中存储信息,而不是直接在 asset packer 代码中分散管理。
  4. 调整数据传递逻辑

    • 以往的方法是直接传递字体文件路径和字体名称,现在改为传递 loaded_font 结构,使得代码更加清晰、可维护。
    • loaded_font 结构中包含了字体的所有必要信息,这样在后续操作时,无需重复传递多个参数,只需使用 loaded_font 结构即可。
  5. 改进资产存储方式

    • 之前的代码会直接在 asset_source 结构中存储文件路径等信息,现在改进为更加统一的方式。
    • 例如,对于 asset_source_bitmap,它的 FileName 现在存储在 bitmap 结构体内部,而不是整个 asset_source 结构的直接成员。
    • 这样能够更好地管理不同类型的资产,避免数据结构过于混乱。
  6. 编译错误修正和代码清理

    • 由于结构调整,代码需要相应修改,特别是在资产打包过程中,原有的一些访问方式已不再适用。
    • 例如,原来添加字符资产时需要传递字体文件路径,现在只需传递 loaded_font,这就需要修改调用代码,使其符合新的数据结构。
    • 通过编译器提示逐步修正所有调用,使其适配新的资产存储方式。

总结

这些改动使得资产打包器更清晰地区分了字体、字形和其他类型的资产,增强了代码的可读性和可维护性。通过 loaded_font 结构,字体的管理更加集中,避免了过去分散管理的问题。此外,引入 asset source 结构,使得所有资产类型的管理方式统一,更加符合良好的软件架构设计。

将字体加载代码移到一个单独的函数中

当前的代码重构主要是优化字体加载逻辑,使其更加合理并支持多个字体,而不是像之前那样依赖静态变量或其他不稳定的方式进行处理。之前的实现方式比较混乱,现在的目标是创建一个独立的字体加载系统,使得字体可以独立管理,并且允许多个字体共存。

主要改动点:

  1. 移除原有的字体加载混乱逻辑

    • 之前的字体加载代码存在诸多问题,例如依赖静态变量、流程混乱等,现在的改动将字体加载逻辑独立出来,避免过去那种"乱糟糟"的实现方式。
    • 现在的加载逻辑更加清晰,字体的加载和使用被划分到不同的模块,避免了直接在 LoadGlyphBitmap 过程中插入大量不相关的字体处理逻辑。
  2. 引入 loaded_font 结构

    • 之前字体的加载没有一个统一的管理方式,现在增加 loaded_font 结构体,作为字体的统一管理单元,存储所有与字体相关的数据。
    • loaded_font 结构中存储了 TextMetric(字体度量信息)和 HFont 句柄,后续可能会扩展更多内容,但目前这两个信息已经能满足基本需求。
    • 通过 loaded_font 结构,使得字体的管理更加清晰,所有字体相关的数据都集中存储,避免了过去分散在代码各处的情况。
  3. 优化 LoadGlyphBitmap 的实现方式

    • 之前 LoadGlyphBitmap 处理字体的方式较为混乱,现在的改动使其只关注位图加载,而不直接涉及字体的细节。
    • 现在的实现方式是将字体加载逻辑从 LoadGlyphBitmap 代码中剥离出来,改为通过 loaded_font 结构进行管理。
    • LoadGlyphBitmap 现在只需要接收 FontCodePoint,不再需要额外的文件路径等参数。
  4. 优化字体的 select 逻辑

    • 设备上下文(DC)是可以复用的,而字体是需要动态切换的。
    • 现在的改动使得在 SelectObject 时,会先从 loaded_font 结构中取出当前所需的 HFont,然后在需要使用时选择正确的字体。
    • 这样就可以在同一个设备上下文中使用多个不同的字体,而不会像之前那样被单个静态字体所限制。
  5. 优化 TextMetric 处理

    • TextMetric 可能不需要每次加载字体时都重新计算,因此可以只在 load font 过程中计算一次,避免重复计算提高效率。
    • 但如果要在 LoadGlyphBitmap 过程中获取 TextMetric,仍然可以在这里进行计算,只是会稍微增加计算开销,不过影响不大。
  6. 实现字体加载、使用和释放的完整流程

    • 通过 load font 统一加载字体,并存储字体句柄(HFont)。
    • LoadGlyphBitmap 过程中,只需要从 loaded_font 结构中获取字体句柄即可,而不需要再额外去创建或查找字体信息。
    • 允许在需要时释放字体(free font),虽然一般情况下不会频繁释放字体,但提供这一机制可以避免潜在的资源泄露问题。

释放字体占用的内存

当前的优化重点在于如何正确管理字体资源,特别是在需要释放字体时的处理方式。之前的代码并未关注字体句柄(HFont)的释放问题,现在的目标是确保在适当的时候释放字体资源,避免资源泄露。

主要优化点:

  1. 增加对字体释放的支持

    • 之前的代码没有提供释放字体的机制,如果加载大量字体但不释放,可能会导致资源泄露。
    • 现在在 loaded font 结构中增加了一个释放字体的方法,确保可以在需要时正确释放字体句柄。
  2. 使用 DeleteObject 释放 HFont

    • 在 Windows API 中,HFont 句柄需要通过 DeleteObject 释放,而不是 DestroyObject(后者并不存在)。
    • DeleteObject 是 Windows GDI(图形设备接口)提供的标准资源释放函数,适用于 GDI 资源(如 HFontHBitmap 等)。
    • 确保 DeleteObject 被正确调用,以避免资源泄露。
  3. 查询 MSDN 以确保正确释放方式

    • 通过查阅 Windows API 文档,确认 DeleteObject 是正确的释放方式。
    • Windows 的 CreateFont 文档通常会指明需要使用 DeleteObject 来释放创建的字体资源。
  4. 优化字体管理的可扩展性

    • 目前的实现已经能够处理多个字体,但如果未来需要加载上百万种字体,可能需要更精细的管理策略,比如使用哈希表存储 HFont,并提供自动回收机制。
    • 目前的优化重点是在当前需求范围内确保资源管理正确,而不考虑超大规模的字体管理需求。
  5. 整体逻辑调整

    • loaded font 结构体析构时调用 DeleteObject,确保资源自动释放。
    • load font 过程中存储 HFont,在 free font 或析构 loaded font 时释放。
    • 避免冗余的 HFont 句柄泄露,确保每个 HFont 在不再使用时被正确销毁。

总结

当前的优化主要是增加了字体资源的释放机制,确保 HFont 句柄不会泄露。通过 DeleteObject 正确释放字体资源,并在 loaded font 结构体中管理 HFont,避免不必要的内存占用。这使得字体管理更加完善,减少了资源泄露的可能性,同时提供了一个更加稳健的字体加载和管理机制。

我们至少需要一个用于游戏的字体和一个用于调试的字体

当前的字体加载机制已经完成了基本转换,现在可以稳定地加载多个字体,并支持多种字体管理方式。这一改进非常重要,因为在实际应用中,可能需要使用多种字体,而不仅仅是单一字体。

主要优化点:

  1. 支持多个字体

    • 现在可以加载多个字体,而不是只能使用单一字体。
    • 未来可能会需要使用不同的字体来显示不同的文本内容,例如:
      • 游戏内字体:用于游戏文本显示,如菜单、对话框等。
      • 调试字体:用于开发调试信息的显示,通常不同于游戏内字体,以便区分。
    • 这些字体在渲染时可能会有不同的用途,因此需要灵活管理。
  2. 支持不同语言的字体

    • 未来可能需要支持不同的语言,而不同语言可能需要不同的字体(例如,中文、日文、韩文等需要特殊的字形支持)。
    • 目前的字体加载机制应该可以扩展,以适应这些需求,例如:
      • 针对特定语言选择相应的字体。
      • 允许在同一场景中混合使用多个字体,以支持多语言显示。
  3. 字体加载机制的改进

    • 现在的字体加载更加稳定,可以加载多个字体并进行管理。
    • 加载位图时,应该可以正确匹配到相应的字体,不会出现错误映射的问题。
    • 未来可能需要进行更多测试,以确保所有字体在不同场景下都能正确渲染。
  4. 未来改进方向

    • 优化字体渲染:目前的改进主要是加载字体,而未来可能需要优化字体渲染方式,例如抗锯齿、自动调整大小等。
    • 增加字体缓存:如果频繁加载字体,可能会导致性能下降,因此可以考虑缓存常用字体,避免重复加载。
    • 自动适配字体:根据不同的屏幕分辨率和设备类型,动态调整字体大小和渲染方式,以保证最佳的视觉效果。

总结

当前的优化已经实现了多字体加载的基础功能,为后续的扩展提供了可能性。未来可能会添加更多特性,如不同语言支持、字体缓存和更精细的渲染优化,以提高整体的灵活性和性能。

目前我们的STB代码路径出现问题

当前的 STB 相关路径已经被破坏,如果此时重新启用 STB 代码,将会遇到一系列问题。因此,目前 STB 相关的功能暂时处于不可用状态。

主要内容总结:

  1. STB 支持的暂时中断

    • 由于最近的代码改动,STB 相关的路径已被破坏,当前无法正常使用。
    • 如果重新启用 STB 代码,将会导致一系列编译或运行时错误。
    • 目前的目标并不是立即修复 STB,而是稍后再进行兼容性调整。
  2. 未来的 STB 修复计划

    • 未来计划恢复 STB 支持,以便让用户可以使用 STB 进行处理。
    • 在适当的时候,会重新调整代码,使 STB 路径恢复正常,并确保其能与当前系统兼容。
  3. 当前优先级

    • 目前的开发重点不在 STB,而是优先完成其他核心功能。
    • STB 相关的修复工作将在后续开发阶段进行,而不是立即处理。

总结

目前 STB 相关路径已经损坏,暂时无法使用,但未来会进行修复,以确保其正常运行。不过,在修复 STB 之前,当前的开发工作会优先关注其他更关键的功能。

使字体作为可用资产

  1. 开始实现新的功能

    • 现在需要着手实现之前计划的功能,即让字体真正作为可用的资源进行管理。
    • 具体而言,字体应该被标记为资产,并能够在程序中实际使用。
  2. 区分不同的字体资源类型

    • BeginAssetType 代码部分,需要对字体资源进行更清晰的分类。
    • 字体 (Font):表示整个字体,包含所有相关信息,例如字体文件、度量数据等。
    • 字体字形 (Font Glyph):表示具体的字符图像,即实际渲染的位图,每个字形对应一个字符。
    • 这样可以区分字体本身与字体字形,确保管理逻辑更加清晰。
  3. 定义字体和字体字形的作用

    • 字体 (Font) 作为核心信息存储点,管理整个字体的元数据。
    • 字体字形 (Font Glyph) 作为具体的可视化表现,存储特定字符的位图数据。
    • 这种结构有助于优化字体加载和渲染流程,提高系统的灵活性和可扩展性。

总结

当前的开发重点是让字体真正作为资源进行管理,并且在 BeginAssetType 代码部分清晰区分 字体 (Font) 与字体字形 (Font Glyph) 。字体存储全局信息,而字形存储具体字符的位图,从而使字体系统更加清晰和易用。

添加字体资产(AddFontAsset)

主要内容总结:

  1. 新增 AddFontAsset 方法

    • 这个方法的作用是将一个字体资源添加到资产管理系统中,确保它能够被正确打包并使用。
    • 逻辑上比较简单,本质上就是让字体进入资产打包表,使其在打包时被正确处理。
    • 需要传入 字体名称字体文件名,然后存储这些信息,以便后续打包使用。
  2. 实现 AddFontAsset 的逻辑

    • 该方法的实现主要沿用了之前处理其他资产(如位图、音频等)的方式,只是应用到字体上。
    • 具体流程包括:
      1. 获取字体的唯一 ID,用于索引和管理。
      2. 创建一个 HHA 资产(HHA asset),用于存储字体的打包数据。
      3. 将字体信息填充到资产表中,确保打包时能够正确处理它。
  3. 代码逻辑优化

    • 观察代码后发现,当前方法和其他资源管理的方式基本一致,可以借鉴已有逻辑进行优化和整合。
    • 可能需要调整部分代码结构,以减少重复性并提升可维护性。

总结

目前的开发重点是实现 AddFontAsset 方法,使字体能够作为资源被正确管理和打包。整个逻辑遵循之前的资产管理方式,包括获取字体 ID、存储字体信息,并确保它能在打包表中正确注册。同时,在编写代码时,需要考虑优化方法,减少重复逻辑,提高代码的整洁度和可维护性。

将重复的代码整合到AddAsset中

主要内容总结:

  1. 代码优化:封装 AddAsset 逻辑

    • 发现代码中存在大量重复逻辑,因此引入 AddAsset 方法进行封装,以减少重复代码,提高可读性和可维护性。
    • 定义 struct added_asset 结构体,包含 IDHHA 资产指针,便于统一管理资产。
    • 通过 AddAsset 统一处理资产的索引、存储和 HHA 资产填充,避免多个地方重复执行相同的逻辑。
  2. 优化字体资源管理

    • 之前的 AddFontAsset 代码较为分散,现在整合到 AddAsset 方法中,使其更清晰、更易维护。
    • AddFontAsset 现在调用 AddAsset,大幅减少重复代码,只需专注于字体特有的属性。
    • 其他资源类型(如字符、位图、声音)也采用相同方法优化,确保整个系统的一致性。
  3. 新增字体资源属性

    • 观察 HHA 资产格式,发现字体需要存储 CodePointCount(字符总数)LineAdvance(行间距)
    • 这些信息需要在 AddFontAsset 过程中获取,并存储到 loaded font 结构中,以便后续使用。
    • 考虑是否需要长期存储这些属性,或者仅在打包过程中使用。
  4. 整体优化目标

    • 减少重复代码,提高可读性和可维护性。
    • 确保资源管理的一致性 ,所有资源(字体、字符、位图、声音)都采用统一的 AddAsset 方法进行管理。
    • 保证字体资源完整性 ,正确存储 CodePointCountLineAdvance 等关键属性。

总结

通过优化 AddAsset 方法,统一了所有资源(包括字体、位图、声音等)的管理方式,大幅减少重复代码,提高了系统的可维护性。同时,在字体资源管理中,明确了需要存储的关键属性(字符总数、行间距),确保字体资源能够正确加载和使用。

在我们知道要加载的字符范围之前,硬编码字符点数量

主要内容总结:

  1. 字体加载过程中获取 CodePointCountLineAdvance

    • CodePointCount 目前无法直接计算,因为尚未决定支持哪些字符范围,因此暂时需要手动硬编码
    • LineAdvance(行间距)可以通过 Windows 设备上下文(Device Context, DC) 提取 TextMetrics 获取。
  2. TextMetrics 提取 LineAdvance

    • 使用 SelectObject 选择字体对象,从设备上下文获取 TextMetrics
    • TextMetrics 结构包含 tmHEIGHT(字体高度)和 tmEXTERNALLEADING(额外行间距)。
    • LineAdvance 计算方式:
      LineAdvance = tmHEIGHT + tmEXTERNALLEADING \text{LineAdvance} = \text{tmHEIGHT} + \text{tmEXTERNALLEADING} LineAdvance=tmHEIGHT+tmEXTERNALLEADING
    • 参考文档确认 tmHEIGHTtmEXTERNALLEADING 的含义,确保计算正确。
  3. 实现步骤

    • 选择字体对象 (SelectObject)。
    • 获取 TextMetrics
    • 计算 LineAdvance 并存储。
  4. 整体优化目标

    • 暂时手动设置 CodePointCount,后续根据支持字符范围进行调整。
    • 动态计算 LineAdvance,避免手动硬编码,提高适配性。
    • 确保 LineAdvance 计算符合 TextMetrics 规范,保证字体渲染正确。

总结

在字体加载过程中,我们可以直接从 TextMetrics 提取 LineAdvance,确保字体行间距计算正确。而 CodePointCount 由于尚未定义支持的字符范围,暂时只能手动指定,后续再调整支持范围和计算方式。

在启动时初始化全局字体设备上下文

主要内容总结:

  1. 全局字体设备上下文(GlobalFontDeviceContext)管理优化

    • 需要一个持久化GlobalFontDeviceContext,确保它始终可用,而不是每次使用时重新初始化。
    • 目前该对象虽然定义了,但实际上并未真正使用,且初始化时被清零。
    • 解决方案:
      • 程序启动时初始化 GlobalFontDeviceContext,使其在整个程序生命周期内可用。
      • 删除冗余的检查 ,因为 GlobalFontDeviceContext 现在始终有效。
  2. 字体设备上下文初始化

    • 创建 InitializeFontDC() 方法 ,用于初始化 GlobalFontDeviceContext
    • 调整命名规范 ,将 MaxWidthMaxHeight 改为 MAX_FONT_WIDHTMAX_FONT_HEIGHT 以提高可读性。
    • 需要存储位图信息,确保所有调用都能访问相同的数据,避免重复计算。
  3. 优化 loaded_font 结构,存储 TextMetric 信息

    • 之前 TextMetric 每次使用时都要计算,现在改为存储在 loaded_font 内部,提高效率。
    • 由于 loaded_font 结构体变大,因此使用 malloc 动态分配内存,并确保在适当时机释放内存,防止泄漏。
  4. 代码优化与调整

    • 移除不必要的 TextMetric 计算 ,直接从 loaded_font->TextMetric 读取数据。
    • 替换所有 MaxWidthMaxHeight 变量 ,确保代码统一使用 MAX_FONT_WIDHTmax_font_height
    • 修正 asset_source_font 结构 ,调整 FileName 逻辑,仅存储必要的指针,减少冗余。
    • load_font() 处理 CodePointCount,目前暂时以固定值填充,后续可以动态调整。
  5. 当前进展与后续工作

    • 目前已实现字体的加载与存储 ,但尚未真正写入资产系统(asset packing)。
    • 下一步计划:
      • 完善 add_font_asset 逻辑,确保字体能够完整加载、存储,并正确写入系统。
      • 优化 GlobalFontDeviceContext 的管理,提升稳定性与兼容性。

总结

本次优化主要集中在改进全局字体设备上下文的管理优化字体存储结构 以及减少冗余计算。调整后,字体数据管理更高效,访问和维护更简单,减少了重复计算的开销。后续需要继续完善字体资产存储与写入,以实现完整的字体管理功能。

构建字体表

我们需要开始构建实际的字体表。在已加载的字体部分,我们需要为这些表分配空间。当分配字体时,我们已经知道代码点的数量,因此接下来需要进行内存分配。

首先,我们需要分配 BitmapIDs 表的空间,使用 malloc(sizeof(bitmap_id) * CodePointCount) 来为每个代码点存储对应的位图 ID。此外,还需要分配 水平进距表(HorizontalAdvance table),这个表是一个浮点数数组,其大小是代码点数量的平方,用于存储每个代码点的水平进距。

在真正处理字体时,我们需要将 DebugFontBitmapIDs 设置好,即根据代码点索引,将 BitmapIDs 设置为 AddCharacterAsset 返回的值。这样可以避免使用标签进行跟踪,直接存储 bitmap_id 即可。此外,还需要确保 horizontal advance 也正确存储。

代码逻辑的基本步骤如下:

  1. 分配 bitmap_id 表的内存,用于存储每个字符的位图 ID。
  2. 分配水平进距表的内存,用于存储字符之间的间距。
  3. 在处理字体时,将代码点索引映射到 bitmap_id ,确保 AddCharacterAsset 返回的 ID 被正确存储。
  4. 存储水平进距信息,以便后续使用。

整体而言,目标是准备好这些必要的数据结构,以便后续能够高效地进行字体渲染和管理。

将字体信息写入资产文件

我们需要以有用的方式将这些信息写出。当处理字体时,不需要对字体字形做额外处理,它们可以像普通位图一样被处理。然而,如果源类型(Source->Type)是 字体资产(asset type font),那么就需要进行额外的处理。

关键点:

  1. 字体不能被释放

    • 之前错误地尝试释放了字体对象,但实际上字体需要保留,因为后续仍然需要使用它。
  2. 写出字体数据

    • 需要将字体的相关表格数据写出,使得运行时能够正确解析它们。
    • 这些表格数据的写入方式和其他数据类似,只是需要确保格式符合预期。
  3. 参考 game_asset.h 确保数据格式正确

    • 需要写出的表格包括:
      • 代码点表(BitmapIDs table)
      • 水平进距表(HorizontalAdvance table)
    • 水平进距表的大小应为 代码点数量的平方 ,而代码点表的大小应为 代码点数量
    • 这些表格的数据来源分别是:
      • 位图 ID(BitmapIDs)
      • 水平进距(HorizontalAdvance)
  4. 按正确的顺序写入

    • 需要使用 fwrite 以正确的顺序写入数据:
      1. 先写入代码点表
      2. 再写入水平进距表
    • 这些 fwrite 操作的方式与之前写入其他资产数据时的方式一致。

整体而言,整个过程比较简单,只需确保按正确的格式和顺序写入数据,以便运行时能够正确读取和解析。

使用当前字符的宽度伪造水平推进

现在需要填充 水平进距表(HorizontalAdvance table),目前这个表还没有被正确填充。这个过程比较麻烦,但必须完成。

临时解决方案

在 Win32 中获取字距调整(kerning)较为复杂,因此 暂时使用简单的方法 来填充水平进距表,以便先让程序运行起来,之后再进行优化。

1. 直接填充字体宽度
  • 加载字体字形(Width) 相关的代码中,一旦获取到了字体的宽度,就将这个宽度填充到水平进距表中。
  • 这个方法的值并不准确,但可以临时模拟之前的比例字体行为。
2. 遍历所有代码点,填充水平进距表
  • 遍历所有代码点,确保为每个代码点填充水平进距值。
  • 对于每个代码点,假设下一个代码点的水平进距等于当前字体的宽度
    • 例如,如果当前代码点是 A,那么 A 之后的所有代码点的水平进距都被设为 Result.Width
    • 这样做的目的是确保所有字符之间至少有基本的间距,而不会影响整体渲染逻辑。
3. 水平进距表的改进
  • 目前使用的是手动索引方式(hand-indexed),但后续可能需要进行优化,使其更加规范化和易于维护。

调试与下一步计划

  • 目前已经完成了 水平进距数据的写入,但代码进行了大量修改,当前状态尚不清楚是否完全正确。
  • 下一步需要尝试读取这些数据,并验证写入的数据是否符合预期。
  • 之后还需要进一步优化 kerning 计算,以便提升字体渲染的精度。

目前代码仍处于一个调整阶段,接下来需要进行数据加载和测试,以确保正确性。

从资产文件加载字体数据

加载字体本身是一个相对简单的过程,不需要额外的特殊操作。一切都会自动进行,现有的加载代码已经能够执行相同的任务,没有任何特别需要调整的地方。

字体加载流程

  • 现有的加载代码能够正常工作,它沿用了之前的逻辑,因此不需要额外的修改。
  • 数据加载是自动化的,加载字体的核心流程已经完善,不需要额外处理特殊情况。
  • 代码兼容性良好 ,不论是字体位图还是相关数据表(如 bitmap IDhorizontal advance),都可以按照既定的方式加载,没有特别需要调整的地方。

下一步关注点

  • 尽管加载本身没有问题,但仍需要确保数据的完整性,特别是 水平进距表(horizontal advance table) 是否正确存储并能够正确解析。
  • 之后需要测试 字体渲染,确保所有数据能够正确映射,并在屏幕上正确显示字体。

整体来看,字体加载的逻辑已经基本完成,接下来的重点将是验证数据并优化最终的渲染效果。

使用多个资产文件时,字符点加载会变得复杂...

当前面临的主要问题是代码点(code points)作为位图 ID(bitmap IDs) 的索引时,会在加载文件时被重新定位(rebased)

1. 位图 ID 重定位问题

  • 位图 ID 会在加载时被偏移
    • 如果只有 一个资产文件(asset_file),那么位图 ID 是稳定的,不会有问题。
    • 如果有多个资产文件 (例如 12 个),那么每个新加载的资产文件中的位图 ID 都会被偏移 ,其偏移量等于此前加载的所有资产的位图 ID 总数
  • 资产 ID(asset IDs)也会受到影响
    • 位图 ID 其实只是资产 ID 的一种,所以这个偏移问题影响的不仅仅是位图,还包括所有其他的资产类型。

2. 解决方案:添加偏移量存储

为了修正这个问题,需要在加载后调整这些位图 ID,方式有两种:

  1. 在加载后进行修正(后处理方式)
  2. 在运行时动态调整(即时修正)

这里选择的是第二种方法(运行时调整),因为它更简单,也能确保所有访问位图 ID 的地方都正确应用偏移量。

3. 具体实现步骤

  • 扩展 asset 结构,存储 BitmapIDOffset
    • game_asset.h 文件中,asset 结构体需要新增一个 BitmapIDOffset 变量,用来存储该资产文件的位图 ID 偏移量。
  • 在加载字体时,正确应用位图 ID 偏移量
    • 默认情况下BitmapIDOffset = 0,即无偏移。
    • 当加载资产时 ,计算该资产文件的偏移量,并存入 BitmapIDOffset
    • 当访问字体的字形(glyph)时,调整位图 ID,使其正确映射到内存中的位图数据。

4. 具体实现代码逻辑

  • 存储 BitmapIDOffset
    • 在资产加载时,计算偏移量并存储到 loaded_font 结构中。
  • 访问字形时,应用偏移量
    • GetBitmapForGlyph 函数中,每次获取位图 ID 时,应用 BitmapIDOffset 进行调整。
    • Result.Value += Font->BitmapIDOffset,确保所有字形都能正确索引到加载的位图。

5. 当前状态和下一步计划

  • 代码结构基本完成 ,但仍需确认 BitmapIDOffset 是否正确计算并应用。
  • 尚未在实际加载逻辑中写入 BitmapIDOffset,需要在资产加载过程中对字体资产进行特殊处理,确保每个字体都记录正确的偏移量。
  • 接下来需要进行调试,确认字体数据在加载后能够正确映射到位图 ID,并确保多个资产文件的加载顺序不会影响最终的渲染结果。

整体而言,这个调整相对简单,但对于支持多个资产文件至关重要。

...但我们可以针对每个文件进行字符点重基!

优化位图 ID 偏移的实现方式

我们之前考虑的 位图 ID 偏移量存储在资产结构(asset structure)中 的方法并不理想 。现在我们有了更优的方案,那就是将偏移量存储在资产文件(asset_file)级别,而不是单个资产对象(asset object)级别


1. 资产类型的连续性简化了偏移计算

  • 资产类型是连续存储的 :所有字体字形(glyphs)数据都存储在一起,因此每个资产文件(asset_file)内的字形数据是相邻的
  • 我们只需要存储资产文件的基准偏移量
    • 之前的方式 :每个 asset 记录自己的偏移值,导致复杂度增加。
    • 优化后的方式 :只要记录 当前文件的基准偏移量,就可以在运行时动态计算所有资产的最终 ID。

这样,我们只需跟踪 某个资产文件的首个字体字形的偏移量,所有后续字形数据都会继承这个偏移量。


2. 具体实现优化

  • 在创建资产文件索引时,初始化 FontBitmapIDOffset
    • 该偏移量最初设为 0
    • 在加载过程中,当遇到字体类型时,更新该文件的 BitmapIDOffset 值,使其存储该资产文件中首个字形的 ID 偏移量。
  • 在访问字形数据时,应用偏移量
    • 通过 GetFile(FileIndex) 获取对应的资产文件信息。
    • 读取该文件的 BitmapIDOffset,并动态计算字形的最终位图 ID。

这样,代码结构变得更清晰,不需要在 asset 结构体中单独存储偏移量,只需在资产文件级别存储一次,访问时动态计算。


3. 具体代码改动

(1)在 asset_file 结构体中添加 FontBitmapIDOffset
cpp 复制代码
struct asset_file
{
    ...
    uint32 FontBitmapIDOffset;;  // 记录该文件的位图 ID 偏移量
};
(2)初始化 bitmap ID offset
cpp 复制代码
File->FontBitmapIDOffset = 0;
(3)在加载过程中,更新 bitmap ID offset
cpp 复制代码
if (SourceType->TypeID == Asset_FontGlyph) {
      File->FontBitmapIDOffset = AssetCount - SourceType->FirstAssetIndex;
}

4. 代码逻辑梳理

  1. 在加载资产文件时 ,记录 首个字体字形的索引值 作为 bitmap ID offset
  2. 在访问字体数据时 ,先获取该文件的 bitmap ID offset,然后动态计算实际的 bitmap ID
  3. 这样可以确保多个资产文件的字形数据不会因加载顺序不同而出现错误。

运行发现段错误

test_asset_builder 加日志端点打印log看看

输出的都没问题

LoadGlyphBitmap 打断点进不来

修改为AssetType_FontGlyph 还是进不来

继续检查一下

先注释掉粒子系统

上线移动的时候英雄朝向不对

我尝试向某人解释颜色只是一个uint32,我不确定我是否解释清楚了

颜色是一个非常模糊的概念,它可以指代不同的东西。在计算机图形学和渲染的上下文中,颜色通常表示为 一个 32 位无符号整数(UN32),但这一点可能并不容易让人直观理解。

1. 颜色的存储方式

在大多数计算机系统中,颜色通常以 RGBA(红、绿、蓝、透明度)ARGB 格式存储,每个通道占 8 位 (1 字节),总共 4 字节(32 位)。例如:

  • 0xFF0000FF 代表 纯红色(RGBA格式)
    • 0xFF(红色)
    • 0x00(绿色)
    • 0x00(蓝色)
    • 0xFF(不透明度)
  • 0x8000FF00 代表 半透明的绿色(ARGB格式)
    • 0x80(透明度 50%)
    • 0x00(红色)
    • 0xFF(绿色)
    • 0x00(蓝色)

2. 颜色的不同表示方式

虽然在代码中颜色通常存储为 32 位整数(UN32),但它也可以有不同的表示方式:

  1. 十六进制(Hex)表示
    • 例如 0xFF112233,颜色通道按照字节排列。
  2. 整数(Decimal)表示
    • 例如 4278190335(对应 0xFF0000FF)。
  3. 浮点数(Float)表示
    • 颜色的每个通道可以用 0 到 1 之间的浮点数存储,例如 (1.0, 0.0, 0.0, 1.0) 代表纯红色。
  4. 归一化整数(Normalized Integer)
    • 例如 255, 0, 0, 255,表示 RGBA(255, 0, 0, 255)

3. 颜色与位运算

由于颜色是 一个 32 位整数 ,我们可以使用位运算来提取或修改特定的颜色通道。例如:

  • 提取红色通道

    cpp 复制代码
    uint32_t color = 0xFF112233;
    uint8_t red = (color >> 16) & 0xFF;  // 0x11
  • 提取绿色通道

    cpp 复制代码
    uint8_t green = (color >> 8) & 0xFF;  // 0x22
  • 提取蓝色通道

    cpp 复制代码
    uint8_t blue = color & 0xFF;  // 0x33
  • 修改颜色值

    cpp 复制代码
    color = (color & 0xFF00FFFF) | (0x55 << 16); // 修改红色通道为 0x55

4. 为什么颜色存储为 UN32?

  • 存储效率高:32 位整数比使用 4 个浮点数(RGBA 0~1)更节省内存。
  • 计算快:使用位运算可以快速提取或修改颜色通道。
  • 硬件兼容性 :现代 GPU 和渲染 API(如 OpenGL、DirectX、Vulkan)通常使用 32 位颜色格式,如 RGBA8

5. 总结

颜色在计算机中通常是 一个 32 位无符号整数(UN32) ,其中每个通道(红、绿、蓝、透明度)占 8 位。虽然它只是一个整数,但可以通过位运算提取或修改颜色通道,并且可以用不同的方式(十六进制、浮点数等)表示。理解这一点对于图形编程和渲染优化至关重要。

黑板: "颜色" -> 模糊的术语!

颜色的概念在计算机图形学中是多种多样的,可以用来描述不同的光学特性。例如,我们可以讨论发光颜色(Emissive Color) ,即一个物体自身发光的颜色;也可以讨论反射颜色(Reflective Color) ,即物体对光的反射方式;或者吸收和透射颜色,即物体如何吸收部分光并透射或反射剩余光线。因此,颜色本身是一个相对模糊的概念,具体含义要视上下文而定。


1. 颜色的存储方式

在大多数情况下,我们存储的颜色是调制颜色(Modulated Color),它表示某个物体对进入的光线进行调制的百分比。例如:

  • 光线进入某个表面时,包含一定比例的红、绿、蓝光子。
  • 部分光子会被材料吸收,而剩下的光子会被反射或透射出去。
  • 我们存储的颜色通常代表反射或透射的百分比,而不是绝对的光强。

由于人眼有红(R)、绿(G)、蓝(B)三种感光细胞,我们一般只存储这三种颜色的百分比值。也就是说,我们存储的颜色值通常是:

  • 红色通道(R):反射的红光百分比
  • 绿色通道(G):反射的绿光百分比
  • 蓝色通道(B):反射的蓝光百分比

此外,我们有时还会存储一个额外的信息,即透明度(Alpha,A),用于表示该物体有多透明。


2. 颜色在 UN32(32 位无符号整数)中的存储

为了优化存储,我们通常将颜色打包进一个 32 位无符号整数(UN32) 中,每个颜色通道占 8 位,即:

  • 8 位 Alpha(透明度)
  • 8 位 红色(R)
  • 8 位 绿色(G)
  • 8 位 蓝色(B)

常见的颜色存储格式包括:

  • Windows 和 OpenGL 格式 (BGRA):A8 R8 G8 B8
  • 其他格式 (RGBA):R8 G8 B8 A8

UN32 中,颜色通常被编码为:

复制代码
0xAARRGGBB

例如:

  • 0xFF112233
    • 0xFF(Alpha:不透明)
    • 0x11(红色)
    • 0x22(绿色)
    • 0x33(蓝色)

我们可以用位运算来提取颜色通道:

cpp 复制代码
uint32_t color = 0xFF112233;
uint8_t red   = (color >> 16) & 0xFF;  // 提取红色通道
uint8_t green = (color >> 8)  & 0xFF;  // 提取绿色通道
uint8_t blue  = color & 0xFF;          // 提取蓝色通道
uint8_t alpha = (color >> 24) & 0xFF;  // 提取Alpha透明度

颜色的存储范围:

  • 0x00(0) 代表 0%(无颜色)
  • 0xFF(255) 代表 100%(全亮)
  • 中间值 代表 线性插值的颜色强度 (如 120 代表 120/255 ≈ 47%

3. 透明度(Alpha 通道)

在 32 位颜色存储中,Alpha 通道用于表示物体的透明度:

  • 0x00(完全透明):像素不会影响最终图像
  • 0xFF(完全不透明):像素完全可见
  • 中间值 :用于半透明渲染,例如 0x80 表示 50% 透明

透明度并不是颜色本身的一部分,但在渲染时经常被一起处理。例如,在 2D 游戏中,Alpha 值可以用于淡入淡出效果 ,在 3D 渲染中可以用于半透明玻璃或水面


4. 颜色的实际使用

在游戏引擎中,我们通常使用 UN32 颜色格式 来存储颜色,因为:

  • 节省存储空间:相比于使用 4 个浮点数(RGBA 0.0~1.0),32 位整数更加紧凑。
  • 计算效率高:可以直接使用位运算进行颜色操作,而不用浮点数运算。
  • 兼容 GPU :现代图形 API(如 OpenGL、DirectX、Vulkan)通常支持 RGBA8 颜色格式,直接映射到 32 位整数。

在实际渲染时,颜色可能会被转换为线性颜色空间HDR(高动态范围)格式 ,但基础存储方式仍然通常是 UN32


5. 颜色的扩展概念

在某些高级渲染技术中,我们可能会使用更复杂的颜色概念:

  • HDR 颜色 :使用 浮点数(float16 / float32) 代替 8 位整数,以支持更高的动态范围。
  • 光照计算:颜色不仅仅代表一个像素的颜色,还可能表示光照强度、漫反射、镜面反射等属性。
  • 材质颜色 :不同材质(如金属、塑料、布料)对光的响应不同,因此颜色可能需要结合法线贴图反射率贴图等额外信息。

6. 总结

颜色在计算机中通常存储为 UN32(32 位无符号整数) ,以 8 位精度分别存储 红(R)、绿(G)、蓝(B)透明度(Alpha)

  • 每个颜色通道范围为 0255(0%100%) ,其中 0x00 = 0%0xFF = 100%
  • 透明度(Alpha 通道) 用于表示物体的透明程度。
  • 通过 位运算 可以快速提取和修改颜色通道。
  • 这种存储方式广泛用于游戏引擎图形渲染硬件加速 ,因为它存储紧凑计算高效 并且兼容 GPU

你将Textmetrics添加到结构体中,然后决定分配它------为什么?有什么好处?

在结构体中添加 text metrics (文本度量信息)后,我们决定将其分配到堆上,而不是直接存储在栈上。这样做的主要原因是结构体的大小开始变得相当庞大,不适合在栈上传递或存储,因此改为堆分配,以优化内存管理并提高可维护性。


1. 为什么要使用堆分配?

我们最初是将 text metrics 作为结构体的一部分直接存储,但后来发现其占用的内存较大,为了避免不必要的栈内存占用,我们决定将其分配到堆上。具体来说,理由如下:

  • 结构体变得太大,不适合存储在栈上
    • text metrics 是一个庞大的结构体,它存储大量与文本渲染相关的信息,如字符尺寸、字间距、基线对齐、光栅化数据等。
    • 如果在栈上存储,会导致占用大量栈空间,影响程序的运行效率,特别是在递归调用或栈空间有限的情况下。
  • 减少栈的负担,提高稳定性
    • 在栈上传递大结构体 会导致函数调用开销增加 ,同时栈的大小也是有限的(例如 1MB~8MB),存储大对象可能导致栈溢出(Stack Overflow)
    • 堆的大小通常远大于栈,并且可动态分配 ,适合存储大型数据结构
  • 提高数据管理的灵活性
    • 通过堆分配,可以灵活地分配和释放内存,避免在栈上存储大量数据带来的不便。
    • 在某些情况下,我们可能不希望每个对象都携带完整的 text metrics 信息,而是希望多个对象共享一个度量数据。这种情况下,堆分配可以实现指针共享,减少冗余数据存储。

2. 结构体的大小评估

在决定使用堆之前,我们评估了 text metrics 结构体的大小,并发现它已经足够大,值得使用堆分配。例如:

cpp 复制代码
typedef struct tagTEXTMETRICA
{
    LONG        tmHeight;
    LONG        tmAscent;
    LONG        tmDescent;
    LONG        tmInternalLeading;
    LONG        tmExternalLeading;
    LONG        tmAveCharWidth;
    LONG        tmMaxCharWidth;
    LONG        tmWeight;
    LONG        tmOverhang;
    LONG        tmDigitizedAspectX;
    LONG        tmDigitizedAspectY;
    BYTE        tmFirstChar;
    BYTE        tmLastChar;
    BYTE        tmDefaultChar;
    BYTE        tmBreakChar;
    BYTE        tmItalic;
    BYTE        tmUnderlined;
    BYTE        tmStruckOut;
    BYTE        tmPitchAndFamily;
    BYTE        tmCharSet;
} TEXTMETRICA, *PTEXTMETRICA, NEAR *NPTEXTMETRICA, FAR *LPTEXTMETRICA;

3. 使用堆分配的优势

  • 避免栈溢出
    • 如果 text metrics 被存储在栈上,可能导致函数调用栈过大 ,最终触发栈溢出
    • 通过堆分配,我们可以让数据存储在更大的内存空间中,而不是受限于栈的大小。
  • 提升代码可读性和维护性
    • 如果 text metrics 直接嵌入到主结构体中,每次结构体被传递时,都会拷贝整个对象 ,导致性能下降
    • 改为堆分配后,我们可以使用指针传递,避免大规模数据复制,提高运行效率。
  • 节省内存,提高共享性
    • 许多情况下,我们可以让多个对象共享同一个 text metrics,减少冗余存储。
    • 例如,同一套字体的多个文本渲染对象可以共用一个 text metrics,避免重复分配和存储。


5. 何时使用栈 vs 何时使用堆?

分配方式 适用情况 优点 缺点
栈分配 (TextMetrics metrics;) 结构体小于 几 KB 快速,自动释放 占用栈空间,可能导致栈溢出
堆分配 (new TextMetrics()) 结构体较大(几十 KB ~ 几百 KB 灵活,适用于大数据存储 需要手动释放,可能导致内存泄漏

由于 text metrics 占用的内存较大属于典型适合堆分配的情况,因此选择将其放在堆上。


关于语言有不同字体的问题。这是否适用于其他人添加的包?

关于语言和不同字体的支持问题,考虑到可能会有其他人提供额外的字体包和语言包,我们设想了一种方案。这个想法是,玩家或开发者可能希望为游戏添加新的字体和语言翻译。虽然游戏中的文本量可能不会很多,但仍然可能会包含一些物品名称之类的内容。

为了支持这一需求,我们可以将物品名称等文本存储在资产文件中,并通过标签来查找这些文本。具体来说,我们可以引入一个"语言"标签,玩家或开发者可以根据这个标签将不同语言的文本和字体添加到游戏中。这样一来,不同语言的支持就变得灵活且易于扩展,任何人都可以为游戏添加他们想要的语言翻译。

通过这种方式,不同的语言和字体可以随着包裹一起被添加到游戏中,玩家可以根据需要选择不同语言版本的文本内容,从而提高了游戏的可扩展性和国际化支持。

我是在谈论GPU如何关心像素的颜色,而不是一个包含r、g、b、a浮动值的结构体,这在缓冲区中并不真正需要

关于GPU如何处理像素颜色和使用结构体存储浮动的RGBA值之间的区别,实际上有一些细微的区别。对于GPU和CPU的处理方式,GPU通常更关注的是像素的颜色值,而不是存储颜色的具体方式。对于颜色的处理,GPU并不需要像CPU那样将每个颜色通道存储为一个浮动值(比如RGBA结构体)。GPU更倾向于直接使用精简的数据格式,如整数表示的颜色值。

例如,在GPU中,颜色通常以一个32位整数的形式存储,这种方式更适合GPU进行高效的计算和渲染处理。每个颜色通道(红、绿、蓝、透明度)可以用8位来表示,从0到255的范围,直接映射到硬件上处理。相比之下,结构体中的浮动RGBA格式,虽然对一些算法有用,但对于像素处理来说,并不是最高效的方式。

总的来说,GPU关心的是如何高效处理颜色数据,而不是每个颜色值的精确存储形式。因此,直接使用32位整数作为颜色数据格式,能够更好地匹配GPU的硬件结构,避免不必要的计算和转换。

黑板:GPU ~ CPU

在图形处理和GPU运作方面,颜色的存储和处理方式与内存带宽密切相关。GPU负责处理图像上的数百万个像素,尤其是在现代分辨率如1920x1080的屏幕上,每一帧的像素数量已经达到两百万,这就需要大量的计算和内存带宽来处理这些数据。因此,图形处理不仅仅是关于颜色的表示,还涉及如何高效地存储和访问这些颜色数据。

对于颜色存储,虽然可以使用浮动的RGBA结构(即每个颜色通道用一个浮动值表示),这种方式在图形渲染中并不总是最优的。问题在于其占用的内存空间------RGBA结构通常占用16个字节,而每个像素的颜色数据仅仅需要8位(1字节)表示每个颜色通道。因此,使用16字节来存储每个像素的数据会增加内存带宽的消耗,因为GPU必须从内存中读取和写入更多的数据。

内存带宽是GPU性能的关键因素之一。如果每个像素占用更少的内存,GPU在处理图像时就能减少内存访问次数,从而提高性能。为了减少带宽消耗,GPU常常采用压缩技术。例如,纹理压缩不仅是为了将更多的纹理存储在显卡的内存中,更重要的是减少所需的内存带宽。通过将每个像素的数据压缩到更小的大小(例如,从4字节压缩到2字节或1字节),可以大大减少内存带宽的需求。

此外,图形卡的内存带宽增长速度相对较慢,尤其是在高端显卡中,虽然其内存容量不断增加,但带宽的提升远不如容量那么快。因此,通过减少每个像素的数据大小,可以有效地提高图形卡的性能,即使图形卡的内存容量很大,减少带宽使用仍然对性能至关重要。

总之,GPU的设计和优化是围绕着如何高效地使用内存带宽进行的,减少内存使用和带宽需求是提升性能的一个重要方面。
https://en.wikipedia.org/wiki/List_of_Nvidia_graphics_processing_units

有没有系统会将光表示为实际的频率,并基于与模拟环境的交互改变该值?

在图形渲染中,某些情况下会使用更复杂的光谱计算来精确模拟光的行为,尤其是在离线渲染系统中。与常见的RGB色彩表示不同,光谱计算考虑了光的实际特性。这种方法通常用于追求更高渲染质量的离线渲染器,而实时系统中则不常使用,因为实时渲染更侧重于性能和速度,通常不会对光的频率特性进行精细的模拟。

光谱计算的一个重要应用是模拟光在不同介质中的行为。例如,当光通过棱镜时,不同颜色的光会发生不同程度的散射,而这在传统的RGB模型中是无法体现的。以纯红光和混合红绿光为例,虽然它们看起来都是"红色",但通过棱镜后,它们的散射方式是不同的,光谱计算能够精确地模拟这一过程。这种技术也用于模拟像肥皂泡表面的薄膜反射现象,光的反射方式会因为光的波长不同而有所不同,光谱计算可以正确地表现这一点。

此外,光谱计算还在模拟复杂反射现象(如油性表面反射)时发挥着重要作用,通过记录光的真实光谱特性,可以更准确地重现这些复杂的视觉效果。总的来说,光谱计算的引入能够使得渲染效果更加真实,尤其是在需要高质量渲染的场景中,如物理模拟、透明介质、复杂反射等方面的应用。
https://nextlimitsupport.atlassian.net/wiki/spaces/maxwell/pages/22682364/Maxwell+Render

Maxwell Render 是一个高质量的离线渲染引擎,以其物理精确的光照模拟而闻名。它基于 光线追踪 (ray tracing)技术,旨在真实地模拟光与物体的交互,以产生逼真的图像。Maxwell Render 的主要特点是其 物理准确性高动态范围(HDR)处理,它可以精确地计算光的传播、反射、折射等效果,模拟光源的真实物理特性。

Maxwell Render 的渲染方法:

Maxwell Render 的一个核心特点是其 物理基础的渲染方法,即每一光线的传播都依据物理原理进行计算,模拟光如何与材质表面、物体、环境进行互动。它能处理复杂的光影效果,如:

  1. 折射和反射:能够精确模拟光线在物体表面折射、反射的过程,比如通过棱镜、玻璃、液体等透明物体的光线弯曲和散射。

  2. 薄膜反射:例如在肥皂泡、油漆表面等薄膜上看到的光反射,Maxwell Render 能够精确模拟这些微小的反射效果。

  3. 高动态范围:Maxwell Render 能够处理高动态范围的光源和色彩,使得渲染出来的图像不仅在亮部和暗部都能保留更多的细节,还能呈现出更多的真实感。

为什么 Maxwell Render 能够做到高精度的光照模拟?

Maxwell Render 使用了一个叫 "Spectral Rendering" 的方法,具体来说,它通过处理每个光线的实际光谱特性(例如红色、绿色、蓝色波长的分布),而不仅仅是简单的 RGB 值。这使得它能够在更复杂的光照环境中生成非常真实的效果。

结论:

Maxwell Render 通过模拟真实光照的物理特性,特别是在反射、折射等复杂现象上的精确处理,使得它成为高端视觉效果和建筑可视化等领域中常用的渲染工具。

如果你在某个时刻接收用户输入,你会担心阻止Unicode控制字符吗?

在讨论游戏中的文本输入时,不会担心 Unicode 控制字符的阻塞问题,因为预期游戏中不会有任何形式的文本输入。因此,这种情况不会发生。

所以你关心堆栈内存。你会在什么时候开始真正测量它,还是凭直觉足够?我在没有遇到实际错误之前从未有过这个想法

在处理栈内存时,主要关注的不是栈的大小,而是避免通过值传递或返回大于32字节的结构体。这是因为结构体越大,传递或返回时会消耗更多的内存和处理资源。所以,通常会尽量保持结构体较小,确保在传递时效率更高。

什么是HDR?

高动态范围(HDR)是指在表示光照强度时,能够处理非常宽广的数值范围。普通的8位RGBA色彩格式只能表示256个不同的色阶,这导致无法精确表达极亮的光源(比如太阳)和较暗的光源(比如蜡烛的光线)之间的差异,因为它们之间的亮度差距过大。当用256个色阶表示这些亮度时,图像会呈现出明显的条带感,无法保留足够的光照细节。

高动态范围的核心概念就是能够捕捉并表示更广泛的光照范围。浮点数格式可以表示任意范围的数值,因此如果图像中的所有数值都使用浮点数格式,就可以实现高动态范围。然而,HDR通常不仅仅指完全使用浮点数格式,它更具体地指通过一些特殊的格式压缩方法,试图在有限的位数(比如32位)内,通过引入一些额外的表示方法(例如RGB和一个指数值来表示颜色的亮度),来恢复一部分原本因色阶限制而丢失的光照信息。这样可以在较小的存储空间内,保持更丰富的光照细节和动态范围。

你认为将来CPU和主带宽是否会足够快,从而完全取代GPU,回到软件渲染器的时代?

有一天,CPU和主内存带宽足够快,能够完全取代GPU,回到软件渲染的时代吗?我相信这一天会到来,并且希望能早日实现,因为我真的厌倦了处理GPU,它们实在太麻烦了。

相关推荐
C++ 老炮儿的技术栈23 分钟前
设计模式,如单例模式、观察者模式在什么场景下使用
c++·笔记·学习·算法
小A1591 小时前
stm32完全学习——NRF24L01模块
stm32·嵌入式硬件·学习
吴梓穆2 小时前
UE4学习笔记 FPS游戏制作10 制作UI准星
笔记·学习·ue4
nuc-1273 小时前
sqli-labs学习笔记3
数据库·笔记·学习·sqli-labs
日暮南城故里4 小时前
Java学习------初识JVM体系结构
java·jvm·学习
小嘚5 小时前
springCloud的学习
学习·spring·spring cloud
狄加山6755 小时前
QT 学习笔记2
笔记·qt·学习
FAREWELL000759 小时前
C#入门学习记录(五)轻松掌握条件分支与循环语句
前端·学习·c#
kaikai_sk10 小时前
Docker和Dify学习笔记
笔记·学习·docker
面包圈蘸可乐10 小时前
DNA语言模型GROVER学习人类基因组中的序列上下文
人工智能·学习·语言模型