SpriteShader

7.8k words

简介

记录常用的 Sprite Shader

Glow(发光)

叠加自发光颜色
Image text

Outline(描边)

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
half spriteLeft = tex2D(_MainTex, i.uv + half2(destUv.x, 0)).a;
half spriteRight = tex2D(_MainTex, i.uv - half2(destUv.x, 0)).a;
half spriteBottom = tex2D(_MainTex, i.uv + half2(0, destUv.y)).a;
half spriteTop = tex2D(_MainTex, i.uv - half2(0, destUv.y)).a;
half result = spriteLeft + spriteRight + spriteBottom + spriteTop;

#if OUTBASE8DIR_ON
half spriteTopLeft = tex2D(_MainTex, i.uv + half2(destUv.x, destUv.y)).a;
half spriteTopRight = tex2D(_MainTex, i.uv + half2(-destUv.x, destUv.y)).a;
half spriteBotLeft = tex2D(_MainTex, i.uv + half2(destUv.x, -destUv.y)).a;
half spriteBotRight = tex2D(_MainTex, i.uv + half2(-destUv.x, -destUv.y)).a;
result = result + spriteTopLeft + spriteTopRight + spriteBotLeft + spriteBotRight;
#endif

result = step(0.05, saturate(result));
result *= (1 - originalAlpha) * _OutlineAlpha;

half4 outline = _OutlineColor * i.color.a;
outline.rgb *= _OutlineGlow;
outline.a = result;
#if ONLYOUTLINE_ON
col = outline;
#else
col = lerp(col, outline, result);
#endif

采样周边像素颜色,step后的值,用来计算原本图片颜色到描边颜色的插值,可以添加透明度的计算
存在的问题,当图片边缘存在像素时,那部分无法显示描边,可以用后处理描边处理
Image text
对于一般的图片可以通过修改 tiling 来调整,但是对于图集中的图片,会导致uv计算出现问题
Image text

UV的获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14

float2 modifyUv=i.uv;
half2 center = half2((_MaxXUV + _MinXUV) / 2.0, (_MaxYUV + _MinYUV) / 2.0);
half2 centerTiled = half2(center.x * _MainTex_ST.x, center.y * _MainTex_ST.y);

modifyUv -= centerTiled;
modifyUv = modifyUv * _UVScale;
modifyUv += centerTiled;
half2 destUv = half2(_OutlinePixelWidth * _MainTex_TexelSize.x, _OutlinePixelWidth * _MainTex_TexelSize.y);

half spriteLeft = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, modifyUv + half2(destUv.x, 0)).a;//tex2D(_MainTex, i.uv + half2(destUv.x, 0)).a;
half spriteRight = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, modifyUv - half2(destUv.x, 0)).a;
half spriteBottom = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, modifyUv + half2(0, destUv.y)).a;
half spriteTop = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, modifyUv - half2(0, destUv.y)).a;
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace AllIn1SpriteShader
{
[ExecuteInEditMode]
public class SetAtlasUvs : MonoBehaviour
{
[SerializeField] private bool updateEveryFrame = false;
[Tooltip("If using a Sprite Renderer it will use the material property instead of sharedMaterial"), SerializeField] private bool useMaterialInstanceIfPossible = false;
private Renderer render;
private SpriteRenderer spriteRender;
private Image uiImage;
private bool isUI = false;
private readonly int minXuv = Shader.PropertyToID("_MinXUV");
private readonly int maxXuv = Shader.PropertyToID("_MaxXUV");
private readonly int minYuv = Shader.PropertyToID("_MinYUV");
private readonly int maxYuv = Shader.PropertyToID("_MaxYUV");

private void Start()
{
Setup();
}

private void Reset()
{
Setup();
}

private void Setup()
{
if (GetRendererReferencesIfNeeded()) GetAndSetUVs();
if (!updateEveryFrame && Application.isPlaying && this != null) this.enabled = false;
}

private void OnWillRenderObject()
{
if (updateEveryFrame)
{
GetAndSetUVs();
}
}

public void GetAndSetUVs()
{
if (!GetRendererReferencesIfNeeded()) return;

if (!isUI)
{
Sprite sprite = spriteRender.sprite;
Rect r = sprite.textureRect;
r.x /= sprite.texture.width;
r.width /= sprite.texture.width;
r.y /= sprite.texture.height;
r.height /= sprite.texture.height;

if(useMaterialInstanceIfPossible && Application.isPlaying)
{
render.material.SetFloat(minXuv, r.xMin);
render.material.SetFloat(maxXuv, r.xMax);
render.material.SetFloat(minYuv, r.yMin);
render.material.SetFloat(maxYuv, r.yMax);
}
else
{
render.sharedMaterial.SetFloat(minXuv, r.xMin);
render.sharedMaterial.SetFloat(maxXuv, r.xMax);
render.sharedMaterial.SetFloat(minYuv, r.yMin);
render.sharedMaterial.SetFloat(maxYuv, r.yMax);
}
}
else
{
Rect r = uiImage.sprite.textureRect;
r.x /= uiImage.sprite.texture.width;
r.width /= uiImage.sprite.texture.width;
r.y /= uiImage.sprite.texture.height;
r.height /= uiImage.sprite.texture.height;

uiImage.material.SetFloat(minXuv, r.xMin);
uiImage.material.SetFloat(maxXuv, r.xMax);
uiImage.material.SetFloat(minYuv, r.yMin);
uiImage.material.SetFloat(maxYuv, r.yMax);
}
}

public void ResetAtlasUvs()
{
if (!GetRendererReferencesIfNeeded()) return;

if (!isUI)
{
if(useMaterialInstanceIfPossible && Application.isPlaying)
{
render.material.SetFloat(minXuv, 0f);
render.material.SetFloat(maxXuv, 1f);
render.material.SetFloat(minYuv, 0f);
render.material.SetFloat(maxYuv, 1f);
}
else
{
render.sharedMaterial.SetFloat(minXuv, 0f);
render.sharedMaterial.SetFloat(maxXuv, 1f);
render.sharedMaterial.SetFloat(minYuv, 0f);
render.sharedMaterial.SetFloat(maxYuv, 1f);
}
}
else
{
uiImage.material.SetFloat(minXuv, 0f);
uiImage.material.SetFloat(maxXuv, 1f);
uiImage.material.SetFloat(minYuv, 0f);
uiImage.material.SetFloat(maxYuv, 1f);
}
}

public void UpdateEveryFrame(bool everyFrame)
{
updateEveryFrame = everyFrame;
}

private bool GetRendererReferencesIfNeeded()
{
if (spriteRender == null) spriteRender = GetComponent<SpriteRenderer>();
if (spriteRender != null)
{
if (spriteRender.sprite == null)
{
#if UNITY_EDITOR
EditorUtility.DisplayDialog("No sprite found", "The object: " + gameObject.name + ",has Sprite Renderer but no sprite", "Ok");
#endif
DestroyImmediate(this);
return false;
}
if (render == null) render = GetComponent<Renderer>();
isUI = false;
}
else
{
if (uiImage == null)
{
uiImage = GetComponent<Image>();
if (uiImage != null)
{
#if UNITY_EDITOR
Debug.Log("You added the SetAtlasUv component to: " + gameObject.name + " that has a UI Image\n " +
"This SetAtlasUV component will only work properly on UI Images if each Image has a DIFFERENT material instance (See Documentation Sprite Atlases section for more info)");
#endif
}
else
{
#if UNITY_EDITOR
EditorUtility.DisplayDialog("No Renderer or UI Graphic found", "This SetAtlasUV component will now get destroyed", "Ok");
#endif
DestroyImmediate(this);
return false;
}
}
if (render == null) render = GetComponent<Renderer>();
isUI = true;
}

if (spriteRender == null && uiImage == null)
{
#if UNITY_EDITOR
EditorUtility.DisplayDialog("No Renderer or UI Graphic found", "This SetAtlasUV component will now get destroyed", "Ok");
#endif
DestroyImmediate(this);
return false;
}
return true;
}
}
}

草的交互

Pixel Graphics
Image Text

原理:

  1. 速度纹理

    • RB:沿着X轴的位移和速度
    • GA:沿着Y轴的位移和速度
    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
        
    #define SAMPLE_PREVIOUS_VELOCITY(coordX, coordY) SAMPLE_TEXTURE2D(_PG_PreviousVelocityTexture, sampler_PG_PreviousVelocityTexture, uv - _PG_CameraPositionDelta.xy + float2(coordX, coordY))


    float4 SimulateVelocity(float2 uv)
    {
    float4 previousData = SAMPLE_PREVIOUS_VELOCITY(0, 0);
    float2 offset = _PG_PreviousVelocityTexture_TexelSize.xy;

    float4 neighbouringData = SAMPLE_PREVIOUS_VELOCITY(offset.x, 0);
    neighbouringData += SAMPLE_PREVIOUS_VELOCITY(-offset.x, 0);
    neighbouringData += SAMPLE_PREVIOUS_VELOCITY(0, offset.y);
    neighbouringData += SAMPLE_PREVIOUS_VELOCITY(0, -offset.y);

    neighbouringData *= 0.25f;
    previousData = lerp(previousData, neighbouringData, _PG_VelocitySimulationParams.z);

    float2 distance = previousData.xy;
    float2 velocity = previousData.zw;

    //判断是否在边界
    if (uv.x < offset.x
    || uv.x > 1 - offset.x
    || uv.y < offset.y
    || uv.y > 1 - offset.y
    )
    {
    velocity = 0;
    distance = 0;
    }

    float dt = min(unity_DeltaTime.x, _PG_VelocitySimulationParams.w);
    float2 acceleration = -distance * _PG_VelocitySimulationParams.x - velocity * _PG_VelocitySimulationParams.y;
    velocity += acceleration * dt;
    distance += velocity * dt;

    return float4(distance.x, distance.y, velocity.x, velocity.y);
    }
  2. 发射器


    将速度(x\y轴)写入ba通道

  3. 着色器

    编辑精灵生成的网格

  4. 属性

Animated circles


ShaderToy