赞
踩
最近看到一个效果还不错的体积云,了解了下其实现原理,将其实现思路做一个简单的整理和记录,并重写该Shader
原始效果图:
该效果来自名为CloudPuff的Unity资源包中示例场景截图
实现的主要思路为:
勾选粒子的 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 选项,设置相关参数
单个粒子系统生成效果:
多个粒子系统生成效果:
相关参考:
天源:Matcap Shader 详解【1】-基础思想与U3D实现zhuanlan.zhihu.com
https://blog.csdn.net/poem_qianmo/article/details/55803629blog.csdn.netCopyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。