计算机图形学 19:表面颜色从哪里来
把材质反射率设为 0.6,像素就应该写成 0.6 吗?同一块表面放在不同照明下,显然不会保持相同的显示颜色。问题在于材质参数、到达表面的光、离开表面的光和屏幕码值分别代表不同的量。 本篇选择一个能手算的场景:不发光、不透射的漫反射平面,法线上方一个圆锥内有恒定入射辐亮度,其余方向为零。先算物理量,再用确定性求积核对,最后才把结果压缩到灰度图。它是已知入射条件下的局部积分,不是通用间接光照求解器。 功率落到面积上,还缺一个方向 辐射通量 Φ 表示单位时间传递的辐射能量,单位 W。把入射通量按表面积分配,得到辐照度 E=dΦ/dA,单位 W/m²。它回答一小块表面总共收到多少功率,没有保留这些光分别来自哪些方向。 方向分布由辐亮度 L 描述。通过面积 dA、沿某个小立体角 dω 传播的功率满足: dΦ=Lcosθ dA dω.d\Phi=L\cos\theta\,dA\,d\omega. dΦ=LcosθdAdω. θ 是该方向与表面法线的夹角,面积投影为 cosθ dA。L 的单位为 W/(m²·sr),sr 是立体角单位球面度。一个方向范围在单位球面上覆盖的面积就是其立体角,...
计算机图形学 18:怎样减少求交次数
第 17 篇每条射线询问场景中的全部图元。128 个球、16,384 条射线,就要执行 2,097,152 次球体求交,即使每条射线附近实际上只有一个球。 本篇给图元建立包围盒层级。验收不只观察渲染图,还逐射线比较最近交点,分别计数包围盒测试、节点访问和精确图元求交。分离球体与完全重叠球体采用同一份实现,观察结构何时有效、何时只增加开销。 一个盒子怎样排除整组图元 如果一个盒子包含组内全部几何,射线没有经过盒子,就不可能碰到组内几何。这个单向推理是加速结构的基础。射线经过盒子却不一定碰到表面,因为盒内允许有空白。 第 16 篇已有轴对齐包围盒 Bounds。球的盒为 center±radius;三角形的盒对三个顶点逐轴取最小、最大。父盒合并子盒的 low 和 high,不需要重新访问孩子内部的所有顶点。 保守包围意味着可以多保留候选,但不能漏掉真实几何。盒子过大主要损害速度,盒子过小会改变渲染结果。这里不使用包围盒命中代替球或三角形命中,它只决定是否继续调用第 17 篇的精确求交函数。 对于 X 轴,盒内要求 lowₓ≤oₓ+t dₓ≤highₓ。当 dₓ 非零时,除法得到进入...
计算机图形学 17:一条光线先碰到什么
同一个像素前方有两个球,应该显示哪一个?光栅器把三角形投到像素上,再比较深度;射线方法从像素反推一个观察方向,分别询问场景中的几何,最后保留沿这个方向最先到达的表面。 两种方法都在解决可见性。本篇只生成物体编号图和法线图,不计算反射光、阴影或多次散射。颜色连续变化可以来自法线可视化,不能据此把图像称为真实感光照结果。 从像素中心到世界射线 射线写成 r(t)=o+td。o 是起点,d 是方向,t 是参数。d 若为单位向量,t 才等于沿射线走过的距离。将 d 放大两倍,同一个交点的 t 就减半,几何位置不变。 沿用第 02 篇相机约定:右手坐标系,相机向局部 −Z 观察,屏幕 y 向下。对于宽 W、高 H 的图像,在像素 (x,y) 中心取样,垂直视场角为 θ,像平面上的横纵坐标为: sx=(2x+0.5W−1)WHtanθ2,sy=(1−2y+0.5H)tanθ2.s_x=\left(2\frac{x+0.5}{W}-1\right)\frac WH\tan\frac\theta2, \qquad s_y=\left(1-2\frac{y+0.5}{H}\right)\ta...
计算机图形学 16:多个模型怎样组成场景
把两个模型挂到同一个父节点下,旋转父节点后,模型画面跟着转了,碰撞或裁剪使用的包围盒却还停在原处。渲染结果和空间查询各自使用一套变换缓存时,很容易出现这种分歧。 本篇建立一个可从文件导入的三节点场景:一个分组节点与两个共享网格的实例。更换父节点变换后,同时核对世界顶点、实例包围盒与整个子树的包围盒;非法层级与缺失资源也必须明确失败。 资源和实例分别保存什么 第 12 篇的 Mesh 保存模型自身的顶点和三角形连接。把同一个方形放到两处,不需要复制它的顶点数组。两个场景节点只需引用同一个网格编号,各自保存局部变换。 资源表中的网格位于模型空间;场景节点记录“这份资源以什么变换出现在场景里”。改变一个实例的位置,不应改写资源表坐标,否则其他引用者也会改变。当前求值接口把资源表作为常量引用,只输出世界矩阵与包围盒。 分组节点可以没有网格。它仍然定义局部坐标系,其变换影响后代。实验中节点 0 就是这样的分组,节点 1 和 2 都引用资源 0。资源 0 只有四个顶点、两个三角形,但画面里出现两个实例。 节点关系采用森林:每个节点只有一个 parent 整数,−1 表示根。这种数据表示本身...
计算机图形学 15:怎样减少面数又控制形变
一个 64 面的封闭模型,能否只保留 8 面?删除任意三角形会留下孔洞;直接合并最近的两个顶点,也可能改变连接或翻转表面。减少面数需要同时回答三个问题:合并哪条边、把合并点放在哪里、怎样判断结果还能使用。 本篇复用第 12 篇的网格和半边结构,实现受约束的边折叠。排序代价采用二次误差度量(QEM),验收另算真实三角形距离与固定视角轮廓。三项数值回答不同的问题,不能用一个低代价数值替代全部检查。 折叠一条边会删除什么 对内部边 (a,b),把两个端点合并到新位置 x。包含整条边的两个三角形因顶点重合而删除;其余引用 b 的三角形改为引用 a,再从顶点数组删掉 b 并修正编号。 合法的闭合三角网格边折叠通常使 V 减 1、E 减 3、F 减 2,因此欧拉示性数 V−E+F 不变。但数量相符不能保证操作合法:两个原本不同的面可能变成相同索引集合,顶点一环也可能不再是单一扇区。 一个反例是四面体。折叠任一边会删除它相邻的两个面,余下两个面却落到同一组三个顶点上。若只检查“公共邻点有两个”,这个反例会通过;完整的邻接条件还涉及边等单纯形,不能缩成邻点数量比较。 collapse_clos...
计算机图形学 14:粗网格怎样变成曲面
把四面体每个面切成四个小三角形,它会变圆吗?如果所有新增点仍在原来的面上,答案是否定的:面数增加,几何外形却没有改变。曲面细分还必须更新顶点位置,而且更新规则要区分内部和边界。 本篇复用第 12 篇的索引网格与半边邻接,主实现选择三角网格 Loop 细分。单三角形检验边界,闭合四面体检验内部规则,两组都实际执行多次更新。第 13 篇的曲线说明了加权平均可以改变形状;这里还要保证相邻面共享同一个新增顶点。 一轮细分有两件事 对旧三角形 (a,b,c),先在三条边上各生成一个新顶点,编号记为 ab、bc、ca。四个子面依次写为 (a,ab,ca)、(b,bc,ab)、(c,ca,bc)、(ab,bc,ca)。这个顺序保留旧面的绕序。 拓扑拆分之外,还有位置更新。旧顶点 a 的新位置不是旧坐标的别名;边点 ab 也不一定是边的中点。所有位置都根据旧层网格计算,之后一起成为新层的顶点数组。 若一边遍历一边改写旧坐标,稍后访问的顶点就会混合新旧两层数据。输出不仅偏离规则,还可能依赖遍历顺序。subdivision.hpp 的 loop_step 复制位置数组用于输出,所有加权输入都明确读...
计算机图形学 13:少量控制点怎样生成曲线
把一条拱形曲线左侧的控制点提高,右半段会不会改变?如果两段曲线在同一点接上,而且切线方向相同,沿曲线运动的物体会不会突然变速?这两个问题分别涉及控制点的影响范围和参数的含义,仅凭曲线看起来平滑无法回答。 本篇用四个控制点构造三次 Bézier 曲线,实际生成移动前后的图像,再用独立多项式检查求值结果。坐标沿用第 01 篇的向量约定,图像沿用累计 C++17 工程;这里的二维曲线嵌在 z=0 平面,不经过透视相机。 从线性插值到曲线 两点之间的线性插值是 L(t)=(1−t)A+tB,t∈[0,1]。四个点 P₀、P₁、P₂、P₃ 可以先两两插值,得到三个点;再对这三个点插值,得到两个点;最后再插值一次,得到曲线上对应 t 的一个点。 用 Pᵢ⁽ʳ⁾ 表示第 r 层的结果,de Casteljau 递推为: Pi(0)=Pi,Pi(r)=(1−t)Pi(r−1)+tPi+1(r−1),B(t)=P0(n).P_i^{(0)}=P_i,\qquad P_i^{(r)}=(1-t)P_i^{(r-1)}+tP_{i+1}^{(r-1)},\qquad B(t)=P_0^{(n)}. P...
计算机图形学 12:三角网格怎样保存邻接
两个三角形组成一个方形,渲染时只需依次提交顶点。若要把方形对角线换成另一条,就必须知道这条边属于哪两个面、两侧相对的顶点是谁,以及修改后每个顶点周围是否仍连成一片。图像数组和三角形列表本身没有直接回答这些问题。 本篇在累计光栅器前增加网格邻接结构。一个方形用于检查边界与翻边,一个四面体用于检查闭合一环,非法输入用于验证拒绝路径。图像展示连接变化,连接表与数值不变量负责验收。 索引相同才表示同一个顶点 索引网格保存一份位置数组和一份三角形索引数组。方形四点为: p0=(−1,−1,−3),p1=(1,−1,−3),p2=(1,1,−3),p3=(−1,1,−3).p_0=(-1,-1,-3),\quad p_1=(1,-1,-3),\quad p_2=(1,1,-3),\quad p_3=(-1,1,-3). p0=(−1,−1,−3),p1=(1,−1,−3),p2=(1,1,−3),p3=(−1,1,−3). 两个面分别为(0,1,2)、(0,2,3)。共有四个顶点、五条无向边、两个面。边(0,2)属于两面,其余四条边各属于一面。 邻接按索引识别,而不是比较坐标是否近...
计算机图形学 11:沿管线定位一张错误图像
立方体的位置、轮廓与阴影都正常,一个原本背光的侧面却出现了棋盘。检查最终颜色只能知道结果变了,无法立即判断是纹理、法线、光方向还是颜色编码出了问题。把同一个可见样本分别画成深度、法线和UV,能缩小排查范围;再用数值检查验证阶段之间的关系,才能确认原因。 本篇复用第10篇的地面、立方体、相机与光源,不再复制一套渲染器。正确路径输出四张诊断图,错误路径只在照明时混用局部法线与世界光方向。几何、纹理查询和阴影比较全部相同,因此图像差异有一个明确来源。 中间结果需要有明确含义 当前累计管线先变换顶点,在齐次空间裁剪,随后除w、映射视口、判断覆盖并比较深度。通过测试的样本保存局部位置、面法线和对象编号。着色阶段据此求UV、查询纹理与阴影,在线性域计算明暗,最后编码为sRGB。 这些阶段对应常见图形管线的变换、光栅化、逐样本着色与可见性职责。但这里的执行次序和数据结构属于CPU教学实现,不描述GPU一定采用的内部调度,也没有实现全部可编程阶段。 test_scene.hpp从第10篇提取原有场景提交代码,仍是地面两片、盒子十二片三角形。shadow_check.cpp与新增的pipelin...
计算机图形学 10:光源看不见的地方怎样变暗
地面没有条纹纹理,渲染结果却出现整齐的明暗带。增大一个深度偏移参数后,条纹消失;继续增大,立方体明明贴着地面,阴影却与底边分开。前一种是错误自阴影,后一种是偏移过大造成的漏影。 本篇固定几何、相机、光源和分辨率,只改变偏移。除了三张实际图像,还用独立射线与盒相交判断地面是否真的被遮挡,分别统计“错误变暗”和“错误受光”,不把看起来干净的图直接认定为正确。 从光源再做一次可见性判断 第06篇从相机出发,为每个像素保存最近表面的深度。阴影映射把同一思路用于光源:第一遍从光源观察场景,保存最近表面的光源深度;第二遍从相机观察可见表面,将每个表面位置投回光源空间,与第一遍比较。 一个表面可能被相机看见,却被另一块几何挡住光源。因此相机深度测试和阴影比较各自回答不同问题,不能复用相机深度值直接判阴影。 设接收点在光源NDC中的深度为z,阴影图在对应位置保存D。沿用近处0、远处1的约定,若z>D,接收点比光源看见的最近表面更远,应被遮挡。加入接收点偏移b后,当前判断为: shadow=[z−b>D].\text{shadow}=[z-b>D]. shadow=[z−b&g...










