美文网首页shader
体积雾——实践篇之十八般武艺

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

作者: 万里_aa3f | 来源:发表于2019-02-26 01:04 被阅读11次

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

    密度
    散射
    灯光阴影

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


    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<Camera>();
            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
    

    相关文章

      网友评论

        本文标题:体积雾——实践篇之十八般武艺

        本文链接:https://www.haomeiwen.com/subject/actoyqtx.html