Shader笔记——使用粒子序列图实现体积云

最近看到一个效果还不错的体积云,了解了下其实现原理,将其实现思路做一个简单的整理和记录,并重写该Shader 
其效果图: 
 CloudPuff
该效果来自名为CloudPuff的Unity资源包中示例场景截图 
实现的主要思路为:

  • 使用MatCap对预先存储的光照进行采样 
    MatCap是使用存储了视空间下不同法线方向的球形光照纹理,在计算光照时直接对纹理进行采样,运算效率高,但也有其局限,只能适用于单一材质的模型,并且无法对光源位置和相机位置的变化做出响应。MatCap Shader的基本思路是,使用某特定材质球的贴图,作为当前材质的视图空间环境贴图(view-space environment map),来实现具有均匀表面着色的反射材质物体的显示。考虑到物体的所有法线的投影的范围在x(-1,1),y(-1,1),构成了一个圆形,所以MatCap 贴图中存储光照信息的区域是一个圆形。 
    关于MatCap的详细原理,可以参考这里这里

  • 使用粒子的序列图播放实现云层的变化 
     粒子序列图
    勾选粒子的 TextureSheetAnimation 的选项,Shader中传入云层的序列帧纹理并设置相关参数即可实现云层的序列帧动画

Shader部分 
Shder主要分为两部分,一个是边缘光Pass,另一个是主体光照颜色计算Pass: 
边缘光Pass部分: 
边缘光Pass部分比较简单,主要是对 边缘光的纹理进行采样:

v2f vert(a2v v){
            v2f o;
            o.pos=UnityObjectToClipPos(v.vertex);
            o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
            o.scrPos=ComputeScreenPos(o.pos);
            o.color=v.color;

            return o;
        }

        fixed4 frag (v2f i):SV_Target{
            fixed4 col=tex2D(_MainTex,i.uv);
            col.rgb*=0;
            col.a*=i.color.a;
            fixed4 edgeCol=tex2D(_EdgeLight,i.scrPos.xy/i.scrPos.w);
            col.rgb=(edgeCol)*col.a*_EdgeStrength;

            return col;
        }

由于边缘光的纹理是通过RenderTexture对当前相机角度下的"场景"进行捕捉,采样时使用当前像素点对应的视口坐标,在计算颜色输出时,使用到了主纹理的Alpha通道值

主体光照计算部分 
主体光照计算部分主要是对MatCap的球形纹理进行采样,由于纹理的坐标范围是[0,1],而视空间下的法线范围是在[-1,1],因此需要将视空间下的法线做一个范围映射: 
即:

    o.cap.xyz=worldNormal*0.5+0.5;   

另外,在开启软粒子效果时,为了使粒子与场景中的有深度值的物体之间过渡自然,会有一个深度判断,并将判断结果影响其Alpha值, 
雾效的叠加过程中,与边缘光的纹理处理类似,是通过RenderTexture对当前相机角度下的"场景"进行捕捉,采样使用当前像素点对应的视口坐标 
着色器代码,附相关注释:

    v2f vert(a2v v){
            v2f o;
            o.pos=UnityObjectToClipPos(v.vertex);
            o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
            //法线变换,转置逆矩阵
            fixed3 worldNormal=normalize(unity_WorldToObject[0].xyz*v.normal.x+unity_WorldToObject[1].xyz*v.normal.y+unity_WorldToObject[2].xyz*v.normal.z);
            //转换法线到视空间
            worldNormal=mul((fixed3x3)UNITY_MATRIX_V,worldNormal);
            o.cap.xyz=worldNormal*0.5+0.5;
            //计算顶点在屏幕空间的位置,未归一化
            o.scrPos=ComputeScreenPos(o.pos);
            //如果使用软粒子效果,计算视空间下的深度值,后续与场景深度值作比较
            #ifdef SOFTPARTICLES_ON
            COMPUTE_EYEDEPTH(o.scrPos.z);
            #endif
            UNITY_TRANSFER_FOG(o,o.pos);
            o.color=v.color;

            return o;
        }

        fixed4 frag(v2f i):SV_Target{
            //如果使用软粒子效果,通过深度值比较,距离云层近的物体,云层透明度高
            #ifdef SOFTPARTICLES_ON
            //对相机深度纹理采样(输入的是未归一化的srcPos,方法内部做srcPos.xy/srcPos.w透视除法,得到视口坐标)
            //通过LinearEyeDepth方法转换到视空间下的深度
            fixed sceneZ=LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture,UNITY_PROJ_COORD(i.scrPos)));
            //这里的i.scrPos.z经过 COMPUTE_EYEDEPTH(o.scrPos.z) 已经存储的是视空间里的深度
            fixed partZ=i.scrPos.z; 
            fixed fade=saturate(_ParticleFade*(sceneZ-partZ));
            i.color.a*=fade;
            #endif

            //光照纹理采样
            fixed4 mc=tex2D(_MatCapLight,i.cap);
            mc.a=1;
            //主纹理采样
            fixed4 col=tex2D(_MainTex,i.uv);
            col.rgb*=i.color*mc*3;
            col.a*=i.color.a;

            //雾效叠加
            #if ADVFOG_ON
            fixed4 advFog=tex2D(_AdvFog,i.scrPos.xy/i.scrPos.w);
            col.rgb=col.rgb+(advFog*_FogStrength);
            #endif
            #if ADVFOG_ON
            advFog.rgb*=0.75;
            UNITY_APPLY_FOG_COLOR(i.fogCoord,col,advFog);
            #endif
            #if ADVFOG_OFF
            UNITY_APPLY_FOG_COLOR(i.fogCoord,col,UNITY_LIGHTMODEL_AMBIENT);
            #endif

            return col;
        }

场景设置部分 
场景设置中,需要提前准备与SkyBox对应的光照纹理,边缘光纹理,环境纹理,即:  

光照纹理
光照纹理
边缘光纹理
边缘光纹理

 

环境光纹理
环境光纹理题


并将该纹理赋予三个双面球体,为什么要这么做呢? 
前面提到过,MatCap的局限在于,使用MatCap的光照效果,由于纹理贴图是静态的,因此在场景中无法对光源和相机的位置变化做出光影反应,同理使用RenderTexture作为边缘光和雾效纹理也会有同样的问题   

那如果这个纹理是动态的,会随着相机角度变化而变化呢? 

因此,在上述 双面球体的中心分别使用三个相机,并与场景主相机保持同步旋转,这样采到的RendTexture就能随着主相机的角度变化而变化,由于光照纹理是使用MatCap,因此需要将相机改为正交模式,并将近裁剪面设置为 1,远裁剪面设置为-1,这样相机就能够得到一个球外视角,中间球形的动态纹理,
对于边缘光纹理和雾效纹理,相机保持透视模式,设置较小的远近裁剪面范围,只捕捉到双面球体内部的贴图就可以了

Shader的_MainTex为一个云朵的序列帧图:

新建一个粒子系统,并将该Shader生成的材质赋予该粒子系统,勾选 TextureSheetAnimation 选项,设置相关参数

 

单个粒子系统生成效果: 


多个粒子系统生成效果:

  • 12
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
对于SPH(Smoothed Particle Hydrodynamics)粒子水模拟,可以使用Compute Shader实现。下面是一个简单的实现步骤: 1. 粒子初始化:使用Compute Shader生成粒子的初始位置和速度,并将它们存储在一个结构体数组中。 2. 粒子排序:根据粒子位置,使用Compute Shader粒子进行排序。这个排序可以使用任何一种排序算法,例如基数排序或快速排序。 3. 密度估计:根据周围粒子的位置和密度,使用Compute Shader计算每个粒子的密度。这个过程可以使用一个双层循环来实现,其中外层循环遍历所有的粒子,内层循环遍历与当前粒子距离在一定范围内的其他粒子。 4. 压力计算:使用Compute Shader计算每个粒子的压力。这个过程可以使用与密度估计类似的双层循环来实现。 5. 力计算:根据粒子的密度和压力,使用Compute Shader计算每个粒子所受的力。这个过程也可以使用与密度估计和压力计算类似的双层循环来实现。 6. 位置更新:根据粒子所受的力和速度,使用Compute Shader更新每个粒子的位置和速度。这个过程可以使用一个单独的Compute Shader实现。 7. 渲染:最后,将粒子的位置和其他属性传递给GPU渲染管线,使用Compute Shader进行渲染。 需要注意的是,这只是一个简单的实现步骤,实际的实现可能会更加复杂。例如,为了加速计算,可以使用各种优化技术,例如领域分解和多级网格。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值