jquave 发表于 2022-4-3 12:20

Unity+FlowMap+薄膜干涉的泡泡渲染效果

首先看效果



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的分享

KaaPexei 发表于 2022-4-3 12:23

18赞6感谢50收藏0评论,大型惨案现场!赞,效果挺不错的[惊喜]
页: [1]
查看完整版本: Unity+FlowMap+薄膜干涉的泡泡渲染效果