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

[笔记] Unity urp-基于Perlin Noise的后处理雾效

[复制链接]
发表于 2021-11-26 12:23 | 显示全部楼层 |阅读模式
一.前言
几个月前网上找柏林噪声资料后自己想用C#实现一个生成柏林噪声图的应用,然后怎么弄都出不来想要的结果,感觉网上的代码拉下来试感觉都是对的,对比一下我代码也没啥问题,就搁置在那了。
现在做雾效的时候看到用perlinnoise实现的于是又重新研究了一遍(指return个float2结果函数体返回值写成了float,unity只报了warning没有理浪费我大量的美好时光),这回对柏林噪声有了更深刻的理解。在这里我分享一下研究心得,希望大家研究这块的时候少走些弯路。
二.简要介绍和体会
1.perlinnoise
要生成二维柏林噪声,先想象一个三维空间,xy是二维坐标,z轴是噪声值。
柏林噪声本质上是将xy平面分成n*m个方格(通常方格大小为单位1的正方形),则噪声图以如下方式生成:
①对于每个方格顶点,手动给定一个噪声值(z值,后文以height特指方格顶点的z值)以及梯度值(grad值,表示方向导数在此处取最大值);
(注意方格顶点不一定与像素点重合,因此方格顶点是虚拟存在的)
②对于位于方格内的像素点,每个像素点根据其位于方格内的坐标(uv正好在0~1之间,通常用像素位置取小数部分表示,方便描述起见称之为方格uv)计算出周围4个方格顶点在此处的噪声值grad0,grad1,grad2,grad3
(根据计算公式z=k(x-x0)+k(y-y0)+height=dot(方格顶点的梯度值,(像素点位置-方格顶点位置)+height)
③用周围四个顶点对此像素点进行双线性插值(在进行双线性插值前通常对方格uv用缓和曲线进行映射使结果图在放个顶点处的变化更柔和)最大的问题在于如何控制最终生成的噪声值的区间(比如落在[0,1]之间)以及概率密度分布。这个问题要求的值受8个变量影响(4个顶点的高度值,梯度值),而且自变量有两个(被采样的像素点坐标xy),求解不能,只能按照经验来了。我参考了一下各路代码,发现有以下处理措施(并不能保证能均匀分布在[0,1]之间):
·height取0。在点积的时候将点积结果/2+1,目的是将点积的[-1,1]映射到[0,1](因为点积范围其实不是[-1,1]实际结果发现颜色基本集中在80-160,没有特别黑特别白的地方看着倒挺舒服)(参考https://blog.csdn.net/o83290102o5/article/details/117426002)
·height取高一点的值,不是0就行。这样做明显有黑色区域,有些点实际上仍小于0(参考https://zhuanlan.zhihu.com/p/360235233)
实际做的时候也可以两个方案都参考一下,毕竟图形学第一定律,看起来对的就是对的嘛。
2.fbm(分型布朗运动)化perlin noise
fbm本质上是不同频率和幅度的noise的叠加
代码如下



_Octaves表示迭代次数
_Frequency表示噪声频率
_Amplitude 表示噪声幅度
_Lacunarity表示每次累加噪声时,噪声的频率变化
_Gain 表示每次累加噪声时,噪声的幅度变化
这里再作一点补充:
通常amplitude取0.5,gain取0.5,这意味着当迭代次数增大时,weight趋向于1,这也是有些代码没有在最后除于总权值的原因。如果amplitude和gain取其他值的话,那么除于总权值(幅度amplitude的和)可以保证fbm结果区间和被fbm化的noise的区间一致

最后给上完整的shader代码
Shader "Custom/Fog2.0"{
   
    Properties{
        _MainTex("主纹理", 2D) = "white" {}
        _Color("雾颜色", color) = (1,1,1,1)
        _FogPower("雾强度", Range(0,1)) = 0.5
        _FogVisibility("雾密度/可见性", Range(0,1)) = 0.2
        _Octaves("分型次数",int) = 4
        _Frequency("采样频率",float) = 1.0
        _Amplitude("幅度",float) = 0.5
        _Lacunarity("频率增加倍数",float) = 2.0
        _Gain("幅度增加倍数",float) = 0.5
        _Speed("速度",float) = 3.0
        _NoiseUnitCount("方格个数/噪声密度",float) = 5.0
    }
    SubShader{
      
        Pass{

            Tags { "LightMode" = "UniversalForward"}
            HLSLPROGRAM
     
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            CBUFFER_START(UnityPerMaterial)
            float4 _MainTex_ST;
            float _FogPower;
            float4 _Color;
            int _Octaves;
            float _Frequency;
            float _Amplitude;
            float _Lacunarity;
            float _Gain;
            float _FogVisibility;
            float _Speed;
            float _NoiseUnitCount;
            CBUFFER_END
            TEXTURE2D (_MainTex);
            SAMPLER(sampler_MainTex);
            //sampler2D _MainTex;
            

            struct a2v {
                float4 positionOS : POSITION;              
                float2 uv : TEXCOORD0;
            };
            struct v2f{
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
        
            half2 rand(float2 p){
                float theta = 52 * sin(p.x * 6+ p.y * 5);//随便编一个
                //float theta = sin(666 + p.x * 5678 + p.y * 1234) * 4321;
                return half2(cos(theta),sin(theta));//单位向量
               
            }

            float2 smoothLerp(float2 x){
               return x * x * x * (x * (x * 6 - 15) + 10);
            
            }

            float perlinNoise(float2 x)
            {
                float2 i = floor(x);
                float2 uv = frac(x);

                float2 posa = uv;
                float2 posb = uv - float2(1.0, 0.0);
                float2 posc = uv - float2(0.0, 1.0);
                float2 posd = uv - float2(1.0, 1.0);

                float2 grada = rand(i);
                float2 gradb = rand(i + float2(1.0, 0.0));
                float2 gradc = rand(i + float2(0.0, 1.0));
                float2 gradd = rand(i + float2(1.0, 1.0));

                float dot1=dot(posa,grada) / 2 + 0.5f;
                float dot2=dot(posb,gradb) / 2 + 0.5f;
                float dot3=dot(posc,gradc) / 2 + 0.5f;
                float dot4=dot(posd,gradd) / 2 + 0.5f;
      
                float2 u = smoothLerp(uv);

                float x1 = lerp(dot1,dot2,u.x);
                float x2 = lerp(dot3,dot4,u.x);
                return lerp(x1,x2,u.y);
            }



            float fbm(float2 x)
            {
                float value = 0;
                float weight = 0;
                float frequency = _Frequency;
                float amplitude = _Amplitude;
                for(int i=0;i<_Octaves;++i)
                {
                    value += perlinNoise(x * frequency) * amplitude;
                    frequency *= _Lacunarity;
                    weight +=amplitude;
                    amplitude *= _Gain;
                }
                return value/weight;
            }

            v2f vert(a2v v)
            {
                v2f o;
                o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
                o.uv= TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
           
            half4 frag(v2f i) : SV_Target
            {
                //float3 mainColor = tex2D(_MainTex,i.uv).rgb;
                float3 mainColor = SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex,i.uv).rgb;
                float noise = perlinNoise(i.uv*_NoiseUnitCount+_Time.x*_Speed);//噪声函数
                float3 fogColor = _Color.rgb*noise*_FogPower;
                float3 final = lerp(mainColor,fogColor,_FogVisibility);
                return float4(final,1.0f);
            }           
            ENDHLSL
        }
    }
}最终效果:


https://www.zhihu.com/video/1436124454967898112
参考链接

讲完shader顺便简单介绍urp中如何对屏幕进行后处理(具体介绍看最后的链接,我介绍起来会很乱所以不再赘述)
1.新建Rengderer Feature。unity会在这个脚本里同时创建Rengderer Feature(用于将pass传入渲染流程的特定阶段中)和Rengderer  Pass(具体的渲染命令,使用command buffer操作),为了结构清晰可以把Rengderer  Pass另放一个脚本
2.编写Rengderer Feature类。继承自criptableRendererFeature,最主要的功能是将pass提交到渲染队列中,其次可以在inspector面板中暴露一些参数用于传递给pass,再由pass对material进行参数设置,参考代码如下:
public class FogFeature : ScriptableRendererFeature
{
    [System.Serializable]
    public class Setting {
        public Material fogMaterial;
        public RenderPassEvent renderPassEvent;
        public string tag;
        public FilterMode filterMode;
    }

    public Setting setting;
   
    FogRenderPass m_FogRenderPass;

    public override void Create()
    {
        m_FogRenderPass = new FogRenderPass(setting.renderPassEvent,setting.fogMaterial,setting.tag,setting.filterMode);
    }

    // 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)
    {
        //获取相机的主纹理
        m_FogRenderPass.Setup(renderer.cameraColorTarget);
        renderer.EnqueuePass(m_FogRenderPass);
    }
}
3.编写Renderer Pass。继承自ScriptableRenderPass,在构造函数进行初始化,然后在Execute中调用Command进行绘制:将m_cameraColorIdentifier摄像机颜色纹理用m_fogMaterial材质(在asset创建材质并用刚写的shader拖拽赋值)绘制到临时纹理上,再用临时纹理绘制回摄像机颜色纹理上(不能对同一个纹理又读又写,因此需要申请一个临时纹理,用完就扔)
class FogRenderPass : ScriptableRenderPass
{
    private string m_commandBufferTag;//用于在frame debugger中与其他标签区分
    private Material m_fogMaterial;
    private RenderTargetIdentifier m_cameraColorIdentifier;
    private FilterMode filterMode;

    private RenderTargetHandle m_temp;
    public FogRenderPass(RenderPassEvent evt, Material material, string tag,FilterMode filterMode)
    {
        renderPassEvent = evt;
        m_commandBufferTag = tag;
        this.filterMode = filterMode;
        if (material == null)
        {
            Debug.LogWarningFormat("urp pp's Fog Material is missing,{0} wouldn't be executed", GetType().Name);
            return;
        }
        m_fogMaterial = material;
        m_temp.Init("temp");
    }
    public void Setup(RenderTargetIdentifier cameraColorTarget)
    {
        this.m_cameraColorIdentifier = cameraColorTarget;
    }


    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (!renderingData.cameraData.postProcessEnabled)
        {
            return;
        }
        var cmd = CommandBufferPool.Get(m_commandBufferTag);
        RenderTextureDescriptor cameraTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor;
        cmd.GetTemporaryRT(m_temp.id, cameraTargetDescriptor,filterMode);
        cmd.Blit(m_cameraColorIdentifier, m_temp.Identifier(), m_fogMaterial);
        cmd.Blit(m_temp.Identifier(), m_cameraColorIdentifier);
        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
        cmd.ReleaseTemporaryRT(m_temp.id);
    }

}
4.最后在asset_renderer里添加自定义的Rengderer Feature并设置参数



添加刚写的Rengderer Feature

参考链接

本帖子中包含更多资源

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

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

本版积分规则

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

GMT+8, 2024-11-24 13:02 , Processed in 0.124055 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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