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

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

密度
散射
灯光阴影

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


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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容