- 先看看效果
- 实现
- 火堆的假泛光
- 软粒子
- 火堆的高温扭曲
- 放一个Billboard面片
- 但是我们Billboard会给其他的物体挡住
- 添加`_OffsetToCam`调整参数模型空间坐标偏移,从而调整深度
- 准备一张噪点图扭曲UV用
- 但是扭曲的边缘太硬的过渡
- 添加半径值过渡平滑
- 太远是扭曲还是太强
- 添加view space z深度判断控制扭曲响度
- 另一种实现方式
- 区别
- ZTest不同
- 扭曲方式
- 网格不同
- Project
- Refernces
- OpenGL Tutorials - Billboards
- Unity Shader - Billboard 广告板/广告牌
学习最好的方法就是:学以致用。
这段时间我报了一些数学相关的知识点课程,写完这篇博客后,后面过一段时间才有空继续写。 内功的东西,真的太缺了,太影响学习其他内容的速度,所以基础的东西千万千万别小看,往往越是简单的基础知识点,就越不能小看。在学习过程中,我将非常非常详尽的记录到我的:程序员的数学-私有,的专栏里,但只是供自己使用,加深、巩固理解,但是这些记录的知识点我不能公开,有大量的视频资源截图引用说明,所以会有版权的问题,当然也有大量自己的总结。
先看看效果我用Unity Cube随意搭建的场景,下面的场景元素都是Cube缩放旋转平移来完成的(除了人物和火焰粒子):
- 远处的山
- 后面的Jave.Lin广告牌(这个就不是我们的功能上的Billboard了,它就真的一个广告牌)
- 石凳
- 火堆木材
火焰使用了Unity内置的Particle System。调整了一些属性可达成的效果,还是比较好用的。
所以我们之前学习了Billboard之后,就可以用它来实现一些效果:
- 火堆的假泛光
- 火堆的高温扭曲
加泛光我我写了个软粒子处理的(手机端一般不开这个效果)
软粒子的意思是:当:片段可见时,片段对应深度缓存的深度与粒子的片段深度很近时,就alpha值很小,几乎看不到,然后随着距离大时,又慢慢的恢复alpha时,这样看起来就不会太硬的过渡边界。
具体Shader如下:
// jave.lin 2020.03.25 - 软粒子
Shader "Custom/SoftParticleAdditive" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_MainColor ("Color", Color) = (1,1,1,1)
}
SubShader {
Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProjector"="True" }
LOD 100 Cull Off Lighting Off Fog { Mode Off } ZWrite Off
Blend One One
ZTest Always // 一直测试通过都OK,因为已处理深度柔和处理了alpha
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float4 projPos : TEXCOORD1;
};
sampler2D _MainTex;
fixed4 _MainColor;
sampler2D _CameraDepthTexture;
v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.projPos = ComputeScreenPos(o.vertex);
COMPUTE_EYEDEPTH(o.projPos.z); // eyeZ
return o;
}
fixed4 frag (v2f i) : SV_Target {
float fragZ = i.projPos.z;
float buffZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)));
// 只有fragZ < buffZ才叠加颜色,与buffZ差值越小,alpha越小
float fade = saturate(max(0, buffZ - fragZ) * _MainColor.a);
fixed4 col = tex2D(_MainTex, i.uv) * _MainColor * fade;
return col;
}
ENDCG
}
}
}
注意我们这个软粒子的ZTest Always
的,但处理了深度柔和alpha处理。
实现比较简单,挂载一个:Billboard Quad面片,片面 GrabPass ColorBuffer后,对背景扭曲
放一个Billboard面片 这个Billboard如下代码:
v2f vert(a2v v) {
v2f o;
float4 viewSpacePos = mul(UNITY_MATRIX_MV, float4(0,0,0,1));
viewSpacePos.xyz += v.vertex.xyz * _BillboardSize.xyz;
o.vertex = mul(UNITY_MATRIX_P, viewSpacePos);
o.uv = v.uv;
o.projPos = ComputeGrabScreenPos(o.vertex);
return o;
}
Billboard的思路是,将Billboard中心点转换到ViewSpace下,在用顶点本身的本地坐标值与_BillboardSize.syz
来缩放顶点位置,然后再转换到clip space
。
_OffsetToCam
调整参数模型空间坐标偏移,从而调整深度
我们提供了一个参数_OffsetToCam
可以对Billboard的模型空间坐标往相机这边偏移一些:float4 viewSpacePos = mul(UNITY_MATRIX_MV, float4(toCamDir * _OffsetToCam, 1.0));
v2f vert(a2v v) {
v2f o;
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
float3 toCamDir = normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz);
float4 viewSpacePos = mul(UNITY_MATRIX_MV, float4(toCamDir * _OffsetToCam, 1.0));
viewSpacePos.xyz += v.vertex.xyz * _BillboardSize.xyz;
o.vertex = mul(UNITY_MATRIX_P, viewSpacePos);
o.uv = v.uv;
o.projPos = ComputeGrabScreenPos(o.vertex);
return o;
}
如下效果:
然后我们使用GrabPass { "_bgTex" }
,这里我偷懒,其实可以不使用GrabPass
,同样可以拿到ColorBuffer
内容,而且消耗没有GrabPass
的大,可参考我之前写的:Unity Shader - 实现武器热扭曲拖尾效果(不需要GrabPass)
拿到屏幕内容后,使用一张NoiseTex
然后在Fragment结对采样对UV偏移,在使用偏移后的UV来扭曲屏幕内容,效果如下:
float4 frag(v2f i) : SV_Target {
i.projPos.xy /= i.projPos.w;
float noise = tex2D(_NoiseTex, float4(i.uv - _Time.xy * _Speed, 0, 0)).r;
i.projPos.y += noise * _Strength; // 这些地方都可以优化除法为乘法
return tex2D(_BgTex, i.projPos.xy);
}
运行效果如下:
但是扭曲的边缘太硬的过渡
我们可以添加一个Fade的半径值_Radius
,只让面片中间指定半径内的平滑过渡,我们直接将过渡的效果输出看看,代码如下:
float2 dist = i.uv - 0.5;
float fade = 1 - saturate(dot(dist, dist) / (_Radius * _Radius));
return fade;
运行看看:
黑色到白色之间过渡,就是处理扭曲的强弱程度的值,黑色不扭曲,白色扭曲。
将fragment代码调整一下:
float4frag(v2f i) : SV_Target {
float2 dist = i.uv - 0.5;
float fade = 1 - saturate(dot(dist, dist) / (_Radius * _Radius));
i.projPos.xy /= i.projPos.w;
float noise = tex2D(_NoiseTex, float4(i.uv - _Time.xy * _Speed, 0, 0)).r;
i.projPos.y += noise * _Strength * fade; // 这些地方都可以优化除法为乘法
return tex2D(_BgTex, i.projPos.xy);
}
在运行看看效果:
嗯,这时效果好很多了
太远是扭曲还是太强但是,如果我们将镜头挪到比较远看效果时,发现扭曲太强了,如下效果:
所以我们还要对距离开控制扭曲强度,在vert函数再添加一个unity内置的宏处理:COMPUTE_EYEDEPTH
计算view space下的Z值,赋值到o.projPos.z
,并将o.projPos.z
传到fragment阶段使用,在fragment下就可以使用其控制扭曲强度:
o.projPos = ComputeGrabScreenPos(o.vertex);
COMPUTE_EYEDEPTH(o.projPos.z);
然后我们在frag中直接输出距离的值与我们添加的一个:_FadeOutDist
的参数,超过该距离就不扭曲(太远就看不到扭曲)
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float4 projPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
...
o.projPos = ComputeGrabScreenPos(o.vertex);
COMPUTE_EYEDEPTH(o.projPos.z);
return o;
}
float4 frag(v2f i) : SV_Target {
return saturate(1 - i.projPos.z / _FadeOutDist);
}
运行效果如下: 同样也是,白色就是扭曲,黑色不扭曲,可以看到距离比较远时,几乎就原来越黑了,近时就很白。OK我们再将代码整理一下:
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float4 projPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
float3 toCamDir = normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz);
float4 viewSpacePos = mul(UNITY_MATRIX_MV, float4(toCamDir * _OffsetToCam, 1.0));
viewSpacePos.xyz += v.vertex.xyz * _BillboardSize.xyz;
o.vertex = mul(UNITY_MATRIX_P, viewSpacePos);
o.uv = v.uv;
o.projPos = ComputeGrabScreenPos(o.vertex);
COMPUTE_EYEDEPTH(o.projPos.z);
return o;
}
float4 frag(v2f i) : SV_Target {
float2 dist = i.uv - 0.5;
float fade = 1 - saturate(dot(dist, dist) / (_Radius * _Radius));
i.projPos.xy /= i.projPos.w;
float noise = tex2D(_NoiseTex, float4(i.uv - _Time.xy * _Speed, 0, 0)).r;
i.projPos.y += noise * _Strength * fade * saturate(1 - i.projPos.z / _FadeOutDist); // 这些地方都可以优化除法为乘法
return tex2D(_BgTex, i.projPos.xy);
}
运行看看:
OK,可以看到远距离时,就不会扭曲太强而影响体验了,最后再将代码整理,运行效果如下:
OK,到这就完成了。
另一种实现方式可以参考:Heat Distortion Shader Tutorial
它的实现方式与我上面的区别在于:
区别 ZTest不同我的方式:默认:ZTest LessEqual 她的方式:ZTest Always
这就在交互效果上的差距会比较大。如:她的方式,可以会将比热扭曲更靠近镜头的不透明物体也扭曲了。 而我的方式 ZTest LessEqual,并且在世界坐标下,提供了一个:OffsetToCam来偏差调整,就问题的情况就会好很多。
扭曲方式我的方式:在frag扭曲uv来采样 她的方式:在vert扭曲顶点坐标,uv不变,那么fragment阶段的片段坐标就会伸纹素,达到扭曲效果
网格不同我的:一个Quad就好了,四个顶点 她的:一个Plane,121个顶点
当然她的在顶点阶段处理顶点位移来替代片段中扭曲uv的方式,性能会更好一些。
Projectbackup : UnityShader_HeatDistortionTesting_2018.3.0f2
Refernces- OpenGL Tutorials - Billboards
- Unity Shader - Billboard 广告板/广告牌
- Heat Distortion Shader Tutorial