资源大湿 发表于 2022-3-18 10:08

[入门级技术美术] Untiy shader简单可交互能量护盾

萌新操作,先上效果

护盾效果
https://www.zhihu.com/video/1483873503874732032


https://www.zhihu.com/video/1483899269895217152


实现思路比较简单
曲面细分


[*]为了让护盾有凸出或者下凹的效果,并且让球体自然,首先实现曲面细分【可选,毕竟可以从DCC里拖一个面数很高的球】,不然使用Untiy自带的球体边缘会非常锐利



没有使用曲面细分



曲面细分后

曲面细分部分代码如下,为了方便这里直接用一个参数表示细分数,实际项目肯定要优化的
如果不了解曲面细分,可以先找大佬们的曲面细分教程看看,或者直接用sufaceShader(几行代码搞定曲面细分),如果实在觉得太难可以直接跳过(最后我附上了完整源码)
            ControPoint tessvert(appdata v)
            {
                ControPoint o;
                o.vertex = v.vertex;
                o.uv = v.uv;
                o.normal = v.normal;
                return o;
            }

            TessellationFactor hs(InputPatch<ControPoint, 3> v)
            {
                TessellationFactor o;
                o.edge = _FixedFactor;
                o.edge = _FixedFactor;
                o.edge = _FixedFactor;
                o.inside = _FixedFactor;
                return o;
            }

            
            
            
            
            
            ControPoint HullProgarm(InputPatch<ControPoint, 3> v, uint id :SV_OutputControlPointID)
            {
                return v;
            }

            
            v2f ds(TessellationFactor tessFactor, const OutputPatch<ControPoint, 3> vi, float3 bary :SV_DomainLocation)
            {
                appdata v;

                v.vertex = vi.vertex * bary.x + vi.vertex * bary.y + vi.vertex * bary.z;
                v.normal = vi.normal * bary.x + vi.normal * bary.y + vi.normal * bary.z;
                v.uv = vi.uv * bary.x + vi.uv * bary.y + vi.uv * bary.z;
                v2f o = vert(v);
                return o;
            }2.基本光照

这里只使用了单色加上一个菲涅尔反射 根据需要大家可以自己加效果
最重要的两点,一是需要对后面的物体产生扰动效果(抓屏GrabPass),二是扰动的具体算法



随便掏了张法线图

由于护盾是不透明的,我使用了双Pass渲染,主要效果集中在渲染前面的半球上,这样也可以减少性能消耗(细分和交互算法已经非常消耗性能了),对抓屏图像的扰动我的算法比较简单,相信网上有不少更好的FlowMap算法(能用就行 )
                float4 offset = tex2D(_Noise, i.noiseUV);
                float4 bumpColor1 = tex2D(_Noise, i.noiseUV + offset+ float2(_NoiseSpeed * _Time.x * _Time.x, 0));
                float4 bumpColor2 = tex2D(
                  _Noise, offset + float2(1 - i.noiseUV.y, i.noiseUV.x) + float2(
                        _NoiseSpeed * _Time.x * _Time.x, 0));
                float3 normal = UnpackNormal((bumpColor1 + bumpColor2) / 2).xyz;


                i.screenGrabPos.x = i.screenGrabPos.x + normal.r * _NoiseScale + subTemp; //subTemp是之后用于计算交互的变量
                i.screenGrabPos.y = i.screenGrabPos.y + normal.g * _NoiseScale + subTemp;
                col = tex2Dproj(_RefractionTex, i.screenGrabPos)护盾的灵魂--边缘处过渡




边上颜色更深,模拟能量沉积的效果

这个是非常简单的了,通过采样深度图,和物体片元比较深度,限定到一定范围,再和护盾颜色做插值
即比较:深度图采样的值(图中蓝色部分)和物体的深度值(图中红色部分)的差。限定到深蓝色范围



灵魂画手~~~~~~~~~~~

//在顶点着色器中。这样计算点的屏幕坐标值
                o.screenPos = ComputeScreenPos(o.pos);
               
                o.screenGrabPos = ComputeGrabScreenPos(o.pos);//这个用于计算抓屏的采样坐标
                COMPUTE_EYEDEPTH(o.screenPos.z);
<hr/>float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos));
//深度图的采样不止这一种形式
                depth = LinearEyeDepth(depth);
                float halfWidth = _IntersectionWidth / 2;
                float diff = saturate(abs(i.screenPos.z - depth) / halfWidth);
                col = col + fresnal * _FresnalColor;
                fixed3 finalColor = lerp(_IntersectionColor.rgb, col.rgb, diff);
关于深度图采样,以及转换到视角空间或者世界空间线性值的操作我就不细讲啦
推荐一位大佬的链接详细讲解了深度图的运用和Z-Fight的相关知识 Unity Shader-深度相关知识总结与效果实现(LinearDepth,Reverse Z,世界坐标重建,软粒子,高度雾,运动模糊,扫描线效果)
如果Unity版本较低,可能需要在相机上挂一个脚本,在脚本里打开深度图抓取
void Start () {
      Camera.main.depthTextureMode = DepthTextureMode.Depth;
}顶点随机扰动

非常简单,采样一个贴图然后沿着法线偏移
                float2 vertexOffsetUV = v.uv * _VertexOffsetTex_ST.xy + _VertexOffsetTex_ST.zw;
                float VertexOffset = tex2Dlod(_VertexOffsetTex,
                                              float4(vertexOffsetUV + frac(_Time.x * _VertexOffsetSpeed), 0, 0));
                v.vertex.xyz += v.normal * _VertexOffsetIntensity * VertexOffset;最后,实现交互

游戏或者影视中的护盾受到打击后往往会出现一种扩散的波纹效果
(玩过LOL都应该知道夜之锋刃的护盾效果吧,从上到下会有一个波纹)



断剑重铸之日~骑士归来之时

高中那会看到这个效果觉得非常不错,现在也能去实现了
我的计算思路:在C#脚本中得到击中的位置,传给shader进行计算,由于是多个打击效果,所以得在shader中使用循环和判断(大佬们如果有好的优化思路请告诉我)
public class ShieldInteractive : MonoBehaviour
{
    public Material _material;
    public float scanSpeed;//波纹传播速度
    public float stopDis = 20f;//波纹消失距离 如果心有余里可以在SHADER里实现渐隐效果
    public float hitRange = 2f;//波纹宽度
    public float _HitOffsetIntensity = 1.5f;//顶点偏移强度
    public static int MaxHit = 4; //最大受打击数 超过后循环覆盖 暂定为4了,需要更多可以自己改
    public GameObject target;
   

    private Vector4[] hitPos = new Vector4;
    private float[] hitDis = new float;
    private int currentIndex = 0;//当前受击点索引

    private void Update()
    {
      if (Input.GetMouseButtonDown(0))
      {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            Physics.Raycast(ray, out hit);

            if (hit.collider.gameObject.name == target.name)
            {
                //我使用了很直接的思路,如果打击点有一个就存一个不为0的点进去   
                hitPos = hit.point;
                hitDis = 0.01f;
                currentIndex++;
            }
            
      }
      _material.SetFloat("_HitArray",MaxHit);
      _material.SetFloat("_HitRange",hitRange);
      _material.SetFloat("_HitOffsetIntensity",_HitOffsetIntensity);
      
      _material.SetVectorArray("_HitPos",hitPos);
      _material.SetFloatArray("_HitDistence",hitDis);

      for (int i = 0; i < MaxHit; i++)
      {
            if (hitDis != 0)
            {
                //如果点数据不为0 开始递增距离
                hitDis += 0.1f * scanSpeed * Time.deltaTime;
            }

            if (hitDis >= stopDis)
            {
                hitDis = 0;//大于消失距离后归零
            }
      }
    }
}
Shader:
            float _HitArray;
            float3 _HitPos;
            float _HitRange;
            float _HitDistence;
            float _HitOffsetIntensity;
            float3 _HitLimitHeight;

            float temp = {1, 1, 1, 1};
            float dis = 0;
            for (int i = 0; i < _HitArray; i++)
            {
                  if (_HitDistence == 0)
                        continue;
                  float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
                  dis = length(worldPos - _HitPos);

                  float halfWidth = _HitRange / 2;
                  temp = saturate(abs(dis - _HitDistence) / halfWidth);
                  float3 offset = v.vertex.xyz + v.normal * _HitOffsetIntensity;
                  v.vertex.xyz = lerp(offset, v.vertex, temp);
            }分别计算顶点到打击点的距离,这里的算法和护盾边缘的算法类似,让范围内的顶点沿着法线产生偏移
(这个算法还可以用于后处理扫面线等效果)
通过更改参数就能实现一些奇奇怪怪的效果



改一个顶点噪声贴图


看着毛茸茸的
https://www.zhihu.com/video/1483897970814410752



如果有合适的贴图 可以做出地球的效果(偷懒啦)

完整源码地址:
https://github.com/DFYX233/-SimpleEffect/tree/main/Shield
留个坑:护盾被击中也可以是先凹陷再凸出(就像真实的水波),原理应该是不难的,我就懒得实现了
这代码都齐了怎么还不抄啊?我不打扰了,我走了哈

mypro334 发表于 2022-3-18 10:10

Cool~
页: [1]
查看完整版本: [入门级技术美术] Untiy shader简单可交互能量护盾