找回密码
 立即注册
查看: 117|回复: 0

[简易教程] Unity技术美术教程,可交互的能量盾,附源文件

[复制链接]
发表于 2024-7-15 18:17 | 显示全部楼层 |阅读模式
演示效果



https://www.zhihu.com/video/1626255034097811456
搭建工程

新建工程,安装Universal RP,我的Unity版本是2022.2


记得勾选DepthTexture和OpaqueTexture,之后会用到


新建Shader





添加UV,法线等信息,用于后续计算
  1. struct appdata
  2. {
  3.     float4 vertex : POSITION;
  4.     float2 uv : TEXCOORD0;
  5.     float3 normal : NORMAL;
  6. };
  7. struct v2f
  8. {
  9.     float4 vertex : SV_POSITION;
  10.     float2 uv : TEXCOORD1;
  11.     float3 normal : TEXCOORD2;
  12.     float3 worldPos : TEXCOORD3;
  13.     float4 screenPos : TEXCOORD4;
  14.     float4 localPos : TEXCOORD5;
  15. };
  16. v2f vert (appdata v)
  17. {
  18.     v2f o;
  19.     o.vertex = TransformObjectToHClip(v.vertex.xyz);
  20.     o.uv = v.uv;
  21.     o.normal = TransformObjectToWorldNormal(v.normal);
  22.     o.worldPos = TransformObjectToWorld(v.vertex.xyz);
  23.     o.localPos = v.vertex;
  24.     o.screenPos = o.vertex;
  25.     #if UNITY_UV_STARTS_AT_TOP
  26.     o.screenPos.y *= -1;
  27.     #endif
  28.     return o;
  29. }
复制代码
边缘光

先添加边缘光,用模型法线和不雅察看标的目的做点乘
  1. //Properties
  2. _RimPower (”RimPower”, Float) = 1
  3. [HDR] _RimColor (”RimColor”, Color) = (1, 1, 1, 1)
  4. float _RimPower;
  5. float4 _RimColor;
  6.         
  7. //frag
  8. float3 normal = normalize(i.normal);
  9. float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
  10. float ndv = dot(normal, viewDir);
  11. if(ndv < 0) {
  12.     ndv = abs(ndv);
  13. }
  14. ndv = 1 - ndv;
  15. float rimIntensity = pow(ndv, _RimPower);
  16. finalColor += _RimColor * rimIntensity;
  17. finalColor.a = saturate(finalColor.a);
复制代码

仿佛缺了点什么,噢,忘记开Bloom了


接触高亮

能量盾和其他物体接触时,需要有亮边,通过深度来实现。用盾像素点的屏幕坐标采样深度图,得参加景深度,再与像素点深度做斗劲,当两者足够接近时,显示亮边
  1. //Properties
  2. _IntersectionWidth (”IntersectionWidth”, Float) = 1
  3. [HDR] _IntersectionColor (”IntersectionColor”, Color) = (1, 1, 1, 1)
  4. float _IntersectionWidth;
  5. float4 _IntersectionColor;
  6. sampler2D _CameraDepthTexture;
  7. //frag
  8. i.screenPos.xyz /= i.screenPos.w;
  9. float2 screenUV = i.screenPos.xy;
  10. screenUV = (screenUV + 1) / 2;
  11. float selfZ = i.screenPos.z;
  12. float sceneZ = tex2D(_CameraDepthTexture, screenUV).r;
  13. float linearSelfZ = LinearEyeDepth(selfZ, _ZBufferParams);
  14. float linearSceneZ = LinearEyeDepth(sceneZ, _ZBufferParams);
  15. float zDifference = linearSceneZ - linearSelfZ;
  16. if(zDifference < _IntersectionWidth) {
  17.     float intersectionIntensity = (1 - zDifference / _IntersectionWidth);
  18.     intersectionIntensity = saturate(intersectionIntensity);
  19.     intersectionIntensity = pow(intersectionIntensity, 4);
  20.     finalColor += _IntersectionColor * intersectionIntensity;
  21.     finalColor.a = saturate(finalColor.a);
  22. }
复制代码

贴图

接下来给能量盾添加贴图,球的UV是不均匀的,如果直接用UV采样贴图,贴图在顶部会被压缩,在中间区域会被拉伸。
这里我把2D贴图合成了Cubemap,用法线采样,而且判断了像素点是否是背面,如果是,则不显示贴图

  1. //Properties
  2. _PatternTex (”PatternTex”, Cube) = ”white” {}
  3. _PatternPower (”PatternPower”, Float) = 1
  4. [HDR] _PatternColor (”PatternColor”, Color) = (1, 1, 1, 1)
  5. samplerCUBE _PatternTex;
  6. float _PatternPower;
  7. float4 _PatternColor;
  8. //frag
  9. int isFrontFace = 1;
  10. //......
  11. if(ndv < 0) {
  12.     isFrontFace = 0;
  13. }
  14. float patternIntensity = texCUBE(_PatternTex, normal).a * isFrontFace;
  15. patternIntensity *= pow(ndv, _PatternPower);
  16. finalColor += patternIntensity * _PatternColor;
  17. finalColor.a = saturate(finalColor.a);
复制代码

接下来给贴图添加流动效果,用一张网格遮罩和贴图做叠加

  1. //Properties
  2. _Mask (”Mask”, 2D) = ”black” {}
  3. [HDR] _MaskColor (”MaskColor”, Color) = (1, 1, 1, 1)
  4. sampler2D _Mask;
  5. float4 _Mask_ST;
  6. float4 _MaskColor;
  7. //frag
  8. float mask = 0;
  9. mask += tex2D(_Mask, i.uv * _Mask_ST.xx + _Mask_ST.zz * _Time.y).a;
  10. mask += tex2D(_Mask, i.uv * _Mask_ST.yy + _Mask_ST.ww * _Time.y).a;
  11. mask = saturate(mask);
  12. finalColor += patternIntensity * mask * _MaskColor;
  13. finalColor.a = saturate(finalColor.a);
复制代码

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


溶解

接下来制作溶解效果,用像素点的y坐标控制溶解,再叠加噪声做犯警则的轮廓
  1. //Properties
  2. _Noise (”Noise”, 2D) = ”white” {}
  3. _DissolveThreshold (”DissolveThreshold”, Float) = 1
  4. _DissolveWidth (”DissolveWidth”, Float) = 0.1
  5. [HDR] _DissolveColor (”DissolveColor”, Color) = (1, 1, 1, 1)
  6. sampler2D _Noise;
  7. float4 _Noise_ST;
  8. float _DissolveThreshold;
  9. float _DissolveWidth;
  10. float4 _DissolveColor;
  11. //frag
  12. if(i.localPos.y > _DissolveThreshold) {
  13.     discard;
  14. }
  15. else if(i.localPos.y > _DissolveThreshold - _DissolveWidth) {
  16.     float t = (i.localPos.y - _DissolveThreshold + _DissolveWidth) / _DissolveWidth;
  17.     float noise = tex2D(_Noise, i.uv * _Noise_ST.xy + _Noise_ST.zw * _Time.y);
  18.     noise = lerp(1, noise * (1 - t), pow(t, 0.5));
  19.     if(noise > 0.5) {
  20.         finalColor = _DissolveColor;
  21.     }else {
  22.         discard;
  23.     }
  24. }
复制代码

https://www.zhihu.com/video/1626236203392466944
颜色交互

接下来制作最复杂的交互功能,思路是用脚本把交互点,交互半径,交互颜色传入材质球,然后在shader中计算像素点和交互点的距离,如果在半径内则显示相应的颜色。
听起来很简单,一句话的事,实现起来


新建Shield.cs脚本,这个脚本负责传递交互信息到材质球
  1. public class Shield : MonoBehaviour {  
  2.     private class InteractionData {
  3.         public Color color;
  4.         public Vector3 interactionStartPos;
  5.         public float timer;
  6.     }
  7.     public List<Material> materials;
  8.     private List<InteractionData> interactionDatas = new List<InteractionData>();
  9.     private void Update() {
  10.         //......
  11.         for (int i = 0; i < materials.Count; i++) {
  12.             materials[i].SetInt(”_InteractionNumber”, interactionDatas.Count);
  13.             if (interactionDatas.Count > 0) {
  14.                 materials[i].SetVectorArray(”_InteractionStartPosArray”, interactionStartPosArray);
  15.                 materials[i].SetFloatArray(”_InteractionInnerRadiusArray”, interactionInnerRadiusArray);
  16.                 materials[i].SetFloatArray(”_InteractionOuterRadiusArray”, interactionOuterRadiusArray);
  17.                 materials[i].SetFloatArray(”_InteractionAlphaArray”, interactionAlphaArray);
  18.                 materials[i].SetColorArray(”_InteractionColorArray”, interactionColorArray);
  19.                 materials[i].SetFloatArray(”_DistortAlphaArray”, distortAlphaArray);
  20.             }
  21.         }
  22.     }
  23.     public void AddInteractionData(Vector3 pos, Color color) {
  24.         if (interactionDatas.Count >= 100) {
  25.             return;
  26.         }
  27.         InteractionData interactionData = new InteractionData();
  28.         interactionData.color = color;
  29.         interactionData.interactionStartPos = pos;
  30.         interactionDatas.Add(interactionData);
  31.     }
  32. }
复制代码
新建ShootManager.cs脚本,在点击鼠标时,做射线检测,如果碰撞到能量盾,调用交互接口
  1. public class ShootManager : MonoBehaviour {
  2.     [ColorUsage(true, true)] public Color interactionColor;
  3.     private void Update() {
  4.         if (Input.GetMouseButtonDown(0)) {
  5.             RaycastHit hitInfo;
  6.             bool hited = Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hitInfo, Mathf.Infinity);
  7.             if (hited) {
  8.                 Shield.instance.AddInteractionData(hitInfo.point, interactionColor);
  9.             }
  10.         }
  11.     }
  12. }
复制代码
然后在Shader里计算像素点到交互点的距离
  1. //Properties
  2. int _InteractionNumber;
  3. float3 _InteractionStartPosArray[100];
  4. float _InteractionInnerRadiusArray[100];
  5. float _InteractionOuterRadiusArray[100];
  6. float _InteractionAlphaArray[100];
  7. float4 _InteractionColorArray[100];
  8.             
  9. float _DistortAlphaArray[100];
  10. float GetInteractionIntensity(v2f i, float3 startPos, float innerRadius, float outerRadius) {
  11.     float dist = distance(i.worldPos, startPos);
  12.     if(dist > outerRadius || dist < innerRadius) {
  13.         return 0;
  14.     }
  15.     else {
  16.       float intensity = (dist - innerRadius) / (outerRadius - innerRadius);
  17.       return intensity;
  18.     }
  19. }
  20. //frag
  21. float interactionIntensity = 0;
  22. float4 interactionColor = 0;
  23. for(int iii = 0; iii < _InteractionNumber; iii++) {
  24.     float tempInteractionIntensity = GetInteractionIntensity(i, _InteractionStartPosArray[iii], _InteractionInnerRadiusArray[iii], _InteractionOuterRadiusArray[iii]) * _InteractionAlphaArray[iii];
  25.     interactionIntensity += tempInteractionIntensity;
  26.     interactionColor += _InteractionColorArray[iii] * tempInteractionIntensity;
  27. }
  28. interactionIntensity = saturate(interactionIntensity);
  29. finalColor += interactionColor;
  30. finalColor.a = saturate(finalColor.a);
复制代码

https://www.zhihu.com/video/1626237758866972672
在交互区域,对贴图做提亮和扭曲
  1. //Properties
  2. _DistortNormal (”DistortNormal”, 2D) = ”bump” {}
  3. _DistortIntensity (”DistortIntensity”, Float) = 1
  4. sampler2D _DistortNormal;
  5. float4 _DistortNormal_ST;
  6. float _DistortIntensity;
  7. float GetDistortIntensity(v2f i, float3 startPos, float innerRadius, float outerRadius) {
  8.     float dist = distance(i.worldPos, startPos);
  9.     if(dist > outerRadius) {
  10.         return 0;
  11.     }
  12.     else {
  13.         float intensity = dist / outerRadius;
  14.         return intensity;
  15.     }
  16. }
  17. //frag
  18. float3 distortNormal = UnpackNormal(tex2D(_DistortNormal, i.uv * _DistortNormal_ST.xy + _DistortNormal_ST.zw * _Time.y));
  19. distortNormal *= _DistortIntensity * distortIntensity;
  20. float distortIntensity = 0;
  21. for(int iii = 0; iii < _InteractionNumber; iii++) {
  22.     //......
  23.     distortIntensity += GetDistortIntensity(i, _InteractionStartPosArray[iii], _InteractionInnerRadiusArray[iii], _InteractionOuterRadiusArray[iii]) * _DistortAlphaArray[iii];
  24.     distortIntensity = saturate(distortIntensity);
  25. }
  26. float patternIntensity = texCUBE(_PatternTex, normal + distortNormal).a * isFrontFace;
  27. patternIntensity *= pow(ndv + interactionIntensity, _PatternPower);
  28. finalColor += patternIntensity * _PatternColor;
  29. finalColor.a = saturate(finalColor.a);
复制代码

https://www.zhihu.com/video/1626213140663791616
扭曲交互

交互功能还差最后一步


接下来做屏幕扭曲效果,道理是用一张法线贴图改削屏幕UV,然后采样_CameraOpaqueTexture,
新建Shield_Distort.shader,把能量盾Shader里的代码复制过来,添加
  1. sampler2D _CameraOpaqueTexture;
  2. float4 _CameraOpaqueTexture_TexelSize;
复制代码
重写片元着色器,改削屏幕UV,采样_CameraOpaqueTexture
  1. float4 frag (v2f i) : SV_Target
  2. {
  3.     float4 finalColor = 0;
  4.     float distortIntensity = 0;
  5.     for(int iii = 0; iii < _InteractionNumber; iii++) {
  6.         distortIntensity += GetDistortIntensity(i, _InteractionStartPosArray[iii], _InteractionInnerRadiusArray[iii], _InteractionOuterRadiusArray[iii]) * _DistortAlphaArray[iii];
  7.         distortIntensity = saturate(distortIntensity);
  8.     }
  9.     float3 distortNormal = UnpackNormal(tex2D(_DistortNormal, i.uv * _DistortNormal_ST.xy + _DistortNormal_ST.zw * _Time.y));
  10.     distortNormal *= _DistortIntensity * distortIntensity;
  11.     i.screenPos.xyz /= i.screenPos.w;
  12.     float2 screenUV = i.screenPos.xy;
  13.     screenUV = (screenUV + 1) / 2;
  14.     finalColor = tex2D(_CameraOpaqueTexture, screenUV + distortNormal.xy * _CameraOpaqueTexture_TexelSize.xy);
  15.     return finalColor;
  16. }
复制代码
再把能量盾复制一份,换上新材质




https://www.zhihu.com/video/1626217980341170177
至此,我们的能量盾就完成了!


源文件下载

Github:https://github.com/MagicStones23/Unity-Shader-Tutorial-Interactable-Energy-Shield
百度网盘:https://pan.baidu.com/s/1R58a9uPzq3pGugyFwZshEA?pwd=1111 提取码:1111
备注:工程中部门素材取自互联网,仅供学习交流使用,请勿在商业项目中使用
主页

此次的教程就到此结束了,如果喜欢记得点个赞,我们下篇文章再见!

本帖子中包含更多资源

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

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-9-21 11:19 , Processed in 0.088869 second(s), 23 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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