table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1rem;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
pre {
background-color: #f8f8f8;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
}
70、渲染“混合”演示中使用的场景的深度复杂度。
测量与可视化深度复杂度的方法
测量深度复杂度
方法描述
:
渲染场景,并使用模板缓冲区作为计数器。
每个像素在模板缓冲区中最初被清零。
在每次处理像素片段时,使用
D3D11_STENCIL_OP_INCR
操作增加其计数。
使用模板比较函数
D3D11_COMPARISON_ALWAYS
,确保每个像素片段对应的模板缓冲区条目始终递增。
示例说明
:
绘制完一帧后,若第 i 行 j 列像素在模板缓冲区对应的条目为 5,则该像素在该帧的深度复杂度为 5。
可视化深度复杂度
方法描述
:
可以根据深度复杂度为每个像素进行唯一着色,以便研究场景的深度复杂度分布。
应用场景
:
按照上述测量方法处理“Blend”演示场景的深度复杂度,再进行可视化操作,即可完成渲染该场景深度复杂度的任务。
71、考虑一个在 xz 平面上用线条带绘制的圆。使用几何着色器将该线条带扩展为一个无顶盖的圆柱体。
在编写几何着色器时,对于输入的圆的线条带,沿着 y 轴方向复制一份相同的线条带,然后将对应的点连接起来形成侧面三角形,从而构建无顶盖的圆柱体。具体实现需根据使用的图形编程框架和语言进行代码编写。
72、在调试时,将网格的顶点法线可视化是很有用的。编写一个效果,将网格的顶点法线渲染为短线段。为此,实现一个几何着色器,该着色器输入网格的点图元(即拓扑为 D3D11_PRIMITIVE_TOPOLOGY_POINTLIST 的顶点),这样每个顶点都会通过几何着色器进行处理。现在,几何着色器可以将每个点扩展为长度为 L 的线段。如果顶点的位置为 p,法线为 n,那么表示顶点法线的线段的两个端点分别为 p 和 p + Ln。实现此功能后,正常绘制网格,然后使用法线向量可视化技术再次绘制场景,以便将法线渲染在场景之上。使用“Blend”演示作为测试场景。
实现此功能通常需要以下步骤:
创建几何着色器,设置输入拓扑为
D3D11_PRIMITIVE_TOPOLOGY_POINTLIST
。
在几何着色器中,针对每个输入顶点,根据其位置
p
和法线
n
以及设定长度
L
计算线段的两个端点
p
和
p + Ln
。
正常绘制网格。
再次绘制场景,使用几何着色器将顶点法线渲染为线段。
以“Blend”演示作为测试场景进行验证。
具体代码实现会因使用的图形编程框架(如 DirectX)而异。
73、编写一个特效,将网格的面法线渲染为短线段。要求使用几何着色器,该着色器输入一个三角形,计算其法线,并输出一个线段。
要实现这个特效,一般步骤如下:
在几何着色器中接收三角形的顶点数据。
根据三角形的顶点计算面法线。可以使用向量叉积来计算,如计算面法线的方法
u = p1 - p0;
v = p2 - p0;
面法线 = 叉积(u, v)
并归一化。
确定线段的起点和终点。起点可以是三角形的中心点(如
(p0 + p1 + p2) / 3
),终点是起点加上一定长度的面法线向量。
输出线段的两个顶点数据。
以下是一个简单的 HLSL 几何着色器示例代码:
[maxvertexcount(2)]
void gs_triangle(triangle VS_OUTPUT input[3], inout LineStream<PS_INPUT> output)
{
// 计算面法线
float3 p0 = input[0].Position;
float3 p1 = input[1].Position;
float3 p2 = input[2].Position;
float3 u = p1 - p0;
float3 v = p2 - p0;
float3 normal = normalize(cross(u, v));
// 计算线段起点,这里取三角形的中心点
float3 start = (p0 + p1 + p2) / 3;
// 计算线段终点,假设线段长度为 0.1
float3 end = start + 0.1f * normal;
// 输出线段的起点
PS_INPUT vertex1;
vertex1.Position = float4(start, 1.0f);
output.Append(vertex1);
// 输出线段的终点
PS_INPUT vertex2;
vertex2.Position = float4(end, 1.0f);
output.Append(vertex2);
output.RestartStrip();
}
以上代码仅为示例,实际使用时需要根据具体情况进行调整,如顶点结构体的定义、输入输出的处理等。
74、编写一个计算着色器,输入一个包含64个随机模长在[1, 10]范围内的3D向量的结构化缓冲区。计算着色器计算这些向量的长度,并将结果输出到一个浮点缓冲区。将结果复制到CPU内存并保存到文件中。验证所有长度都在[1, 10]范围内。
可按以下步骤实现:
定义输入的结构化缓冲区和输出的浮点缓冲区;
编写计算着色器代码,计算输入向量的长度并输出到浮点缓冲区;
将结果从GPU复制到CPU内存;
将结果保存到文件;
验证所有长度都在[1, 10]范围内。
75、研究双边模糊技术并在计算着色器上实现它。使用双边模糊重新实现“模糊”演示。
一般来说,实现步骤可能包括:
研究双边模糊的原理和算法
在计算着色器代码中实现双边模糊算法
修改原“模糊”演示的代码,将原模糊算法替换为双边模糊算法
对修改后的代码进行测试和优化,确保双边模糊效果符合预期
76、到目前为止,在演示中,使用Waves.h/.cpp中的Waves类在CPU上实现了二维波动方程。现在要将其移植到GPU实现。使用浮点纹理来存储之前、当前和下一个高度解:Texture2D gPrevSolInput; Texture2D gCurrSolInput; RWTexture2D
gCurrSolOutput; RWTexture2D
gNextSolOutput; 使用计算着色器执行波浪更新计算。可以使用单独的计算着色器来扰动水以生成波浪。在更新网格高度后,渲染一个与波浪纹理具有相同顶点分辨率的三角形网格(因此每个网格顶点都对应一个纹理元素),并将当前波浪解纹理绑定到一个新的“波浪”顶点着色器。然后在顶点着色器中,采样解纹理以偏移高度(这称为位移映射)并估计法线。将性能结果(每帧时间)与在发布模式下具有512 × 512网格点的CPU实现进行比较。
以下是实现步骤的大致思路:
数据存储
:使用指定的纹理(
Texture2D gPrevSolInput
、
Texture2D gCurrSolInput
、
RWTexture2D<float> gCurrSolOutput
、
RWTexture2D<float> gNextSolOutput
)来存储波浪的前一时刻、当前时刻和下一时刻的高度解。
波浪更新
:编写计算着色器,在GPU上执行波浪更新的计算。可以根据波动方程的数学原理,在计算着色器中实现相应的算法来更新波浪的状态。
波浪生成
:编写另一个计算着色器,用于扰动水以生成波浪。可以通过在特定位置添加扰动来模拟波浪的产生。
网格渲染
:更新网格高度后,渲染一个与波浪纹理具有相同顶点分辨率的三角形网格。确保每个网格顶点对应一个纹理元素。
顶点着色器处理
:将当前波浪解纹理绑定到一个新的“波浪”顶点着色器。在顶点着色器中,采样解纹理以偏移顶点的高度(位移映射),并估计顶点的法线。
性能比较
:将GPU实现的每帧时间与在发布模式下具有
512 × 512
网格点的CPU实现进行比较。可以使用性能分析工具来测量每帧的执行时间。
77、探索分数细分。即,使用 [partitioning(“fractional_even”)] 和 [partitioning(“fractional_odd”)] 来尝试“基本细分”演示。
可按照要求使用
[partitioning("fractional_even")]
和
[partitioning("fractional_odd")]
运行“基本细分”演示进行探索。
78、通过改变控制点来改变贝塞尔曲面,对“贝塞尔面片”演示进行实验。
在“贝塞尔面片”演示中,改变控制点以改变贝塞尔曲面。同时可借助在线的贝塞尔曲线小程序设置和操作控制点,直观观察曲线形状的交互变化。
79、重新进行“贝塞尔面片”演示,使用具有九个控制点的二次贝塞尔曲面。
修改“贝塞尔面片”演示代码,将其配置为使用九个控制点的二次贝塞尔曲面。可能需要调整顶点着色器、外壳着色器等相关部分以适应九个控制点的情况。
80、研究并实现贝塞尔三角形面片。
可将三角形(3个控制点)转换为三次贝塞尔三角形面片(10个控制点)
在第一个演示中可使用简单的直通着色器,让控制点不做修改直接通过
具体实现需要进一步研究相关理论和代码编写
81、修改相机演示以支持“翻滚”。即相机绕其视线向量旋转,这在飞机游戏中可能很有用。
可通过以下通用步骤实现:
在相机类中添加一个表示翻滚角度的变量;
在相机更新函数中,根据输入或预设逻辑更新该翻滚角度;
根据新的翻滚角度更新相机的旋转矩阵或方向向量;
确保在计算视图矩阵时考虑到新的旋转状态。
82、修改“实例化与裁剪”演示,使用边界球代替边界框。
可按以下通用步骤操作:
计算边界球
:使用
ComputeBoundingSphereFromPoints
函数或先计算轴对齐边界框(AABB)再确定边界球的中心和半径。
替换数据结构
:将原使用的边界框数据结构替换为边界球数据结构。
更新裁剪逻辑
:修改原基于边界框的视锥体裁剪逻辑,使用边界球与视锥体进行相交测试。
测试与调试
:运行修改后的演示,检查裁剪效果并调试可能出现的问题。
83、修改“拾取”演示,使用边界球而不是轴对齐边界框(AABB)来表示网格。
先将计算边界的方式从计算AABB改为计算边界球:
可使用公式
c = 0.5(vmin + vmax)
确定球心
使用公式
r = max{||c – p||: p ∈ mesh}
确定半径
也可使用XNA碰撞库函数
ComputeBoundingSphereFromPoints
然后在射线与边界体的相交测试部分:
将射线与AABB的相交测试改为射线与边界球的相交测试
84、研究进行射线/轴对齐包围盒(AABB)相交测试的算法。
使用XNA碰撞库函数 `XNA::IntersectRayAxisAlignedBox` 进行射线/AABB相交测试。
该函数原型如下:
```cpp
BOOL IntersectRayAxisAlignedBox(
FXMVECTOR Origin, // 射线原点
FXMVECTOR Direction, // 射线方向(必须为单位长度)
const AxisAlignedBox* pVolume, // 盒子
FLOAT* pDist // 射线相交参数
);
若射线与盒子相交,函数返回
true
,否则返回
false
。最后一个参数输出射线参数
t0
,该参数对应的点
p = r(t0) = q + t0u
为实际相交点。
此外,先进行射线与近似网格的包围体的相交测试是一种性能优化策略:
若射线未与包围体相交,则必然未与三角形网格相交,无需进一步计算;
若相交,则进行更精确的射线/网格测试。
##85、如果场景中有数千个对象,为了进行拾取操作,你仍然需要进行数千次射线/边界体测试。研究八叉树,并解释如何使用它们来减少射线/边界体相交测试。顺便说一下,同样的通用策略也适用于减少视锥体剔除中的视锥体/边界体相交测试。
# 八叉树的应用
八叉树是一种用于**空间划分**的数据结构。在三维空间中,它将一个大的立方体空间**递归地划分为八个小立方体**。
在进行**射线/边界体相交测试**时,可先利用八叉树对场景空间进行划分,判断射线与八叉树节点的相交情况:
- **若射线与某个节点不相交**,就无需对该节点内的所有对象进行射线/边界体测试,从而减少总体的测试次数。
在**视锥体剔除**中同样如此:
- **先判断视锥体与八叉树节点的相交情况**,不相交则跳过该节点内的对象,以此减少视锥体/边界体相交测试。
##86、在“立方体映射”演示中尝试不同的 Material::Reflect 值。同时尝试让圆柱体和盒子具有反射效果。
要在“立方体映射”演示里尝试不同的 `Material::Reflect` 值,可找到代码中设置该值的部分,修改为不同数值并重新运行演示。要让圆柱体和盒子具有反射效果,需确保它们的材质设置支持反射,可能要在着色器代码里添加反射相关的计算逻辑,并且正确绑定立方体映射纹理。
##87、我们可以不选择在世界空间中进行光照计算,而是将眼睛和光线向量从世界空间转换到切线空间,并在该空间中进行所有光照计算。请修改法线映射着色器以在切线空间中进行光照计算。
一般思路如下:
1. 在顶点着色器中,将世界空间的法线、切线和副切线变换到切线空间。
2. 在像素着色器中,将光照向量和视线向量从世界空间变换到切线空间。
3. 对采样的法线纹理进行解压缩,使其范围从[0, 1]到[-1, 1]。
4. 在切线空间中进行光照计算。
以下是一个简单示例代码展示修改思路:
```hlsl
// 顶点着色器
struct VS_INPUT {
float4 Pos : POSITION;
float2 Tex : TEXCOORD0;
float3 Normal : NORMAL;
float3 Tangent : TANGENT;
};
struct VS_OUTPUT {
float4 Pos : SV_POSITION;
float2 Tex : TEXCOORD0;
float3 NormalT : TEXCOORD1;
float3 TangentT : TEXCOORD2;
float3 BinormalT : TEXCOORD3;
};
VS_OUTPUT VS(VS_INPUT input) {
VS_OUTPUT output;
output.Pos = mul(input.Pos, WorldViewProj);
output.Tex = input.Tex;
float3 normalW = mul(input.Normal, (float3x3)World);
float3 tangentW = mul(input.Tangent, (float3x3)World);
float3 binormalW = cross(normalW, tangentW);
float3x3 TBN = float3x3(tangentW, binormalW, normalW);
float3x3 TBNInverse = transpose(TBN);
output.NormalT = mul(normalW, TBNInverse);
output.TangentT = mul(tangentW, TBNInverse);
output.BinormalT = mul(binormalW, TBNInverse);
return output;
}
// 像素着色器
float4 PS(VS_OUTPUT input) : SV_TARGET {
float3 normalMapSample = gNormalMap.Sample(samLinear, input.Tex).rgb;
float3 normalT = 2.0f * normalMapSample - 1.0f;
float3 lightDirT = mul(lightDirW, TBNInverse);
float3 viewDirT = mul(viewDirW, TBNInverse);
float3 diffuse = max(dot(normalT, lightDirT), 0.0f) * lightColor;
float4 color = float4(diffuse, 1.0f);
return color;
}
此代码仅为示例,实际应用中需根据具体需求和场景调整。
88、在演示中,原本沿“视线”方向移动相机,然后应用Terrain::GetHeight函数将相机固定在地形表面上方一点,以模拟相机在地形上行走。但为避免玩家在上下山坡时出现不自然的加速现象,应沿与地形表面相切的方向移动相机。请修改相关演示,使相机能在地形上正确移动。
可按照以下思路操作:
首先要获取地形表面的切线方向,可根据地形网格数据计算得出;
接着将相机的移动方向改为该切线方向;
最后在代码中更新相机的移动逻辑,使其在新方向上以稳定速度移动,避免上下坡时不自然的加速。
89、在地形处理中,为了更好呈现细节,常对细分进行偏向处理,让包含高频区域的面片更多细分,平坦区域则少细分。现在需要研究如何计算每个面片的粗糙度因子,已知 SDK 示例“DetailTessellation11”计算了一个密度图,该图指示了位移图中具有高频区域的部分,请问该如何开展研究来计算粗糙度因子?
可从 SDK 示例“DetailTessellation11”入手,进一步研究其计算密度图的方式,以此为基础探索计算粗糙度因子的方法,可能涉及分析高度图数据的变化率、梯度等,以量化面片的粗糙度。
90、实现一个爆炸粒子系统,其中发射器向随机方向发射n个外壳粒子。经过短暂时间后,每个外壳粒子应爆炸成m个粒子。每个外壳不需要在完全相同的时间爆炸——为增加多样性,可以给它们不同的倒计时时间。尝试不同的n和m值,直到获得良好效果。注意,在发射器产生外壳粒子后,将其销毁,以便不再发射外壳。
以下是实现该爆炸粒子系统的大致步骤:
定义粒子结构体,包含位置、速度、倒计时等属性。
初始化发射器,设置其位置和发射n个外壳粒子的逻辑,外壳粒子的发射方向随机。
为每个外壳粒子分配不同的倒计时时间。
在更新循环中,减少每个粒子的倒计时时间,当外壳粒子倒计时为0时,使其爆炸成m个粒子。
销毁发射器,确保不再发射新的外壳粒子。
渲染所有粒子。
不断调整n和m的值,直到达到理想的效果。
在具体编程实现时,需要使用图形编程库(如DirectX),结合流输出技术、初始化顶点缓冲区等方法来高效地存储和更新粒子数据。
91、实现一个喷泉粒子系统。粒子应从一个源点出发,并随机地通过一个圆锥体发射到空中。最终,重力应使它们落回地面。注意:要给粒子足够高的初始速度大小,以便最初能克服重力。
可按以下思路实现:
定义粒子系统框架,包含粒子的表示、存储和渲染方式。可将粒子建模为点,在几何着色器中扩展为面向相机的四边形。
确定粒子的初始状态,包括从源点出发,随机通过圆锥体发射到空中,赋予足够高的初始速度以克服重力。
运用基本物理概念,如恒定加速度公式,模拟粒子在重力作用下的运动轨迹。
利用流输出(SO)阶段,让 GPU 处理粒子系统的更新和渲染。使用仅流输出技术更新粒子系统,使用渲染技术绘制粒子系统。
不断调整参数,如粒子的初始速度、发射角度、重力系数等,直到达到理想的喷泉效果。
92、编写一个程序,通过将纹理投影到场景中来模拟幻灯片投影仪。分别使用透视投影和正交投影进行实验。
可按以下思路编写程序:
定义视图矩阵V和投影矩阵P,用于表示投影仪位置、方向和视锥体;
对于透视投影,使用包含非线性部分(除以w)的投影变换;对于正交投影,使用完全线性的正交投影矩阵;
实现将点p投影到投影仪投影窗口并转换到NDC空间的步骤;
将投影坐标从NDC空间转换到纹理空间,生成投影纹理坐标;
用生成的纹理坐标将纹理投影到场景中。
93、通过使用聚光灯修改练习方案,使聚光灯锥体之外的点不会从投影仪接收任何光线。
可以将聚光灯与投影仪关联,利用聚光灯函数
kspot(ϕ)=max(cosϕ,0)s=max(−L⋅d,0)skspot(ϕ)=max(cosϕ,0)s=max(−L⋅d,0)s
控制光线强度,当点在聚光灯锥体外(即角度 $ phi $ 大于 $ phi_{ ext{max}} $)时,强度为 0,以此实现聚光灯锥体之外的点不接收投影仪光线。
94、修改“阴影演示”以使用单点采样阴影测试(即不使用百分比渐近过滤)。你应该会观察到硬阴影和锯齿状的阴影边缘。
要实现此修改,可移除代码中与PCF相关的部分,例如移除对
SampleCmpLevelZero
函数的调用,直接使用简单的阴影映射比较,如将当前像素的深度值与阴影图中对应位置的深度值进行比较,若当前像素深度大于阴影图深度则认为在阴影中,反之不在阴影中。
95、研究BuildShadowMap.fx中的细分代码,了解如何对渲染到阴影映射中的几何体进行细分。
当将细分几何体绘制到阴影映射中时,需要以与绘制到后缓冲区时相同的方式对几何体进行细分(即基于到玩家眼睛的距离)以保证一致性;若细分几何体的位移不太明显,阴影中的位移可能不显著,此时可选择在渲染阴影映射时不对几何体进行细分,这是一种
以精度换速度
的优化方法。
96、修改“SSAO”演示程序,使用高斯模糊代替边缘保留模糊。你更喜欢哪一种?
从性能上看,高斯模糊可将二维模糊拆分为两个一维模糊,减少纹理采样,若最终图像效果足够准确,为性能考虑会更倾向高斯模糊;而边缘保留模糊可平滑噪声。具体更喜欢哪种取决于对图像效果和性能的侧重。
97、SSAO能否在计算着色器上实现?如果可以,请简述实现方案。
SSAO可以在计算着色器上实现。实现步骤大致如下:
1. **初始化阶段**:设置计算着色器所需的常量缓冲区,包含纹理宽度、高度、权重、模糊半径等参数;构建视锥体远平面角点数组。
2. **数据准备**:将法线深度图等必要的纹理数据加载到GPU。
3. **执行计算着色器**:在计算着色器中,根据输入的纹理和常量缓冲区数据,对每个像素进行环境光遮蔽计算。对于每个像素,从法线深度图中获取深度值,结合插值得到的到远平面向量重建视空间位置;随机采样半球内的点,计算遮挡情况;对采样结果进行平均以估计环境光遮蔽。
4. **后处理**:可使用双边边缘保留模糊算法对计算得到的SSAO图进行模糊处理,以减少噪声。
98、修改“SSAO”演示程序以移除自相交检查,并观察屏幕空间环境光遮蔽(SSAO)贴图的变化情况。
一般来说,需要在代码中找到自相交检查的部分并将其删除或注释掉,然后重新运行“SSAO”演示程序,但具体实现会因代码结构而异。
99、手动创建一个 .m3d 文件,并将立方体数据填充到其中。将每个面的几何信息放在各自的子集中,这样就有六个子集。为立方体的每个面使用不同的纹理进行贴图。
了解
.m3d
文件格式的规范和结构;
创建一个新的
.m3d
文件;
定义立方体的六个面的几何数据,将每个面的数据作为一个子集;
准备六种不同的纹理;
将纹理与对应的面的子集关联;
将所有数据按照
.m3d
文件格式要求写入文件。
100、将单位四元数用极坐标表示法表示。
一般单位四元数 $ q $ 可写成极坐标表示
q=cosθ+nsinθq=cosθ+nsinθ
其中 $ mathbf{n} $ 是单位向量。
101、手动建模并渲染一个动画化的线性层级结构。例如,你可以用球体和圆柱体网格来建模一个简单的机械臂;球体可以模拟关节,圆柱体可以模拟手臂。
机械臂建模与动画渲染流程
手动建模并渲染动画化线性层级结构的机械臂,具体步骤如下:
1. 3D建模
使用3D建模软件(如
Blender
、
3ds Max
等)创建模型
球体
:用于表示关节
圆柱体
:用于表示手臂部分
2. 层级结构组合
按照线性层级关系将关节和手臂组合
确保每个关节与其相连的手臂形成正确的父子关系
3. 动画设置
设置关节的
转动动画
设置手臂的
移动动画
使用关键帧记录动画变化
4. 渲染
使用
渲染软件
或
游戏引擎
(如
Unity
、
Unreal Engine
)对模型进行渲染
调整材质、光照等参数以获得理想效果