体积雾——实践篇之十八般武艺

结合上篇的理论基础,这篇文章我们将体积雾要考虑的因素一一实现

密度
散射
灯光阴影

可喜的是,很多数值我并没有写死,暴露出来,能调出很多意想不到的效果


texture实际没有用到,可以其实可以用它来做一些光影的蒙版,但每一次投射都需要矩阵变换很费,所以先放着没做
这些效果很多只需要一次两次的ray cast,所以细心分离,完全可以用在普通的shader中做特效
中毒效果(2层采样)
烟雾性毒圈(也很省)
冰墙
这个我也不知道叫什么好

总之再开发性还是很高的

通过RayMarching的方法,每投射一步算一次区一次雾的属性,继续投射前进知道遇到场景物体或投射最大值停止,将此射线所有点的属性按一定方式叠加,为此点的雾效果;
每次投射将浓度、散射、透光强度、光影等因素考虑进去;浓度通过世界坐标采样3Dtexture、散射、透光强度按之前的公式计算、光影向每光源方向做积分运算(此篇简化只做一次)

分析shader主要部分(其他部分见前几篇):

1.投射方式、取舍与优化
2.采样方式及高度、noise密度、移动设置
3.云雾效果光影实现

1.投射方式、取舍与优化

投射方式在控制参数中添加:_startDistance/_endDistance/_stepNum
每次投射的间隙是相等的:(_endDistance-_startDistance)/_stepNum
带来的问题:与场景的交接除有明显痕迹


应对措施:参考shadertoy 上的iq的3D cloud

将每次步进的距离设为 t=max(0.05,0.02*t); 等于暴力的加强近出的计算量;但这对于游戏来说显然是太费了
每条射线所到达的深度又是不一样的,所以一般的立方体限制算法行不通
所以我采用虚化射线末端边缘

//消除层投射与场景的硬结边1
float alpha=1.0;
if(t>=rz-10){
    alpha = smoothstep(0,10,rz-t);
}
for(int i =0;i<_stepNum;i++){

        p = ro + t *rd;

        //各种限制节省投射次数------------------------------------
        if(light.a>0.99||t>=rz){
            break;
        }
        if(rd.y>0 && p.y>_maxHeight){
            break;
        }

        //消除层投射与场景的硬结边1
        float alpha=1.0;
        if(t>=rz-10){
            alpha = smoothstep(0,10,rz-t);
        }

        //每个采样点的各种限制------------------------------------
        float den = getDen(p);
        
        //光照计算--------------
        "略"
        
        }
        t+=stepSize;
    }

2.采样及雾高度、密度、移动设置

得到密度、抽出_minCloud/_maxCloud 控制雾量

//得到密度
float getDen(float3 p ){
    float den = FBM(p , _cloudFra , _maxHeight,_cloudSize);  
    den = smoothstep(_minCloud , _maxCloud,den);
    return den;
}

采样函数 的高度、密度、移动

float FBM( float3 p,float iterNum,float _maxHeight,float _cloudSize)
{
    //设置高度
    float alpha = smoothstep(_maxHeight , _maxHeight-5, p.y);
    //调整cloud大小
    p *= _cloudSize/5;
    p+= float3(1,1,3) * _Time.y * _moveSpeed;
    float f = 0.0;
    float s = 0.5;
    float s2 = 2.00;
    float sum = 0.0;
    for(int i = 0;i< iterNum;i++){
        f += s * VNoise( p ); 
        p *=s2;
        sum+=s;
        s*= 0.5;s2+=0.01;
    }
    return (f/sum) * alpha;
}

3.云雾的光影实现

结合理论篇
单说的点:
getDen(p-1*_lightDir) 得到该处雾向光源方向前进1个单位处 的雾的浓度
透光比与理论篇有些出入,我是实际效果改了下,任意发挥吧

//每个采样点的各种限制------------------------------------
        float den = getDen(p);

        if(den>0.01){
            //此处的云是否被灯源方向的云遮挡
            float dif =  clamp((den - getDen(p-1*_lightDir))/0.6,  0.0 , 1.0 );
            float3 lin = float3(0.65,0.7,0.75)*1.4 + _lightColor * dif * _lightIntensity;

            //浓度做Alpha
            float4 col = float4(_fogColor,den); 
            col.xyz*=lin;

            //透光比 距离与浓度
            col.xyz = lerp(col.xyz , sceneCol.xyz , exp(-t*t));  

            //消除层投射与场景的硬结边2
            col.a *= alpha ;

            //添加散射衰减
            float cosTheta=dot(_lightDir,-rd);
            float result = 1/(4*3.14)*(1-pow(_g,2))/pow(1+pow(_g,2)-2*_g*cosTheta,1.5);

            //此层的颜色 * den * 散射 * 强度控制
            col.xyz *= col.a * result * _densityInstence;
            light = light + col*(1.0-light.a);
        }

脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

[RequireComponent(typeof(Camera))]
[ExecuteInEditMode]
public class VolumeFog : MonoBehaviour
{
    public Light dirLight=null;
    private Vector4 lightDir;
    public Texture lightShadow=null;
    //fog
    public Shader fogShader=null;
    private Material fogMaterial=null;
    //cast
    public Color fogColor=Color.yellow;
    public Color LightColor=Color.yellow;
    public float LightIntensity=1.0f;
    public float cloudSize=1.0f;
    [Range(0,1)]
    public float moveSpeed=0.1f;
    public float startDistance=2;
    public float endDistance=300;
    public int stepNum = 100;
    public float maxHeight=50;

    [Range(0,10)]
    public float densityInstence=0.6f;
    public Vector2 cloudDenAdjust = new Vector2(1.0f,1.0f);
    [Range(2,5)]
    public int cloudFra=3;

    public float g = 0.6f;
    

    //camera
    private Camera mainCamera=null;

    private void OnEnable() {
        mainCamera = this.GetComponent();
        if(dirLight!=null&&fogShader!=null&&fogShader.isSupported&&mainCamera!=null){
            lightDir=dirLight.transform.forward;
            fogMaterial=new Material(fogShader);
            //让摄像机产生深度纹理
            mainCamera.depthTextureMode |=DepthTextureMode.Depth;
        }else
        {
            enabled = false;
        }
    }

    private void OnDisable() {
        if(mainCamera){
            mainCamera=null;
        }
        if(fogMaterial){
            fogMaterial=null;
        }
    }

    private  void SetRay(){
        Matrix4x4 fourPoint=Matrix4x4.identity; //角度与弧度的转换 Π 与 0
        float fov = mainCamera.fieldOfView;
        float near =mainCamera.nearClipPlane;
        float aspect=mainCamera.aspect;

        float halfHeight = near* Mathf.Tan(fov*0.5f*Mathf.Deg2Rad);
        float halfRight = halfHeight * aspect;
        Vector3 toHeight = mainCamera.transform.up * halfHeight;
        Vector3 toRight = mainCamera.transform.right * halfRight;

        Vector3 topLeft = mainCamera.transform.forward*near + toHeight - toRight;
        float scale= topLeft.magnitude/near;
        topLeft*= scale;

        Vector3 topRight=mainCamera.transform.forward*near +toHeight +toRight;
        topRight*=scale;

        Vector3 bottomLeft=mainCamera.transform.forward*near - toHeight -toRight;
        bottomLeft*=scale;

        Vector3 bottomRight=mainCamera.transform.forward*near -toHeight +toRight;
        bottomRight*=scale;

        fourPoint.SetRow(0,bottomLeft);
        fourPoint.SetRow(1,bottomRight);
        fourPoint.SetRow(2,topRight);
        fourPoint.SetRow(3,topLeft);
        
        fogMaterial.SetMatrix("_FourRay",fourPoint);
    }

    void OnRenderImage(RenderTexture src,RenderTexture dest){
        if(fogMaterial!=null){
            SetRay();
            fogMaterial.SetVector("_lightDir",lightDir);

            fogMaterial.SetColor("_fogColor",fogColor);
            fogMaterial.SetFloat("_lightIntensity",LightIntensity);
            fogMaterial.SetColor("_lightColor",LightColor);
            fogMaterial.SetFloat("_cloudSize",cloudSize);
            fogMaterial.SetFloat("_moveSpeed",moveSpeed);
            fogMaterial.SetFloat("_startDistance",startDistance);
            fogMaterial.SetFloat("_rayDistance",endDistance);
            fogMaterial.SetInt("_stepNum",stepNum);
            fogMaterial.SetFloat("_maxHeight",maxHeight);

            fogMaterial.SetFloat("_densityInstence",densityInstence);
            fogMaterial.SetFloat("_minCloud",cloudDenAdjust.x);
            fogMaterial.SetFloat("_maxCloud",cloudDenAdjust.y);
            fogMaterial.SetInt("_cloudFra",cloudFra);
            fogMaterial.SetFloat("_g",g);
            
            
            Graphics.Blit(src,dest,fogMaterial);
        }else{
            Graphics.Blit(src,dest);
        }
    }

}

shader1

Shader "imageEffect/volumeFog"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "VolumeFog.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 Ray:TEXCOORD1;
            };

            //sceneCol  depthtexture
            sampler2D _MainTex;
            float2 _MainTex_TexelSize;
            sampler2D _CameraDepthTexture;

            float4x4 _FourRay;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                int index=0;
                if( o.uv.x<0.5 && o.uv.y<0.5){
                    index = 0 ;
                }else if( o.uv.x>0.5 && o.uv.y<0.5){
                    index = 1;
                }else if( o.uv.x>0.5 && o.uv.y>0.5){
                    index = 2;
                }else if( o.uv.x<0.5 && o.uv.y>0.5){
                    index = 3;
                }

                #if UNITY_UV_STARTS_AT_TOP
                    if (_MainTex_TexelSize.y < 0)
                        index = 3 - index;
                #endif

                o.Ray = _FourRay[index];
                return o;
            }


            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 sceneCol=tex2D(_MainTex,i.uv);
                //depth
                float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);
                float viewDepth = LinearEyeDepth(depth);
                float rz = viewDepth *length(i.Ray.xyz);
                //camera ro+rd
                float3 ro = _WorldSpaceCameraPos;
                float3 rd = normalize(i.Ray.xyz);
                
                return ProcessRayMarch(ro,rd,rz,sceneCol);

            }
            ENDCG
        }
    }
}

shader2


#ifndef VOLUME_FOG
#define VOLUME_FOG

#define HASHSCALE1 .1031

float Hash13(float3 p3)
{
    p3  = frac(p3 * HASHSCALE1);
    p3 += dot(p3, p3.yzx + 19.19);
    return frac((p3.x + p3.y) * p3.z);
}

float VNoise(float3 p)
{
    float3 pi = floor(p);
    float3 pf = p - pi;
    
    float3 w = pf * pf * (3.0 - 2.0 * pf);
    
    return  lerp(
                lerp(
                    lerp(Hash13(pi + float3(0, 0, 0)), Hash13(pi + float3(1, 0, 0)), w.x),
                    lerp(Hash13(pi + float3(0, 0, 1)), Hash13(pi + float3(1, 0, 1)), w.x), 
                    w.z),
                lerp(
                    lerp(Hash13(pi + float3(0, 1, 0)), Hash13(pi + float3(1, 1, 0)), w.x),
                    lerp(Hash13(pi + float3(0, 1, 1)), Hash13(pi + float3(1, 1, 1)), w.x), 
                    w.z),
                w.y);

}

float _moveSpeed;
float3 _lightDir;
float3 _fogColor;
float _g;
int _stepNum;
float _startDistance;
float _rayDistance;
float _densityInstence;
float _minCloud;
float _maxCloud;
int _cloudFra;
float _maxHeight;
float _cloudSize;
float3 _lightColor;
float _lightIntensity;

float FBM( float3 p,float iterNum,float _maxHeight,float _cloudSize)
{
    //设置高度
    float alpha = smoothstep(_maxHeight , _maxHeight-5, p.y);
    //调整cloud大小
    p *= _cloudSize/5;
    p+= float3(1,1,3) * _Time.y * _moveSpeed;
    float f = 0.0;
    float s = 0.5;
    float s2 = 2.00;
    float sum = 0.0;
    for(int i = 0;i< iterNum;i++){
        f += s * VNoise( p ); 
        p *=s2;
        sum+=s;
        s*= 0.5;s2+=0.01;
    }
    return (f/sum) * alpha;
}

//得到密度
float getDen(float3 p ){
    float den = FBM(p , _cloudFra , _maxHeight,_cloudSize);  
    den = smoothstep(_minCloud , _maxCloud,den);
    return den;
}


float4 rayMarch(float3 ro , float3 rd , float rz,float3 sceneCol){

    //num
    float4 light=float4(0,0,0,0);
    float t = _startDistance;   //开始距离

    float stepSize = (_rayDistance-_startDistance)/_stepNum;
    float3 p=float3(0,0,0);

    for(int i =0;i<_stepNum;i++){

        p = ro + t *rd;

        //各种限制节省投射次数------------------------------------
        if(light.a>0.99||t>=rz){
            break;
        }
        if(rd.y>0 && p.y>_maxHeight){
            break;
        }

        //消除层投射与场景的硬结边1
        float alpha=1.0;
        if(t>=rz-10){
            alpha = smoothstep(0,10,rz-t);
        }

        //每个采样点的各种限制------------------------------------
        float den = getDen(p);

        if(den>0.01){
            //此处的云是否被灯源方向的云遮挡
            float dif =  clamp((den - getDen(p-1*_lightDir))/0.6,  0.0 , 1.0 );
            float3 lin = float3(0.65,0.7,0.75)*1.4 + _lightColor * dif * _lightIntensity;

            //浓度做Alpha
            float4 col = float4(_fogColor,den); 
            col.xyz*=lin;

            //透光比 距离与浓度
            col.xyz = lerp(col.xyz , sceneCol.xyz , exp(-t*t));  

            //消除层投射与场景的硬结边2
            col.a *= alpha ;

            //添加散射衰减
            float cosTheta=dot(_lightDir,-rd);
            float result = 1/(4*3.14)*(1-pow(_g,2))/pow(1+pow(_g,2)-2*_g*cosTheta,1.5);

            //此层的颜色 * den * 散射 * 强度控制
            col.xyz *= col.a * result * _densityInstence;
            light = light + col*(1.0-light.a);
        }
        t+=stepSize;
    }
    
    return light;

}

float4 ProcessRayMarch(float3 ro,float3 rd, float rz,float3 sceneCol){
    float4 fogmask =  rayMarch(ro,rd,rz,sceneCol);
    float3 final = fogmask.xyz * fogmask.w + (1-fogmask.w) * sceneCol;
    return float4(final,1.0);
}

#endif

你可能感兴趣的:(体积雾——实践篇之十八般武艺)