找回密码
 立即注册
查看: 319|回复: 1

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

[复制链接]
发表于 2022-3-18 10:08 | 显示全部楼层 |阅读模式
萌新操作,先上效果

护盾效果
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[0] = _FixedFactor;
                o.edge[1] = _FixedFactor;
                o.edge[2] = _FixedFactor;
                o.inside = _FixedFactor;
                return o;
            }

            [UNITY_domain("tri")]
            [UNITY_partitioning("fractional_odd")]
            [UNITY_outputtopology("triangle_cw")]
            [UNITY_patchconstantfunc("hs")]
            [UNITY_outputcontrolpoints(3)]
            ControPoint HullProgarm(InputPatch<ControPoint, 3> v, uint id :SV_OutputControlPointID)
            {
                return v[id];
            }

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

                v.vertex = vi[0].vertex * bary.x + vi[1].vertex * bary.y + vi[2].vertex * bary.z;
                v.normal = vi[0].normal * bary.x + vi[1].normal * bary.y + vi[2].normal * bary.z;
                v.uv = vi[0].uv * bary.x + vi[1].uv * bary.y + vi[2].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[MaxHit];
    private float[] hitDis = new float[MaxHit];
    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[currentIndex%MaxHit] = hit.point;
                hitDis[currentIndex%MaxHit] = 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[10];
            float _HitRange;
            float _HitDistence[10];
            float _HitOffsetIntensity;
            float3 _HitLimitHeight;

            float temp[4] = {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
留个坑:护盾被击中也可以是先凹陷再凸出(就像真实的水波),原理应该是不难的,我就懒得实现了
这代码都齐了怎么还不抄啊?我不打扰了,我走了哈

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2022-3-18 10:10 | 显示全部楼层
Cool~
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-8 18:24 , Processed in 0.094448 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表