|
首先看效果
https://www.zhihu.com/video/1489957292074704897
效果我觉得还是挺好的..不过考虑到游戏中应用场景还是太少了 ,所以作用不大。
看了看知乎上几位博主的泡泡实现,总感觉要么就是太高端了,要么就是效果不好,最近刚好看到FlowMap。觉得拿来做泡泡正合适。
存在的问题
从结果大致一看 的确是个泡泡了 而且是有流动效果的泡泡,
跟真实的图片对比一下,特别小的Noise还是没有,可以考虑多采样个NOise图 或者是在绘制FlowMap的时候多讲究讲究
实现思路
薄膜干涉部分
使用FlowMap 采样水面法线贴图,作为泡泡表面法线 计算模拟法线,获得干扰过后的入射角。
并且取FlowMap的一个值 作为页面厚度的干扰值。
组合作为UV值采样下面的贴图。
这么看来确实还真挺物理的,利用泡泡表面流动采样真实计算出来的贴图。
镜面反射光部分
因为泡泡是双层的,所以会生成两个镜面反转的像。
只需要正常使用 ReflectDir和倒转ReflectDir ,采样两次CubeMap后叠加即可实现。
有了思路之后,这个Shader我觉得最难的其实是中间的调参的过程,如果利用手头的素材让效果更好才是难点
正式开始计算
需要用到的贴图资源
薄膜干涉贴图、FLowmap、反射的CubeMap、水面的Normal
薄膜干涉贴图下载剪切下就好
FlowMap 按照百人计划中的使用方法 绘制出来,
Ball模型是按纵向展开的,绘制的时候特意保证从上往下流动的感觉,因为泡泡上面的水也受到重力的影响向下流。所以我就大概按照这个趋势画出来。
反射的QubeMap用个探针Bake出来就好,
Normal就拿隔壁Demo的水面效果,用起来感觉不错。
计算薄膜干涉
第一步 计算采样NormalMap的UV
因为我们要使用FLowMap 采样Normal
同时为了避免FlowMap太重复 跟个岩浆的感觉一样,所以也要给FlowMap加上Time 让FlowMap的UV也有所变化。
实现流程可以参考FlowMap的使用方法
(这里的代码可能不能复制过去直接运行,看个大概 最后有完整的Shader)
//采样 FLowMap
float2 flowUV = (i.uv.xy+_Time*0.1*_FlowUVDir)*_FlowTex_ST.xy;
float3 flowDir = tex2D(_FlowTex, flowUV) * 2 - 1;
flowDir *= _FlowSpeed;
//控制时间周期
float phase0 = frac(_Time * 0.1 * _TimeSpeed);
float phase1 = frac(_Time * 0.1 * _TimeSpeed + 0.5);
float4 tex0 = tex2D(_NormalTex, i.uv.xy - flowDir.xy * phase0);
float4 tex1 = tex2D(_NormalTex, i.uv.xy - flowDir.xy * phase1);
float flowlerp = abs((0.5 - phase0) / 0.5);
float4 packedNormal = lerp(tex0, tex1, flowlerp) ;
float3 normalTS = UnpackNormal(packedNormal);
计算后 获得使用FlowMap采样出来的NormalMap
第二步,把采样出来的NormalMap用TBN 套用到模型表面并计算nDotV
float3 tDirWS : TEXCOORD1;
float3 nDirWS : TEXCOORD2;
float3 bDirWS : TEXCOORD3;
o.tDirWS = normalize( mul( unity_ObjectToWorld, float4( v.tangent.xyz, 0.0 ) ).xyz );
o.nDirWS = UnityObjectToWorldNormal(v.normal);
o.bDirWS = normalize(cross(o.nDirWS, o.tDirWS) * v.tangent.w);
//TBN
half3x3 TBN = float3x3(i.tDirWS,i.bDirWS,i.nDirWS);
//计算Ndir
half3 nDirWS = normalize(mul(normalTS,TBN));
float nDotv = saturate(dot(nDirWS,vDirWS));
让我们看下带有FlowMap的菲尼尔效果
https://www.zhihu.com/video/1490014719922991106
如果使用这个直接作为采样薄膜干涉图的UV 就会发现 会有一个中心向周围的整体颜色变化趋势。
看真实泡泡图的话,其实这种感觉没那么强烈。
所以下一步
就是使用 采样出来的nDirWS dot vDirWS 减去 原生的模型的 nDirWS dot vDirWS
相当于减轻了菲尼尔效果 只保留了部分采样的Noise效果。
(这里可能不需要这么麻烦,可能有其他更好的办法 请大佬帮我指出)
//减弱菲尼尔效果 对泡泡影响
float nDotv = saturate(dot(nDirWS,vDirWS));
float nDotv2 = saturate(dot(i.nDirWS,vDirWS));
float RampYAxis = saturate((nDotv-nDotv2*0.95)+0.4-wave*0.8);
不带Wave 光使用nDotV相减的结果
可能有人注意到上面的这个Wave 了
Wave是我自己根据FlowMap采样出来的一个数值,感觉最后结果中少了点大块的颜色,
所以利用了上面FlowMap计算出来的过程量 ,绘制出个Wave波,参与运算。 让效果好点
//引入波控制
float wave = lerp(flowDir.xy * phase0,flowDir.xy * phase1,flowlerp);
单wave的效果
把Wave纳入计算后 得到的效果
将采样出来的Wave值 作为采样薄膜干涉图的Y轴
采样图的X轴,并采样图片
X轴就利用NormalMap的X 作为模拟的厚度,并加入自定义的偏移量和权重来控制
采样出来的颜色也乘以一个亮度方便控制。
float RampXAxis = _RampXAxisOffset+packedNormal.r*_RampXAxisNoiseStrength;
float2 rampTexUV = float2(RampXAxis,RampYAxis);
float3 rampColor = tex2D(_RampTex,rampTexUV)*_ColorReflectIntensity;
好像有点诡异
计算反射图像
因为泡泡是反射两个颠倒的Cube
所以我们需要
1 计算反射方向
2计算反的反射方向
3利用这两个方向分别采样Cube
4将这两个采样出来的结果合并到一起
过程都很简单 直接上代码
o.posWS=mul(unity_ObjectToWorld,v.vertex);
o.rDirWS = reflect(-UnityWorldSpaceViewDir(o.posWS),o.nDirWS);
//反射效果
float3 NegaReflectDir = float3(-i.rDirWS.x,-i.rDirWS.y,i.rDirWS.z);
//计算两个反射 然后混合
float3 reflectCol1 = texCUBE(_EnvCube,i.rDirWS)*_ReflectAmount;
float3 reflectCol2 = texCUBE(_EnvCube,NegaReflectDir)*_ReflectAmount;
float3 reflectCol = reflectCol1+reflectCol2;
一样加入了一些参数控制反射的亮度
效果还不错
混合图像
其实我觉得这个最难的就是混合图像 调参
有几个要点,
薄膜干涉计算出来的颜色 受到反射图像的明度影响很大,
明度高的地方反射出来的颜色也很明显
(透明度也是这样,越亮越不透明)
真实的图像
所以
首要,就是先计算反射图像的明度 然后作为权重来控制反射颜色
其次,反射图像的对比度也比我计算的结果更强一点 ,需要 平方一下增加下对比度
还有,泡泡的边缘效果也强烈一些 可以计算出 fresnel 控制下强度。
大概思路就是这样 剩下全是自己控制大小调参的过程了
//菲尼尔效果
float fresnel= pow(1-nDotv2,_FresnelPow);
//开始混合
//获取明度
float reflectLumin = dot(reflectCol,float3(0.22,0.707,0.071));
//Ramp颜色受反射图像明度影响很大
float3 finalRampCol = rampColor*(pow(reflectLumin,1.5)+0.05);
finalRampCol = pow(finalRampCol,1.4);
float3 finalCol =finalRampCol+fresnel*_FresnelIntenisty*finalRampCol*reflectLumin + reflectCol*reflectCol;
//透明度受反射图像明度 边缘厚度影响
float finalAlpha = _BubbleAlpha*(reflectLumin*0.5+0.5)+fresnel*0.2;
//输出
return float4(finalCol ,finalAlpha);
所有Shader代码
Shader "Unlit/Bubble"
{
Properties
{
_NormalTex ("NormalTex", 2D) = "bump" {}
_FlowTex("_FlowTex",2D) ="white"{}
_TimeSpeed("_TimeSpeed",float) =1
_FlowSpeed("_FlowSpeed",float) =1
_FlowUVDir("flow UV Dir",vector) = (1,1,1,1)
_RampTex ("RampTex",2D) ="white" {}
_RampXAxisOffset ("X Axis_Offset",Range(0,1))=0.2
_RampXAxisNoiseStrength("Ramp Tex X Axis Noise Strength",float) = 2
_ColorReflectIntensity("薄膜干涉亮度",Range(0,20))=2
_EnvCube("Reflection Cubemap",Cube)="_Skybox" {}
_ReflectAmount("反射的强度",Range(0,1))=0.5
_BubbleAlpha("泡泡透明度",Range(0,2)) = 1
_FresnelPow("菲尼尔 对比度",float) =3
_FresnelIntenisty("菲尼尔 亮度",float)=0.2
}
SubShader
{
Tags { "RenderType"="Transparent" }
LOD 100
Pass
{
Tags {"LightMode" = "ForwardBase"}
ZWrite off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 normal:NORMAL;
float4 tangent:TANGENT;
};
struct v2f
{
float4 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 tDirWS : TEXCOORD1;
float3 nDirWS : TEXCOORD2;
float3 bDirWS : TEXCOORD3;
float4 posWS : TEXCOORD4;
float3 rDirWS : TEXCOORD5;
};
sampler2D _NormalTex;
sampler2D _FlowTex;
float4 _FlowTex_ST;
sampler2D _RampTex;
float _FlowSpeed;
float _TimeSpeed;
float2 _FlowUVDir;
float _RampXAxisOffset;
float _RampXAxisNoiseStrength;
//反射
samplerCUBE _EnvCube;
float _ReflectAmount;
float _BubbleAlpha;
float _ColorReflectIntensity;
float _FresnelPow;
float _FresnelIntenisty;
v2f vert (appdata v)
{
v2f o;
o.uv.xy = v.uv;
o.posWS=mul(unity_ObjectToWorld,v.vertex);
o.vertex = UnityObjectToClipPos(v.vertex);
o.tDirWS = normalize( mul( unity_ObjectToWorld, float4( v.tangent.xyz, 0.0 ) ).xyz );
o.nDirWS = UnityObjectToWorldNormal(v.normal);
o.bDirWS = normalize(cross(o.nDirWS, o.tDirWS) * v.tangent.w);
o.rDirWS = reflect(-UnityWorldSpaceViewDir(o.posWS),o.nDirWS);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//TBN
half3x3 TBN = float3x3(i.tDirWS,i.bDirWS,i.nDirWS);
//采样 FLowMap
float2 flowUV = (i.uv.xy+_Time*0.1*_FlowUVDir)*_FlowTex_ST.xy;
float3 flowDir = tex2D(_FlowTex, flowUV) * 2 - 1;
flowDir *= _FlowSpeed;
//控制时间周期
float phase0 = frac(_Time * 0.1 * _TimeSpeed);
float phase1 = frac(_Time * 0.1 * _TimeSpeed + 0.5);
float4 tex0 = tex2D(_NormalTex, i.uv.xy - flowDir.xy * phase0);
float4 tex1 = tex2D(_NormalTex, i.uv.xy - flowDir.xy * phase1);
float flowlerp = abs((0.5 - phase0) / 0.5);
float4 packedNormal = lerp(tex0, tex1, flowlerp) ;
float3 normalTS = UnpackNormal(packedNormal);
//计算Ndir
half3 nDirWS = normalize(mul(normalTS,TBN));
half3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS);
//引入波控制
float wave = lerp(flowDir.xy * phase0,flowDir.xy * phase1,flowlerp);
//减弱菲尼尔效果 对泡泡影响
float nDotv = saturate(dot(nDirWS,vDirWS));
float nDotv2 = saturate(dot(i.nDirWS,vDirWS));
float RampYAxis = saturate((nDotv-nDotv2*0.95)+0.4-wave*0.8);
float RampXAxis = _RampXAxisOffset+packedNormal.r*_RampXAxisNoiseStrength;
float2 rampTexUV = float2(RampXAxis,RampYAxis);
float3 rampColor = tex2D(_RampTex,rampTexUV)*_ColorReflectIntensity;
//反射效果
float3 NegaReflectDir = float3(-i.rDirWS.x,-i.rDirWS.y,i.rDirWS.z);
//计算两个反射 然后混合
float3 reflectCol1 = texCUBE(_EnvCube,i.rDirWS)*_ReflectAmount;
float3 reflectCol2 = texCUBE(_EnvCube,NegaReflectDir)*_ReflectAmount;
float3 reflectCol = reflectCol1+reflectCol2;
//菲尼尔效果
float fresnel= pow(1-nDotv2,_FresnelPow);
//开始混合
//获取明度
float reflectLumin = dot(reflectCol,float3(0.22,0.707,0.071));
//Ramp颜色受反射图像明度影响很大
float3 finalRampCol = rampColor*(pow(reflectLumin,1.5)+0.05);
finalRampCol = pow(finalRampCol,1.4);
float3 finalCol =finalRampCol+fresnel*_FresnelIntenisty*finalRampCol*reflectLumin + reflectCol*reflectCol;
//透明度受反射图像明度 边缘厚度影响
float finalAlpha = _BubbleAlpha*(reflectLumin*0.5+0.5)+fresnel*0.2;
//输出
return float4(finalCol ,finalAlpha);
}
ENDCG
}
}
}
项目文件
打包成UnityPackages的形式 上传到百度云
链接:https://pan.baidu.com/s/1n0ruUrebWFTYDuGw87MwBw
提取码:54dw
--来自百度网盘超级会员V5的分享 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|