回顾
今天我们正在进行过场动画序列的制作,因此我想深入探讨这个部分。昨天,我们暂时停止了过场动画的制作,距离最终结局还有一些内容没有完成。今天的目标是继续完成这些内容。
我们已经制作了一个过场动画的系列,并把它们集中在一个文件里。在这个文件中,我们可以在代码中选择要使用哪个过场动画。我们添加了一个名为game_cutscene
的文件,里面包含了每个过场动画的基本逻辑。
接下来,我会直接跳到我们最后制作的过场动画——镜头9。你可以看到,这个镜头内容是镜头逐渐拉近,聚焦在一个小孩身上,他正走向自己的房间。
game_cutscene.cpp: 组合镜头 10
现在我们需要制作镜头十和镜头十一,完成后就能结束这一部分内容。我将开始在这里粘贴镜头十的内容,并且这一开始我们只使用一个镜头层。你可以看到,这个镜头是从一个较远的背景开始,镜头应该是固定不动的,可能在背景中要做一些调整。我打算将背景设置为一个层级比较远的对象。
首先,我会设置这个对象的层级为“无限远”,也就是它不会随镜头移动。接着,我不确定它的大小,但会通过一些测试来调整。设置好这些参数后,我还需要添加第二个层次的内容。
接下来,我对摄像机的运动做了一些假设,预计镜头会稍微缩放。我将继续保留这个缩放效果,尝试调整镜头的大小,以确保它能展现出理想的效果。我觉得这个镜头的距离大约应该是10米左右,但还要做一些调整以达到最合适的效果。
调整过程中,我注意到窗口旁边的天空有些杂乱,因此我需要调整它的位置。为了让窗口的景象看起来更自然,我将天空背景稍微向右移动几米,并且稍微提高它的位置,直到它与窗户位置匹配。
接下来,镜头中出现了主角,我将他放置在一个小凳子上。由于他目前的位置偏离了中心,所以我会将他稍微移到中心位置,确保他看起来更自然。如果他的位置还不合适,我可以进一步调整,但目前他的位置已经较为接近理想状态。
然后,我加入了新的背景层,展示了房间的门。为了使门在镜头中显得合适,我将其尺寸调整得较大,以填充整个屏幕,避免出现不合适的间隙。通过这种方法,我能确保门的显示效果正确。
接着,我使用了之前的方法,把门锁定在初始镜头的位置,并调整它的大小,以确保它的比例合适。随着调整,主角的大小也需要增大,因为他此时显得太小了。
最终,通过不断的调整,我逐步找到了合适的比例和位置,接下来将继续添加第五层内容,继续完善镜头的细节和效果。
"…他有很多帽子"α
我们现在在制作主角的房间镜头,镜头中展示的是他收集的帽子。这些帽子象征着他在 Steam 上花了很多时间购买的物品,具有一定的“用户价值”。我们把这些帽子作为一个独立的图层加入镜头中,调整它们的大小以匹配画面宽度,并将其整体向下移动以对齐画面。
添加帽子层之后,我们继续加入最后一层内容,也进行位置和缩放的调整,确保其对齐并居中。这个层略小了一些,我们对它做了放大,直到达到合适的显示效果。完成这一部分后,我们检查了摄像机的缩放效果,此时开始出现一些视差效果,也就是前景、中景和背景在缩放时产生的相对移动差异。
我们注意到层与层之间的深度区分还不够明显,于是对图层的位置进行了更精细的调整。我们尝试拉开这些元素在空间中的分布,比如将更远的背景设为深度为 2,将中景设置为 1,将最前景设置为 0.5。通过这种方式来增加层级间的运动感,使得镜头缩放时有更明显的层次变化。
在对深度调整后,我们对各个图层的大小再次进行了微调,避免它们缩放时出现穿帮或边缘露出的问题。特别是门、窗户这些元素,我们增大了它们的尺寸,确保在屏幕缩放和镜头运动时不会露出缝隙。同时我们还需要注意图层的渲染顺序,以避免因为深度变化而导致图层前后关系错乱。
我们对主角的位置也进行了优化,让他的位置更贴近画面中央,成为镜头关注的焦点,并调整了主角与门之间的前后关系,进一步增加了画面的空间感。
为了让整个镜头更自然,我们对背景也做了相应的扩展,让它覆盖画面的所有边缘区域,避免缩放时出现留白或“漏景”的现象。最终镜头会有一个朝主角缩放的过程,我们微调了摄像机的运动方向,让它更准确地聚焦到主角身上。
我们还注意到一个靠近近裁剪面的图层在摄像机靠近时突然消失,因此将其稍微向远处移动,避免出现画面突兀的跳变。
最后,我们还将主角轻微放大,使他在缩放后依然保持视觉上的重点,并加入了一些轻微的上移动画,使画面更有动感。目前整体效果已经比较满意,层次感和深度效果得到了明显提升,镜头运动也更加自然流畅,画面完整度也得到了保障。整体来看,这一镜头的构建已经达到了预期的视觉效果。
game_cutscene.cpp: 组合镜头 11
我们现在正在处理最后一个镜头,也就是镜头11。完成这个之后,我们就可以把所有镜头组装成一个完整的过场动画了,这是我们一直追求的目标。
这个镜头的构图与开场镜头类似,属于那种多层次的山丘场景,背景中带有诡异树林的光线氛围。我们一开始把背景层设置在“无限远”,虽然 Z 值实际不会影响表现,但为了未来可能加入基于 Z 值的排序,我们把它设为 -100,确保始终处在最远处。
接下来我们一层层地叠加不同的地形元素:左侧的远山、前景的中层丘陵、近处的地面,再到最前方的元素以及角色本身(小孩)。我们依次对每一层进行了缩放、定位和Z值设置。
最开始的几层我们发现视差效果不够明显,感觉没有深度,所以我们采用了指数型的距离设定方式,比如背景层设为80,下一层为40、再下一层20、10、5、2.5,直到前景0.5和主角所在的0.1或更近。通过这样分布距离,增强了视差效果,画面在运动时能更强烈地体现出空间深度。
我们不断对每一层的缩放比例进行调整,确保每层都能覆盖住镜头的可视范围,并且在动画过程中不会因为视差运动而露出底部空隙。有些图层尺寸本身就不是很理想,需要手动调整放大并重新定位。尤其是靠近前景的几层,因为视差运动更剧烈,更容易出现边缘“露白”的情况,所以对它们的尺寸和位置要求更高。
小孩的位置也经过了几次调整。我们尝试了不同的距离值(如 -1、-0.5、0.5等),同时调整他的位置确保其面朝右侧,符合镜头构图的意图。镜头运动也做了优化,让相机更集中地朝向小孩运动,突出主角视角。
最后我们反复检视了各个图层在动画过程中的表现,修复了一些图层之间因为视差产生的“缝隙”问题,尤其是画面上下边缘容易产生小间隙的地方,进行了多次微调,逐一排查修正。
整体上,现在所有图层已经合理分层、排序、缩放并且设置了合适的空间位置,动画在运动中展现出良好的空间感,画面层次清晰,小孩作为主角的位置突出,镜头运镜自然流畅。可以认为这是一组相当合理的镜头搭建,已经达到了第一版预期的效果。
我们也认识到,手动处理多个镜头的调试工作量很大,未来如果需要制作更多(比如上百个)类似镜头,势必要开发一些小工具来快速调整参数或者导入数值来提高效率。但就目前这个阶段而言,整体效果已经令人满意。
game_cutscene.cpp: 引入 IntroCutscene 让这些镜头按顺序播放
我们现在的任务是将所有的过场动画镜头组合成一个连续播放的序列,使它们能按顺序自动播放,从而完整展现整个过场动画的效果。为实现这个目标,我们决定采用数据驱动的方式来组织这些镜头。由于每个镜头的数据结构已经非常清晰,并且层级信息也已整理好,我们可以将这些镜头打包成一个包含多个“分层场景”的数组。
具体方法如下:
首先,为了方便管理,我们将每一个镜头打包成一个对象,命名为 layeredScene
,并将它们依次放入一个名为 introsCutscene
的数组中。每个 layeredScene
包含以下字段:
assetType
:资产类型,用来标识该镜头的素材类别。shotIndex
:镜头编号,用于标识该镜头在序列中的顺序。layerCount
:图层数量,用来告知该镜头包含多少个图层。layers
:图层数组,具体存储每一层的位置信息、缩放比例和资源路径等。
为了让这个数据结构更具自动化,我们使用了一个小技巧:自动推断每组图层的数量。我们为每个镜头预先存储了一个图层数组,如 introLayers1
, introLayers2
等,并通过 .length
来自动获取图层数量,从而避免手动填写 layerCount。同时,我们在代码中通过宏定义(或函数封装)的方式进一步简化镜头的初始化过程,将镜头索引作为参数传入,就可以快速生成对应的数据结构,减少重复代码。
此外,我们还可以更进一步,考虑将所有镜头的信息用一个宏或者函数统一生成,以避免重复硬编码镜头编号和图层列表。虽然这种方式在某些语言中可能稍显“hacky”,但在目前的场景下是有效的,并且逻辑非常清晰。
接下来,我们将所有镜头的图层数组逐个提取出来,格式统一化,放入我们上面定义的数据结构中。这一过程中,主要是做一些结构性上的转换,把原先每个镜头中分散的图层对象整理为统一格式的数组。
在处理完所有图层之后,我们还需整理每个镜头的相机动画信息,例如相机的初始位置和移动路径等。这些信息将作为每个 layeredScene
的附加参数,以支持镜头的动态表现。
最后,我们将所有这些数据组合起来,形成一个完整的 introsCutscene
数组。这个数组中的每一项代表一个镜头,我们可以通过遍历这个数组,逐个播放这些镜头,实现完整的过场动画序列播放。
整体思路是以数据驱动的方式构建整个过场动画流程,使镜头的内容与表现解耦,提高了灵活性和可维护性。通过结构化、自动化地管理镜头信息,我们大大简化了后续的播放控制逻辑。现在所有准备工作基本完成,只需最后整理一下摄像机信息,就能实现完整过场动画的连续播放。
挨着整理一下
game_cutscene.cpp: 设置 ShotChangeTime
现在只剩下最后一个需要处理的部分,那就是镜头切换的时间设置。目前所有镜头之间的切换时间统一为 0.5秒。这个值目前是固定的,我们决定暂时就用这个默认值来处理。我们可以在之后的调试和微调阶段,根据实际的演出节奏和效果,再来调整这个时间。
为了实现这一点,我们直接在引用镜头切换时间的地方,将其统一设定为 0.5
,这样每个镜头在播放时就会按照这个时间间隔自动过渡。这个值我们也保留为一个可调参数,方便将来根据需要进行修改或优化。
至此,所有的数据和逻辑都已经整理完毕:
- 所有的镜头数据已经按顺序封装进数组,方便统一管理。
- 图层信息也已经结构化并标准化,易于后续渲染。
- 镜头之间的切换逻辑已经具备基础节奏控制。
接下来要做的就是实现场景的渲染部分。为了测试整体的功能是否正常运行,我们可以直接调用某一个 introCutscene
中的镜头,比如选择数组中的任意一个条目,然后将其作为参数传入播放函数中。
这样我们就能够开始测试,是否可以顺利播放一个完整的镜头,或者顺利地依次播放整个过场动画序列,实现镜头的顺畅切换。这一套流程已经基本成型,可以支持我们快速测试、调试和扩展其他的过场镜头,整个系统的灵活性和可控性也得到了提升。
总结一下:我们现在已经具备了镜头数据的结构化封装、过场动画播放的序列逻辑、镜头切换时间的基础控制、以及可以直接调用测试的能力,整个系统已经可以流畅运作并进入验证阶段。
game_cutscene.h: 给 layered_scene 添加 Duration
现在我们需要解决一个新的问题:要让所有的镜头按顺序播放,而不是单个播放。为了解决这个问题,我们打算引入一个新参数,用于控制每个镜头的持续时间,因为每个镜头在画面上停留的时间并不一定相同。
在设置摄像机的起始与结束位置之前,我们首先要考虑的就是镜头的时长(duration)。因此,我们决定在每个镜头数据的前面加入一个 duration
字段,例如将某些镜头设置为 23
帧的持续时间。我们就可以在镜头数据中直接写明该镜头的播放时长。
接下来,我们注意到目前镜头切换的逻辑中使用了一个 map to range
的映射函数,它将输入的 cosine
值映射到 0 到 1 的区间。但问题是,这个映射范围现在是硬编码为 0 到 20,这显然不灵活。尤其是在我们现在要支持镜头时长不一致的情况下,这种写死的方式不适合继续使用。
所以我们计划改进这一部分逻辑:
- 改为将当前播放时间
t
传入函数中,让它自己根据当前镜头的 duration 进行动态处理; - 不再在外部写死时间长度,而是让内部根据每个镜头的具体时长自己判断;
- 传入时使用地址引用的方式传递参数,这样如果后续需要封装或者做额外处理也会更加灵活;
- 保留可扩展性,比如可以根据
t
值判断是否要切换下一个镜头、是否播放到尾声等等。
这样,镜头播放系统将不再依赖固定长度,可以根据每个镜头的实际时长来自适应播放逻辑。同时我们在后期也可以很方便地做出更多复杂的控制,比如提前切换、自动过渡、镜头回环等效果。
总结一下目前的进展:
- 每个镜头已经拥有独立的
duration
参数; - 镜头数据结构更具通用性和灵活性;
- 去除了硬编码的时间范围控制;
- 采用传入时间
t
的方式控制镜头状态,增强系统的可控性; - 为后续镜头自动播放、序列联动、时长可调等功能打下基础。
game_cutscene.cpp: 给 RenderCutscene 增加按顺序播放所有镜头的能力
我们现在的目标是处理镜头播放的逻辑,也就是根据当前的时间 t
,判断处于哪个镜头,并播放对应镜头。具体的做法如下:
首先我们知道一个前提:整个片段中镜头数量并不多,例如当前的片段中只有 11 个镜头。后续如果还有比如结局片段,它的镜头可能会更少。因此我们不需要做复杂的查找算法,可以直接使用线性搜索来查找当前时间对应的镜头。
实现思路如下:
- 初始化时间起点
tBase
为 0,表示第一个镜头的起始时间是 0。 - 遍历镜头列表,对于每一个镜头:
- 获取该镜头的数据(比如索引、持续时间
duration
等); - 记录当前镜头的
start
(开始时间)为tBase
; - 计算当前镜头的
end
(结束时间)为start + duration
; - 判断当前时间
tCosine
是否处于这个时间段内(即start <= tCosine < end
):- 如果是,那我们就找到了当前应该播放的镜头;
- 播放该镜头;
- 然后可以
break
退出循环,因为每一帧只会播放一个镜头(没有镜头重叠);
- 将
tBase
更新为end
,用于下一个镜头的起始时间。
- 获取该镜头的数据(比如索引、持续时间
这样一来,每一帧就能根据 tCosine
查出当前的镜头,并进行播放。
然后,我们还做了一些额外的考虑:
- 时间归一化:在播放某个镜头时,我们还需要将当前时间
tCosine
映射到该镜头的局部时间范围(比如 0 到 1 之间),便于计算动画进度等。为此,我们使用了一个映射函数map(t, start, end)
,将tCosine
映射到当前镜头的范围内。 - 播放结束后的处理:当
tCosine
大于所有镜头的总时长(也就是tBase
累加到最后),我们暂时将tCutscene
重置为 0,重新播放,但更理想的做法是向外部发送信号,说明所有镜头已播放完毕,此时应该切回主游戏流程。
总结目前的处理逻辑:
- 使用线性遍历,根据时间判断处于哪个镜头;
- 为每个镜头记录
start
和end
,并计算是否命中当前时间; - 命中后播放对应镜头,并将
tCosine
映射到局部时间; - 所有镜头播放完后,准备退出镜头播放状态;
- 整个过程结构清晰,便于后续扩展(如增加镜头过渡、跳转控制等)。
运行游戏,看到过场动画实际上并没有播放
现在的目标是验证整体镜头播放逻辑是否真正起作用,从目前的情况来看,虽然从逻辑上推理没问题,但画面上没有任何动作、看不到任何角色或元素在动,说明可能存在某些隐藏问题。
当前逻辑回顾与排查流程
我们已经完成的逻辑如下:
- 每个镜头有自己的持续时间;
- 使用一个变量
tBase
作为累计时间起点; - 遍历所有镜头,通过判断:
tCutscene >= tStart
(当前时间大于等于当前镜头的起始时间)tCutscene < tEnd
(当前时间小于该镜头结束时间)- 若命中,则认为当前正在播放这个镜头;
- 将当前的
tCutscene
时间映射到当前镜头的 0~1 范围; - 然后将该时间传入镜头渲染逻辑中,用于控制动画。
当前异常现象
- 没有任何输出、没有角色动作、没有镜头变化;
- 理论上应该有东西动,但实际上什么都没有看到;
- 没有任何错误信息提示出错。
排查与可能的问题点
条件判断逻辑错误
重点检查判断条件是否真的能命中某个镜头,是否tCutscene
没有落入任何镜头时间段范围?- 例如:
>= start && < end
这个区间判断是否正确? - 镜头
duration
值是否正确设置?
- 例如:
映射范围逻辑问题
mapRange(t, start, end)
映射是否成功?
如果start
和end
值是一样的,或者有误,就会导致映射失败或者得到无效值。是否成功触发渲染函数
是否真正执行了渲染当前镜头的逻辑?可能遍历命中后并没有调用镜头播放代码。是否有初始值问题
tCutscene
是不是从 0 开始计数的?或者根本没在变?视觉元素未绑定 / 数据未连接
即使逻辑判断正确,是否当前时间没有影响到模型或摄像机的可见属性?例如:- 镜头动画没绑定时间值;
- 模型动画控制器未响应传入的映射值;
- 层级或遮罩设置不当导致无法显示任何图像。
顺序问题或初始化未完成
有可能逻辑上设置了播放结构,但系统中对应的图形对象还未加载、还未准备好。
初步方案建议
- 打印调试信息,例如每次循环输出当前
tCutscene
、当前匹配到的镜头编号、对应的start/end
; - 检查
duration
是否确实非零、且时间区间合理; - 验证
tCutscene
是否在按预期增长; - 验证渲染函数是否被调用,是否传入了正确的映射值;
- 如果方便,可以强制设置某个时间点手动渲染一个镜头,看是否能看到画面;
- 确保至少一个镜头的时间段能被命中,逻辑能进入执行路径。
总结
从目前情况来看,问题很可能出在判断条件失效、时间映射逻辑异常,或者渲染函数未被成功触发上。建议逐步用打印输出或调试工具验证关键逻辑路径是否按预期执行,一旦某一步没发生,那就是问题所在。通过排除法可以迅速锁定关键错误点。
game_cutscene.cpp: 设置 &IntroCutscene 索引来自 ShotIndex
当前的问题聚焦在一个核心变量 tNormal 的值异常 ——在理应变化的情况下却没有发生变化,导致画面依旧停滞,没有动画效果。
当前排查结果与思路总结如下:
问题定位总结
- 核心异常:tNormal(用于归一化时间)在时间推进过程中没有按预期变化;
- 已知现象:
- 正确识别了当前镜头的索引(修正为 shotIndex);
- 但
tNormal
依赖于tStart
与tEnd
的映射结果看起来非常不对劲或干脆恒定; - 映射公式逻辑是 mapRange(tCutscene, tStart, tEnd),理论上应该在每个镜头内部 0~1 变化;
- 实际输出发现 tNormal 似乎卡住了,无法随着时间推进。
分析推理:可能原因
mapRange 本身出错:
- 检查是否使用了错误的参数顺序;
- 是否
tStart == tEnd
?如果两者相等,则无法映射,会出现除以零的情况,可能返回 NaN 或恒定值; - 是否
tCutscene
根本没有落入 [start, end] 区间?
时间推进机制失效:
- tCutscene 是否真的在不断增长?是否定时器正常运行、tick 正常触发?
镜头时间段有误:
- 是否有镜头 duration 为 0?或者 duration 根本未设定?
- 是否所有镜头累计起来后总时长不合理?
判断逻辑写错:
if tCutscene >= tStart && tCutscene < tEnd
语句是否真的能命中?- 是否所有镜头都被略过了,导致渲染逻辑从未真正执行?
映射值未传入最终使用方:
- 即使 tNormal 正确计算了,是否成功被用在模型动画或相机控制中?
可行检查方案
- 打印 tCutscene、tStart、tEnd、tNormal:
- 输出这些值确认变化趋势;
- 输出 mapRange 结果:
- 看看是否得到 NaN、0 或始终是一个恒定值;
- 检查镜头 duration 值:
- 确保没有 0、NaN、未定义的情况;
- 直接用固定值测试映射函数:
- 例如临时设置
tCutscene = 5
,tStart = 3
,tEnd = 7
,看是否正常返回 0.5;
- 例如临时设置
- 确认当前镜头是否真的被命中;
- 输出当前索引或
shotIndex
,看是否进入渲染逻辑;
- 输出当前索引或
- 替代 mapRange 实现:
手动计算映射值,替换掉 mapRange 检查是否生效。let tNormal = (tCutscene - tStart) / (tEnd - tStart);
总结结论
当前表现说明 tNormal 失效的根源很可能在于:
- tStart 与 tEnd 值计算异常;
- 映射区间错误或无效(如除以零);
- 判断条件未进入,根本没触发渲染逻辑;
- 时间推进机制本身出了问题,tCutscene 恒定。
建议重点检查映射公式中的时间变量,确认它们是动态变化且数值有效的,逐步定位是时间推进问题、判断逻辑问题,还是映射计算问题。只要 tNormal
正确变动,动画流程就会随之正常运行。
调试器: 进入 RenderCutscene
当前正在排查 cutscene 播放逻辑中的一个关键问题,核心聚焦在变量 tNormal
的映射异常,导致镜头无法按预期播放或过渡。以下是详细中文总结:
当前调试背景
- 已进入场景执行逻辑,打印了当前镜头的数据。
- 当前镜头索引是
0
,即第一个镜头,相关信息已经成功获取。 - 变量
tStart
和tEnd
分别是当前镜头的开始时间与结束时间,这两者的计算结果看起来是正确的。 tCutscene
值确认也落在tStart
和tEnd
区间之间,说明时间确实已经进入该镜头的播放阶段。
发现的异常
- 在这个前提下,
tNormal
理应是一个处于 0 到 1 之间的值,用于在当前镜头内部执行归一化处理(控制动画、位移、镜头变换等)。 - 然而,实际观察发现
tNormal
的值为 0,这与预期不符,因为tCutscene
并不等于tStart
,也不等于 0。 - 从逻辑上讲,这一段映射应类似:
在tNormal = (tCutscene - tStart) / (tEnd - tStart);
tCutscene
落入镜头时间范围时,tNormal
应随时间推进从 0 增长到 1。
推测问题来源
- 尽管数值上
tCutscene
、tStart
、tEnd
全部正常,但有可能:- 映射函数本身未正确执行或写错;
- tCutscene 数据类型或精度问题,导致计算精度误差;
- 最终渲染逻辑没有使用
tNormal
,或其值被覆盖、忽略; - 其他隐藏逻辑重置了
tNormal
或拦截了渲染; - mapRange 函数实现逻辑错误或不返回预期值;
- 变量作用域问题导致打印的
tNormal
并非实际使用的那一个。
后续排查建议
- 手动打印并比对:
对照原始值验证映射结果;console.log('tCutscene:', tCutscene, 'tStart:', tStart, 'tEnd:', tEnd); let tNormal = (tCutscene - tStart) / (tEnd - tStart); console.log('tNormal:', tNormal);
- 确认是否存在
mapRange
之类的函数覆盖或实现错误; - 检查
tCutscene
数据是否为整数、浮点数,有无小数截断; - 确认最终是否将
tNormal
正确传入动画播放逻辑; - 若有缓存或更新延迟,需确保
tCutscene
与渲染同步。
小结
当前问题处在归一化时间映射阶段,逻辑设计上没有问题,但某处细节可能导致映射结果恒为 0,需聚焦检查 mapRange
执行和 tCutscene
实际值的动态更新。同时也要确认渲染链路中是否真正使用并响应 tNormal
。此类问题常见于变量传递、作用域、数据类型或值覆盖导致的“表面正常、逻辑失效”。
game_cutscene.cpp: 引入 b32 PrettyStupid 来指示过场动画结束
当前问题的根源已明确,并进行了针对性的修复和优化,以下是详细中文总结:
问题根本原因
- 先前逻辑中在进入匹配到的某一个镜头段落后,一旦找到符合
tCutscene
范围的那一段镜头(即当前需要渲染的 cutscene),就直接break
跳出了循环。 - 然而由于这个
break
提前中断了遍历,导致无法得知整个镜头序列最终的“结束时间”,从而在判断 cutscene 是否已经播完、是否需要复位等逻辑中,出现了逻辑漏洞或无法判断状态。 - 此逻辑会让系统永远认为“cutscene 还没结束”,或者不能准确判断是否应退出当前序列,进入游戏主流程或下一个模块。
解决方案及修改思路
- 对逻辑结构进行了优化,不再在找到当前镜头后立刻退出循环,而是继续遍历完整个镜头序列。
- 引入或更新用于记录整体 cutscene 总时长的变量(如
finalEndTime
、globalEnd
等),即使已经找到当前镜头,也要将该变量更新到最后一个镜头的endTime
。 - 这样在循环外部就能准确判断
tCutscene
是否已经超出所有镜头范围,从而决定是否将tCutscene
重置(或切换状态)。 - 更新后的逻辑结构可能类似:
let endTime = 0; for (let i = 0; i < shots.length; i++) { const start = tBase; const end = tBase + duration; tBase = end; if (tCutscene >= start && tCutscene < end) { // 当前镜头处理逻辑... } endTime = end; // 每轮更新,最终得到的是总时长 } if (tCutscene >= endTime) { tCutscene = 0; // 或其他处理逻辑 }
当前改动效果
- 成功避免了由于过早
break
导致无法获取cutscene
总长度的问题。 - 实现了对整个 cutscene 序列的时长追踪,确保在超出播放范围后能够正确地复位或进入下一个流程。
- 当前逻辑结构也更加清晰,便于后续扩展,例如添加循环播放、暂停、跳转控制等高级逻辑。
小结
本轮调试解决了一个隐藏但重要的逻辑漏洞 —— 提前中断循环导致后续状态判断失败。通过保留完整遍历并记录最终时间点,使得整个 cutscene 系统在结构上更加健壮和可维护。这一改动为后续加入更复杂的播放控制逻辑(如中断回退、镜头过渡、剧情交互等)打下了基础。
当然可以!下面用一个简单易懂的例子来帮你理解这段代码的作用:
情景设定:我们要播放一段动画,它由三个镜头组成:
镜头编号 | 持续时间(秒) | 播放区间(秒) |
---|---|---|
0 | 5 秒 | [0, 5) |
1 | 3 秒 | [5, 8) |
2 | 7 秒 | [8, 15) |
假设当前时间 *tCutScene = 6
那么我们程序是怎么工作的呢?
执行流程:
- 初始化
tBase = 0
,表示第一个镜头开始时间。 - 遍历镜头列表:
第一次循环(镜头0):
tStart = 0
tEnd = tStart + Duration = 0 + 5 = 5
- 判断
6 >= 0 && 6 < 5
❌ 不在播放范围 → 跳过
更新 tBase = 5
第二次循环(镜头1):
tStart = 5
tEnd = 5 + 3 = 8
- 判断
6 >= 5 && 6 < 8
在播放范围内 - 归一化当前时间:
tNormal = (6 - 5) / (8 - 5) = 1 / 3 ≈ 0.33
- 渲染镜头1,传入归一化时间
0.33
第三次循环(镜头2):
- 不执行了,因为已经找到了匹配镜头。
如果时间是 20(超出全部区间)
- 三次循环都没有匹配成功 →
PrettyStupid = false
- 那么最后执行:
自动从头重新开始(或者可以设计成播放结束)。*tCutScene = 0.0f;
简单理解总结:
就像我们放幻灯片(cutscene),每一页有不同的播放时间,代码会检查当前时间是在哪一页的播放时间范围内,然后播放那一页,并根据当前时间计算这页动画播放的进度。如果当前时间不属于任何一页,就重置或停止。
需要我用生活比喻再讲一遍,或者帮你写个伪代码/动画时间线图演示吗?
好奇怪还是不对
没注意传进去的值晕死
运行游戏,看到我们的过场动画正在播放
当前我们终于可以看到更多完整的过场动画效果了。流程已经能够连贯运行。完成了“孤儿之家”场景之后,我们接着进入了以下内容:
首先出现的是小孩站在窗边的画面,然后镜头切到他回头看玩具。这一段是目前整个动画中效果最差的一段,整体画面不够理想,可能之后需要重新调整美术资源或者进行优化。
接着是圣诞老人登场,依然是那个没啥用的形象,又送了小孩一顶帽子作为圣诞礼物。紧接着是反派角色克朗普斯强势登场,破门而入,气势恢宏,目标是抓走小男孩。
小男孩出现在画面前景,奋力逃跑,试图躲避克朗普斯的追捕。不过幸运的是,克朗普斯被另一位小小英雄拦下来了。虽然小男孩自己并不知道发生了什么,但正是这位孩子阻止了危机的发生。
克朗普斯被这位小英雄的勇气打动,决定送他一只手作为礼物。这是个重要的转折点——他终于拥有了过去渴望的东西。小男孩回到房间,兴奋地想要从圣诞老人送的一堆帽子中选出一顶,戴上它,开始属于自己的冒险。之前他一直缺少一只手,无法开始,现在他终于准备出发了。
最后,帽子的选择是由玩家决定的,所以虽然不知道是哪顶帽子,但整个情节的氛围和目标已经非常明确。
现在所有过场动画已经可以按照指定的时长连续播放,并且可以方便地调整每个镜头的时间长度。初步版本已经达到预期目标,之后只需要在配音素材完成后再进行精细的时间调整。
总的来说,经过前期的渲染系统构建和逻辑设计,整体流程非常清晰,实现起来也比较顺利,算是一个良好的第一版完成阶段。
会有帽子 DLC 吗?
在目前的设定中,尽管剧情最后让玩家选择帽子,并赋予了“冒险开始”的象征意义,但关于是否会推出帽子相关的DLC(下载内容)目前并没有计划。
可以理解为:虽然帽子在剧情中起到了一个重要的象征作用——代表主角梦想成真的起点,但并不会基于这个内容再扩展出额外的可下载内容或者后续更新。
因此,整体剧情在这一点上是收束而非延伸,玩家在结尾选择帽子只是作为结局的一部分,不会引申出新的内容线索或者额外玩法。简而言之,目前没有帽子相关DLC的打算。
你更喜欢 OpenGL 还是 DirectX?
我们更倾向于使用 OpenGL,因为它具有跨平台的优势。相比之下,DirectX 只能在 Windows 上运行,而 OpenGL 可以在 Linux 和 macOS 上运行,这使得我们在开发过程中拥有更多的灵活性和兼容性。
使用 OpenGL,可以更方便地将程序部署到多个平台上,不需要因为平台的不同而进行大量的代码调整。这种跨平台的能力对于需要在多操作系统下运行的项目来说非常有吸引力。
相反,DirectX 只能运行在 Windows 系统上,这种局限性在开发过程中会带来不少麻烦,特别是当我们希望软件能支持更广泛的用户群体时,就显得非常不便。因此在可移植性这一点上,OpenGL 更加符合需求,也是我们首选的图形 API。
谁画了所有的图片?
画画的是那个女孩,其实她负责绘制所有图像的部分。其他负责处理剩下的工作,比如其他方面的支持与实现。绘图的工作完全由她来完成,她负责把整个画面视觉部分的风格和细节呈现出来。整体来说,图像相关的任务都由她主导,而其他人则专注于配合和完成其余系统层面的事情。
我认为镜头 2 的顶部依然有一个小缝隙,特别是在镜头的最后部分
在窗口顶部的栏杆处,可能仍然有一个非常细小的间隙,出现在镜头接近结束的时候。这种情况有可能出现在镜头二中。也许当前的镜头索引实际上是 1,因此最好对这个部分做一些补齐处理。为了避免后续调试时出现混乱,应该尽早进行这种填补和修正,不然会让人完全摸不着头脑,搞不清楚到底哪里出问题了。为了解决这个问题,建议为这一段进行合理的填充处理,以免在时间轴或视觉呈现上出现错位或漏洞。整体而言,这种调整有助于让镜头过渡更流畅,避免出现突兀或画面撕裂的情况。
game_cutscene.cpp: 调整镜头 2 中窗口的位置
确实,镜头二中存在一些问题。英雄角色和树的尺寸需要稍微放大一些,否则就会导致画面在视觉上出现不匹配或空隙的现象。当前的尺寸显然略小,无法很好地填满画面顶部的区域,尤其是在镜头推进到最后阶段时,这种不足更加明显。只要将英雄角色和树稍作放大,就可以消除那个顶部的缝隙,让整个场景显得更加完整自然。同时这也避免了因尺寸问题而引发的渲染错误。只要调整完成,整体画面表现就应该没有问题了,状态也基本算是达标。
看起来过场动画差不多完成了(至少是第一次版本)。接下来是回到调试吗?
目前看来,过场动画的第一轮初步版本已经接近完成。接下来是否要回到“bug”那一部分还没有完全确定,大概率是会回去处理,但也有可能根据情况再决定下一步的工作。暂时还没有明确的安排,可能会根据进展和需要灵活调整,不管接下来做什么,都会顺其自然地推进下去。
你是怎么这么快编译的?
如果是的话,那要怎么让程序运行得很快呢?其实方法很简单:只要不使用大量的库、模板以及那些臃肿的无用工具,就能让程序运行得非常快。避免依赖那些复杂、低效的系统,保持代码简洁直接,就能够获得良好的性能表现。
过场动画开场会有音效吗(例如门开门的音效)?这是独立的,还是会作为配音的一部分?
关于过场动画中的音效,当前的设想是,如果有一些已经预先制作好的音效,比如开门声之类的,它们可能会直接包含在整个配音或音轨中。因为在游戏正常运行时,音效通常是根据实际事件实时播放的,比如角色动作、物体碰撞等。但在过场动画中,这些声音大概率会作为完整音频的一部分统一处理,不会像游戏流程中那样由系统单独播放。也就是说,过场动画的声音处理更偏向于电影化,是整体制作和统一播放的。
我刚开始做游戏开发,C++ 已经掌握了不少,但学习 OpenGL 或高级图形渲染库感觉太乏味了。有什么建议吗?
关于图形开发的学习和使用,有一些思考如下:
我们开始接触游戏开发的时候,可能会觉得很多人的帖子在讨论三角形渲染、OpenGL 或是一些高级图形渲染库,这一切显得非常繁琐。如果觉得使用 OpenGL 很“繁琐”,那可能并不是一个好兆头,因为相较于更底层的图形开发(比如自己完全从头编写渲染器),OpenGL 已经算是相对简单和高级的了。
当然,也有可能这里说的“繁琐”并不是指“无趣”,而是指“困难”。如果是因为觉得它太难,那么确实可以理解,特别是因为现在的 OpenGL 文档不再像以前那样完整。由于 OpenGL 在发展过程中发生了很多变更,许多老的教程已经不再适用,新教程也相对较少,导致很难找到真正好的学习资源。
这就使得想认真学习 OpenGL 的人面临不小的挑战,而目前也确实没有太多可以推荐的解决方法或者替代资源。如果真的对底层图形不感兴趣,可能需要考虑使用更高层的图形引擎或框架,比如 Unity、Unreal 或者更简单的封装好的库来进行开发。总之,OpenGL 的学习门槛在当前阶段确实存在一定的障碍,特别是对刚入门的开发者来说。
帽子选好之后会出现在过场动画里吗?
在讨论关于角色和帽子的选择时,讨论的目标是动态地基于玩家的选择来确定角色所戴的帽子。如果无法实现这种动态选择,也许就需要采用一些“简单”的方法,比如随机给角色戴上一顶帽子。这个想法的背后是考虑到,如果游戏有一个类似“体验模式”的设置,那么在某些时刻角色可能还没选择帽子,因此这时候随机选择一顶帽子可能是合理的。
所以,可能会有一个折中的方案,即通过随机化的方式来解决帽子的选择问题,尤其是在玩家尚未做出选择的情况下。这种方式虽然可能不完美,但至少能保证在没有其他方式时,依旧能够继续进行游戏,并且不会让玩家感觉到卡住。
对 Vulkan 有期待吗?
对于Vulkan的设计,虽然有些人可能很兴奋,但并不觉得自己对它有特别的期待。实际上,认为Vulkan的设计并不理想,并且不同意这种设计。对于Vulkan的实现方式,个人并不认为它是一个很好的选择。
知道什么好的 OpenGL 教程吗?
关于OpenGL的教程,提到了一些之前的不错教程,比如提到过的Arc Synthesis系列和Lichens等,尽管这些教程在某些地方已经被下架,但好像有人保存了相关的PDF文件。这些教程还算不错,但确实市面上能找到的高质量OpenGL教程并不多。考虑到这一点,可能应该有人写些好的OpenGL教程,以帮助其他开发者学习。
对于肩上视角的场景,如果你给男孩一个速度,让他部分夸张或反向相机的运动,是否能让他看起来有些实际运动感?
对于那个肩膀后面的镜头,可以考虑给男孩加上一些哲学性的动作,甚至可以让这些动作有所夸张,或者与镜头的运动产生某种对比,这样可以给他一些实际的动作感。至于具体的做法,可以考虑放慢他的动作速度,但目前还没有完全想清楚应该如何处理这个问题,还需要再思考一下,暂时还不确定最合适的做法。
对于那些反对全局变量的人来说,如何回应他们对过场动画代码中的全局变量的抱怨?
对于那些反对使用全局变量的人,我会告诉他们,这些其实是数据。首先,如果他们认为这些是全局变量,那么他们必须也认为所有从数据文件中加载的数据也是不好的。接着,我会问他们,如果不使用这些数据,他们打算如何构建一个游戏呢?
我觉得最后一镜头的结尾部分(英雄的头部)还是有裁剪问题
最后的镜头在结束时有一些裁剪问题,英雄看起来有些地方被裁切了。我觉得可能是通过了近裁剪平面。虽然不太明白为什么玩家的设置会在那个位置,因为似乎并不需要这么设置,但确实在那一刻会消失。好奇的是,近裁剪平面的设置是什么?是否有特别的原因这么设置?我们是否真的了解这个设置是为了什么?想要回去查看录像,看是否有相关的线索。
CurlingChamp: 你最喜欢的头文件是什么?在开始项目时会加入哪些头文件?
最喜欢包含的头文件是那些自己写的,而不会包含任何不是自己写的文件。因此,回答这个问题可能没有什么意义。在自己的项目中,主游戏并没有使用任何外部的头文件,完全没有。
有一天你提到过流叠加层的带状问题,说那很容易修复。你会怎么修复它?
问题出在一个以前为完全不同目的编写的小程序上,这个程序在图形上并没有做得特别好。这个程序产生了颜色带状效果,但如果将其放在一个普通的平面背景上,就能明显看到这种带状效果。解决这个问题的方法非常简单,只需要对其进行"抖动"(dither)。抖动可以采用任何方式,甚至使用最基本的 ordered dithering 就可以解决问题。如果要做得非常专业,可以使用蓝噪声(blue noise),但一般情况下不必那么复杂,使用普通的抖动方法也能达到不错的效果。
你理想中的“Krampus 声音”是什么样的?
校园角色在这个项目中应该没有台词或对话,因此没有理想的配音演员人选。
这些帽子会赋予你魔法能力吗?
有一个角色(女孩)在故事中将魔法般的力量赋予某个帽子。这种情节可能会引导故事的发展,帽子因此具备某些特殊能力。
我在预播时问过这个问题,不确定你有没有看到。虽然我还在赶上这一系列,但你在模拟区域集的剧集之后写了更好的哈希算法吗?
在之前的讨论中,提到了一些未被触及的功能或改进,比如关于哈希(hash)和“better half”功能的问题。虽然这些功能并没有被修改或更新,但已经有版本中包含了一个“better half”功能。因此,尽管没有做过进一步的改进,未来仍然有可能会进行相关调整或优化。
- 对于一个大型(超过一年)的软件项目,你会做多少前期规划?你怎么估算完成工作所需的时间?
在进行大型、持续一年以上的软件项目时,关于规划的工作量和时间估算,很难给出固定的答案。这主要取决于项目的性质,特别是是否涉及到研究或其他不可预见的因素。通常情况下,这类项目的计划并不严格固定在时间表上,而是更像是一系列决策点。每个决策点后,团队会根据当前的进展和实验结果做出下一步的计划和调整。也就是说,项目的进展往往是根据实际情况灵活调整,而不是完全按照原定的时间表推进。
Vulkan 什么时候发布?你为什么不喜欢 Vulkan?
对于Vulkan的发布时机并不清楚,同时对Vulkan的不喜欢主要是因为不喜欢它的设计。
你了解 FreeGLUT 吗?对此有什么看法?
不熟悉FreeGLUT,也不知道它是什么。
我也在尽量让我的代码库更独立。我有个问题,就是如何替代 GLEW,也就是如何自己加载 OpenGL 函数。你提到过你有自己的库。有什么建议或者从哪里开始写吗?
要动态加载 OpenGL 函数,可以使用 wglGetProcAddress
。首先需要加载 OpenGL 库文件(例如 opengl32.dll
)。然后,通过 wglGetProcAddress
获取 OpenGL 函数的地址。具体过程是,先加载库文件,再调用 wglGetProcAddress
来获取每个 OpenGL 函数的地址,动态地加载并使用这些函数。这种方法不需要包含标准的 OpenGL 头文件,可以动态获取 OpenGL 的功能地址。
Krampus 在把手赠送给我们的英雄后,对其他孩子做了什么?他来访的目的是什么?
关于他将手赠送给主角后的行为以及他访问的目的,具体细节可能需要等到配音完成后才能知道。至于是否包括 Windows 系统,答案是否定的,并没有将 Windows 包含在内。
在帽子平面上的蟑螂天线附近有个图形故障
在“Mapus Hover”中,图形出现了一个问题,特别是在帽子附近,那里似乎有半平面错误。此外,实际上存在很多问题,处理起来也相当复杂。至于这些问题是否值得清理,取决于我们是否愿意将这些问题反馈给艺术团队进行清理。
有一些地方的透明度(Alpha值)没有完全为1,比如角色的手部,甚至在某些镜头中都会显示出来。这些问题需要进一步修正,但是否要对此进行严格清理,仍然没有决定。由于这款游戏的重点在于编程,而不是艺术方向,因此是否要对这些艺术细节进行过多打磨,仍然存在疑问。总体来说,这些问题并不少,需要评估是否值得一一解决。
我感觉剪辑平面可能是我们在处理多个地面层时引入的
在处理多个地面层时,可能引入了剪裁平面。剪裁平面的位置可能是随机设置的,原因不太明确,也许根本没有特别的考虑。我的猜测是,这个设置可能并没有特别的目的,可能只是随意选择的。
Krampus 听起来真酷。你是怎么想到这个名字的?
“校园男孩”是一个真实存在的人物,基于一个真实的故事,可以查找相关资料。故事中的校园和圣诞老人都是现实存在的,因此这一部分没有任何疑问,可以说是科学事实。至于是否真的有孤儿院收养无家可归的孩子,虽然故事中提到了,但这并不影响校园和圣诞老人真实存在的事实。
有时候从一个场景过渡到另一个场景时并不完美。你会修复这个问题吗?
在场景切换时,系统遇到了一个性能问题。当场景切换发生时,系统需要加载并呈现一大堆位图资源。由于资源量巨大(大约半GB的位图),而系统只分配了大约20-30MB的缓存空间,所以当玩家经历完整个过场动画后,回到游戏初始位置时,所有资源都会被从缓存中清除。这样,系统在第一次切换时,无法获取所需的位图资源,从而导致画面无法渲染,可能会出现空白画面,或者加载一部分资源后仍然无法正常显示。
为了解决这个问题,计划通过预加载来修复。具体做法是,在每次场景切换时,系统会向前预加载下一部分时间段所需的位图资源,而不仅仅依赖当前时间段的资源。这类似于以前对音频的预加载,即在播放时提前请求并缓存可能需要的音频数据。这样,系统在切换时就可以快速从缓存中提取资源,从而避免渲染空白画面的情况。
这一方法可以在明天的工作中实现,目的是通过预加载下一段场景的位图,避免由于资源加载延迟而造成的性能问题。
Windows 任务管理器:检查我们的内存使用情况
这段内容主要讲解了一个设计和实现的优化问题。系统设计时,通过限制内存使用量,确保在场景切换时只加载特定数量的资源,而不会超过预设的内存限制。尽管实际艺术资源的数量比内存能容纳的更多,但由于系统的设计,内存使用量始终保持不变。
具体来说,虽然在场景切换时,系统确实加载了一些新的资源,但内存的使用量并没有因此增加,始终维持在一个稳定的范围内。这种设计确保了即使资源量超过了内存的承载能力,系统也能继续正常运行,而不会出现性能问题或内存溢出。
此外,提到了一些在实际开发中还未得到完全优化的部分,比如实时编程的相关问题,虽然这些问题存在,但整体设计和资源管理是按预期运行的,这也是设计的一个亮点。
Windows 任务管理器:检查我们的 CPU 使用情况
这段内容讨论了渲染性能优化的几个方面。首先,尽管可以将系统设置为使用100%的CPU以获得更高的帧率,但实际上,系统设置了较少的线程来进行渲染,这样做是为了避免影响流媒体的流畅性。如果将线程数增加到机器的最大线程数,CPU使用率会达到100%,从而提升帧率。
此外,渲染过程存在一些不必要的时间浪费。虽然渲染过程使用了大量的计算资源,但仍存在“过度绘制”现象,即即使某些区域不需要绘制,系统仍然进行纹理采样并判断是否有物体在这些区域。特别是在后景的物体被绘制时,即使它们被前景物体遮挡,系统也会浪费时间进行绘制。这种不必要的计算造成了效率低下,尽管现代计算机的速度非常快,依然能够在没有优化的情况下以很高的帧率完成渲染。
尽管如此,如果对深度复杂度进行优化,系统的渲染效率可以得到显著提升。通过优化深度复杂度,可以使渲染更加高效,进而进一步提高帧率。这个优化虽然目前没有进行,但如果将来有需要,完全可以进行尝试。
看看release模式
为什么要让 CPU 做这些事?
这段内容讨论了开发一个图形渲染引擎的重要性及其与GPU的关系。首先,如果要理解如何使用GPU进行图形渲染,必须先学会在没有GPU的情况下编写渲染程序。如果没有理解渲染的基本原理,单纯使用GPU可能就会把一些随机的代码塞进去,而无法理解其真正的工作原理。因此,开发了一个软件渲染器,这个渲染器模仿了GPU的工作方式,帮助开发者理解GPU是如何运作的,这对于优化GPU性能尤为重要。
原本,开发软件渲染器只是一个学术练习,目的是让大家了解GPU的工作方式。然而,经过优化后,软件渲染器的性能竟然非常高,能够以60帧每秒的速度流畅运行游戏,达到了非常令人惊讶的效果。于是,决定将整个产品的渲染都用这种软件渲染方式实现,这样就可以拥有一个完整的软件渲染版本的游戏,供大家体验。
此外,尽管在以后可以将这个渲染系统与GPU结合使用,但实际上目前已经不再需要GPU支持,软件渲染已经足够高效,这让整个过程变得非常令人兴奋和有趣。
是否有可以用来使用 GPU 的接口?
这段内容讨论了使用GPU时需要通过特定的接口进行访问。提到的接口有OpenGL和DirectX,这些接口是为了方便访问GPU的计算能力而设计的。通过这些接口,开发者可以与GPU进行交互,利用其强大的图形处理能力,进行图形渲染和计算。因此,OpenGL和DirectX就是用于访问GPU并进行图形处理的标准接口。
这是你做过的最深入的游戏吗(即过场动画、游戏引擎等)?
这段内容讨论了之前所制作的游戏,相比之下,目前这款游戏可能并不是最复杂的。虽然这款游戏包含了很多功能,比如过场动画和游戏引擎等,但过去所做的项目在复杂度上可能更高。
NearClipPlane 是在第 108 天引入的:透视投影(感谢 insofaras 的 git-fu)
这段内容提到了一个关于“前景投影”的讨论,并且表示对这一点了解了。
GLSL 和 DirectX 着色器语言不适合这个吗?
这段内容中提到,对于“GLSL”和“direct x”是否适合某种用途提出了疑问,而回应则表示不确定对方所指的“适合”是什么。
是否可以在没有驱动程序的情况下访问 GPU?
在没有驱动的情况下,直接进行图形处理并不现实。如果运行的是内核模式代码,理论上可以编写自己的图形驱动,但前提是必须拥有相应的文档,而像视频驱动这样的文档通常是无法获得的,因为厂商不会公开相关信息。如果使用的是例如英特尔这样的开放平台,可能可以编写自己的内核模式驱动代码,直接操作图形硬件,但在大多数情况下,显卡厂商不会公开其硬件文档,因此需要通过厂商提供的API,如OpenGL或DirectX,来访问显卡并进行编程。
把图形移到屏幕上,让 CPU 做其他计算
在讨论图形处理时,实际上使用CPU和GPU是协同工作的,GPU作为一个专门的处理单元,能够卸载某些计算任务。了解这些计算的性质对于学习编程至关重要。虽然显然如果想要运行GPU进行计算并不困难,但在教育性游戏或项目中,编写所有代码从零开始是非常重要的学习过程。这有助于更好地理解GPU的功能和它的限制,而不是直接依赖GPU的现成功能。其实,很多人已经理解了这一点,因此讨论这一点并没有太多帮助。如果有兴趣,可以回去查看以前的视频流,其中有详细的讨论。
不想妨碍你的风格。我也做过一些 BitBlting 的东西
为了澄清一下,这里并不是采用位深度精度的处理方法,也不是逐位操作。实际情况是,这些都是完整的、像素级精确的采样,支持旋转和缩放。这与位深度操作完全不同,它实际上实现了完整的纹理采样,和图形卡的做法一样。因此,结果也非常平滑,没有那些常见的像素跳动的伪影,通常在一些游戏中会出现这些问题,即使它们使用了GPU,但因为不了解GPU的工作原理,仍然会看到像素逐步跳动的现象,这看起来非常奇怪。
还有伽玛校正!
我们确实进行了一些校正,但并不是完整的伽马校正,而仅仅是做了2的幂次方伽马校正。我们并没有采用完整的源到目标的伽马校正,因为实际上并不觉得那样做有太大意义。尽管如此,我们仍然保留了一些校正,尽量让层次处理更加线性,确保视觉效果的提升。
双线性过滤在 3D 中是否适用?
双线性滤波在3D图形中通常是常见的做法。大多数图形处理单元(GPU)会使用双线性滤波,因为它是最基本的纹理过滤方法。尽管有一些更高级的过滤技术,如三次插值和各向异性过滤,通常用于细节更高的纹理处理,但大多数GPU还是主要使用双线性滤波。更复杂的过滤通常用于处理细节纹理或者环境贴图等特定情况。总的来说,双线性滤波是最常见的纹理处理方法。
是的,据我了解,你设定 NearClipPlane 是相当随意的
“NearClipPlane”似乎是设置了剪辑播放的起始和结束位置,确保其正确播放。