仓库:
平铺 Grid
对于图片,平铺设置为:
Sprite Mode - Mesh Type = Full Rect
Advanced - Wrap Mode = Repeat
对于 Sprite Render,平铺设置为:
Draw Mode = Tiled
假设设置 Draw Mode - Size = (5, 5),效果为:
简单的 Grid Shader
Shader "Unlit/UnlitGrid"
{
Properties
{
[PerRendererData] _MainTex ("Texture", 2D) = "white" {}
[FloatRange] _GridLineSize ("GridLineSize", Range(0,1)) = 0.1
_LineColor ("Line Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _GridLineSize;
float4 _LineColor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 bl = step(float2(_GridLineSize, _GridLineSize), i.uv); // bottom-left
float2 tr = step(float2(_GridLineSize, _GridLineSize), float2(1.0, 1.0) - i.uv); // top-right
float grid_brightness = 1.0 - bl.x * bl.y * tr.x * tr.y;
fixed4 col = tex2D(_MainTex, i.uv) * (1.0 - grid_brightness) + _LineColor * grid_brightness;
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
其中画网格的部分是
float2 bl = step(float2(_GridLineSize, _GridLineSize), i.uv); // bottom-left
float2 tr = step(float2(_GridLineSize, _GridLineSize), float2(1.0, 1.0) - i.uv); // top-right
float grid_brightness = 1.0 - bl.x * bl.y * tr.x * tr.y;
fixed4 col = tex2D(_MainTex, i.uv) * (1.0 - grid_brightness) + _LineColor * grid_brightness;
这里还少了一个缩放网格的功能,不过效果差不多了
但是这样做有一个坏处就是,图片和网格是绑定在一起的,图片动了,网格也很会跟着动
这样,如果需要找到网格上面的某一个格子做一些功能的话,就需要考虑到图片的坐标系,图片的缩放,想想感觉挺麻烦
如果让网格变成世界坐标的,与图片坐标无关,那么找网格上面某一个格子就会很方便
WorldSpace Grid
World Position
获取图片中某个像素的世界坐标的 Shader
Shader "Unlit/UnlitGrid"
{
Properties
{
[PerRendererData] _MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float4 worldSpacePos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
o.worldSpacePos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = i.worldSpacePos;
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
其中的关键点在于从顶点着色器向片元着色器传递一个世界坐标的 float4
首先要在 v2f 中设置一个属性 float4 worldSpacePos
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float4 worldSpacePos : TEXCOORD1;
};
然后在顶点着色器里面计算这个世界坐标
o.worldSpacePos = mul(unity_ObjectToWorld, v.vertex);
在片元着色器中就可以测试了
fixed4 col = i.worldSpacePos;
效果
可以看到颜色是分坐标系的,说明 shader 计算正确
WorldSpace Grid
对上面的着色器稍作修改,就得到了世界坐标中的 Grid
Shader "Unlit/UnlitGrid"
{
Properties
{
[PerRendererData] _MainTex ("Texture", 2D) = "white" {}
_GridCellSize ("GridCellSize", float) = 64
[FloatRange] _GridLineSize ("GridLineSize", Range(0,1)) = 0.02
_LineColor ("Line Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float4 worldSpacePos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _GridCellSize;
float _GridLineSize;
float4 _LineColor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
o.worldSpacePos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 AddGrid(fixed4 col, float2 fpos)
{
float scaled_line_size = _GridLineSize/_GridCellSize;
float2 bl = step(float2(scaled_line_size, scaled_line_size), fpos); // bottom-left
float2 tr = step(float2(scaled_line_size, scaled_line_size), float2(1.0, 1.0) - fpos); // top-right
float grid_brightness = 1.0 - bl.x * bl.y * tr.x * tr.y;
col = col * (1.0 - grid_brightness) + _LineColor * grid_brightness;
return col;
}
fixed4 frag (v2f i) : SV_Target
{
float2 st = i.worldSpacePos.xy / _GridCellSize;
float2 ipos = floor(st); // integer
float2 fpos = frac(st); // fraction
fixed4 col = tex2D(_MainTex, i.uv);
col = AddGrid(col, fpos);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
传递鼠标的世界坐标到材质
在 Shader 中定义一个 uniform 变量
uniform float4 mouseWorldPos;
创建一个脚本,传递鼠标的世界坐标到材质
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Playground : MonoBehaviour
{
private Renderer render;
// Start is called before the first frame update
void Start()
{
render = GetComponent<Renderer>();
}
// Update is called once per frame
void Update()
{
Vector3 mouseWorldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
print(mouseWorldPos);
render.material.SetVector("mouseWorldPos",new Vector4(mouseWorldPos.x, mouseWorldPos.y, 0, 0));
}
}
在 Shader 中画一个圆来测试是否正确收到鼠标位置
fixed4 frag (v2f i) : SV_Target
{
float2 st = i.worldSpacePos.xy / _GridCellSize;
float2 ipos = floor(st); // integer
float2 fpos = frac(st); // fraction
fixed4 col = distance(st, mouseWorldPos.xy);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
结果:
鼠标点击控制 Tint
现在要实现一个功能,鼠标点击一次,使得图片上相应位置的格子变色,再点击一次,图片恢复原来的颜色
首先从点击一次,让整个图片变色开始
在 Shader 中添加一个属性,使得 Tint 的颜色可以调整
_DeactiveTint ("DeactiveTint", Color) = (0.6, 0.6, 0.6, 1)
在 Shader 的 Pass 中设置对应的变量
float4 _DeactiveTint;
在 Shader 的 Pass 中设置一个 float 变量,来完成跟布尔值差不多的功能
uniform float shouldTint;
通过 float 变量 = 1 还是 = 0 来切换颜色
fixed4 add_tint(fixed4 col, float2 ipos)
{
col = col * (1.0 - shouldTint) + col * _DeactiveTint * shouldTint;
return col;
}
最终在片元着色器中应用
fixed4 frag (v2f i) : SV_Target
{
float2 st = i.worldSpacePos.xy / _GridCellSize;
float2 ipos = floor(st); // integer
float2 fpos = frac(st); // fraction
fixed4 col = tex2D(_MainTex, i.uv);
col = add_tint(col, ipos);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
脚本多添加一行,将点击次数传入材质
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Playground : MonoBehaviour
{
private Renderer render;
private float shouldTint = 0f;
// Start is called before the first frame update
void Start()
{
render = GetComponent<Renderer>();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 mouseWorldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
shouldTint = 1f - shouldTint;
render.material.SetVector("clickPos",new Vector4(mouseWorldPos.x, mouseWorldPos.y, 0, 0));
render.material.SetFloat("shouldTint", shouldTint);
}
}
}
这样就完成了鼠标点击一遍变色,再点击一遍恢复的功能
Selective Grid
更进一步,使用 ipos 来决定哪里需要着色,就完成了对指定格子着色的功能
Shader "Unlit/UnlitGrid"
{
Properties
{
[PerRendererData] _MainTex ("Texture", 2D) = "white" {}
_GridCellSize ("Grid Cell Size", float) = 1
[FloatRange] _GridLineSize ("Grid Line Size", Range(0,1)) = 0.02
_LineColor ("Line Color", Color) = (0.7, 0.7, 0.7, 1)
_ReachableTint ("Reachable Tint", Color) = (0.9, 0.9, 0.9, 1)
_UnreachableTint ("Unreachable Tint", Color) = (0.6, 0.6, 0.6, 1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float4 worldSpacePos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _GridCellSize;
float _GridLineSize;
float4 _LineColor;
float4 _ReachableTint;
float4 _UnreachableTint;
uniform float4 click_pos;
uniform float should_tint;
uniform float tint_radius;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
o.worldSpacePos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 add_tint(fixed4 col, float2 ipos)
{
float2 ipos_relatived_to_click_ipos = ipos - floor(click_pos/_GridCellSize);
float dist = ipos_relatived_to_click_ipos.x * ipos_relatived_to_click_ipos.x
+ ipos_relatived_to_click_ipos.y * ipos_relatived_to_click_ipos.y;
float reachable = step(dist, tint_radius * tint_radius + 0.001);
fixed4 tint = col * _UnreachableTint * (1.0 - reachable) + col * _ReachableTint * reachable;
col = col * (1.0 - should_tint) + tint * should_tint;
return col;
}
fixed4 add_grid(fixed4 col, float2 fpos)
{
float scaled_line_size = _GridLineSize/_GridCellSize;
float2 bl = step(float2(scaled_line_size, scaled_line_size), fpos); // bottom-left
float2 tr = step(float2(scaled_line_size, scaled_line_size), float2(1.0, 1.0) - fpos); // top-right
float grid_brightness = 1.0 - bl.x * bl.y * tr.x * tr.y;
col = col * (1.0 - grid_brightness) + _LineColor * grid_brightness;
return col;
}
fixed4 frag (v2f i) : SV_Target
{
float2 st = i.worldSpacePos.xy / _GridCellSize;
float2 ipos = floor(st); // integer
float2 fpos = frac(st); // fraction
fixed4 col = tex2D(_MainTex, i.uv);
col = add_tint(col, ipos);
col = add_grid(col, fpos);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
使用脚本传入选择半径
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Playground : MonoBehaviour
{
public float tintRadius = 1f;
private Renderer render;
private float shouldTint = 0f;
// Start is called before the first frame update
void Start()
{
render = GetComponent<Renderer>();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 mouseWorldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (IsMouseClickChessboard(mouseWorldPos))
{
print(GetClickCellPos(mouseWorldPos));
shouldTint = 1f - shouldTint;
render.material.SetVector("click_pos", new Vector4(mouseWorldPos.x, mouseWorldPos.y, 0, 0));
render.material.SetFloat("should_tint", shouldTint);
render.material.SetFloat("tint_radius", tintRadius);
}
}
}
private Vector2 GetTiledSpriteSize()
{
Vector2 trSize = new Vector2(transform.localScale.x, transform.localScale.y);
Vector2 renderSize = render.size;
Vector2 tiledSpriteSize = trSize * renderSize;
return tiledSpriteSize;
}
/// <summary>
/// 根据 SpriteRenderer Tile 的 Size 来判断点击位置是否在 Sprite 区域内
/// </summary>
/// <param name="mouseWorldPos">鼠标点击位置的世界坐标</param>
/// <returns></returns>
private bool IsMouseClickChessboard(Vector3 mouseWorldPos)
{
Vector2 tiledSpriteSize = GetTiledSpriteSize();
// mouse Position Relative to Chessboard
Vector2 mouseRelPos = mouseWorldPos - transform.position;
if (mouseRelPos.x > 0 &&
mouseRelPos.x < tiledSpriteSize.x &&
mouseRelPos.y > 0 &&
mouseRelPos.y < tiledSpriteSize.y)
{
return true;
}
return false;
}
private Vector2Int GetClickCellPos(Vector3 mouseWorldPos)
{
Vector2 mouseGridWorldPos = (mouseWorldPos - transform.position) / gridCellSize;
Vector2Int clickCellPos = Vector2Int.FloorToInt(mouseGridWorldPos);
return clickCellPos;
}
}