|
演示效果
https://www.zhihu.com/video/1626255034097811456
搭建工程
新建工程,安装Universal RP,我的Unity版本是2022.2
记得勾选DepthTexture和OpaqueTexture,之后会用到
新建Shader
添加UV,法线等信息,用于后续计算- struct appdata
- {
- float4 vertex : POSITION;
- float2 uv : TEXCOORD0;
- float3 normal : NORMAL;
- };
- struct v2f
- {
- float4 vertex : SV_POSITION;
- float2 uv : TEXCOORD1;
- float3 normal : TEXCOORD2;
- float3 worldPos : TEXCOORD3;
- float4 screenPos : TEXCOORD4;
- float4 localPos : TEXCOORD5;
- };
- v2f vert (appdata v)
- {
- v2f o;
- o.vertex = TransformObjectToHClip(v.vertex.xyz);
- o.uv = v.uv;
- o.normal = TransformObjectToWorldNormal(v.normal);
- o.worldPos = TransformObjectToWorld(v.vertex.xyz);
- o.localPos = v.vertex;
- o.screenPos = o.vertex;
- #if UNITY_UV_STARTS_AT_TOP
- o.screenPos.y *= -1;
- #endif
- return o;
- }
复制代码 边缘光
先添加边缘光,用模型法线和不雅察看标的目的做点乘- //Properties
- _RimPower (”RimPower”, Float) = 1
- [HDR] _RimColor (”RimColor”, Color) = (1, 1, 1, 1)
- float _RimPower;
- float4 _RimColor;
-
- //frag
- float3 normal = normalize(i.normal);
- float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
- float ndv = dot(normal, viewDir);
- if(ndv < 0) {
- ndv = abs(ndv);
- }
- ndv = 1 - ndv;
- float rimIntensity = pow(ndv, _RimPower);
- finalColor += _RimColor * rimIntensity;
- finalColor.a = saturate(finalColor.a);
复制代码
仿佛缺了点什么,噢,忘记开Bloom了
接触高亮
能量盾和其他物体接触时,需要有亮边,通过深度来实现。用盾像素点的屏幕坐标采样深度图,得参加景深度,再与像素点深度做斗劲,当两者足够接近时,显示亮边- //Properties
- _IntersectionWidth (”IntersectionWidth”, Float) = 1
- [HDR] _IntersectionColor (”IntersectionColor”, Color) = (1, 1, 1, 1)
- float _IntersectionWidth;
- float4 _IntersectionColor;
- sampler2D _CameraDepthTexture;
- //frag
- i.screenPos.xyz /= i.screenPos.w;
- float2 screenUV = i.screenPos.xy;
- screenUV = (screenUV + 1) / 2;
- float selfZ = i.screenPos.z;
- float sceneZ = tex2D(_CameraDepthTexture, screenUV).r;
- float linearSelfZ = LinearEyeDepth(selfZ, _ZBufferParams);
- float linearSceneZ = LinearEyeDepth(sceneZ, _ZBufferParams);
- float zDifference = linearSceneZ - linearSelfZ;
- if(zDifference < _IntersectionWidth) {
- float intersectionIntensity = (1 - zDifference / _IntersectionWidth);
- intersectionIntensity = saturate(intersectionIntensity);
- intersectionIntensity = pow(intersectionIntensity, 4);
- finalColor += _IntersectionColor * intersectionIntensity;
- finalColor.a = saturate(finalColor.a);
- }
复制代码
贴图
接下来给能量盾添加贴图,球的UV是不均匀的,如果直接用UV采样贴图,贴图在顶部会被压缩,在中间区域会被拉伸。
这里我把2D贴图合成了Cubemap,用法线采样,而且判断了像素点是否是背面,如果是,则不显示贴图
- //Properties
- _PatternTex (”PatternTex”, Cube) = ”white” {}
- _PatternPower (”PatternPower”, Float) = 1
- [HDR] _PatternColor (”PatternColor”, Color) = (1, 1, 1, 1)
- samplerCUBE _PatternTex;
- float _PatternPower;
- float4 _PatternColor;
- //frag
- int isFrontFace = 1;
- //......
- if(ndv < 0) {
- isFrontFace = 0;
- }
- float patternIntensity = texCUBE(_PatternTex, normal).a * isFrontFace;
- patternIntensity *= pow(ndv, _PatternPower);
- finalColor += patternIntensity * _PatternColor;
- finalColor.a = saturate(finalColor.a);
复制代码
接下来给贴图添加流动效果,用一张网格遮罩和贴图做叠加
- //Properties
- _Mask (”Mask”, 2D) = ”black” {}
- [HDR] _MaskColor (”MaskColor”, Color) = (1, 1, 1, 1)
- sampler2D _Mask;
- float4 _Mask_ST;
- float4 _MaskColor;
- //frag
- float mask = 0;
- mask += tex2D(_Mask, i.uv * _Mask_ST.xx + _Mask_ST.zz * _Time.y).a;
- mask += tex2D(_Mask, i.uv * _Mask_ST.yy + _Mask_ST.ww * _Time.y).a;
- mask = saturate(mask);
- finalColor += patternIntensity * mask * _MaskColor;
- finalColor.a = saturate(finalColor.a);
复制代码
https://www.zhihu.com/video/1626235816711299073
溶解
接下来制作溶解效果,用像素点的y坐标控制溶解,再叠加噪声做犯警则的轮廓- //Properties
- _Noise (”Noise”, 2D) = ”white” {}
- _DissolveThreshold (”DissolveThreshold”, Float) = 1
- _DissolveWidth (”DissolveWidth”, Float) = 0.1
- [HDR] _DissolveColor (”DissolveColor”, Color) = (1, 1, 1, 1)
- sampler2D _Noise;
- float4 _Noise_ST;
- float _DissolveThreshold;
- float _DissolveWidth;
- float4 _DissolveColor;
- //frag
- if(i.localPos.y > _DissolveThreshold) {
- discard;
- }
- else if(i.localPos.y > _DissolveThreshold - _DissolveWidth) {
- float t = (i.localPos.y - _DissolveThreshold + _DissolveWidth) / _DissolveWidth;
- float noise = tex2D(_Noise, i.uv * _Noise_ST.xy + _Noise_ST.zw * _Time.y);
- noise = lerp(1, noise * (1 - t), pow(t, 0.5));
- if(noise > 0.5) {
- finalColor = _DissolveColor;
- }else {
- discard;
- }
- }
复制代码
https://www.zhihu.com/video/1626236203392466944
颜色交互
接下来制作最复杂的交互功能,思路是用脚本把交互点,交互半径,交互颜色传入材质球,然后在shader中计算像素点和交互点的距离,如果在半径内则显示相应的颜色。
听起来很简单,一句话的事,实现起来
新建Shield.cs脚本,这个脚本负责传递交互信息到材质球- public class Shield : MonoBehaviour {
- private class InteractionData {
- public Color color;
- public Vector3 interactionStartPos;
- public float timer;
- }
- public List<Material> materials;
- private List<InteractionData> interactionDatas = new List<InteractionData>();
- private void Update() {
- //......
- for (int i = 0; i < materials.Count; i++) {
- materials[i].SetInt(”_InteractionNumber”, interactionDatas.Count);
- if (interactionDatas.Count > 0) {
- materials[i].SetVectorArray(”_InteractionStartPosArray”, interactionStartPosArray);
- materials[i].SetFloatArray(”_InteractionInnerRadiusArray”, interactionInnerRadiusArray);
- materials[i].SetFloatArray(”_InteractionOuterRadiusArray”, interactionOuterRadiusArray);
- materials[i].SetFloatArray(”_InteractionAlphaArray”, interactionAlphaArray);
- materials[i].SetColorArray(”_InteractionColorArray”, interactionColorArray);
- materials[i].SetFloatArray(”_DistortAlphaArray”, distortAlphaArray);
- }
- }
- }
- public void AddInteractionData(Vector3 pos, Color color) {
- if (interactionDatas.Count >= 100) {
- return;
- }
- InteractionData interactionData = new InteractionData();
- interactionData.color = color;
- interactionData.interactionStartPos = pos;
- interactionDatas.Add(interactionData);
- }
- }
复制代码 新建ShootManager.cs脚本,在点击鼠标时,做射线检测,如果碰撞到能量盾,调用交互接口- public class ShootManager : MonoBehaviour {
- [ColorUsage(true, true)] public Color interactionColor;
- private void Update() {
- if (Input.GetMouseButtonDown(0)) {
- RaycastHit hitInfo;
- bool hited = Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hitInfo, Mathf.Infinity);
- if (hited) {
- Shield.instance.AddInteractionData(hitInfo.point, interactionColor);
- }
- }
- }
- }
复制代码 然后在Shader里计算像素点到交互点的距离- //Properties
- int _InteractionNumber;
- float3 _InteractionStartPosArray[100];
- float _InteractionInnerRadiusArray[100];
- float _InteractionOuterRadiusArray[100];
- float _InteractionAlphaArray[100];
- float4 _InteractionColorArray[100];
-
- float _DistortAlphaArray[100];
- float GetInteractionIntensity(v2f i, float3 startPos, float innerRadius, float outerRadius) {
- float dist = distance(i.worldPos, startPos);
- if(dist > outerRadius || dist < innerRadius) {
- return 0;
- }
- else {
- float intensity = (dist - innerRadius) / (outerRadius - innerRadius);
- return intensity;
- }
- }
- //frag
- float interactionIntensity = 0;
- float4 interactionColor = 0;
- for(int iii = 0; iii < _InteractionNumber; iii++) {
- float tempInteractionIntensity = GetInteractionIntensity(i, _InteractionStartPosArray[iii], _InteractionInnerRadiusArray[iii], _InteractionOuterRadiusArray[iii]) * _InteractionAlphaArray[iii];
- interactionIntensity += tempInteractionIntensity;
- interactionColor += _InteractionColorArray[iii] * tempInteractionIntensity;
- }
- interactionIntensity = saturate(interactionIntensity);
- finalColor += interactionColor;
- finalColor.a = saturate(finalColor.a);
复制代码
https://www.zhihu.com/video/1626237758866972672
在交互区域,对贴图做提亮和扭曲- //Properties
- _DistortNormal (”DistortNormal”, 2D) = ”bump” {}
- _DistortIntensity (”DistortIntensity”, Float) = 1
- sampler2D _DistortNormal;
- float4 _DistortNormal_ST;
- float _DistortIntensity;
- float GetDistortIntensity(v2f i, float3 startPos, float innerRadius, float outerRadius) {
- float dist = distance(i.worldPos, startPos);
- if(dist > outerRadius) {
- return 0;
- }
- else {
- float intensity = dist / outerRadius;
- return intensity;
- }
- }
- //frag
- float3 distortNormal = UnpackNormal(tex2D(_DistortNormal, i.uv * _DistortNormal_ST.xy + _DistortNormal_ST.zw * _Time.y));
- distortNormal *= _DistortIntensity * distortIntensity;
- float distortIntensity = 0;
- for(int iii = 0; iii < _InteractionNumber; iii++) {
- //......
- distortIntensity += GetDistortIntensity(i, _InteractionStartPosArray[iii], _InteractionInnerRadiusArray[iii], _InteractionOuterRadiusArray[iii]) * _DistortAlphaArray[iii];
- distortIntensity = saturate(distortIntensity);
- }
- float patternIntensity = texCUBE(_PatternTex, normal + distortNormal).a * isFrontFace;
- patternIntensity *= pow(ndv + interactionIntensity, _PatternPower);
- finalColor += patternIntensity * _PatternColor;
- finalColor.a = saturate(finalColor.a);
复制代码
https://www.zhihu.com/video/1626213140663791616
扭曲交互
交互功能还差最后一步
接下来做屏幕扭曲效果,道理是用一张法线贴图改削屏幕UV,然后采样_CameraOpaqueTexture,
新建Shield_Distort.shader,把能量盾Shader里的代码复制过来,添加- sampler2D _CameraOpaqueTexture;
- float4 _CameraOpaqueTexture_TexelSize;
复制代码 重写片元着色器,改削屏幕UV,采样_CameraOpaqueTexture- float4 frag (v2f i) : SV_Target
- {
- float4 finalColor = 0;
- float distortIntensity = 0;
- for(int iii = 0; iii < _InteractionNumber; iii++) {
- distortIntensity += GetDistortIntensity(i, _InteractionStartPosArray[iii], _InteractionInnerRadiusArray[iii], _InteractionOuterRadiusArray[iii]) * _DistortAlphaArray[iii];
- distortIntensity = saturate(distortIntensity);
- }
- float3 distortNormal = UnpackNormal(tex2D(_DistortNormal, i.uv * _DistortNormal_ST.xy + _DistortNormal_ST.zw * _Time.y));
- distortNormal *= _DistortIntensity * distortIntensity;
- i.screenPos.xyz /= i.screenPos.w;
- float2 screenUV = i.screenPos.xy;
- screenUV = (screenUV + 1) / 2;
- finalColor = tex2D(_CameraOpaqueTexture, screenUV + distortNormal.xy * _CameraOpaqueTexture_TexelSize.xy);
- return finalColor;
- }
复制代码 再把能量盾复制一份,换上新材质
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
备注:工程中部门素材取自互联网,仅供学习交流使用,请勿在商业项目中使用
主页
此次的教程就到此结束了,如果喜欢记得点个赞,我们下篇文章再见! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|