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

游戏引擎学习第98天

仓库:https://gitee.com/mrxiao_com/2d_game_2

开始进行一点回顾

今天的目标是继续实现正常贴图的操作,尽管目前我们还没有足够的光照信息来使其完全有用。昨日完成了正常贴图相关的基础工作,接下来将集中精力实现正常贴图的基本操作,并准备好在下周进一步处理如何生成可用的光照字段,供正常贴图使用。

首先,计划实现一个模拟球形正常贴图生成器,然后将其与环境贴图进行采样,最终验证生成的正常贴图是否合理。尽管之前没有正确连接这些部分,但目标是确保正常贴图能正确显示并为接下来的光照处理做好准备。

讨论 MakeSphereNormalMap 和 GPU 与 CPU 渲染器

首先,实现正常贴图时,需要传入一个位图和粗糙度值。粗糙度值作为材质参数,被打包到透明度通道中。正如之前所讨论的那样,并不一定需要在正常贴图中存储所有三个法线值,因为在GPU中,通常只会存储两个值。因此,可能在CPU端也只存储两个值。

主要目标是使用CPU渲染的方式帮助理解整个过程,并展示如何优化,而不是为了实现游戏中高效的性能。毕竟,CPU并非为图形渲染设计,尽管随着AVX-512等技术的发展,CPU的图形处理能力有所提升,但从总体上看,CPU在图形渲染方面无法与GPU相匹敌。因此,CPU渲染的质量和效率不能与GPU相比较,主要还是为了教学和理解过程。
在这里插入图片描述

为 MakeSphereNormalMap 创建 TreeNormal

首先,正常贴图生成器已经准备好了。接下来,在加载时,将生成一个正常贴图供使用。最初使用的是树的位图来进行测试。虽然现在并没有真正的树的正常贴图,但假设我们有一个树的正常贴图,并将粗糙度设为1,即完全平滑和反射的材质,这样有助于调试。

接下来,需要为此分配空间。通过调用 MakeEmptyBitmap 来分配空间。需要传入内存区域、宽度和高度,宽度和高度与树的位图的宽高一致。由于接下来会直接写入数据,因此不需要将其清零。

这些贴图会被存储在临时数组(transient array)中,这与地面缓冲区类似。树的正常贴图将被存放在相同的内存区域中,这样可以确保它们与其他临时状态共享内存。

接下来,将生成一个球形正常贴图,并将其存储在该内存区域。然后,可以将树的正常贴图传递到适当的函数中,确保能够从中进行采样,下一步需要返回到相关的例程,处理和应用该正常贴图。

在这里插入图片描述

让游戏运行,查看 NormalMap 的解包并改变其打包方式

在对法线贴图进行采样时,首先进行了解包操作。法线的值是从0到255以及0到128的范围。这种打包方式存在一定问题,尤其是法线的Z值应该是128加127的范围,而不是直接使用0到128的范围,因为法线的Z值应该始终是正值。为了一致性,可以采用一种更合理的打包方式,即将法线的Z值加1,再除以0.5并进行缩放处理,这样就可以确保所有值都在相同的坐标空间内。

目前的做法是只解包了法线,并进行线性插值,但仍未对解包后的值进行适当的缩放处理,因此无法得知这些值的具体含义。为了正确使用法线,需要将其调整到正确的范围,考虑到正负值的变化,并进一步处理这些值。
在这里插入图片描述

在这里插入图片描述

黑板:考虑问题的 hacky 特性

在处理3D问题时,尤其是在一个本质上是2D的世界中,常常会遇到一些问题,这些问题通常来源于需要将3D的概念压缩到2D环境下进行处理。具体来说,法线(Normals)计算是这种问题的一个例子,尤其是在2D渲染中,如何处理法线方向和深度(Z轴)的问题。

在绘制物体时,例如绘制一个球体,可以观察到法线指向的方向。例如,球体上的法线可能会指向我们,或者指向其他方向(比如上、下、左右等),而且这些法线会随着物体的远离而变得越来越小。需要注意的一点是,球体的法线永远不会指向屏幕后面,也就是说,我们不可能有指向屏幕后方的法线。

具体来说,在这种2D环境中,法线的意义变得难以直观理解。可以通过把法线的方向与世界坐标系的轴进行比较来帮助理解:

  • 法线的 X 轴对应于世界的 X 轴。
  • 法线的 Y 轴更像是世界的 Z 轴,用于描述地面和天空之间的过渡。
  • 法线的 Z 轴则描述了物体的“外向性”,表示物体的指向。

这种映射方式的背后逻辑是:在2D中,物体的法线方向无法指向物体的背面,所有可见的法线方向都指向物体的前面。因此,在2D渲染环境中,法线的 X、Y、Z 坐标实际上映射到了一个类似的坐标系统中。在这个系统中,法线的 XY 轴代表着物体的空间位置,而 Z 轴则决定了物体的相对外向性,即指向屏幕的方向。

通过这种方式,即使在2D世界中处理3D法线的问题,我们也可以通过这种转换方法将其映射到合适的方向和位置,从而在图形渲染中正确地表示法线信息。

黑板:地面的法线

在处理地面和物体的法线时,地面法线的情况与其他物体有所不同。地面通常是平坦的,但如果地面有一些起伏(例如不平整的地形),那么地面的法线就不再是单一方向的,而是可能指向半球的各个方向。因此,地面法线的处理相对复杂,它不像其他物体那样总是指向一个固定方向。

对于这种情况,可以考虑两种类型的法线贴图:

  1. 正面法线贴图:这类贴图的法线主要指向物体的前面,通常用于表示像树等物体的表面。法线的方向是确定的,主要指向物体的外部。
  2. 上方向法线贴图:这类贴图的法线是真正的三维法线,代表物体表面的实际方向。在地面这种场景中,法线方向可能指向不同的方向,以适应地面的起伏和变化。

因此,对于这两种法线贴图,可能需要不同的纹理采样路径。一种用于处理类似树木等物体的法线,另一种则用于处理地面等平坦或起伏的物体。根据绘制的物体类型,可以切换使用不同的法线贴图采样路径,具体取决于物体是地面类型还是其他类型的物体。

虽然大多数情况下法线指向物体的外部,但也可以考虑一些特殊情况。例如,在某些游戏场景中,如俯视角的地牢,墙壁的法线可能会指向不同的方向。墙壁的法线不仅指向外部,还可能指向不同的方向。这表明法线不仅仅限于指向物体的外面,某些特殊情况下,物体(如墙壁、地牢等)的法线也可能指向其他方向。因此,在处理这些场景时,可能需要更多的法线信息,尤其是对于像精灵(sprite)等物体,它们的法线有时会指向不同的方向。

综上所述,法线的方向处理不仅仅依赖于物体表面是否指向一个固定方向,还要根据具体场景的需要来调整法线的处理方式。在不同的纹理类型和物体类型之间,可能需要采用不同的法线贴图和采样路径。

处理前向位图,首先引入 UnscaleAndBiasNormal

目前,我们只处理正面朝向的法线贴图,地面部分会稍后处理,可能会使用不同的路径来处理。假设我们已经将法线贴图正确地加载进来,接下来需要做的就是处理法线值的缩放。

首先,我们需要从法线贴图中提取法线,并对这些法线进行缩放。法线的每个分量(X、Y、Z)都被压缩到了0到255的范围内,因此首先需要将这些值从0到255的范围转换到0到1的范围。这是通过简单的除以255来实现的。

接下来,我们需要将0到1的范围映射到-1到1的范围。这意味着,我们需要对转换后的法线分量进行扩展,使其从-1开始,然后通过将值乘以2来覆盖整个-1到1的范围。这样,法线的每个分量就能正确地反映其在三维空间中的方向。

至于法线的W分量,它表示粗糙度,通常已经是一个从0到1的值,因此无需进行任何额外的处理,只需要像处理颜色值一样进行转换即可。

最终,经过这些处理后,我们就能够获得一个合理的法线值,它已正确缩放并适用于渲染计算。
在这里插入图片描述

在这里插入图片描述

在游戏中查看,并回顾代码

在进行法线处理时,当前的操作是将法线的每个分量从[0, 255]的范围转换到[0, 1],然后再从-1开始,向正负两个方向扩展到[-1, 1]范围。对于法线的处理似乎是正确的,但结果显示的法线效果不如预期。虽然一半的结果看起来正常,但另一半的法线却显得有些奇怪,给人一种不太对劲的感觉。

接下来需要完成的部分是对法线进行插值(lerp),然后将其重新调整到正常范围。虽然法线从[0, 255]编码的方式可能会引起一些后续的影响,但这个步骤可能依然是可行的。理论上,先进行插值,再将法线解压并调整回正常的范围,应该是可行的。

在此之后,还有一个关于环境采样的操作,预计是要通过法线来作为颜色进行显示。然而,这一部分可能并没有按预期的效果展示。

仅绘制法线并将 Texel.a 强制为 1.0f

首先,打算直接绘制法线,将其作为纹素(Texel)显示。为了避免颜色闪烁,将纹素的透明度(Texel.a)强制设置为1.0,以确保没有不必要的视觉效果。然而,显示的结果却是完全黑色,说明出现了某种问题。

由于最近在代码处理上有所不确定,决定先停下来验证所有的逻辑,确保每一步都按照预期执行。尤其是对于昨天编写的代码,虽然今天继续处理,但仍然缺乏完全的信心,因此需要仔细检查一下各个环节的工作情况。
在这里插入图片描述

步进 NormalMap 代码

为了验证从法线贴图中获取的数据,打算直接查看样本值。发现解包后,法线的各个分量(A、B、C、D)都为零,这显然不合理。正常情况下,法线贴图的值不应该为零,除非某些特殊的区域,比如边缘区域。感到这种情况不太可能出现,因此决定进一步调查这个问题。计划直接查看法线贴图,以了解为什么会出现这种异常情况。

领悟时刻:我们仍然在从纹理中采样

发现了第一个问题:昨天的代码复制粘贴时没有修改法线贴图,仍然从错误的纹理中进行采样。这显然没有任何帮助,导致了当前的问题。
在这里插入图片描述

继续步进代码

发现了法线贴图中的问题,法线 B 和 D 显示为零,其他法线的值也看起来完全错误。例如,1, 1, 0 不是一个有效的法线。于是决定查看法线贴图的实际内容,查看内存中的值,并比较这些值是否合理。同时,通过查看生成球体位图的函数,进一步验证问题的来源。
在这里插入图片描述

在这里插入图片描述

领悟时刻:通常写一个像素时,你想移动到下一个像素

对昨天的代码产生了怀疑,指出在一个像素上写入多次是没有意义的。通常,在写入一个像素后,应该移动到下一个像素。
在这里插入图片描述

快速检查数学

检查了数学公式,发现之前的计算存在问题。通过正确生成位图 UV,将 X 除以宽度来获得正确的值,并使用该值生成适当的 X 和 Y 坐标。
在这里插入图片描述

黑板:Nz 应该通过毕达哥拉斯定理解决

通过使用勾股定理来计算球体的 Z 坐标。已知 X 和 Y 坐标,以及球体的半径为 1,可以使用公式 x 2 + y 2 + z 2 = 1 x^2 + y^2 + z^2 = 1 x2+y2+z2=1 来计算 Z 坐标。通过将 X 和 Y 的平方移到方程的右边,并对其进行平方根运算,得到 Z 坐标的值: Z = 1 − x 2 − y 2 Z = \sqrt{1 - x^2 - y^2} Z=1x2y2

计算 Nz

通过使用勾股定理计算 Z 坐标,公式为 Z = 1 − x 2 − y 2 Z = \sqrt{1 - x^2 - y^2} Z=1x2y2 ,可以得到球体上的 Z 坐标。

设置法线并查看像素是如何打包的

在处理法线时,首先将其范围从 -1 到 1 转换到 0 到 1,然后乘以 255 来进行打包,最后将粗糙度值放入 ALF 位置。完成打包后,进行截断和位移操作。这样就能将法线打包到适当的颜色通道中。需要确保解包操作能正确恢复原始顺序,以保证法线数据的正确性。
在这里插入图片描述

步进代码

正在检查计算出来的法线,期望能够得到实际可用的法线。尝试查看法线值,但发现值仍然是非常不合理的,如 2.5 或 250。对于这些值感到困惑,认为它们没有意义。
在这里插入图片描述

在这里插入图片描述

黑板:处理球体外的区域

面临的另一个问题是,有时计算结果可能会偏离球面。因为填充整个位图时,可能会得到一些超出球面范围的值。为了解决这个问题,可以考虑将这些值处理为合理的法线方向。例如,可以将这些超出范围的值设定为指向上方的法线(例如,(0, 0, 1)),或者可以选择将它们设置为球面上最近的点所对应的法线方向。

引入 RootTerm

为了处理球面之外的情况,首先计算根式项,确保在计算时根项不会小于零。如果根项大于等于零,则可以正常计算平方根。如果根项变为负数,则意味着将进入复数范围,这显然是不合适的。因此,在这种情况下,可以将其替换为更有用的值。为了简化处理,当前可以假设不在球面上的点直接指向上方((0, 0, 1)),但是未来可能会考虑采用更复杂的方案来处理这些情况。
在这里插入图片描述

检查法线值

通过处理越界的情况,得到了更合理的结果,值为(0, 0, 1),这正是预期的结果。当进行线性插值时,应该能够得到合理的结果。接着,通过对法线进行去偏移和缩放,期望得到的结果是(0, 0, 1),实际上也得到了相似的结果。不过,有些地方可能需要进一步检查,尤其是确保128能够精确映射到预期的值。
在这里插入图片描述

在游戏中查看,发现一些错误的负值

遇到的问题是没有任何合理的值被填充进来,结果总是指向正上方,这显得很奇怪。原本应该通过计算根式值(1.0减去x和y的平方和)来得到一些结果,但实际情况是,这个计算似乎没有正确执行。通过检查,发现即使期望的值为(0, 0),也没有得到合适的计算结果,可能存在某些环节的问题,导致无法得到正确的值。
在这里插入图片描述

查看正确的 NormalMap

现在代码看起来已经回到一个更合理的空间,虽然有些部分可能还需要进一步调整,但至少不再完全荒谬了,算是取得了一些进展。现在的结果符合预期,虽然还存在一些小问题,但总体上已经接近正确的方向。这是逐步前进的过程。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

重新计算 Texel.rgb 使其在有效颜色范围内

为了绘制效果,可以在渲染过程中调整值的范围,将其从负一到一的范围转换为有效的颜色范围。通过将值乘以0.5并加上0.5,可以将其调整到有效的颜色范围内。这可以帮助观察正常的映射效果,比如随着横向移动,红色的数量增加,纵向移动时绿色增加等。然而,可能存在某些细节上的问题,比如lerp计算不太正确,导致某些地方的效果出现异常。因此,需要更仔细地检查其中的实现,确保所有的计算都正确。如果关闭正常映射后,效果回归正常,可能是与正常映射相关的某些计算出现了偏差。
在这里插入图片描述

关闭 NormalMap,并考虑使用比树位图更明确定义的东西

为了更好地调试问题,考虑用一个非常明确的形状而不是当前的树形进行测试。这可以帮助更清晰地显示出是否存在问题,因为树形可能不如预期那样干净,导致问题不容易察觉。使用一个简单且明确的形状进行测试,可能会提供更直观的反馈,帮助发现并解决潜在的问题。
在这里插入图片描述

在这里插入图片描述

暂时将 TreeNormal 当作纹理图绘制

为了调试法线贴图,考虑将树的法线直接绘制出来,并将其当作一个纹理来观察。这样可以更清楚地了解为什么采样结果看起来如此奇怪。通过观察,发现图像存在sRGB颜色空间的异常,尤其是黄色区域,可能是由于128作为零值导致的。此外,虽然边缘看起来没有明显的畸形,但采样结果仍然显得有些不对劲。因此,下一步需要进一步检查法线贴图的采样过程,确保没有发生任何异常,导致图像出现带状现象。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

引入 BilinearSample 函数

为了避免出错,可以考虑将代码封装成一个函数,使其更加简洁且不易出错。例如,可以定义一个内联函数来执行双线性采样,该函数接收加载的位图(即纹理)以及采样的 x 和 y 坐标,完成采样后返回一个结构体,其中包含四个采样结果。

接下来,将所有相关的法线贴图采样操作替换为此封装好的双线性采样函数,使得代码更加简洁和易于管理。通过这种方式,可以更轻松地进行法线贴图的处理和调整,同时保持代码的整洁性。

区分一下几个单词 Linear 线性 Nearest 是临近

在插值方法中,“临近” 和 “双线性” 通常是用来描述不同的插值算法。以下是这些词语的英文翻译以及简要说明:

  1. 临近插值 - Nearest Neighbor Interpolation
    这种方法选择离目标点最近的像素值,简单快速,但效果较粗糙。

  2. 双线性插值 - Bilinear Interpolation
    该方法使用目标点周围四个像素点的加权平均来计算目标点的值。相比于临近插值,双线性插值生成的结果更加平滑。

  3. 单线性插值 - Linear Interpolation
    这种插值方法通常用于一维数据中,通过两个相邻数据点的线性插值来估算目标点的值。在二维空间中,可以将其视为沿着两个轴进行线性插值,常用于图像的一维变化。

在这里插入图片描述

在这里插入图片描述

在游戏中查看,并切换到法线图调节纹理的路径

目前的状态看起来相对合理,虽然仍然有些不对劲,可能存在轻微的剪切问题,需要进一步检查。总体上,当切换回正常路径时,可以看到正常映射的颜色与树的纹理相互作用,表明两者的采样已经相对正确。现在可以开始进一步进行操作,继续朝着目标推进。
在这里插入图片描述

用 NormalMap 照亮纹理

当前的问题是如何处理环境映射。需要传入环境映射以进行采样,并决定如何调节这些映射。可以考虑将它们直接相加,这可能是一个合理的选择,但也需要确保不会发生溢出,因此需要对结果进行限制,确保所有值都在0到1之间。此外,还存在一个问题,即光照颜色是预乘 alpha 的,因此从光照颜色中获取的值需要乘以纹理的 alpha 值,因为纹理的 alpha 值是我们预期的值,这也是需要额外处理的部分。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在游戏中查看

当前的情况是,虽然已经使用法线贴图进行照明,但法线贴图本身并没有包含任何光照值,实际上只使用了法线贴图中的值来进行照明,这样的效果非常有限。因此,下一步的目标是从某个来源获取光照值,以便真正实现照明效果。
在这里插入图片描述

处理 environment_map 的内容

在处理法线贴图时,最初的代码中使用了错误的法线轴(Z轴),实际上应该使用法线的Y轴来确定方向,因为Y轴值决定了位置的高低。法线的Y值范围从-1到1,因此,之前的测试方法不太适用,应该调整为更合理的范围测试。根据法线Y值的大小,决定使用哪个地图:如果Y值小于-0.5,则使用底部地图,如果大于0.5,则使用顶部地图。

为了混合顶部和底部地图的光照值,需要根据法线Y值计算权重。如果法线Y值小于-0.5(即最小值),则混合底部地图的光照,如果法线Y值接近0.5,则混合顶部地图的光照。计算时,首先将法线Y值从-1范围内的数值映射到0到0.5的区间,再进行适当的处理以得到正确的结果。

接下来,在选择了顶部或底部地图后,需要进行采样,获取相应的光照值。对于采样过程,传递法线的X、Y、Z值有些不清楚是否完全符合要求,因此需要进一步思考这些值在采样中的具体意义,并决定是否需要调整。
在这里插入图片描述

黑板:从环境映射中选择

在处理这个问题时,首先需要理解的是,当前场景中的点和法线信息可以视为类似于重心坐标。通过屏幕空间中的UV坐标,可以确定环境贴图中对应的位置。UV坐标告诉我们在环境贴图中的相对位置,例如,某个点可能对应UV值(0.2, 0.8)。此外,法线会指示观察方向,帮助确定视角和环境贴图之间的交点。

接下来,需要计算的是如何根据法线与环境贴图的关系来确定需要采样的贴图位置。假设法线与Z轴的偏离角度不超过45度(即0.5),这样可以简化计算过程。这个角度范围可以视作一个锥形区域,向上和向下各有一个锥形视野。我们需要确定每个法线值对应的环境贴图上的像素。

在面对水平区域(即正中央平面)时,计算变得更加复杂,因为我们没有明确的标准来处理这一部分。为此,需要更细致地思考如何处理这个区域,尤其是在定义环境类型时,可能会有不同的处理方式。总的来说,目标是根据法线的方向,从环境贴图中选出合适的像素进行采样。

使中间采样为空,并让 SampleEnvironmentMap 生成我们可以使用的值

在处理环境贴图的采样时,首先需要解决的一个问题是如何去除粗糙度的影响。通过去除粗糙度的计算,可以简化处理过程,将其暂时从方程中移除。这样,粗糙度值不会直接影响我们选择的 LOD(细节层次)地图。接下来,需要将粗糙度量化到一个合适的桶中,以决定选择哪个 LOD 索引。

通过将粗糙度值乘以数组的计数(即细节层次的数量),我们可以得到一个在范围内的整数,这个整数表示粗糙度的层次。然后,这个索引值将用于选择对应的 LOD 图像。接下来,使用双线性插值采样从相应的位图中获取颜色值。

在完成粗糙度的计算和位图选择后,进一步的处理是进行双线性插值的计算。通过获得的纹理坐标 (FX, FY),可以进行 sRGB 双线性插值,得到需要的颜色样本。

当完成这些步骤后,接下来需要处理的是如何在环境贴图中进行交点计算。通过法线的方向与环境贴图中的对应关系,可以计算出正确的采样位置,获取合适的颜色样本。然而,这一部分的交点数学计算尚未完全完成,预期在下次工作时继续进行。

为了避免错误和不一致,程序会检查环境贴图是否有效,确保不会因为无效的贴图而导致程序崩溃。在确认有效的环境贴图后,程序将从指定的 LOD 中获取相应的颜色值,并继续后续的处理。
在这里插入图片描述

展望接下来的内容

目前,已经准备好继续进行的步骤包括生成环境贴图和处理交点数学计算。交点计算是为了确定应该在哪里进行采样,这一步骤非常关键,需要计算出正确的采样位置。完成这些后,还需要确保地面和天空等元素能够参与到采样中,这意味着需要进一步处理与这些元素相关的内容。

回顾今天的清理工作

今天主要是进行了一些清理工作,修正了昨天输入的错误。还进行了代码的简化,将一些代码抽取成函数,这样可以避免因拼写错误而带来的麻烦。在开发的初期,抽象出可以正常工作的函数是非常有帮助的,虽然在优化阶段可能需要将这些函数展开,但在确保功能正确的过程中,这种做法可以减少错误的发生。

当你插值两个法线时,你可能得不到一个标准化的向量。你对这个问题有什么看法?请简要评论一下。

在讨论关于法线的插值时,提到了一些可能会导致非标准化向量的问题。首先,插值(lerp)时可能会得到一个不正常的法线,这就是为什么需要特别注意插值过程。虽然现在的插值方法可能产生不标准化的法线,但在做交集计算时,可能可以通过在除法步骤中进行标准化来解决这个问题,这样可以避免手动标准化向量。不过,如果在实际计算中需要一个真正标准化的法线,就会遇到问题。

目前,代码中的法线选择处理不完全正确,因为插值时使用了法线的Y分量,导致插值权重不准确。为了确保正确性,虽然可以选择在计算过程中始终对输入的法线进行标准化,这样可以确保正确性,尤其是在硬件上,特别是桌面PC上,通常会有快速的逆平方根运算,避免存储Z分量,而是通过计算X和Y来得到Z值。因此,可以在着色器中直接计算Z分量,而不需要在CPU端进行这一操作,这也是一个高效的做法。

问:我们如何在计算每个精灵的像素值时进行光源采样?会有一个所有光源的列表吗?还是这会是某种参数?

在计算每个像素的亮度值时,光源的采样方法是通过环境贴图(environment maps)来实现的。所有的光源将会被写入到环境贴图中,然后通过从贴图中提取来进行采样。因此,不需要维护一个光源列表。只需要一个纹理,就能够处理任意数量的光源。无论光源的数量有多少,这种方法的开销是固定的,不会因为光源数量的增加而增加额外的成本。这是一种简化处理的方式,可以高效地处理多个光源。

会为艺术制作法线图吗?

目前计划是将法线贴图(normal maps)包含在艺术素材集中,但具体如何生成这些贴图还不确定。可能会使用离线处理的方式生成法线贴图,而不是由艺术家直接制作。因此,虽然法线贴图会作为艺术集的一部分,但它们的创建过程尚未确定。

你会讲解更高级的 BRDF 吗?还是只会讲像 Phong 这样的简单光照方程?

目前的照明模型尚未采用BRDF(双向反射分布函数),因为在当前的简化照明模型中,使用BRDF并不会带来显著的效果。BRDF通常需要复杂的照明环境才能看到其效果,而目前的模型较为简单,尚未考虑视角角度的影响。虽然可以通过简单的线性组合来进行照明计算,但目前的做法并不包括视角衰减或光照反弹等计算。

尽管如此,如果未来需要,可以考虑在现有模型中加入视角依赖的光照衰减计算,但目前这些功能不在优先考虑之内。此外,由于整体模型仍然是为适应2D方案进行简化的,加入更复杂的计算可能会造成效率上的浪费,因此目前并不计划进一步增加这些计算。

如果我不想让我的游戏看起来“很普通”?有没有其他类型的图可以让我游戏更独特?

可以使用一种叫做“异常法线贴图”(Abnormal Maps)来让游戏的外观更独特。异常法线贴图的概念来源于将两个不同的纹理(A和B)进行操作,通常通过加法、乘法或卷积等方式进行组合。这种方法会破坏原本法线贴图中的计算,从而产生全新的效果。

例如,可以将两个法线贴图进行乘法操作,生成一个全新的异常法线贴图。通过这种方式,虽然会失去原有的法线计算精度,但可以得到一个独特且不同的视觉效果,从而使游戏的画面显得更加与众不同。不过,这样的效果往往会显得不太自然,因此使用时要小心平衡。

问:有些游戏会使用“凹凸贴图”作为法线图的补充或替代。它们之间有什么区别?各自的优缺点是什么?

现代游戏中,通常不再使用凹凸贴图(Bump Map)替代法线贴图(Normal Map)进行光照计算。凹凸贴图的作用通常是为了压缩法线贴图的大小,或者通过凹凸贴图中的值来近似法线,从而减少法线贴图的存储需求。

另外,凹凸贴图还可以用来实现其他效果,例如位移或遮蔽效果,而不仅仅是光照计算。这些效果可能会用来增强视觉表现,但相比法线贴图,凹凸贴图在真实光照的模拟上并不如法线贴图准确和高效。因此,凹凸贴图在现代游戏中更多地用于非光照效果的实现。

黑板:凹凸贴图与法线贴图

凹凸贴图(Bump Map)和法线贴图(Normal Map)之间有着明显的区别。凹凸贴图主要存储的是表面点的高度值,即每个像素的高度信息,而法线贴图则存储每个表面点的法向量,即表面在该点的方向。法线贴图提供了更精确的信息,因为它直接给出了表面的法线方向,能够更好地用于光照计算。

凹凸贴图通过高度差来计算法线,通常无法准确表达表面细节,特别是硬边等特征,因为它只存储高度变化信息,不能明确表示表面的方向。相比之下,法线贴图能够直接表达表面法线,可以捕捉到更多的细节和更精确的光照效果。

法线贴图的存储效率比凹凸贴图高,因为它直接给出法线的方向,不需要通过计算周围像素的差异来推测。而凹凸贴图通常用于需要节省存储空间的情况,或者在某些情况下,凹凸贴图可以用来生成其他效果,比如遮蔽效应。

在技术上,法线贴图实际上可以被视为凹凸贴图的导数,或者更准确地说,是凹凸贴图的梯度,即凹凸贴图的高度变化方向。总之,法线贴图比凹凸贴图更为强大和直观,可以更好地表达复杂的表面形态和光照效果。

问:这个效果最终会像凹凸贴图的效果吗?

法线贴图(Normal Map)实际上是凹凸贴图(Bump Map)的更强大版本。虽然凹凸贴图可以产生类似的效果,但它的表现远不如法线贴图精确。简单来说,凹凸贴图可以看作是法线贴图的简化版。

我们能否通过在绘制位图时使用帧缓冲区作为环境映射来实现反射?

通过将帧缓冲区作为环境贴图来实现反射是目前的计划。这样,物体的反射效果,比如地面上的反射,就可以通过这种方式实现。虽然还不确定最终效果如何,但这是目前的尝试方向。

如果你把表面看作一个隐式函数 f(x,y,z) = 0,那么它就是该函数的梯度。它与表面切线的叉积相同。

在讨论时,假设表面是一个隐式函数,实际上是在谈论该函数的梯度。与显式的高度图(如 bump map)不同,隐式函数会在每个位置都有一个值,然后寻找等高线。这时的确会涉及到梯度。然而,bump map 并非隐式函数,它是显式的,因此不能直接通过梯度来处理。为了从 bump map 中得到法线,必须通过计算切线方向,并将它们的叉积作为法线。具体来说,在任何点上,我们有两个切线方向,分别是 U 和 V 方向,然后将它们的叉积得到法线。虽然这类似于梯度,但由于 bump map 是显式的,不能直接像梯度那样操作,因此需要其他方法来处理。

我们已经结束了所有问题

总结来说,讨论结束后,计划继续进行接下来的步骤。主要目标是下周开始构建照明采样缓冲区,并使用正确传递的法线贴图来进行采样。尽管这仍然是一个实验性的过程,因为具体的实现方法还不明确,但会继续努力并尽力实现所需的效果。虽然精确的光照模拟非常复杂,通常需要进行全光照传输计算,但由于时间限制,通常只能进行近似的光照计算,且这种近似方法在3D环境中也属于常见的做法。接下来几周将继续调整这些近似值,直到找到一个能够作为渲染设置基础的方案。

相关文章:

游戏引擎学习第98天

仓库:https://gitee.com/mrxiao_com/2d_game_2 开始进行一点回顾 今天的目标是继续实现正常贴图的操作,尽管目前我们还没有足够的光照信息来使其完全有用。昨日完成了正常贴图相关的基础工作,接下来将集中精力实现正常贴图的基本操作,并准备…...

机器学习 - 进一步理解最大似然估计和高斯分布的关系

一、高斯分布得到的是一个概率吗? 高斯分布(也称为正态分布)描述的是随机变量在某范围内取值的概率分布情况。其概率密度函数(PDF)为: 其中,μ 是均值,σ 是标准差。 需要注意的是…...

物联网水质监测系统设计与实现/基于STM32的水产养殖云监控系统设计

背景 随着物联网技术的飞速发展,各行各业都在逐步实现智能化管理,水质监测系统作为环境监测中的一个重要环节,近年来备受关注。如何高效、精准地监测水质,尤其是在远程无法到达的地方,成为了一个迫切需要解决的问题。…...

【学习笔记】计算机网络(三)

第3章 数据链路层 文章目录 第3章 数据链路层3.1数据链路层的几个共同问题3.1.1 数据链路和帧3.1.2 三个基本功能3.1.3 其他功能 - 滑动窗口机制 3.2 点对点协议PPP(Point-to-Point Protocol)3.2.1 PPP 协议的特点3.2.2 PPP协议的帧格式3.2.3 PPP 协议的工作状态 3.3 使用广播信…...

Android 系统面试问题

一.android gki和非gki的区别 Android GKI(Generic Kernel Image)和非GKI内核的主要区别在于内核设计和模块化程度,具体如下: 1. 内核设计 GKI:采用通用内核设计,与设备硬件分离,核心功能统一…...

大疆无人机二次开发调试准备

以下机场和遥控器模式只能同时支持一种,因为无人机只能同时对频一种设备,如果同时对频了两种,以最后对频设备为准 机场模式 保证机场的网口闪烁,网络正常在mqtt中给dock建立用户,配置新建的MQTT账号和密码。组织ID任…...

【嵌入式Linux应用开发基础】文件I/O基础编程

目录 一、文件I/O简介 二、文件描述符 2.1. 唯一性 2.2. 抽象性 2.3. 有限性 三、文件操作函数 四、标准文件I/O函数 五、文件执行权限 5.1. 权限类型 5.2. 权限分配对象 5.3. 权限表示方法 5.4. 权限设置命令 5.5. 权限设置的重要性 5.6. 实例说明 六、设备文件…...

【StableDiffusion容器化部署】分步指南

使用Docker部署和管理Stable Diffusion环境可以有效解决依赖冲突、环境隔离和可移植性问题。以下是分步指南和相关技术细节: 1. 基础环境准备 1.1 安装Docker和GPU支持 安装Docker Engine:参考官方文档配置NVIDIA Container Toolkit:# 安装…...

2.11 sqlite3数据库【数据库的相关操作指令、函数】

练习: 将 epoll 服务器 客户端拿来用 客户端:写一个界面,里面有注册登录 服务器:处理注册和登录逻辑,注册的话将注册的账号密码写入数据库,登录的话查询数据库中是否存在账号,并验证密码是否正确…...

安装 Ollama 需要哪些步骤?(windows+mac+linux+二进制+Docker)

安装 Ollama 的步骤根据操作系统不同会有所差异,以下是针对不同操作系统的详细安装指南: Windows 系统 下载安装包:访问 Ollama 官方下载页面,下载适用于 Windows 的安装程序 OllamaSetup.exe。运行安装程序:双击下载的安装包,按照提示完成安装。默认安装路径为 C:\User…...

【力扣】148.排序链表

AC截图 题目 思路 基本情况处理: 如果链表为空 (head NULL) 或者链表仅有一个节点 (head->next NULL),则链表已经是有序的,直接返回头节点 head。 分割链表: 使用快慢指针法找到链表的中间节点。slow 指针每次前进一格&…...

Springboot框架扩展功能的使用

Spring Boot 提供了许多扩展点,允许开发者在应用程序的生命周期中插入自定义逻辑。这些扩展点可以帮助你更好地控制应用程序的行为,例如在启动时初始化数据、在关闭时释放资源、或者自定义配置加载逻辑。以下是 Spring Boot 中常见的扩展点: …...

绿虫储能仿真软件解决储能项目中的哪些痛点

痛点一:储能方案定制难 如何根据不同用户的需求,制定科学合理的储能方案,一直是行业内的一大难题。每个用户的用电情况、场地条件、预算等都存在差异,想要实现 “千人千面” 的专属方案设计谈何容易。 绿虫储能仿真设计软件凭借…...

保姆级教程Docker部署Zookeeper镜像

目录 一、安装Docker及可视化工具 二、创建Zookeeper网络 三、镜像选择 四、单节点部署 1、创建挂载目录 2、命令运行容器 3、Compose运行容器 4、查看运行状态 5、验证是否正常运行 一、安装Docker及可视化工具 Docker及可视化工具的安装可参考:Ubuntu上…...

【leetcode】滑动窗口刷题总结

滑动窗口算法技巧主要用来解决子数组问题,比如让你寻找符合某个条件的最长/最短子数组或者子串。对于某些题目,并不需要穷举所有子串,就能找到题目想要的答案。滑动窗口就是这种场景下的一套算法模板,帮你对穷举过程进行剪枝优化&…...

【MySQL】通过shell脚本一键同步MySQL数据库结构和数据到指定库中

通过shell脚本对数据库进行覆盖式备份/迁移,简单方便,适合需要快速同步某个库结构和数据到目标库的场景。 通过AI调试了好些次得到能用的脚本,本文主要是做一个对该脚本的记录| 安装依赖 # 安装进度条库 sudo apt install pv注:如…...

C# COM 组件在.NET 平台上的编程介绍

.NET学习资料 .NET学习资料 .NET学习资料 一、COM 组件简介 COM(Component Object Model)即组件对象模型,是一种微软提出的软件组件技术,它允许不同的软件模块在二进制层面进行交互。COM 组件可以用多种编程语言开发&#xff0…...

数据结构与算法:动态规划dp:背包问题:理论基础(状态压缩/滚动数组)和相关力扣题(416. 分割等和子集、1049.最后一块石头的重量Ⅱ、494.目标和)

背包问题 01背包理论基础 对于01背包问题,物品下标为0到i,对应的重量为weight[0]到weight[i],价值为value[0]到value[i],每个物品只可以取或不取,背包最大容量为j的场景。 常见的状态转移方程如下: dp[i…...

【MySQL例题】我在广州学Mysql 系列——有关数据备份与还原的示例

ℹ️大家好,我是练小杰,今天周二,明天就是元宵节了呀!!😆 俗话说“众里寻他千百度。蓦然回首,那人却在,灯火阑珊处。” 本文主要对数据库备份与还原的知识点例题学习~~ 前情回顾&…...

【Git】完美解决git push报错403

remote: Permission to xx.git denied to xx. fatal: unable to access https://github.com/xx/xx.git/: The requested URL returned error: 403出现这个就是因为你的(personal access tokens )PAT过期了 删掉旧的token 生成一个新的 mac系统 在mac的…...

2021 年 9 月青少年软编等考 C 语言五级真题解析

目录 T1. 问题求解思路分析T2. 抓牛思路分析T3. 交易市场思路分析T4. 泳池思路分析T1. 问题求解 给定一个正整数 N N N,求最小的 M M M 满足比 N N N 大且 M M M 与 N N N 的二进制表示中有相同数目的 1 1 1。 举个例子,假如给定 N N N 为 78 78 78,二进制表示为 …...

玩转适配器模式

文章目录 解决方案现实的举例适用场景实现方式适配器模式优缺点优点:缺点:适配器模式可比上一篇的工厂模式好理解多了,工厂模式要具有抽象的思维。这个适配器模式,正如字面意思,就是要去适配某一件物品。 假如你正在开发一款股票市场监测程序, 它会从不同来源下载 XML 格…...

Batch Normalization (BN) 和 Synchronized Batch Normalization (SyncBN) 的区别

Batch Normalization 和 Synchronized Batch Normalization 的区别 Batch Normalization (BN) 和 Synchronized Batch Normalization (SyncBN) 的区别1. BN(Batch Normalization)2. SyncBN(Synchronized Batch Normalization)3. 选…...

MySQL主从同步

目录 一、MySQL主从同步 1、基于binlog的主从同步 2、基于gtid的主从同步配置 二、MySQL 主从读写分离实现方案 2.1 ProxySQL实现mysql8主从同步读写分离 1、ProxySQL基本介绍 2、ProxySQL结构 2、实验环境 3、实现数据库主从复制 4、安装ProxySQL 5、配置ProxySQL …...

CCFCSP认证考试 ——202403-1 词频统计

题目: 在学习了文本处理后,小 P 对英语书中的 n 篇文章进行了初步整理。 具体来说,小 P 将所有的英文单词都转化为了整数编号。假设这 n 篇文章中共出现了 m 个不同的单词,则把它们从 1 到 m 进行编号。 这样,每篇文章…...

关于“i18n“在vue中的使用

关于"i18n"在vue中的使用 <!-- vue2中 --> <template><div>{{ $t("This campaign has expired.") }}}}</div> </template> <script> export default {created() {this.onLoading();},methods: {onLoading () {this.$…...

MATLAB中count函数用法

目录 语法 说明 示例 对出现次数计数 使用模式对数字和字母进行计数 多个子字符串的所有出现次数 忽略大小写 对字符向量中的子字符串进行计数 count函数的功能是计算字符串中模式的出现次数。 语法 A count(str,pat) A count(str,pat,IgnoreCase,true) 说明 A c…...

Spring中的@Component和@Bean有什么区别?

在Spring框架中&#xff0c;Component和Bean都用于定义Bean&#xff0c;但它们的使用场景和方式有所不同。 ### 1. Component - **作用范围**&#xff1a;Component是一个类级别的注解&#xff0c;通常用于标记一个类为Spring的组件。Spring会自动扫描并注册这些类为Bean。 -…...

泛化、选择、分化

泛化是指记忆联系的“发散”&#xff0c;泛化兴奋的基础是模糊兴奋。记忆联系的“发散”有以下几种种情况&#xff1a; 1、联络区的一原始记忆柱群&#xff08;A1&#xff09;具有直接或间接与其它任意联络区的任意原始记忆柱群建立记忆联系的潜力。也就是说任何两个对象&…...

剖析 C++ 模拟算法:数据结构、随机数生成与模型验证

模拟算法 (Simulation Algorithms) 是一种通过计算机程序来模拟现实世界或系统行为的算法。它不依赖于特定的数学公式或优化技术&#xff0c;而是直接按照系统的规则和逻辑进行步骤一步地模拟。 模拟算法的复杂度和效率取决于模拟系统的复杂程度和模拟的精度要求。 在 C 中&…...

51单片机俄罗斯方块整行消除函数

/************************************************************************************************************** * 名称&#xff1a;flash * 功能&#xff1a;行清除动画 * 参数&#xff1a;NULL * 返回&#xff1a;NULL * 备注&#xff1a; * 采用非阻塞延时&#xff0…...

IDEA升级出现问题Failed to prepare an update Temp directory inside installation

IDEA升级出现问题"Failed to prepare an update Temp directory inside installation…" 问题来源&#xff1a; 之前修改了IDEA的默认配置文件路径&#xff0c;然后升级新版本时就无法升级&#xff0c;提示"Failed to prepare an update Temp directory insid…...

Windows系统下设置Vivado默认版本:让工程文件按需打开

在FPGA开发过程中&#xff0c;我们常常需要在一台电脑上安装多个不同版本的Vivado软件&#xff0c;以满足不同项目的需求。然而&#xff0c;当双击打开一个Vivado工程文件&#xff08;.xpr&#xff09;时&#xff0c;系统默认会调用一个固定的版本&#xff0c;这可能并不是我们…...

CSS3+动画

浏览器内核以及其前缀 css标准中各个属性都要经历从草案到推荐的过程&#xff0c;css3中的属性进展都不一样&#xff0c;浏览器厂商在标准尚未明确的情况下提前支持会有风险&#xff0c;浏览器厂商对新属性的支持情况也不同&#xff0c;所有会加厂商前缀加以区分。如果某个属性…...

Kotlin 2.1.0 入门教程(十一)for、while、return、break、continue

for 循环 for 循环会遍历任何提供迭代器的对象。 for (item in collection) print(item)for (int: Int in ints) {println(int) }for 循环会遍历任何提供迭代器的对象&#xff0c;这意味着该对象必须满足以下条件&#xff1a; 具有一个成员函数或扩展函数 iterator()&#xf…...

深度探索DeepSeek:成本效益之辩与市场展望

摘要 DeepMind的CEO对DeepSeek的成本效益提出质疑&#xff0c;认为其成本被过度炒作。他指出&#xff0c;DeepSeek所使用的技术大多源自谷歌和DeepMind。然而&#xff0c;分析机构SemiAnalysis强调&#xff0c;DeepSeek的优势在于其成本与能力的卓越组合。尽管目前DeepSeek的成…...

DeepSeek投喂数据(训练AI)

1、拉取nomic-embed-text 打开命令行&#xff0c;运行&#xff1a;ollama pull nomic-embed-text 这里需要先安装ollama &#xff0c;不过大家应该在本地部署模型时已经安装了 拉取成功就行了&#xff0c;后续在配置AnythingLLM时用到 2、下载 AnythingLLM 地址&#xff1a…...

Docker 安装与配置 Nginx

摘要 1、本文全面介绍了如何在 Docker 环境中安装和配置 Nginx 容器。 2、文中详细解释了如何设置 HTTPS 安全连接及配置 Nginx 以实现前后端分离的代理服务。 2、同时&#xff0c;探讨了通过 IP 和域名两种方式访问 Nginx 服务的具体配置方法 3、此外&#xff0c;文章还涵…...

常用电路(过压保护、电流/电压采集)

过压保护电路 输入电压使用电源&#xff08;36V&#xff09;或者typec&#xff08;20V&#xff09;&#xff0c;需要过压保护电路处理输入再连接到CH224K&#xff0c;保证输入不高于最大获取电压20V MOS管导通条件为栅源极有压差&#xff0c;一般为5-10V 三极管导通条件为基极…...

12.Python模块:模块中的__all__、模块制作、打包模块、模块安装与使用

在 Python 中&#xff0c;模块是一个包含 Python 代码的文件。模块可以包含函数、类和变量&#xff0c;也可以包括可执行的代码。Python提供了一套强大的模块系统&#xff0c;支持模块的制作、打包、安装和使用。接下来&#xff0c;我们将详细介绍 __all__、模块制作、打包模块…...

Socket通信端口绑定的逻辑实现

在实现网络通信时&#xff0c;一个 Socket 需要维护输入端与输出端的 IP 地址和端口号&#xff0c;同时也需要输入与输出字节缓冲区&#xff1a; 输入端与输出端的 IP 地址和端口号 作用 标识通信端点&#xff1a;IP 地址用于标识网络中的设备&#xff0c;端口号用于标识设备…...

在freertos中,中断优先级和任务优先级之间的关系和使用方法

中断优先级和任务优先级如何匹配&#xff1f;任务优先级不同任务之间该用多高的优先级&#xff1f;中断优先级不同中断中该用多高的优先级&#xff1f;中断优先级和任务优先级设置时&#xff0c;怎样设置可以让任务在调度时屏蔽中断&#xff1f;怎样设置可以让任务在调度时&…...

解锁摄影潜能:全面解析相机镜头的选择与使用逻辑

目录 一、镜头分类&#xff1a;从焦距到用途的底层逻辑 &#xff08;一&#xff09;按焦距和视角分类&#xff08;一级分类&#xff09; &#xff08;二&#xff09;按特殊用途分类&#xff08;一级分类&#xff09; 二、参数解码&#xff1a;超越 “光圈越大越好” 的思维定…...

java项目之直销模式下家具工厂自建网站源码(ssm+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的直销模式下家具工厂自建网站源码。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 直销模式下家具…...

CNN-BiLSTM卷积神经网络双向长短期记忆神经网络多变量多步预测,光伏功率预测

代码地址&#xff1a;CNN-BiLSTM卷积神经网络双向长短期记忆神经网络多变量多步预测&#xff0c;光伏功率预测 CNN-BiLSTM卷积神经网络双向长短期记忆神经网络多变量多步预测 一、引言 1.1、研究背景和意义 光伏功率预测在现代电力系统中占有至关重要的地位。随着可再生能源…...

grid网格布局中实现父盒子比较大,子元素顶部对齐

css布局中使用grid布局&#xff0c;父盒子的高度是1000px&#xff0c;每个子元素高度是100px&#xff0c;现在有三个子元素&#xff0c;如何实现每行显示两个&#xff0c;并且子元素都顶部对齐&#xff1f;如图所示的效果。 grid布局文档&#xff1a;grid - CSS&#xff1a;层…...

P4814 [CCO 2014] 国王格鲁夫

题目描述 本题译自 CCO 2014 Day1 T2「King Gruff」 狼国王格鲁夫统治着一个居住着可爱的狐狸的繁荣、快乐的领地。对狐狸们来说,不幸的是,他根本不是一个好国王,而且还想让他们的生活过得很惨。 他的国家有 N 个城市,由 M 条路连接,第 i 条路可以让你从城市 Xi​ 走到…...

[QMT量化交易小白入门]-二十二、deepseek+cline+vscode,让小白使用miniQMT量化交易成为可能

本专栏主要是介绍QMT的基础用法&#xff0c;常见函数&#xff0c;写策略的方法&#xff0c;也会分享一些量化交易的思路&#xff0c;大概会写100篇左右。 QMT的相关资料较少&#xff0c;在使用过程中不断的摸索&#xff0c;遇到了一些问题&#xff0c;记录下来和大家一起沟通&a…...

吃瓜教程Day1笔记

主要内容&#xff1a; 1. 什么是机器学习以及 2. 机器学习的相关数学符号&#xff0c;为后续内容作铺垫&#xff0c;并未涉及复杂的算法理论&#xff0c; 因此阅读本章时只需耐心梳理清楚所有概念和数学符号即可。 3. “模型评估与选择” 是在模型产出以后进行的下游工作&…...

在 C# 中,处理 Excel 和 PDF 文件的库有很多。以下是一些比较常用的选择

读取 Excel 文件的库 NPOI 用途&#xff1a;可以读取和写入 .xls 和 .xlsx 文件。特点&#xff1a;无需安装 Microsoft Office&#xff0c;支持简单的 Excel 操作&#xff0c;如格式化、公式、图表等。 EPPlus 用途&#xff1a;主要用于 .xlsx 格式&#xff08;Excel 2007 及以…...