Global Illumination_Directional Volumetric Light (定向体积光)

定向体积光

最近PS5重玩大表哥2,发现他们的体积光有点棒,遂实现一下:
在这里插入图片描述
附YYDS大表哥2里的体积光:
在这里插入图片描述

一、原理

1.1 体积光散射算法(GPU Gems 3)

此算法属于上古大神的处理方式,全部在屏幕空间处理,具体参照本书13章节体积光散射算法,具体不再赘述,仅放出效果:
在这里插入图片描述

1.2 Ray Matching处理

本方法主要是 NVIDIA 2016年开发者大会 发布的Fast, Flexible, Physically-Based Volumetric Light Scattering算法。

总结来说,本算法就是SM的升级版本:SM仅判断物体上的点处理光照,而体积光算法则是,在计算物体上着色的时候,从视点出发,按一定的采样频率逐步进行计算空间点针对光源的可见性,从而计算最终着色
具体示意见下图:
在这里插入图片描述

其中实现方向红色即为体积光作用区域,蓝色为不可见区域。
在这里插入图片描述
不要被式子中积分吓唬到了,到具体执行的时候都是各种近似,直接沿步长采样即可。想具体了解的可以参照原版PDF。

二、代码实现

本此算法主要是以Ray Matching的方式处理定向体积光。在场景生成后,多加一个render pass,专门来处理体积光即可,下边主要贴出此pass的shader如下:

VS: 屏幕正方形绘制:


struct VertexOut
{
    float4 PosH : SV_POSITION;
    float2 Tex : TEX;
};


VertexOut main(uint vI : SV_VERTEXID)
{
    int2 texcoord = int2(vI & 1, vI >> 1);
    VertexOut vout;
    vout.Tex = float2(texcoord);
    vout.PosH = float4(2 * (texcoord.x - 0.5f), -2 * (texcoord.y - 0.5f), 0.0, 1);
    return vout;
}

PS: Ray Matching处理体积光

Texture2D<float> depthTx : register(t2);
Texture2D shadowDepthMap : register(t4);

struct VertexOut
{
    float4 PosH : SV_POSITION;
    float2 Tex  : TEX;
};

// *************** DITHER 抖动算法 ***************
static const float2x2 BayerMatrix2 =
{
    1.0 / 5.0, 3.0 / 5.0,
	4.0 / 5.0, 2.0 / 5.0
};

static const float3x3 BayerMatrix3 =
{
    3.0 / 10.0, 7.0 / 10.0, 4.0 / 10.0,
	6.0 / 10.0, 1.0 / 10.0, 9.0 / 10.0,
	2.0 / 10.0, 8.0 / 10.0, 5.0 / 10.0
};

static const float4x4 BayerMatrix4 =
{
    1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0,
	13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0,
	4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0,
	16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0
};

static const float BayerMatrix8[8][8] =
{
    { 1.0 / 65.0, 49.0 / 65.0, 13.0 / 65.0, 61.0 / 65.0, 4.0 / 65.0, 52.0 / 65.0, 16.0 / 65.0, 64.0 / 65.0 },
    { 33.0 / 65.0, 17.0 / 65.0, 45.0 / 65.0, 29.0 / 65.0, 36.0 / 65.0, 20.0 / 65.0, 48.0 / 65.0, 32.0 / 65.0 },
    { 9.0 / 65.0, 57.0 / 65.0, 5.0 / 65.0, 53.0 / 65.0, 12.0 / 65.0, 60.0 / 65.0, 8.0 / 65.0, 56.0 / 65.0 },
    { 41.0 / 65.0, 25.0 / 65.0, 37.0 / 65.0, 21.0 / 65.0, 44.0 / 65.0, 28.0 / 65.0, 40.0 / 65.0, 24.0 / 65.0 },
    { 3.0 / 65.0, 51.0 / 65.0, 15.0 / 65.0, 63.0 / 65.0, 2.0 / 65.0, 50.0 / 65.0, 14.0 / 65.0, 62.0 / 65.0 },
    { 35.0 / 65.0, 19.0 / 65.0, 47.0 / 65.0, 31.0 / 65.0, 34.0 / 65.0, 18.0 / 65.0, 46.0 / 65.0, 30.0 / 65.0 },
    { 11.0 / 65.0, 59.0 / 65.0, 7.0 / 65.0, 55.0 / 65.0, 10.0 / 65.0, 58.0 / 65.0, 6.0 / 65.0, 54.0 / 65.0 },
    { 43.0 / 65.0, 27.0 / 65.0, 39.0 / 65.0, 23.0 / 65.0, 42.0 / 65.0, 26.0 / 65.0, 38.0 / 65.0, 22.0 / 65.0 }
};

//bayer dithering
inline float ditherMask2(in float2 pixel)
{
    return BayerMatrix2[pixel.x % 2][pixel.y % 2];
}

inline float ditherMask3(in float2 pixel)
{
    return BayerMatrix3[pixel.x % 3][pixel.y % 3];
}

inline float ditherMask4(in float2 pixel)
{
    return BayerMatrix4[pixel.x % 4][pixel.y % 4];
}

inline float ditherMask8(in float2 pixel)
{
    return BayerMatrix8[pixel.x % 8][pixel.y % 8];
}

inline float dither(in float2 pixel)
{
    return ditherMask8(pixel);
}

//other
float2 dither(float2 coord, float seed, float2 size)
{
    float noiseX = ((frac(1.0 - (coord.x + seed * 1.0) * (size.x / 2.0)) * 0.25) + (frac((coord.y + seed * 2.0) * (size.y / 2.0)) * 0.75)) * 2.0 - 1.0;
    float noiseY = ((frac(1.0 - (coord.x + seed * 3.0) * (size.x / 2.0)) * 0.75) + (frac((coord.y + seed * 4.0) * (size.y / 2.0)) * 0.25)) * 2.0 - 1.0;
    return float2(noiseX, noiseY);
}

float2 mod_dither(float2 u)
{
    float noiseX = fmod(u.x + u.y + fmod(208. + u.x * 3.58, 13. + fmod(u.y * 22.9, 9.)), 7.) * .143;
    float noiseY = fmod(u.y + u.x + fmod(203. + u.y * 3.18, 12. + fmod(u.x * 27.4, 8.)), 6.) * .139;
    return float2(noiseX, noiseY) * 2.0 - 1.0;
}
// *************** DITHER 抖动算法 ***************

// *************** Saturate算法 ***************
bool IsSaturated(float value)
{
    return value == saturate(value);
}
bool IsSaturated(float2 value)
{
    return IsSaturated(value.x) && IsSaturated(value.y);
}
bool IsSaturated(float3 value)
{
    return IsSaturated(value.x) && IsSaturated(value.y) && IsSaturated(value.z);
}
bool IsSaturated(float4 value)
{
    return IsSaturated(value.x) && IsSaturated(value.y) && IsSaturated(value.z) && IsSaturated(value.w);
}
// *************** Saturate算法 ***************

// SM 3x3 PCF
float CalcShadowFactor_PCF3x3(SamplerComparisonState samShadow,
	Texture2D shadowMap,
	float3 uvd, int smSize, float softness)
{
    if (uvd.z > 1.0f)
        return 1.0;

    float depth = uvd.z;

    const float dx = 1.0f / smSize;

    float percentLit = 0.0f;

    float2 offsets[9] =
    {
        float2(-dx, -dx), float2(0.0f, -dx), float2(dx, -dx),
		float2(-dx, 0.0f), float2(0.0f, 0.0f), float2(dx, 0.0f),
		float2(-dx, +dx), float2(0.0f, +dx), float2(dx, +dx)
    };

	[unroll]
    for (int i = 0; i < 9; ++i)
    {
        offsets[i] = offsets[i] * float2(softness, softness);
        percentLit += shadowMap.SampleCmpLevelZero(samShadow,
			uvd.xy + offsets[i], depth).r;
    }
    return percentLit /= 9.0f;

}

//根据可见距离与雾级强度计算雾化指数参数
float ExponentialFog(float dist)
{
    float fog_dist = max(dist - fog_start, 0.0);    
    float fog = exp(-fog_dist * fog_density);
    return 1 - fog;
}

float4 main(VertexOut input) : SV_TARGET
{
    //是否考虑阴影
    if (current_light.casts_shadows == 0)
    {
        return 0;
    }

    //当前像素在当前视角下的场景深度
    float depth = max(input.PosH.z, depthTx.SampleLevel(linear_clamp_sampler, input.Tex, 2));
    //获取视空间下点位置
    float3 P = GetPositionVS(input.Tex, depth);
    //视空间最远点距离
    float3 V = float3(0.0f, 0.0f, 0.0f) - P;
    float cameraDistance = length(V);
    V /= cameraDistance;

    float marchedDistance = 0;
    float3 accumulation = 0;
	
	//定向光方向,后续点光源聚光灯等皆需要对应调整
    const float3 L = current_light.direction.xyz;

    float3 rayEnd = float3(0.0f, 0.0f, 0.0f);
	//沿视线方向的采样数,影响体积光的采样质量与耗
    const uint sampleCount = 16;
    const float stepSize = length(P - rayEnd) / sampleCount;

	// 抖动射线法来弥补采样不足:
    P = P + V * stepSize * dither(input.PosH.xy);

	// 执行ray match,沿着视图光线积分光量:  
	[loop]
    for (uint i = 0; i < sampleCount; ++i)
    {
    	//不同match下的阴影采样点世界系下的位置  shadow_matrix1:当前V的逆矩阵与sm中的VP矩阵结果
        float4 posShadowMap = mul(float4(P, 1.0), shadow_matrix1);
        float3 UVD = posShadowMap.xyz / posShadowMap.w;

        UVD.xy = 0.5 * UVD.xy + 0.5;
        UVD.y = 1.0 - UVD.y;
        
        [branch]
        if (IsSaturated(UVD.xy))
        {             
        	//处理阴影衰减作用             
            float attenuation = CalcShadowFactor_PCF3x3(shadow_sampler, shadowDepthMap, UVD, shadow_map_size, softness);
			//处理雾级衰减作用 
            attenuation *= ExponentialFog(cameraDistance - marchedDistance);

            accumulation += attenuation;

        }
		//ray match +
		marchedDistance += stepSize;
		P = P + V * stepSize;
    }

    accumulation /= sampleCount;
	//定向体积光效果
    return max(0, float4(accumulation * current_light.color.rgb * current_light.volumetric_strength, 1));
}

具体流程见代码注释即可,运行后可见如下效果:

在这里插入图片描述

叠加之前场景后整体渲染效果如下:

在这里插入图片描述

至于点光源与聚光灯原理相同,以后有时间了再整吧

所支持的Unity版本 5.2.0 及以上版本 WebGL Showcase | WebGL压力测试|文档|论坛 这个插件允许您通过生成真正容积的程序束来大大改善场景的照明。 这是模拟聚灯和手电筒的密度,深度和音量的完美,简单而便宜的方法。 简单高效的体积照明解决方案兼容各种平台(Windows PC,Mac OS X,Linux,WebGL,iOS,Android,VR)! 即使在移动设备上,也能为您的聚灯和手电筒模拟密度,深度和体积的完美,简单且便宜的方式! 它通过自动高效地生成真正的体积程序束来渲染高质量的线效果,从而极大地改善了场景的照明。 特征: - 真正的体积效果:即使你在束中也能工作。 - 非常容易使用和集成/需要零设置。 - 程序生成:一切都是在引擎盖下动态计算的。 - 在任何地方添加无限束:替代解决方案通常只需要实时灯:此插件不需要。您可以制作烘烤的量,甚至可以在没有任何线的情况下添加束。 - 动态3D噪声功能,用于模拟动画体积雾/雾/烟雾效果。 - 体积粉尘颗粒功能可模拟高度详细的防尘灯和微尘效果。 - 动态遮挡:可以通过移动几何体来阻挡束。 - 您可以实时移动和旋转束。 - 触发区域功能:您可以跟踪通过束的对象。 - 完全动态:在游戏时间内从脚本,动画师或时间轴更改或动画每个属性。 - Super FAST:不需要任何后处理,命令缓冲区和计算着色器:即使在移动设备和WebGL等低性能平台上也能很好地工作。 - VR Ready:支持Normal和VR Single pass立体声。 - 平滑交叉并与几何和相机混合。 - 自定义截头圆锥几何体。 - 支持许多图形变体:延迟和前向渲染路径,Gamma和线性颜色空间,HDR颜色,多种混合模式。 - 调整分层图层和图层顺序,以使用2D精灵调整束渲染。 - 开箱即用的透视和正交相机。 - 支持Unity内置雾。 - WYSIWYG:在场景视图中立即可以看到每个修改:无需在编辑器和播放模式之间切换以查看您的更改。 - 完整源代码可用/无DLL。束设置和处理通过功能强大的API完全暴露。 - 详细的文件。 - 支持从Unity 5.2到最新的2017.X和2018.X版本。 - 示例场景包括:展示演示。 请注意,此资产不是全屏后期处理/图像效果。这与Unity内置的Sun Shafts图像效果不相似。 相反,体积束将产生优化的几何形状和材料谱。这种技术有几个优点: - 更精细:独立精确定制每个束。 - 您可以在任何地方添加束,即使在没有线的地方也是如此。 - 当连接到聚灯时,它支持实时,烘焙和混合模式。 - 您可以渲染的束数量没有限制。 - 更容易与您自己的管道集成:无需与您自己的图像效果或后处理堆栈混合,没有命令缓冲区,不需要计算着色器功能。 - 运行得更快。没有后期处理添加到您的相机。 - 支持移动等低端平台。 如何使用它? 体积束设计非常易于使用。无需设置。您不必将多个对象链接在一起。您只需要使用一个简单的新组件。你可以通过2次点击添加一个新的束! 您可以通过调整一组用户友好的属性来精确定制每个束的渲染。为了获得更好看的效果,一些属性会自动绑定到附加的聚灯。 限制: 目前,此资产的当前版本有一些小的限制: - 此资产仅支持“聚灯”(形状像锥形的束)。不支持点源(线向各个方向平等)。 - “3D噪声”功能要求着色器功能等于或高于Shader Model 3.5 / OpenGL ES 3.0。 2012年之后发布的任何移动设备都应该支持它。 - 仅在Unity 5.5或更高版本上支持“体积粉尘颗粒”。 - “动态遮挡”功能计算遮挡的近似值,但尚不支持“部分遮挡”。
这个插件允许您通过生成真正容积的程序束来大大改善场景的照明。 这是模拟聚灯和手电筒的密度,深度和音量的完美,简单而便宜的方法。 The simple and efficient volumetric lighting solution compatible with every platforms: Windows PC, Mac OS X, Linux, WebGL, iOS, Android, VR, AR, Consoles, Built-in/Legacy Render Pipeline, SRP (URP & HDRP)! The perfect, easy and cheap way to simulate density, depth and volume for your spotlights and flashlights, even on Mobile! It greatly improves the lighting of your scenes by automatically and efficiently generating truly volumetric procedural beams of light to render high quality light shafts effects. A production ready plugin proven by awesome released games showcasing it: - BONEWORKS released for high-end PC VR - Carly and the Reaperman released for Playstation 4 PSVR and high-end PC VR - Kingspray Graffiti released for high-end PC VR and Oculus Quest - Hexagroove released for Nintendo Switch - Covert released for Playstation 4 PSVR, Oculus Rift and Oculus Go Features: - Truly volumetric: works even if you are INSIDE the beam of light. - Incredibly easy to use and integrate / Import it instantly / Zero setup required. - In addition to the Built-in Legacy Render Pipeline, it fully supports the Universal Render Pipeline (URP) and the High Definition Pipeline (HDRP). - Optimized for VR: tested with high-end headsets (Oculus Rift, HTC Vive, Valve Index...) and standalone hardware (Oculus Go, Oculus Quest...), supports all Stereo Rendering Methods (Multi Pass, Single Pass and Single Pass Instanced or Multiview). - AR Ready: supports both Apple iOS ARKit and Google Android ARCore. - GPU Instancing & SRP Batcher: render and batch thousands of beams in 1 single drawcall. - Super FAST w/ low memory footprint: doesn't require any post-process, command buffers, nor compute shaders: works great even on low-performance platforms such as Mobiles and WebGL. - Procedural generation: everything is dynamically computed under the hood. - Add unlimited light beams everywhere: alternative solutions usually requi
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值