找回密码
 立即注册
查看: 223|回复: 0

[Unity Shader入门]体积光实现

[复制链接]
发表于 2022-12-25 18:11 | 显示全部楼层 |阅读模式
上文说到了SDF(有向向量场)的原理,实现了一个圆体的体积渲染。本文将来实现一个常见的体积渲染案例--体积光。效果如下:


https://www.zhihu.com/video/1589734709482901504
一.实现

1.创建RenderFeature

体积光是一种后处理,所以我们创建一个RenderFeature来实现比较方便。需要用到深度图,因此要把copy depth打开。
public class VolumeLightFeature : ScriptableRendererFeature
{
    ...
    class LightRenderPass : ScriptableRenderPass
    {
         ...
         public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get(Cmd_Tag);
            var cameraData = renderingData.cameraData;
            using (new ProfilingScope(cmd, m_ProfilingSampler))
            {
                Camera camera = cameraData.camera;
                cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity);
                cmd.SetViewport(camera.pixelRect);
                cmd.DrawMesh(RenderingUtils.fullscreenMesh,Matrix4x4.identity, material);
                cmd.SetViewProjectionMatrices(camera.worldToCameraMatrix, camera.projectionMatrix);
            }

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }

         ...
    }

    LightRenderPass m_ScriptablePass;

    /// <inheritdoc/>
    public override void Create()
    {
        m_ScriptablePass = new LightRenderPass(material);

        // Configures where the render pass should be injected.
        m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    }

    // Here you can inject one or multiple render passes in the renderer.
    // This method is called when setting up the renderer once per-camera.
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(m_ScriptablePass);
    }
}
看代码,其实是很基本的renderFeature,只是渲染一次全屏mesh。
2.shader

创建一个volumelight.shader。
首先要根据屏幕的像素点uv坐标来获取到对应的世界空间的坐标。原理是先通过屏幕uv获取到该点的深度,根据uv和深度值反算世界坐标。
#if UNITY_REVERSED_Z
    real depth = tex2D(_CameraDepthTexture,i.uv);
#else
    // Adjust z to match NDC for OpenGL
    real depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, tex2D(_CameraDepthTexture,i.uv));
#endif

float3 worldPos = ComputeWorldSpacePosition(i.uv,depth,UNITY_MATRIX_I_VP);我们有 _WorldSpaceCameraPos 内置参数 来获取了相机的世界坐标。
这样结合上文的sdf原理,可以从相机的位置,步进地扫描沿途的点,当点可以被光照射到的时候,叠加一个光量,最后所有光量的平均值就是当前屏幕像素点的体积光色。
为了方便,我们可以直接用urp内置的函数,做一些光的计算。
float4 shadowCoord = TransformWorldToShadowCoord(pos);
float shadowF = MainLightRealtimeShadow(shadowCoord);TransformWorldToShadowCoord 、MainLightRealtimeShadow 这两个方法即可算出该世界坐标pos是否有被光照射到,shadowF为1即被照射到。
因此结合上文的sdf算法,得到以下算法。
half4 Raymarch(float3 ro,float3 rd,float length)
{
    half4 result = half4(0,0,0,0);
    float stepLength = length / SimpleCount;
    for (int i = 0; i < SimpleCount; i++) {
        half3 pos = ro + i*stepLength*rd;
        float4 shadowCoord = TransformWorldToShadowCoord(pos);
        float shadowF = MainLightRealtimeShadow(shadowCoord);
        result += step(0.1,shadowF) * _LightDensity * _LightColor;
    }
    result /= SimpleCount;
    return result;
}由此基本的体积光算法完成。
3.优化

目前实现的光照效果太过僵硬,为了看起来更贴近现实,在渲染出光照效果后,最好再做两次blur,然后再和输出纹理融合,看起来效果更好。
二.源码

附上源码
希望大家喜欢。如果对大家有所帮助,希望能点个赞。如文章有错误,欢迎指出。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-4 14:40 , Processed in 0.095794 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表