Unity Shader 之 简单实现物体被黑洞吸收吞噬(或者从黑洞中出来)的效果

Unity Shader 之 简单实现物体被黑洞吸收吞噬(或者从黑洞中出来)的效果

目录

Unity Shader 之 简单实现物体被黑洞吸收吞噬(或者从黑洞中出来)的效果

一、简单介绍

二、实现原理

三、注意事项

四、效果预览

五、实现步骤

六、关键代码


一、简单介绍

Shader Language的发展方向是设计出在便携性方面可以和C++、Java等相比的高级语言,“赋予程序员灵活而方便的编程方式”,并“尽可能的控制渲染过程”同时“利用图形硬件的并行性,提高算法效率”。

本文介绍,实现黑洞再任意位置,被吸收物体再任意位置,黑洞吸收吞噬的简单效果。

二、实现原理

1、通过脚本求得物体的距离黑洞的最近顶点和最远顶点;

2、传给shader,得到最近顶点和最远顶点的范围,并计算各顶点到最近顶点的距离归一化比;

3、在通过一个参数,来控制物体被黑洞吸收吞噬或生成的状态的程度;

三、注意事项

1、通过一个参数,来控制物体吸收或生成的状态的程度,范围是 0 - 2(如果小于2 最远的点吸收不到);

2、因为控制参数再0-2范围,所以对超过黑洞的模型顶点做处理,让他一直处于黑洞位置;

3、_Control = sin(_Time.y * _Speed ) + 1; 用来代码模拟的,正式用的时候可以用其他方法代替;

4、注意顶点区分是世界坐标,还是模型坐标;

四、效果预览

五、实现步骤

原理图示:

具体步骤:

1、打开Unity,新建一个工程

2、在工程中,新建一个Shader,并新建对应材质,导入一张图片,再新建一个脚本来见识鼠标点击监控,并把相关信息传给shader

3、在场景中,添加两个 Sphere(一个作为黑洞) 和 Capsule,适当布局

4、把材质给 Sphere 和 Capsule,并挂载脚本,把黑洞赋给脚本

5、运行场景,效果如上

六、关键代码

1、BlackHoleEffeck.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlackHoleEffeck : MonoBehaviour
{

    public Transform BlackHoleGO;

    private float nearDistance = float.MaxValue;
    private float farDistance = 0;

    void Start()
    {
        Material mat = GetComponent<Renderer>().material;


        Vector3 nearPos, farPos;
        CalculateMinMaxY(out nearPos, out farPos);
        mat.SetVector("_NearPos", nearPos);
        mat.SetVector("_FarPos", farPos);
        mat.SetVector("_BlackHolePos", BlackHoleGO.position);
    }

    /// <summary>
    /// 计算模型 距离黑洞的最近值顶点和最远值顶点
    /// </summary>
    /// <param name="nearPos"></param>
    /// <param name="farPos"></param>
    void CalculateMinMaxY(out Vector3 nearPos, out Vector3 farPos)
    {
        nearPos = Vector3.zero;
        farPos = Vector3.zero;
        Vector3[] vertices = GetComponent<MeshFilter>().mesh.vertices;
       
        for (int i = 1; i < vertices.Length; i++)
        {
           

            Vector3 tmp = vertices[i] + transform.position;
            float distance = Vector3.Distance(tmp, BlackHoleGO.position);
            if (distance < nearDistance) {
                nearDistance = distance;
                nearPos = tmp;
            }

            if (distance > farDistance) {
                farDistance = distance;
                farPos = tmp;
            }
               

        }

     
    }
}

2、ShaderBlackHole.shader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
Shader "Unlit/ShaderBlackHole"
{
     Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _NearPos("Near Postion",Vector) =(0,0,0,0)
        _FarPos("Far Position",Vector)=(0,0,0,0)
        _Control("Control Squash",Range(0,2))=0
        _Speed("Speed",Range(0,10))=1
        _ObjectWorldPos("Object World Position",Vector)=(0,0,0,0)
        _BlackHolePos("Black Hole Position",Vector)=(0,0,0,0)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
           CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag

           #include "UnityCG.cginc"

           struct appdata{
                float4 vertex:POSITION;
                float2 uv:TEXCOORD0;
           };

           struct v2f{
                float2 uv:TEXCOORD0;
                float4 vertex:SV_POSITION;
                float3 worldPos:TEXCOORD1;
           
           };

           sampler2D _MainTex;
           float4 _MainTex_ST;

           float3 _NearPos;
           float3 _FarPos;
           float3 _BlackHolePos;

           float _Control;
           float _Speed;
           float rangeFar_Near;

           // 求顶点到最高顶点的距离比例
           float GetNormalizedDist(float3 worldPos){
                // 加上在世界坐标的位置
                worldPos = worldPos ;

                // 最大最小范围
                rangeFar_Near = length(_FarPos-_NearPos);

                // 以最高顶点为黑洞吸收点开始点
                float3 border = _NearPos;

                // 求顶点据最高顶点的距离绝对值
                float dist = length( worldPos-border);

                //归一化距离值
                float normalizedDist = saturate(dist/rangeFar_Near);

                return normalizedDist;
           }

           v2f vert(appdata v){
               
                v2f o;
                o.uv = TRANSFORM_TEX(v.uv,_MainTex);

                // 把顶点撞到世界坐标上
                float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
               

                // 得到顶点距离顶部的比例
                float normalizedDist = GetNormalizedDist(worldPos);
               
                // sin 值范围为 (-1,1),+1 使得范围再(0,2)周期变化
                _Control = sin(_Time.y * _Speed ) + 1;

                // 变化比例(至于为什么max 是因为变化normalizedDist 为0-1, _Control - 为负,说明未变化到,故取0,不然就,你可以试试不要 max 看看结果)
                float val = max(0,_Control-normalizedDist);
               
                float3 toBlackHole = mul(unity_WorldToObject, (_BlackHolePos - worldPos)).xyz;

                float3 srcVertex = v.vertex.xyz;
                // 向黑洞方向偏移顶点数值,渐渐被黑洞吸收
                v.vertex.xyz += toBlackHole* val;

                // 因为 val 的值会大于1,所以当顶点偏移到黑洞就一直保持
                // length(v.vertex.xyz - srcVertex) 当前顶点偏移的值
                // _BlackHolePos - worldPos 顶点到黑洞的最大值
                // 故当 当前顶点偏移的值 大于 顶点到黑洞的最大值,让顶点值设置为黑洞处
                if(length(v.vertex.xyz - srcVertex) > length(_BlackHolePos - worldPos)){
                    v.vertex.xyz = srcVertex+ toBlackHole;
                }

                // 转到裁剪空间
                o.vertex = UnityObjectToClipPos(v.vertex);

               
                return o;

           }

           fixed4 frag(v2f i):SV_Target{               

                fixed4 col = tex2D(_MainTex,i.uv);         

                return col;
           }

           ENDCG

        }
    }
}