TA

Posted by Jqy on April 1, 2021

简介

雾气作为一种自然现象,应用在游戏场景中,可以增加场景的真实性,做一些风格化的操作,也能让物体超出FarPlane淡入淡出,而不是一下子消失那么突兀。

雾气的实现原理比较简单,基本就是一个Blend操作,公式为:

finalColor = lerp(finalColor,fogColor,fogAmount);

其中fogAmount可以通过各种方式来实现。

本偏主要介绍的是后处理的雾气特效,体积雾不涉及。

代码实践

struct PassConstants
{
    //...
    DirectX::XMFLOAT4 AmbientLight = { 0.0f, 0.0f, 0.0f, 1.0f };
    DirectX::XMFLOAT4 FogColor = { 0.7f,0.7f,0.7f,0.7f };
    float gFogStart = 5.0f;
    float gFogRange = 150.0f;
    DirectX::XMFLOAT2 cbPerObjectPad2;
};

#ifdef FOG
    float fogAmount = saturate((distToEye - gFogStart) / gFogRange);
    litColor = lerp(litColor, gFogColor, fogAmount);
#endif

这里有个小坑,就是cbPerObjectPad2这个字段不能少。因为DirectX官方要求结构体的成员必须4个单位对齐。不加的话会这样: 不过具体原理不太了解。

修改完发现,貌似没有任何的变化,这里也记录一下如何在原生的DirectX层面上调试吧。

首先判断出CPU端传入的数据没有任何问题,PSO也没有出错,那么便把问题定位在了Shader层面上。

因为Fog的实现相关代码很短,所以这里先偷个懒,没有上PIX。而是先尝试简单的print变量来看看哪些变量可能出现差错

FogAmount:

很明显,这里FogAmount出现了差错,但这里脑子一时间没转过来,让我误以为是由于saturate让Amount产生了这样的结果。没办法,上必杀武器PIX。

最终确定问题:toEyeW变量在后续计算以前就做了个归一化。。这居然没看出来。。太蠢了。

解决完这个问题以后,雾气的效果也就自然出来了。

拓展

指数雾衰减

    float b = 0.02;
    float fogAmount = saturate(1.0 - exp(-distToEye * b));

双颜色雾气(随便取的名字),可以模拟太阳光带来的效果(太阳周围的雾气颜色多为黄色,远离处则为蓝色)

    float3 blueish = float3(0.5,0.6,1.0);
    float3 yellowish = float3(1.0,0.9,0.1);
    float b = 0.02;
    float3 sunDir = -gLights[0].Direction;
    float fogAmount = saturate(1.0 - exp(-distToEye * b));
    float sunAmount = saturate(dot(toEyeW,sunDir));
    float3 fogColor = lerp(blueish, yellowish, pow(sunAmount, 10.0));
    litColor = lerp(litColor, fogColor, fogAmount);

现象不明显,没截图(应该是光源随时在变化的原因,有空了改改代码截张图出来),可以参考: Snipaste_2021-04-01_15-34-36

高度雾

    float3 fogColor = float3(0.0,1.0,0.0);
    float fogAmount = saturate((5 - pin.PosW.y) / 10);
    litColor = lerp(litColor, fogColor, fogAmount);

换成绿色看的清楚一点。这里有一点不是很明白,看了很多资料,对高度雾的实现都是通过采样DepthMap并且还原他们在世界空间的坐标,但是这个好像可以直接在VS里通过加一个结构成员PosW就可以解决。。。不是很懂原因,目前的猜测是提高效率?因为这样做的实现效果好像也没什么问题。。

动态雾

    float3 fogColor = float3(0.0,1.0,0.0);
    float b = 0.02;
    float fogAmount = saturate(1.0 - exp(-distToEye * b));
    float2 uv = float2(pin.TexC.x+sin(gTotalTime),pin.TexC.y+sin(gTotalTime));
    float noise = saturate(gTextureMaps[diffuseMapIndex].Sample(gsamAnisotropicWrap,uv).r);
    fogAmount *= noise;
    litColor = lerp(litColor, fogColor, fogAmount);

思路也很直白,就是引入一张NoiseMap(这里偷懒用diffuseMap代替了,本质一样的),然后根据时间信息对其进行采样。便可以营造出雾气移动的效果。不过截屏截不出来这个效果。 这里的雾气是会移动的

暂时就先写这么多,后续随缘更新,可能会补一下根据DepthMap重建,顺便分析一下Unity的雾气实现的相关代码。