概述
来自人宅DX12引擎第二期,24章雾实现的笔记。https://renzhai.net/
https://www.yuque.com/chenweilin-tryw7/gb93ve/xn5hu25quu24q73x?singleDoc# 《雾的实现》
大致原理,当前位置画一个圈。半径内物体的颜色都是原来的颜色。
半径外,物体颜色和灰色做一个差值,越远越灰,就形成了雾的效果。
雾的对象组件设计
创建雾对象,GFog类基本是空的,中转调用组件,主要逻辑在雾气组件中
if (GFog* Fog = World->CreateActorObject<GFog>())
{
Fog->SetFogColor(FColor(0.5f, 0.5f, 0.5f, 1.f));
Fog->SetFogStart(10.f);
Fog->SetFogRange(100.f);
}
组件提供雾颜色,开始位置,范围,高度等参数。还有基本的set,get。
class CFogComponent : public CComponent
{
using Super = CComponent;
// ...
protected:
FColor FogColor;
float FogStart;
float FogRange;
float FogHeight;
bool bDirty;
};
雾的常量缓冲区
首先是根签名新增雾的部分,在3
void FDirectXRootSignature::BuildRootSignature(UINT InTextureNumber)
{
constexpr int CountBuffer = 7;
// root signature
CD3DX12_ROOT_PARAMETER RootParameters[CountBuffer];
// SRV
// ...
RootParameters[0].InitAsConstantBufferView(0); // OBJ
RootParameters[1].InitAsConstantBufferView(1); // ViewPort
RootParameters[2].InitAsConstantBufferView(2); // Light
RootParameters[3].InitAsConstantBufferView(3); // Fog
RootParameters[4].InitAsShaderResourceView(0, 1); // Material
RootParameters[5].InitAsDescriptorTable(1, &DescriptorRangeTextureSRV, D3D12_SHADER_VISIBILITY_PIXEL); // Texture
RootParameters[6].InitAsDescriptorTable(1, &DescriptorRangeCubeMapSRV, D3D12_SHADER_VISIBILITY_PIXEL); // CubeMap
// ...
}
注意,由于我们Fog是中途插进来的3。后面4,5,6向后挪了,SetGraphicsRootConstantBufferView 用到根签名的地方需要调整
常量缓冲区结构
struct FFogConstantBuffer
{
FFogConstantBuffer();
XMFLOAT3 Color;
float FogStart;
float FogRange;
float FogHeight;
float Padding[2];
};
FGeometryMap 新增
FConstantBufferView FogConstantBufferView;
构建常量缓冲区,暂时就一个
void FGeometryMap::BuildFogConstantBuffer()
{
FogConstantBufferView.CreateConstantBufferView(sizeof(FFogConstantBuffer), 1);
}
在RenderingPipeLine 里面调用一下
void FRenderingPipeLine::BuildPipeline()
{
// 初始化GPS描述
PipelineState.ResetGPSDesc();
// 初始化渲染层级
RenderLayerMgr.Init(&GeometryMap, &PipelineState);
// 渲染层级排序
RenderLayerMgr.SortRenderLayers();
// 读取贴图
GeometryMap.LoadTexture();
// 构建雾气 《----------------------------------------------------------------------------- new
GeometryMap.BuildFog();
// 构建根签名
RootSignature.BuildRootSignature(GeometryMap.GetDrawTextureNumber());
PipelineState.BindRootSignature(RootSignature.GetRootSignature());
// 构建模型
GeometryMap.Build();
// 构建常量描述符堆
GeometryMap.BuildDescriptorHeap();
// 构建Mesh常量缓冲区
GeometryMap.BuildMeshConstantBuffer();
// 构建材质常量缓冲区
GeometryMap.BuildMaterialShaderResourceView();
// 构建光照常量缓冲区
GeometryMap.BuildLightConstantBuffer();
// 构建视口常量缓冲区
GeometryMap.BuildViewportConstantBufferView();
// 构建纹理常量缓冲区
GeometryMap.BuildTextureConstantBuffer();
// 构建雾常量缓冲区 《----------------------------------------------------------------------------- new
GeometryMap.BuildFogConstantBuffer();
// 分层构建PSO
RenderLayerMgr.BuildPSO();
}
在ShaderCommon.hlsl定义雾气的常量缓冲区
cbuffer FogConstantBuffer : register(b3)
{
float3 FogColor;
float FogStart;
float FogRange;
float FogHeight;
float FogPadding1;
float FogPadding2;
};
绘制中添加 DrawFog
void FGeometryMap::Draw(float DeltaTime)
{
DrawViewport(DeltaTime);
DrawLight(DeltaTime);
DrawTexture(DeltaTime);
DrawMaterial(DeltaTime);
DrawFog(DeltaTime);
}
绘制
void FGeometryMap::DrawFog(float DeltaTime)
{
GetD3dGraphicsCommandList()->SetGraphicsRootShaderResourceView(3, FogConstantBufferView.GetUploadBuffer()->GetGPUVirtualAddress());
}
在FGeometryMap里面新增一个雾的组件 CFogComponent* Fog; 并在构造函数制空。如果用户设置了雾。就更新常量缓冲区
根据对象是否空判断是否启动雾气
void FGeometryMap::UpdateCalculations(float DeltaTime, const FViewportInfo& ViewportInfo)
{
// ...
// Fog
if (Fog)
{
FFogConstantBuffer FogConstantBuffer;
{
FColor FogColor = Fog->GetFogColor();
FogConstantBuffer.Color = XMFLOAT3(FogColor.R, FogColor.G, FogColor.B);
FogConstantBuffer.FogStart = Fog->GetFogStart();
FogConstantBuffer.FogRange = Fog->GetFogRange();
FogConstantBuffer.FogHeight = Fog->GetFogHeight();
}
FogConstantBufferView.Update(0, &FogConstantBuffer);
}
}
构建雾, 从全局对象里面找到雾的对象。如果找到了,更新的时候就会把CPU的值更新到GPU去。后面我们在shader里面写
void FGeometryMap::BuildFog()
{
for (auto& Obj : GObjects)
{
if (auto InFogComponent = dynamic_cast<CFogComponent*>(Obj))
{
Fog = InFogComponent;
break;
}
}
}
雾的算法
在像素着色器的最后,在颜色输出前,最后做一次雾的计算即可。
float4 PSMain(MeshVertexOut MVOut) : SV_TARGET
{
// ...
MVOut.Color = GetFogValue(MVOut.Color, MVOut.WorldPosition.xyz);
return MVOut.Color;
}
差不多是这样,有雾计算,没雾直接返回
#include "ShaderCommon.hlsl"
float4 GetFogValue(float4 InColor, float3 InPointPositon)
{
#if START_UP_FOG
float Distance = length(InPointPositon - ViewportPosition.xyz);
float AlphaValue = saturate((Distance - FogStart) / FogRange);
InColor.xyz = lerp(InColor.xyz, FogColor, AlphaValue);
#endif
return InColor;
}
很好理解,距离远颜色就越接近雾的颜色,否则就是材质本身的颜色
远一点会有灰蒙蒙的效果
雾的高度
仔细想想,大雾天,我们是能看到远处很高的高楼的。大概这种感觉
对高度在做一次lerp
float4 GetFogValue(float4 InColor, float3 InPointPositon)
{
#if START_UP_FOG
float Distance = length(InPointPositon - ViewportPosition.xyz);
float AlphaValue = saturate((Distance - FogStart) / FogRange);
float3 Color = lerp(InColor.xyz, FogColor, AlphaValue);
float HeightAlphaValue = saturate((InPointPositon.y - ViewportPosition.y) / max(FogHeight, 0.1f));
InColor.xyz = lerp(Color, InColor.xyz, HeightAlphaValue);
#endif
return InColor;
}
雾的通透感
远的物体就完全没了,一片灰
通透感变量,体现在这里
InColor.xyz = lerp(Color, InColor.xyz, HeightAlphaValue - FogTransitionCoefficient);