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

第八章 光照

...后续的内容大部分作者都知道,为了为了聚集实际的direct12的使用,跳过部分理论的讲解。

上图a是不受光照的球体,b是受光照球体,正如我们所见,左侧看起来过于扁平,似乎完全不像是一个球体。

本章目标:

  1. 对光照和材质的交互有基本的理解。
  2. 了解局部光照与全局光照之间的差异
  3. 探究如何用数学来描述位于物体表面上某点的朝向,以此来确定入射光照射到表面的角度。
  4. 学习如何正确地变换法向量
  5. 能够区分环境光,漫反射光以及镜面光。
  6. 学习如何实现平行光、点光以及聚光灯这3中光源。
  7. 理解如何通过控制距离函数的衰减参数来实现各种不同的光照强度

光照与材质的交互

我们不再指定顶点的颜色,而是通过指定材质与光照在运用光照方程去计算顶点的颜色。我们可以把材质看作是确定光照与物体表面如何进行交互的属性集。此属性集中的属性有:表面反射光和吸收光的颜色、表面下材质的折射率、表面的光滑度以及表面的透明度。通过指定材质属性,我们就能为真实世界中如木材、石头、玻璃、金属以及水等不同种类的物质表面进行建模

人类的视网膜有三刺激值。本质上是对不同的波长能量的接收程度

法向量

平面法线,由于在计算机几何学中法线是有方向的向量,所以是一种描述多变形朝向的单位向量,曲面法线是一种垂直曲面一点处切平面的单位向量。

计算法向量

面的法向量,就是利用面的两个边去得到

点的法线就是根据相邻的面add然后normalize得到:

可以为每个顶点单独设定一个normal的向量,然后再计算平面法线的时候,累加这个向量。最后归一化处理

变换法向量

也就是我们model处理顶点的时候,会导致法向量不对,比如旋转

根据下面的公式得到对应的变换矩阵:我们假设顶点的切线为t, t一定会随着顶点而变换 (行优先存储)

tN^{T} = 0

加上M矩阵变换 -> tN^{T} = 0 -> tM(NK)^{T} = 0 -> MK^{T} == 1 -> K = (M^{-1})^{T}当M是正交矩阵,也就是M^{-1} == M^{T}那么就是M本身

下面是对应的代码,去掉了平移,因为平移不会改变法线

inline DirectX::XMMATRIX InverseTanspose(DirectX::CXMMATRIX M){DirectX::XMMATRIX A = M;A.r[3] = DirectX::XMVectorSet(0.f, 0.f, 0.f, 1.f);DirectX::XMVECTOR det = DirectX::XMMatrixDeterminant(A);return  DirectX::XMMatrixTranspose(DirectX::XMMatrixInverse(&det, A));}

镜面光照

漫反射的模拟过程为:光进入介质,发生反射,部分光被吸收,而剩下的光则向介质外的各个方向散射。第二种反射的发生是根据一种名为菲涅耳效应的物理现象。当光线到达两种不同折射率介质之间的界面时,一部分光将被反射,而剩下的光则发生折射。

本书使用的菲涅耳方程:

R_{F}(\theta_{i}) = R_{F}(0^{o}) + (1 - R_{F}(0^{o}))(1 - cos\theta_{i})^{5}

其中

R_{F}是反射光量,
R_{F}(0^{o})是介质的一种属性,这里就是空气和其它介质的比值

  • 当光从水(n1≈1.33)入射到空气(n2≈1.0)时:RF(0∘)=(1.33+1.01.33−1.0)2≈0.02(即 2% 的光被反射)。

光照模型概述

  1. 环境光C_{0}模拟经表面反射的间接光量
  2. 漫反射光C_{d}:对进入介质内部,又经过表面下吸收而最终散射出表面光进行模拟。由于对表面下的散射光建模比较困难,我们便假设在表面下与介质相互作用后的光从进入表面处返回,并向各个方向均匀散射
  3. 镜面光C_{s}:模拟经菲涅耳效应与表面粗造度共同作用的表面反射光。

得到direct12的光照方程:后面的镜面反射就是,模拟微表面模型,微表面的法线是h也就是直接的反射到我们眼睛的法线分布。R_{F}就是菲涅耳项,决定了多少能量直接反射(基于这个微表面的法线)。然后我们需要将光能量(基于宏观法线n)投影到微表面法线h上,得到我们实际上感受到的光能

  1. L:指向光源的光向量
  2. n:表面法线
  3. h:列于光向量与观察向量(由表面点指向观察点的单位向量)之间的中间向量。
  4. A_{L}表示入射的环境光量
  5. B_{L}:表示入射的直射光量
  6. m_{d}:指示根据表面漫反射率而反射的入射光量
  7. L\cdot n:朗伯余弦定律
  8. a_{h}中间向量h与光向量L之间的夹角
  9. R_{F}(a_{h}):根据菲涅耳效应,关于中间向量h所反射到观察者眼中的光量。
  10. m:控制表面粗糙度
  11. (n\cdot h)^{m}:指定法线h与宏观表面法线n之间夹角为\theta_{h}的所有微平面片段的分布情况(所占比例)
  12. \frac{m+8}{8}:在镜面反射过程中,为模拟能量守恒所采用的归一化因子。

材质的实现

编写了材质结构体

struct Material
{
// 便于查找材质唯一对应名称
std::string Name;
//本材质的常量缓冲区
int MatCBIndex = -1;
//漫反射纹理在SRV堆中的索引。在第9章纹理贴图时会用到
int DiffuseSrvHeapIndex = -1;// 已更新标志(dirty flag, 也作脏标志)表示本材质已有变动,而我们也就需要更新常量缓冲区了。
// 由于每个帧资源FrameResource都有一个材质常量缓冲区。所以必须对每个FrameResource都进行更新
// 因此当修改某个材质时,应当设置NumFrameDirty = gNumFrameResource,以使每个帧资源都能得到更新
int NumFrameDirty = gNumFrameResources;
//用于着色的材质常量缓冲区数据
DirectX::XMFLOAT4 DiffuseAlbedo = {1.0f, 1.0f, 1.0f, 1.0f};
DirectX::XMFLOAT3 FresnelR0 = {0.01f, 0.01f, 0.01f};
float Roughness = 0.25f;
DirectX::XMFLOAT4X4 MatTransform = MathHelper::Identity4x4();
};

为了模拟真实世界中的材质,需要设置DiffuseAlbedo与FresnelR0,这对与真实度相关的数值组合,再辅以一些关于艺术性的细节调整。

这里定义这个Material结构体的目的是什么呢?因为这一章还没有涉及到纹理贴图,所以我们如果需要实现下面的不同区域的渲染使用不同的属性,就需要定义对应的material结构体。

所以定义了一个材质数组,将需要用到的材质加入到数组中

std::unordered_map<std::string, std::unique_ptr<d3dUtil::Material>> m_Materials;void LitWavesApp::BuildMaterial()
{std::unique_ptr<d3dUtil::Material> grass = std::make_unique<d3dUtil::Material>();grass->Name = "grass";grass->MatCBIndex = 0;grass->DiffuseAlbedo = DirectX::XMFLOAT4(0.2f, 0.6f, 0.2f, 1.0f);grass->FresnelR0 = DirectX::XMFLOAT3(0.01f, 0.01f, 0.01f);grass->Roughness = 0.125f;//当前这种水的材质定义得并不是很好,但是由于我们还未学会所需的全部渲染工具(如透明度、环境反射等),因此暂时先用这些数据解决auto water = std::make_unique<d3dUtil::Material>();water->Name = "water";water->MatCBIndex = 1;water->DiffuseAlbedo = DirectX::XMFLOAT4(0.0f, 0.2f, 0.6f, 1.0f);water->FresnelR0 = DirectX::XMFLOAT3(0.1f, 0.1f, 0.1f);water->Roughness = 0.0f;m_Materials[grass->Name] = std::move(grass);m_Materials[water->Name] = std::move(water);
}

通过上面的方式,可以把材质存储到内存中,然后我们把他绑定到constbuffer中,为了着色器可以访问到这些资源,我们需要创建一个对应的const结构体,并且把这些material加入到帧资源中

struct MaterialConstants
{DirectX::XMFLOAT4 DiffuseAlbedo = {1.0f, 1.0f, 1.0f, 1.0f};DirectX::XMFLOAT3 FresnelR0 = {0.01f, 0.01f, 0.01f};float Roughness = 0.25f;DirectX::XMFLOAT4X4 MatTransform = MathHelper::Identity4x4();
};class FrameResource
{
...// Materialstd::unique_ptr<UploadBuffer<MaterialConstants>> MaterialCB = nullptr;
...
};

这个就和update一起用,和渲染项一样,会查看是否需要更新。也就是如果材质的属性发生了更新,就需要更新到帧资源中

void LitWavesApp::UpdateMaterialCBs(const GameTimer& gt)
{UploadBuffer<MaterialConstants>* currMaterialCB = m_CurrentFrameResource->MaterialCB.get();for (auto& e : m_Materials){d3dUtil::Material* mat = e.second.get();if (mat->NumFrameDirty > 0){MaterialConstants matConstants;matConstants.DiffuseAlbedo = mat->DiffuseAlbedo;matConstants.FresnelR0 = mat->FresnelR0;matConstants.Roughness = mat->Roughness;matConstants.MatTransform = mat->MatTransform;currMaterialCB->CopyData(mat->MatCBIndex, matConstants);mat->NumFrameDirty--;}}
}

到现在为止,每一个渲染项都已含有一个指向Material结构体的指针了。注意,多个渲染项可以引用相同的Material对象,如多个渲染项能够使用相同的板砖材质。而每个Material对象都存有一个索引,用于在材质常量缓冲区中指向它自己的常量数据。至此,我们就能在绘制渲染项时,找到对应常量数据的虚拟地址,并将它与所需材质常量数据的根描述符相绑定(其实也可以通过偏移到堆中的CBV描述符的方式来设置一个描述符表。不过,在此使用的根参数是描述符,而非描述符表)。下面演示了如何在绘制渲染项中使用:

也就是渲染项有记录需要使用什么材质

也就是我们定义了顶点的数据,自然在光栅化离散像素的时候,会插值计算像素对应的法线。(这里有一个点就是不要normalize 法线,具体可以搜搜,normalize 法线后,插值的法线就会有问题)

光照的具体实现

我们定义下列结构体来描述光源。此结构体可以表述方向光源、点光源与聚光灯光源。但是根据光源的具体类型,我们并不会用到其中的所有数据。比如说,在使用点光时就不会使用到Direction数据。

我们Light中数据成员的排列顺序并不是随意指定的(结构体MaterialConstants也是如此),这要遵从HLSL的结构体封装规则。

struct Light{DirectX::XMFLOAT3 Strength = {0.5f, 0.5f, 0.5f}; //光源的颜色float FalloffStart = 1.0f; // 仅供点光源/聚光灯光源使用DirectX::XMFLOAT3 Direction = {0.0f, -1.0f, 0.0f}; // 仅供点光源/聚光灯光源使用float FalloffEnd = 10.0f; // 仅供点光源/聚光灯光源使用DirectX::XMFLOAT3 Position = {0.0f, 0.0f, 0.0f};// 仅供点光源/聚光灯光源使用float SpotPower = 64.0f; // 仅供聚光灯光源使用};

Lighting.hlsl中定义了与之对应的结构体

struct Light
{
float3 Strength;
float FalloffStart;
float3 Direction;
float FalloffEnd;
float3 Position;
float SpotPower;
};

可以看出,第二种方法占用了更多的空间,但这还是其次。此方法存在的更严重的问题是:在C++应用程序代码中,应有与HLSL部分相对应的结构体,但是C++结构体与HLSL结构体封装规则并不相同。因此,若非小心地按HLSL封装法则来实现C++与HLSL的结构体,那么两者的结构体布局很有可能是不匹配的。如果这两种结构体不匹配,则通过memcpy函数从CPU上传至GPU常量缓冲区的数据会导致渲染错误。(也就是cpu中是紧凑排的,那么真实数据会有一个字节给到empty)

常用辅助函数

我们知道在ue中很多的功能只在部分的shader中定义,然后所有的shader公用,现在就是打算在light.hlsl中定义多个辅助函数

struct Light
{float3 Strength;float FalloffStart;float3 Direction;float FalloffEnd;float3 Position;float SpotPower;
};struct Material
{float4 DiffuseAlbedo;float3 FresnelR0;float Shininess;
};float CalcAttenuation(float d, float falloffStart, float falloffEnd)
{//线性衰变// 0 ~ 1return saturate((falloffEnd - d) / (falloffEnd - falloffStart));
}float3 SchlickFresnel(float3 R0, float3 normal, float3 lightVec)
{float cosTheta = saturate(dot(normal, lightVec));return R0 + (1.0f - R0) * pow(1.0f - cosTheta, 5.0f);
}float3 BlinnPhong(float3 lightStrength, float3 lightVec, float3 normal, float3 toEye, Material mat)
{//m由光泽度推导而来,而光泽度则根据粗糙度求得const float m = mat.Shininess * 256.0f;float3 halfVec = normalize(toEye + lightVec);float roughnessFactor = (m + 8.0f) * pow(max(dot(halfVec, normal), 0.0f), m) / 8.0f;float3 fresnelFactor = SchlickFresnel(mat.FresnelR0, halfVec, lightVec);// 尽管我们进行的是LDR(low dynamic range, 低动态范围)渲染,但spec(镜面反射)公式得到的结果//仍会超出范围[0, 1],因此现将其按比例缩小一些float3 specularAlbedo = fresnelFactor * roughnessFactor / (fresnelFactor * roughnessFactor + 1.0f);return  lightStrength * (mat.DiffuseAlbedo.rgb + specularAlbedo);
}

  1. CalcAttenuation:实现了一种线性衰减因子的计算方法,可将其应用于点光源与聚光灯光源。
  2. SchlickFresnel:代替菲涅耳方程的石里克近似。此函数基于光向量L与表面法线n之间的夹角,并根据菲涅耳效应近似计算出以n为法线的表面所反射光的百分比。
  3. BlinnPhong: 计算反射到观察者眼中的光量,该值为漫反射光量与镜面反射光量的总和

实现光源

  1. 方向光源

给定观察位置E、材质属性,与以n为法线的表面上可见一点p,则下列hlsl函数将输出自某方向光源发出,经上述表面以方向v = normalize(E -p)反射入观察者眼中的光量。在我们的示例中,此函数将由像素着色器所调用,以基于光照确定像素的颜色

float3 ComputeDirectionalLight(Light L, Material mat, float3 normal, float3 toEye)
{float3 lightVec = -L.Direction;float3 lightStrength = L.Strength * max(0.0f, dot(normal, lightVec));return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}
  1. 点光源

给出观察位置E,以n作为法线的表面上可视化一点p以及材质属性。

float3 ComputePointLight(Light L, Material mat, float3 pos, float3 normal, float3 toEye)
{float3 lightVec = L.Position - pos;float d = length(lightVec);if (d > L.FalloffEnd) return 0.0f;lightVec /= d;float ndotI = max(dot(normal, lightVec), 0.0f);float3 lightStrength = L.Strength * ndotI;float att = CalcAttenuation(d, L.FalloffStart, L.FalloffEnd);lightStrength *= att;return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}
  1. 实现聚光灯光源
    1. 指定观察点E,以n为法线的表面上可视化一点p以及材质属性,则下面的hlsl函数将会输出来自聚光灯光源,经过上述表面以方向v = normalize(E - p)反射入观察者眼中的光量。在我们的示例中,此函数将在像素着色器中被调用,以根据光照确定像素的颜色。
float3 ComputeSpotLight(Light L, Material mat, float3 pos, float3 normal, float3 toEye)
{//从表面指向光源的向量float3 lightVec = L.Position - pos;float d = length(lightVec);if (d > L.FalloffEnd) return 0.0f;lightVec /= d;float nDotI = max(dot(lightVec, normal), 0.0f);float3 lightStrength = L.Strength * nDotI;float att = CalcAttenuation(d, L.FalloffStart, L.FalloffEnd);lightStrength *= att;float spotFactor = pow(max(dot(-lightVec, L.Direction), 0.0f), L.SpotPower);lightStrength *= spotFactor;return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}
  1. 多种光照的叠加
    1. 光强是可以叠加的。我们只需要遍历场景中的所有光源就可以了,示例程序最多支持16个光源
//对于每个以MaxLights为光源数量最大轴的对象来说,索引[0, NUM_DIR_LIGHTS)表示的是方向光源
// [NUM_DIR_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHTS)表示的是点光源
// [NUM_DIR_LIGHTS+NUM_POINT_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHTS+NUM_SPOT_LIGHTS] //表示聚光灯
#define MaxLights 16
cbuffer cbPass : register(b2)
{Light gLights[MaxLights];
};
...
float4 ComputeLighting(Light gLights[MaxLights], Material mat, float3 pos, float3 normal, float3 toEye, float3 shadowFactor)
{float3 result = 0.0f;int i = 0;
#if (NUM_DIR_LIGHTS > 0)for (int i = 0; i < NUM_DIR_LIGHTS; i++){result += shadowFactor * ComputeDirectionalLight(gLights[i], mat, normal, toEye);}
#endif
#if (NUM_POINT_LIGHTS > 0)for (int i = NUM_DIR_LIGHTS; i < NUM_DIR_LIGHTS+NUM_POINT_LIGHTS;i++){result += ComputePointLight(gLights[i], mat, pos, normal, toEye);}
#endif
#if (NUM_SPOT_LIGHTS > 0)for (int i = NUM_DIR_LIGHTS+NUM_POINT_LIGHTS; i < NUM_DIR_LIGHTS+NUM_POINT_LIGHTS+NUM_SPOT_LIGHTS; i++){result += ComputeSpotLight(gLights[i], mat, pos, normal, toEye);}
#endifreturn float4(result, 1.0f);
}

这样我们就用了一种静态开关的方式定义了一个个的shader变体。这样一来,着色器将仅针对实际所需的光源数量进行光照方程的计算。因此,如果一个应用程序只需3个光源,则我们仅对这3个光源展开计算。如果应用程序在不同阶段要支持不同数量的光源,那么只需生成以不同#define 来定义的不同着色器即可。

HLSL主文件

default.hlsl

#ifndef NUM_DIR_LIGHTS
#define NUM_DIR_LIGHTS 1
#endif#ifndef NUM_POINT_LIGHTS
#define NUM_POINT_LIGHTS 0
#endif#ifndef NUM_SPOT_LIGHTS
#define NUM_SPOT_LIGHTS 0
#endif#include "LightingUtils.hlsl"cbuffer cbPerObject : register(b0)
{float4x4 gWorld;float4x4 gWorldInvTranspose;
};cbuffer cbMaterial : register(b1)
{float4 gDiffuseAlbedo;float3 gFresnelR0;float gRoughness;float4x4 gMatTransform;
};cbuffer cbPass : register(b2)
{float4x4 gView;float4x4 gInvView;float4x4 gProj;float4x4 gInvProj;float4x4 gViewProj;float4x4 gInvViewProj;float3 gEyePosW;float gcbPerObjectPad1;float2 gRenderTargetSize;float2 gInvRenderTargetSize;float gNearZ;float gFarZ;float gTotalTime;float gDeltaTime;float4 gAmbientLight;Light gLights[MaxLights];
};struct VertexIn
{float3 posL : POSITION;float3 NormalL : NORMAL;
};struct VertexOut
{float4 PosH : SV_POSITION;float3 PosW : POSITION;float3 NormalW : NORMAL;
};VertexOut VS(VertexIn vin)
{VertexOut vout = (VertexOut)0.0f;float4 posW = mul(float4(vin.posL, 1.0f), gWorld);vout.PosW = posW.xyz / posW.w;vout.NormalW = mul(vin.NormalL, (float3x3)gWorldInvTranspose);vout.PosH = mul(posW, gViewProj);return vout;
}float4 PS(VertexOut pin) : SV_Target
{pin.NormalW = normalize(pin.NormalW);float3 toEyeW = normalize(gEyePosW - pin.PosW);float4 ambient = gAmbientLight * gDiffuseAlbedo;const float shininess = 1.0f - gRoughness;Material mat = { gDiffuseAlbedo, gFresnelR0, shininess};float3 shadowFactor = 1.0f;float4 directLight = ComputeLighting(gLights, mat, pin.PosW, pin.NormalW, toEyeW, shadowFactor);float4 litColor = ambient + directLight;litColor.a = gDiffuseAlbedo.a;return litColor;
}

光照演示程序

准备阶段

这个光照演示程序就是上一节的山水

我们需要修改一些地方

  1. 首先是我们的数据结构需要修改,我们把color的位置改为法线
struct Vertex
{DirectX::XMFLOAT3 Pos;DirectX::XMFLOAT3 Normal;
};
  1. 然后是我们的物体的矩阵也需要增加一个法线的作用矩阵
struct ObjectConsts
{DirectX::XMFLOAT4X4 World;DirectX::XMFLOAT4X4 TInvWorld;
};
  1. 然后是我们的PassConst需要加入light的相关属性
#define MaxLights 16
struct PassConstant
{DirectX::XMFLOAT4X4 View;DirectX::XMFLOAT4X4 InvView;DirectX::XMFLOAT4X4 Proj;DirectX::XMFLOAT4X4 InvProj;DirectX::XMFLOAT4X4 ViewProj;DirectX::XMFLOAT4X4 InvViewProj;DirectX::XMFLOAT3 EyePosW;float cbPerObjectPad1;DirectX::XMFLOAT2 RenderTargetSize;DirectX::XMFLOAT2 InvRenderTargetSize;float NearZ;float FarZ;float TotalTime;float DeltaTime;DirectX::XMFLOAT4 gAmbientLight;d3dUtil::Light gLights[MaxLights];
};
  1. 为了使用我们的材质,我们需要在渲染项中加入我们的材质
struct RenderItem
{RenderItem() = default;//描述物体局部空间相对于世界空间的世界矩阵//它定义了物体位于世界空间中的位置、朝向以及大小DirectX::XMFLOAT4X4 World = MathHelper::Identity4x4();//用已更行标记(dirty flag)来表示物体的相关数据已发生改变,这意味着我们此时需要更新常量缓冲区。//由于每个FrameResource中都有一个物体常量缓冲区,所以我们必须对每个FrameResource都进行更新//即,当我们修改物体数据的时候,应当按NumFramesDirty=gNumFrameResource进行设置//从而每个帧资源都得到更新int NumFrameDirty = gNumFrameResources; // 这里的意思就是物体primitive更新以后,那么所有的帧资源都应该更新//该索引指向的GPU常量缓冲区对应于当前渲染项中的物体常量缓冲区UINT ObjCBIndex  =-1;//此渲染项参与绘制的几何体。注意,绘制一个几何体可能会用到多个渲染项d3dUtil::MeshGeometry* Geo = nullptr;d3dUtil::Material* mat = nullptr;//DrawIndexedInstanced方法的参数UINT IndexCount = 0;UINT StartIndexLocation = 0;int BaseVertexLocation = 0;D3D12_PRIMITIVE_TOPOLOGY PrimitiveType;
};
  1. 在FrameResource中加入Material的const buffer的创建
FrameResource::FrameResource(ID3D12Device* device, UINT passCount, UINT objectCount, UINT materialCount)
{ThrowIfFailed(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(CmdListAlloc.GetAddressOf())));PassCB = std::make_unique<UploadBuffer<PassConstant>>(device, passCount, true);ObjectCB = std::make_unique<UploadBuffer<ObjectConsts>>(device, objectCount, true);MaterialCB = std::make_unique<UploadBuffer<MaterialConstants>>(device, materialCount, true);
}

然后就按照绘制来。

  1. 创建Material的数据
void LitWavesApp::BuildMaterial()
{std::unique_ptr<d3dUtil::Material> grass = std::make_unique<d3dUtil::Material>();grass->Name = "grass";grass->MatCBIndex = 0;grass->DiffuseAlbedo = DirectX::XMFLOAT4(0.2f, 0.6f, 0.2f, 1.0f);grass->FresnelR0 = DirectX::XMFLOAT3(0.01f, 0.01f, 0.01f);grass->Roughness = 0.125f;//当前这种水的材质定义得并不是很好,但是由于我们还未学会所需的全部渲染工具(如透明度、环境反射等),因此暂时先用这些数据解决auto water = std::make_unique<d3dUtil::Material>();water->Name = "water";water->MatCBIndex = 1;water->DiffuseAlbedo = DirectX::XMFLOAT4(0.0f, 0.2f, 0.6f, 1.0f);water->FresnelR0 = DirectX::XMFLOAT3(0.1f, 0.1f, 0.1f);water->Roughness = 0.0f;m_Materials[grass->Name] = std::move(grass);m_Materials[water->Name] = std::move(water);
}
  1. 创建对应的几何数据,我们需要创建山体的grid还有就是海的grid,这块因为海需要动态的修改顶点数据,所以这里不能存放到一个resource中,也就是需要两个geometry,这里我们就模拟静态的海平面。渲染需要的Geometry,包括陆地和海洋,这两者实际上使用的是同一个geometry。对于海洋我们需要修改其vertexgpubuffer,把它设置为上传堆
void LitWavesApp::CreateLandGeometry()
{GeometryGenerator geoGen;GeometryGenerator::MeshData landGeometry = geoGen.CreateGrid(160.0f, 160.0f, 50, 50);std::vector<Vertex> vertices(landGeometry.Vertices.size());auto geo = std::make_unique<d3dUtil::MeshGeometry>();geo->Name = 'land';UINT vbByteSize = sizeof(Vertex) * landGeometry.Vertices.size();UINT ibByteSize = sizeof(uint16_t) * landGeometry.GetIndices16().size();ThrowIfFailed(D3DCreateBlob(vbByteSize, geo->VertexBufferCPU.GetAddressOf()));ThrowIfFailed(D3DCreateBlob(ibByteSize, geo->IndexBufferCPU.GetAddressOf()));CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), landGeometry.Vertices.data(), sizeof(Vertex) * landGeometry.Vertices.size());CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), landGeometry.Indices32.data(), sizeof(uint8_t) * landGeometry.GetIndices16().size());geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), geo->VertexBufferCPU->GetBufferPointer(),sizeof(Vertex) * landGeometry.Vertices.size(), geo->VertexBufferUploader);geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), geo->IndexBufferCPU->GetBufferPointer(),sizeof(uint16_t) * landGeometry.GetIndices16().size(), geo->IndexBufferUploader);geo->VertexByteStride = sizeof(Vertex);geo->VertexByteSize = vbByteSize;geo->IndexFormat = DXGI_FORMAT_R16_UINT;geo->IndexBufferByteSize = ibByteSize;d3dUtil::SubmeshGeometry gridSubMesh;gridSubMesh.IndexCount = landGeometry.GetIndices16().size();gridSubMesh.StartIndexLocation = 0;gridSubMesh.BaseVertexLocation = 0;geo->DrawArgs["grid"] = std::move(gridSubMesh);m_Geometries[geo->Name] = std::move(geo);
}void LitWavesApp::CreateSeaGeometry()
{GeometryGenerator geoGen;GeometryGenerator::MeshData sea = geoGen.CreateGrid(160.0f, 160.0f, 50, 50);std::unique_ptr<d3dUtil::MeshGeometry> seaGeo = std::make_unique<d3dUtil::MeshGeometry>();UINT vbByteSize = sizeof(Vertex) * sea.Vertices.size();UINT ibByteSize = sizeof(uint16_t) * sea.GetIndices16().size();seaGeo->Name = "sea";ThrowIfFailed(D3DCreateBlob(vbByteSize, seaGeo->VertexBufferCPU.GetAddressOf()));ThrowIfFailed(D3DCreateBlob(ibByteSize, seaGeo->IndexBufferCPU.GetAddressOf()));CopyMemory(seaGeo->VertexBufferCPU->GetBufferPointer(), sea.Vertices.data(), vbByteSize);CopyMemory(seaGeo->IndexBufferCPU->GetBufferPointer(), sea.GetIndices16().data(), ibByteSize);m_SeaVertexBuffer = std::make_unique<UploadBuffer<Vertex>>(m_Device.Get(), sea.Vertices.size(), false);for (int i = 0; i < sea.Vertices.size(); i++){Vertex p;p.Pos = sea.Vertices[i].Position;p.Normal = sea.Vertices[i].Normal;m_SeaVertexBuffer->CopyData(i, p);}seaGeo->VertexBufferGPU = (ID3D12Resource*)*m_SeaVertexBuffer;seaGeo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), seaGeo->IndexBufferCPU->GetBufferPointer(), ibByteSize, seaGeo->IndexBufferUploader);seaGeo->VertexByteStride = sizeof(Vertex);seaGeo->VertexByteSize = vbByteSize;seaGeo->IndexFormat = DXGI_FORMAT_R16_UINT;seaGeo->IndexBufferByteSize = ibByteSize;d3dUtil::SubmeshGeometry seaSubmesh;seaSubmesh.IndexCount = sea.GetIndices16().size();seaSubmesh.BaseVertexLocation = 0;seaSubmesh.StartIndexLocation = 0;seaGeo->DrawArgs["grid"] = std::move(seaSubmesh);m_Geometries[seaGeo->Name] = std::move(seaGeo);
}void LitWavesApp::CreateLandRenderItems()
{std::unique_ptr<RenderItem> landRItem = std::make_unique<RenderItem>();landRItem->Geo = m_Geometries["land"].get();landRItem->ObjCBIndex = 0;landRItem->IndexCount = landRItem->Geo->DrawArgs["grid"].IndexCount;landRItem->StartIndexLocation = landRItem->Geo->DrawArgs["grid"].StartIndexLocation;landRItem->BaseVertexLocation = landRItem->Geo->DrawArgs["grid"].BaseVertexLocation;landRItem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;landRItem->mat = m_Materials["grass"].get();m_OpaqueRitems.push_back(landRItem.get());m_AllRitems.push_back(std::move(landRItem));
}void LitWavesApp::CreateSeaRenderItems()
{std::unique_ptr<RenderItem> seaRItem = std::make_unique<RenderItem>();seaRItem->ObjCBIndex = 1;seaRItem->Geo = m_Geometries["sea"].get();seaRItem->mat = m_Materials["sea"].get();seaRItem->IndexCount = seaRItem->Geo->DrawArgs["grid"].IndexCount;seaRItem->StartIndexLocation = seaRItem->Geo->DrawArgs["grid"].StartIndexLocation;seaRItem->BaseVertexLocation = seaRItem->Geo->DrawArgs["grid"].BaseVertexLocation;seaRItem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;m_OpaqueRitems.push_back(seaRItem.get());m_AllRitems.push_back(std::move(seaRItem));
}
  1. 接下来,我们需要去创建对应的const buffer,这两步实际上都是为了得到渲染需要的数据。我们需要下面几步来创建meshbatch需要用到的资源
    1. 创建需要用的资源缓冲区,也就是FrameResource
void LitWavesApp::CreateConstBuffer()
{for (UINT i = 0; i < gNumFrameResources; i++){m_FrameResources.push_back(std::make_unique<FrameResource>(m_Device.Get(), 1, (UINT)m_AllRitems.size(), (UINT)m_Materials.size()));}
}
    1. 创建对应的descriptor(这步其实也可以不用,我们打算使用根描述符, 如果要用这个需要创建对应的view去指示对应的缓冲区区域)
void LitWavesApp::CreateConstDescriptor()
{D3D12_DESCRIPTOR_HEAP_DESC desc{};desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;UINT constBufferCount = gNumFrameResources * (1 + m_AllRitems.size() + m_Materials.size());desc.NumDescriptors = constBufferCount;desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;desc.NodeMask = 0;ThrowIfFailed(m_Device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(m_CbvHeap.GetAddressOf())));
}void LitWavesApp::CreateConstBufferView()
{UINT objectCount = (UINT)m_AllRitems.size();UINT objectByteSize = d3dUtil::CalcConstBufferByteSize(sizeof(ObjectConsts));UINT k = 0; //descriptor中的实际偏移位置for (UINT frame = 0; frame < gNumFrameResources; frame++){ID3D12Resource* objCurrentResource = (ID3D12Resource*)*m_FrameResources[frame]->ObjectCB;D3D12_GPU_VIRTUAL_ADDRESS objGPUAddress = objCurrentResource->GetGPUVirtualAddress();for (UINT i = 0; i < objectCount; ++i, ++k){D3D12_CONSTANT_BUFFER_VIEW_DESC viewDesc{};viewDesc.BufferLocation = objGPUAddress + (UINT)(i * objectByteSize);viewDesc.SizeInBytes = objectByteSize;CD3DX12_CPU_DESCRIPTOR_HANDLE handle(m_CbvHeap->GetCPUDescriptorHandleForHeapStart());handle.Offset(k, m_CbvSrvUavDescriptorSize);m_Device->CreateConstantBufferView(&viewDesc, handle);}}// materialm_MaterialCbvBeginIndex = k;UINT materialCount = m_Materials.size();UINT materialByteSize = d3dUtil::CalcConstBufferByteSize(sizeof(MaterialConstants));for (UINT frame = 0; frame < gNumFrameResources; frame++){ID3D12Resource* materialCurrentResource = (ID3D12Resource*)*m_FrameResources[frame]->MaterialCB;D3D12_GPU_VIRTUAL_ADDRESS materialGPUAddress = materialCurrentResource->GetGPUVirtualAddress();for (UINT i = 0; i < materialCount; ++i, ++k){D3D12_CONSTANT_BUFFER_VIEW_DESC viewDesc{};viewDesc.BufferLocation = materialGPUAddress + (UINT)(i * materialByteSize);viewDesc.SizeInBytes = materialByteSize;CD3DX12_CPU_DESCRIPTOR_HANDLE handle(m_CbvHeap->GetCPUDescriptorHandleForHeapStart());handle.Offset(k, m_CbvSrvUavDescriptorSize);m_Device->CreateConstantBufferView(&viewDesc, handle);}}//passUINT passByteSize = d3dUtil::CalcConstBufferByteSize(sizeof(PassConstant));m_PassCbvBeginIndex = k;for (UINT frame = 0; frame < gNumFrameResources; ++frame, ++k){ID3D12Resource* passCurrentResource = (ID3D12Resource*)*m_FrameResources[frame]->PassCB;D3D12_GPU_VIRTUAL_ADDRESS passGPUAddress= passCurrentResource->GetGPUVirtualAddress();D3D12_CONSTANT_BUFFER_VIEW_DESC viewDesc{};viewDesc.BufferLocation = passGPUAddress;viewDesc.SizeInBytes = passByteSize;CD3DX12_CPU_DESCRIPTOR_HANDLE handle(m_CbvHeap->GetCPUDescriptorHandleForHeapStart());handle.Offset(k, m_CbvSrvUavDescriptorSize);m_Device->CreateConstantBufferView(&viewDesc, handle);}
}
    1. 创建rootSignature
      1. 描述符表
void LitWavesApp::CreateRootSignature()
{CD3DX12_DESCRIPTOR_RANGE descriptorRange;descriptorRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 3, 0);CD3DX12_ROOT_PARAMETER rootParameter;rootParameter.InitAsDescriptorTable(rootParameter, 1, &descriptorRange);CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;rootSignatureDesc.Init(1, &rootParameter, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);ComPtr<ID3DBlob> error;ComPtr<ID3DBlob> rootSignature;D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1_0, rootSignature.GetAddressOf(), error.GetAddressOf());ThrowIfFailed(m_Device->CreateRootSignature(0, rootSignature->GetBufferPointer(), rootSignature->GetBufferSize(), IID_PPV_ARGS(m_RootSignature.GetAddressOf())));
}

现在我们有了需要的资源,需要编译shader,对应的input layout还有创建对应的渲染的pipeline,这两个其实也是一种资源

  1. 编译shader
void LitWavesApp::BuildShadersAndInputLayout()
{LPCSTR SemanticName;UINT SemanticIndex;DXGI_FORMAT Format;UINT InputSlot;UINT AlignedByteOffset;D3D12_INPUT_CLASSIFICATION InputSlotClass;UINT InstanceDataStepRate;m_InputElementDescs = {{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},{"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}};m_VSShader = d3dUtil::CompileShader(L"source/shader/Default.hlsl", nullptr, "VS", "vs_5_0");m_PSShader = d3dUtil::CompileShader(L"source/shader/Default.hlsl", nullptr, "PS", "ps_5_0");
}
  1. 构建pso
void LitWavesApp::BuildsPSO()
{D3D12_GRAPHICS_PIPELINE_STATE_DESC desc{};desc.pRootSignature = m_RootSignature.Get();desc.VS = CD3DX12_SHADER_BYTECODE(m_VSShader.Get());desc.PS = CD3DX12_SHADER_BYTECODE(m_PSShader.Get());desc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);desc.SampleMask = UINT_MAX;desc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);desc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);desc.InputLayout = {m_InputElementDescs.data(), (UINT)m_InputElementDescs.size()};desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;desc.NumRenderTargets = 1;desc.RTVFormats[0] = m_BackBufferFormat;desc.DSVFormat = m_DepthStencilFormat;desc.SampleDesc.Count = m4xMsaaState ? 4 : 1;desc.SampleDesc.Quality = m4xMsaaState ? m4xMsaaQuality - 1 : 0;desc.NodeMask = 0;ComPtr<ID3D12PipelineState> pPipeline;ThrowIfFailed(m_Device->CreateGraphicsPipelineState(&desc, IID_PPV_ARGS(pPipeline.GetAddressOf())));m_PipelineStates["landSea"] = std::move(pPipeline);
}

开始绘制了

我们要绘制这个场景,根据程序的步骤,首先是update函数,我们通过这个函数更新帧资源。这里还没有管海洋的顶点变动

void Update(const GameTimer& gt) override;
void UpdateMaterialCBs(const GameTimer& gt);
void UpdateRenderItems(const GameTimer& gt);
void UpdateFrameBuffer(const GameTimer& gt);
void LitWavesApp::Update(const GameTimer& gt)
{m_CurrentFrameIndex = (m_CurrentFrameIndex + 1) % gNumFrameResources;m_CurrentFrameResource = m_FrameResources[m_CurrentFrameIndex].get();// 更新viewfloat x = m_Radius * sinf(m_Phi) * cosf(m_Theta);float y = m_Radius * cosf(m_Phi);float z = m_Radius * sinf(m_Phi) * sinf(m_Theta);DirectX::XMVECTOR pos = DirectX::XMVectorSet(x, y, z, 1.0f);DirectX::XMVECTOR target = DirectX::XMVectorZero();DirectX::XMVECTOR up = DirectX::XMVectorSet(0.0f, 1.0f, 0.0f, 1.0f);DirectX::XMMATRIX view = DirectX::XMMatrixLookAtLH(pos, target, up);DirectX::XMStoreFloat4x4(&m_View, view);DirectX::XMStoreFloat3(&m_Eyepos, pos);UpdateFrameBuffer(gt);UpdateMaterialCBs(gt);UpdateRenderItems(gt);
}void LitWavesApp::UpdateFrameBuffer(const GameTimer& gt)
{PassConstant passCB;DirectX::XMMATRIX view = DirectX::XMLoadFloat4x4(&m_View);DirectX::XMMATRIX proj = DirectX::XMLoadFloat4x4(&m_Projection);DirectX::XMMATRIX viewProj = DirectX::XMMatrixMultiply(view, proj);DirectX::XMVECTOR viewDeter = DirectX::XMMatrixDeterminant(view);DirectX::XMMATRIX invView = DirectX::XMMatrixInverse(&viewDeter, view);DirectX::XMVECTOR projDeter = DirectX::XMMatrixDeterminant(proj);DirectX::XMMATRIX invProj = DirectX::XMMatrixInverse(&projDeter, proj);DirectX::XMVECTOR viewProjDeter = DirectX::XMMatrixDeterminant(viewProj);DirectX::XMMATRIX invViewProj = DirectX::XMMatrixInverse(&viewProjDeter, viewProj);DirectX::XMStoreFloat4x4(&passCB.View, DirectX::XMMatrixTranspose(view));DirectX::XMStoreFloat4x4(&passCB.InvView, DirectX::XMMatrixTranspose(invView));DirectX::XMStoreFloat4x4(&passCB.Proj, DirectX::XMMatrixTranspose(proj));DirectX::XMStoreFloat4x4(&passCB.InvProj, DirectX::XMMatrixTranspose(invProj));DirectX::XMStoreFloat4x4(&passCB.ViewProj, DirectX::XMMatrixTranspose(viewProj));DirectX::XMStoreFloat4x4(&passCB.InvViewProj, DirectX::XMMatrixTranspose(invViewProj));passCB.EyePosW = m_Eyepos;passCB.RenderTargetSize = DirectX::XMFLOAT2(static_cast<float>(m_ClientWidth), static_cast<float>(m_ClientHeight));passCB.InvRenderTargetSize = DirectX::XMFLOAT2(static_cast<float>(1.0f / m_ClientWidth), static_cast<float>(1.0f / m_ClientHeight));passCB.NearZ = 1.0f;passCB.FarZ = 1000.0f;passCB.TotalTime = gt.TotalTime();passCB.DeltaTime = gt.DeltaTime();m_CurrentFrameResource->PassCB->CopyData(0, passCB);
}void LitWavesApp::UpdateRenderItems(const GameTimer& gt)
{for (auto& r : m_AllRitems){if (r->NumFrameDirty > 0){r->NumFrameDirty--;DirectX::XMMATRIX world = DirectX::XMLoadFloat4x4(&r->World);DirectX::XMMATRIX TInvWorld =MathHelper::InverseTanspose(world);ObjectConsts objConsts;DirectX::XMStoreFloat4x4(&objConsts.World, DirectX::XMMatrixTranspose(world));DirectX::XMStoreFloat4x4(&objConsts.TInvWorld, DirectX::XMMatrixTranspose(TInvWorld));m_CurrentFrameResource->ObjectCB->CopyData(r->ObjCBIndex, objConsts);}}
}void LitWavesApp::UpdateMaterialCBs(const GameTimer& gt)
{UploadBuffer<MaterialConstants>* currMaterialCB = m_CurrentFrameResource->MaterialCB.get();for (auto& e : m_Materials){d3dUtil::Material* mat = e.second.get();if (mat->NumFrameDirty > 0){MaterialConstants matConstants;matConstants.DiffuseAlbedo = mat->DiffuseAlbedo;matConstants.FresnelR0 = mat->FresnelR0;matConstants.Roughness = mat->Roughness;matConstants.MatTransform = mat->MatTransform;currMaterialCB->CopyData(mat->MatCBIndex, matConstants);mat->NumFrameDirty--;}}
}

然后我们就需要调用Draw去绘制这个场景

void LitWavesApp::Draw(const GameTimer& gt)
{ID3D12CommandAllocator* alloc = m_CurrentFrameResource->CmdListAlloc.Get();ThrowIfFailed(alloc->Reset());ThrowIfFailed(m_CommandList->Reset(alloc, m_PipelineStates["landSea"].Get()));m_CommandList->RSSetViewports(1, &m_ViewPort);m_CommandList->RSSetScissorRects(1, &m_ScissorRect);ID3D12Resource* renderTarget =  CurrentBackBuffer();CD3DX12_RESOURCE_BARRIER rtvBarrier = CD3DX12_RESOURCE_BARRIER::Transition(renderTarget, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = CurrentBackBufferHandle();D3D12_CPU_DESCRIPTOR_HANDLE depthHandle = DepthStencilView();m_CommandList->ResourceBarrier(1, &rtvBarrier);m_CommandList->OMSetRenderTargets(1, &rtvHandle, true, &depthHandle);m_CommandList->ClearRenderTargetView(rtvHandle, DirectX::Colors::LightSteelBlue, 0, nullptr);m_CommandList->ClearDepthStencilView(depthHandle, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 0.0f, 1, 0, nullptr);ID3D12DescriptorHeap* cbvD[] = { m_CbvHeap.Get() };m_CommandList->SetDescriptorHeaps(_countof(cbvD), cbvD);m_CommandList->SetGraphicsRootSignature(m_RootSignature.Get());CD3DX12_GPU_DESCRIPTOR_HANDLE cbvHandle(m_CbvHeap->GetGPUDescriptorHandleForHeapStart());cbvHandle.Offset(m_PassCbvBeginIndex + m_CurrentFrameIndex, m_CbvSrvUavDescriptorSize);m_CommandList->SetGraphicsRootDescriptorTable(2, cbvHandle);DrawRenderItems(m_CommandList.Get(), gt);rtvBarrier = CD3DX12_RESOURCE_BARRIER::Transition(renderTarget, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);m_CommandList->ResourceBarrier(1, &rtvBarrier);ThrowIfFailed(m_CommandList->Close());ID3D12CommandList* cmdList[] = { m_CommandList.Get() };m_CommandQueue->ExecuteCommandLists(_countof(cmdList), cmdList);ThrowIfFailed(m_SwapChain->Present(0, 0));m_CurrentBackBufferIndex = (m_CurrentBackBufferIndex + 1) % SwapChainBufferCount;m_CurrentFrameResource->Fence = ++m_CurrentFence;m_CommandQueue->Signal(m_Fence.Get(), m_CurrentFence);
}void LitWavesApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const GameTimer& gt)
{for (auto& r : m_AllRitems){d3dUtil::MeshGeometry* geo = r->Geo;D3D12_VERTEX_BUFFER_VIEW vView = geo->VertexBufferView();D3D12_INDEX_BUFFER_VIEW iView = geo->IndexBufferView();cmdList->IASetVertexBuffers(0, 1, &vView);cmdList->IASetIndexBuffer(&iView);cmdList->IASetPrimitiveTopology(r->PrimitiveType);CD3DX12_GPU_DESCRIPTOR_HANDLE Mathandle(m_CbvHeap->GetGPUDescriptorHandleForHeapStart());CD3DX12_GPU_DESCRIPTOR_HANDLE objHandle(m_CbvHeap->GetGPUDescriptorHandleForHeapStart());d3dUtil::Material* mat = r->mat;Mathandle.Offset(m_MaterialCbvBeginIndex + (m_CurrentFrameIndex * m_Materials.size()) + mat->MatCBIndex, m_CbvSrvUavDescriptorSize);objHandle.Offset(m_CurrentFrameIndex * m_AllRitems.size() + r->ObjCBIndex, m_CbvSrvUavDescriptorSize);cmdList->SetGraphicsRootDescriptorTable(0, objHandle);cmdList->SetGraphicsRootDescriptorTable(1, Mathandle);cmdList->DrawIndexedInstanced(r->IndexCount, 1, r->StartIndexLocation, r->BaseVertexLocation, 0);}
}

这会儿出来是一个

因为我们还没有加光照,看看书中对应的修改

  1. 计算法线

DirectX::XMFLOAT3 LitWavesApp::GetHillsNormal(float x, float z) const
{DirectX::XMFLOAT3 n(-0.03f * z * cosf(0.1f * x) - 0.3f * cosf(0.1f * z), 1.0f,-0.3f * sinf(0.1f * x) + 0.03f * x * sinf(0.1f * z));DirectX::XMVECTOR unitNormal = DirectX::XMVector3Normalize(DirectX::XMLoadFloat3(&n));DirectX::XMStoreFloat3(&n, unitNormal);return n;
}
void LitWavesApp::CreateLandGeometry()
{GeometryGenerator geoGen;GeometryGenerator::MeshData landGeometry = geoGen.CreateGrid(160.0f, 160.0f, 50, 50);std::vector<Vertex> vertices(landGeometry.Vertices.size());for (UINT i = 0; i < landGeometry.Vertices.size(); i++){Vertex v;v.Pos = landGeometry.Vertices[i].Position;v.Pos.y = GetHillHeight(v.Pos.x, v.Pos.z);vertices[i] = std::move(v);v.Normal = GetHillsNormal(v.Pos.x, v.Pos.z);}auto geo = std::make_unique<d3dUtil::MeshGeometry>();geo->Name = "land";UINT vbByteSize = sizeof(Vertex) * landGeometry.Vertices.size();UINT ibByteSize = sizeof(uint16_t) * landGeometry.GetIndices16().size();ThrowIfFailed(D3DCreateBlob(vbByteSize, geo->VertexBufferCPU.GetAddressOf()));ThrowIfFailed(D3DCreateBlob(ibByteSize, geo->IndexBufferCPU.GetAddressOf()));CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), sizeof(Vertex) * vertices.size());CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), landGeometry.GetIndices16().data(), sizeof(uint16_t) * landGeometry.GetIndices16().size());geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), geo->VertexBufferCPU->GetBufferPointer(),sizeof(Vertex) * landGeometry.Vertices.size(), geo->VertexBufferUploader);geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), geo->IndexBufferCPU->GetBufferPointer(),sizeof(uint16_t) * landGeometry.GetIndices16().size(), geo->IndexBufferUploader);geo->VertexByteStride = sizeof(Vertex);geo->VertexByteSize = vbByteSize;geo->IndexFormat = DXGI_FORMAT_R16_UINT;geo->IndexBufferByteSize = ibByteSize;d3dUtil::SubmeshGeometry gridSubMesh;gridSubMesh.IndexCount = landGeometry.GetIndices16().size();gridSubMesh.StartIndexLocation = 0;gridSubMesh.BaseVertexLocation = 0;geo->DrawArgs["grid"] = std::move(gridSubMesh);m_Geometries[geo->Name] = std::move(geo);
}void LitWavesApp::CreateSeaGeometry()
{GeometryGenerator geoGen;GeometryGenerator::MeshData sea = geoGen.CreateGrid(160.0f, 160.0f, 50, 50);std::unique_ptr<d3dUtil::MeshGeometry> seaGeo = std::make_unique<d3dUtil::MeshGeometry>();UINT vbByteSize = sizeof(Vertex) * sea.Vertices.size();UINT ibByteSize = sizeof(uint16_t) * sea.GetIndices16().size();seaGeo->Name = "sea";ThrowIfFailed(D3DCreateBlob(vbByteSize, seaGeo->VertexBufferCPU.GetAddressOf()));ThrowIfFailed(D3DCreateBlob(ibByteSize, seaGeo->IndexBufferCPU.GetAddressOf()));CopyMemory(seaGeo->VertexBufferCPU->GetBufferPointer(), sea.Vertices.data(), vbByteSize);CopyMemory(seaGeo->IndexBufferCPU->GetBufferPointer(), sea.GetIndices16().data(), ibByteSize);m_SeaVertexBuffer = std::make_unique<UploadBuffer<Vertex>>(m_Device.Get(), sea.Vertices.size(), false);for (int i = 0; i < sea.Vertices.size(); i++){Vertex p;p.Pos = sea.Vertices[i].Position;p.Normal = GetHillsNormal(p.Pos.x, p.Pos.z);m_SeaVertexBuffer->CopyData(i, p);}seaGeo->VertexBufferGPU = (ID3D12Resource*)*m_SeaVertexBuffer;seaGeo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), seaGeo->IndexBufferCPU->GetBufferPointer(), ibByteSize, seaGeo->IndexBufferUploader);seaGeo->VertexByteStride = sizeof(Vertex);seaGeo->VertexByteSize = vbByteSize;seaGeo->IndexFormat = DXGI_FORMAT_R16_UINT;seaGeo->IndexBufferByteSize = ibByteSize;d3dUtil::SubmeshGeometry seaSubmesh;seaSubmesh.IndexCount = sea.GetIndices16().size();seaSubmesh.BaseVertexLocation = 0;seaSubmesh.StartIndexLocation = 0;seaGeo->DrawArgs["grid"] = std::move(seaSubmesh);m_Geometries[seaGeo->Name] = std::move(seaGeo);
}
  1. 加入ambient light
    DirectX::XMStoreFloat4(&passCB.gAmbientLight, DirectX::XMVectorSet(1.0f, 1.0f, 1.0f, 1.0f));

  1. 更新其它光照
    1. 演示程序使用了一个方向光源来表示太阳,并允许用户通过左右上下4个方向键来控制光源方位。我们用球坐标来追踪太阳的位置,我们这里设置太阳围绕单位球旋转,这里就和view的旋转是一样的。
    2. GetAsyncKeyState 是 Windows API 中的一个函数,用于异步获取指定虚拟键的状态(是否被按下、是否刚刚被触发等)。它常用于实时检测键盘输入,比如游戏中的按键响应、快捷键处理等场景。
    3. 返回值是一个 16 位的 SHORT 类型,通过其位信息判断键的状态:
    • 最高位(第 15 位):若为 1,表示该键当前正被按下;若为 0,表示未按下。
    • 最低位(第 0 位):若为 1,表示该键在当前调用与上一次调用之间被按下过(即触发了一次按键事件);若为 0,表示未触发。
void LitWavesApp::OnKeyboardInpput(const GameTimer& gt)
{const float dt = gt.DeltaTime();if (GetAsyncKeyState(VK_LEFT) & 0x8000){m_SunTheta -= 1.0f * dt;}if (GetAsyncKeyState(VK_RIGHT) & 0x8000){m_SunTheta += 1.0f * dt;}if (GetAsyncKeyState(VK_UP) & 0x8000){m_SunPhi -= 1.0f * dt;}if (GetAsyncKeyState(VK_DOWN) & 0x8000){m_SunPhi += 1.0f * dt;}m_SunPhi = MathHelper::Clamp(m_SunPhi, 0.1f, DirectX::XM_PIDIV2);
}

海面还是平的,因为我们目前并没有任何的模拟

卡通风格

看到练习里面有一个卡通风格的练习,卡通风格光照的一个特点是颜色之间的过渡比较突兀(与大家通常喜闻乐见的平滑渐变刚好相反),从而塑造夸张的效果如下图所示。这种光照一般可以通过计算kd 与ks来得以实现,但是在像素着色器中使用这种光照之前,先要运用下列离散函数对这两个参数进行变换

float3 BlinnPhong(float3 lightStrength, float3 lightVec, float3 normal, float3 toEye, Material mat)
{//m由光泽度推导而来,而光泽度则根据粗糙度求得const float m = mat.Shininess * 256.0f;float3 halfVec = normalize(toEye + lightVec);float ks = pow(max(dot(halfVec, normal), 0.0f), m);
#ifdef CARTONif (ks <= 0.1f){ks = 0.0f;}else if (ks <= 0.8f){ks = 0.5f;}else if (ks <= 1.0f){ks = 0.8f;}
#endiffloat roughnessFactor = (m + 8.0f) *  ks / 8.0f;float3 fresnelFactor = SchlickFresnel(mat.FresnelR0, halfVec, lightVec);// 尽管我们进行的是LDR(low dynamic range, 低动态范围)渲染,但spec(镜面反射)公式得到的结果//仍会超出范围[0, 1],因此现将其按比例缩小一些float3 specularAlbedo = fresnelFactor * roughnessFactor / (fresnelFactor * roughnessFactor + 1.0f);return  lightStrength * (mat.DiffuseAlbedo.rgb + specularAlbedo);
}float3 ComputeDirectionalLight(Light L, Material mat, float3 normal, float3 toEye)
{float3 lightVec = -L.Direction;float kd = max(0.0f, dot(normal, lightVec));
#ifdef CARTONif (kd <= 0.0f){kd = 0.4f;}else if (kd <= 0.5f){kd = 0.6f;}else if (kd <= 1.0f){kd = 1.0f;}
#endiffloat3 lightStrength = L.Strength * kd;return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}

实现水的波动

我们就用GPUGAMES里面的内容来写这个方程

其中有些参数是提前定义的:

amplitude(A_{i}), WaveLength(w_{i}), direction(D_{i})

struct WaveParams
{
float A_min = 0.1f;
float A_max = 0.5f;
float WaveLength_min = 2.0f;
float WaveLength_max = 8.0f;
float Speed_min = 1.0f;
float Speed_max = 3.0f;
};
void LitWavesApp::UpdateWaterHeight(const GameTimer& gt)
{// 从原始网格数据获取顶点数量GeometryGenerator geoGen;GeometryGenerator::MeshData sea = geoGen.CreateGrid(160.0f, 160.0f, 50, 50);WaveParams waveParams;//然后根据数据去修改float time = gt.TotalTime();UINT vIdx = 0;for (auto& vv : sea.Vertices){Vertex v;v.Pos = vv.Position;v.Normal = vv.Normal;DirectX::XMFLOAT2 seed;seed.x = MathHelper::frac(v.Pos.x * 0.1234f + v.Pos.z * 0.5678f);seed.y = MathHelper::frac(v.Pos.x * 0.8765f - v.Pos.z * 0.4321f);for (UINT i = 0; i <3; i++){float seedOffset = static_cast<float>(i) * 0.333f;DirectX::XMFLOAT2 waveSeed;waveSeed.x = MathHelper::frac(seed.x + seedOffset);waveSeed.y = MathHelper::frac(seed.y + seedOffset * 1.7f);float amplitude = waveParams.A_min + (waveParams.A_max - waveParams.A_min) * waveSeed.x;float waveLength = waveParams.WaveLength_min + (waveParams.WaveLength_max - waveParams.WaveLength_min) * waveSeed.y;float speed = waveParams.Speed_min + (waveParams.Speed_max - waveParams.Speed_min) * MathHelper::frac(waveSeed.x + waveSeed.y);// 生成波浪方向float angle = waveSeed.x * DirectX::XM_2PI;DirectX::XMFLOAT2 direction(cosf(angle), sinf(angle));DirectX::XMFLOAT2 pos_xy = {v.Pos.x, v.Pos.z};// 累加波浪高度v.Pos.y += CalculateWaveHeight(amplitude, waveLength, speed, time, direction, pos_xy);// 累加法线偏移DirectX::XMFLOAT3 deltaNormal = calculateNormal(amplitude, waveLength, speed, time, direction, pos_xy);v.Normal.x += deltaNormal.x;v.Normal.z += deltaNormal.z;v.Normal.y = 1.0f;}DirectX::XMVECTOR n = DirectX::XMLoadFloat3(&v.Normal);n = DirectX::XMVector3Normalize(n);DirectX::XMStoreFloat3(&v.Normal, n);m_SeaVertexBuffer->CopyData(vIdx, v);++vIdx;}
}

http://www.xdnf.cn/news/19392.html

相关文章:

  • Qt QNetworkAccessManager 简述及例程
  • C++11——万能模板及完美转发
  • GMTapSDK 扩展使用文档
  • 【开题答辩全过程】以 基于springboot的垃圾分类管理系统为例,包含答辩的问题和答案
  • LSTM原理理解
  • 8.29学习总结
  • 大语言模型(LLM)简介与应用分享
  • Linux-数据库
  • 旅游景点库系统的设计与实现(代码+数据库+LW)
  • 力扣hot100:轮转数组(常规思路与三步反转讲解)(189)
  • mmaction安装的详细说明帖
  • 王立群《读史记-刘邦》读书笔记
  • 嵌入式C学习笔记之编码规范
  • 数学分析原理答案——第七章 习题12
  • AI大模型实战解析-RAG知识库+LangChain项目实战
  • Linux系统的进程管理
  • Unity3D Gizmos 调试可视化
  • Qt中UDP回显服务器和客户端
  • 第二十七天-ADC模数转换实验
  • python反转字符串
  • 三维重建模型、3DGS、nerf、 mip-nerf
  • 《WINDOWS 环境下32位汇编语言程序设计》第9章 通用控件(2)
  • 点接触混合润滑完整数值解
  • 免税商品优选购物商城系统|java技术开发
  • MATLAB R2010b系统环境(三)MATLAB操作界面
  • JavaWeb01
  • 【Linux】创建线程
  • 基于K8s部署Redis高可用
  • mit6.031软件构造 笔记 Testing
  • Redis进阶(上)