最近有个很实用的shader效果要用到我的游戏中,就是在模型网格纹理上画个小圆圈。
原理也不复杂,我们先把二维坐标系中纹理和uv想象成一张“画布”,就是根据圆心的uv和纹理的宽高去确定圆心像素,然后圆形的半径以及“扩展”和“收缩”的像素宽度去判断“画布”中每个uv像素是否处于圆环上,然后混合着色即可。语言描叙可能不太好理解,画一张图就一目了然,如下:
去判断“画布”中pixel是否处于center为圆心,in和out组成的圆环上即可,shader和c#代码也贴出来,如下:
Shader "Custom/ProjectionShader"
{
Properties
{
_MainTex ("Main Texture", 2D) = "white" {}
_CircleCenter("Circle Center Texcoord",vector) = (0,0,0,0)
_CircleRadius("Circle Radius Pixel",Range(0,512)) = 100
_CircleOutWidth("Circle Out Width",Range(0,100)) = 0
_CircleInWidth("Circle In Width",Range(0,100)) = 0
_CircleColor("Circle Color",Color) = (1,1,1,1)
_CircleAlpha("Circle Alpha",Range(0,1)) = 0.5
}
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;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _MainTex_TexelSize;
float4 _CircleCenter;
int _CircleRadius;
int _CircleOutWidth;
int _CircleInWidth;
fixed4 _CircleColor;
float _CircleAlpha;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
//判断当前uv是否处于center为圆心,radius,out,in组成的圆环上
//使用点到圆心向量模长判断即可,为了效率无需开方处理
bool isInoutCircle(float2 center, float2 uv, int radius, int outwidth, int inwidth)
{
float2 cpixel = float2(_MainTex_TexelSize.z*center.x, _MainTex_TexelSize.w*center.y);
float2 fpixel = float2(_MainTex_TexelSize.z*uv.x, _MainTex_TexelSize.w*uv.y);
float2 f2c = fpixel - cpixel;
float f2cLenPow2 = f2c.x*f2c.x + f2c.y*f2c.y;
if (f2cLenPow2 >= (radius - inwidth) * (radius - inwidth) && f2cLenPow2 <= (radius + outwidth)*(radius + outwidth))
{
return true;
}
return false;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
float2 centeruv = float2(_CircleCenter.x, _CircleCenter.y);
if (isInoutCircle(centeruv,i.uv, _CircleRadius, _CircleOutWidth, _CircleInWidth))
{
col = lerp(col, _CircleColor, _CircleAlpha);
}
return col;
}
ENDCG
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SphereCircle : MonoBehaviour
{
private Material cloneMat;
void Start()
{
cloneMat = GetComponent<MeshRenderer>().material;
}
void Update()
{
if (Input.GetMouseButton(0))
{
//使用射线获取当前point的texcoord0
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit) && hit.transform == transform)
{
#if UNITY_EDITOR
Debug.DrawLine(ray.origin, hit.point, Color.red);
#endif
Vector4 uv = new Vector4(hit.textureCoord.x, hit.textureCoord.y);
cloneMat.SetVector("_CircleCenter", uv);
}
}
}
}
shader代码无非就是将纹理和uv当做“画布”,然后fragment函数中逐行逐列去判断当前像素点是否处于“圆环”上,然后混合着色即可,c#代码使用raycast去获取当前碰撞点的texcoord0,十分好用的方法。
by the way,现在我就不花那么多时间给代码添加过于复杂的注释了,这里默认小伙伴们已经在图形学上学到一定程度了,实际上沿着我的博客从开始看到现在,这些都属于简单的东西,有个idea就能实现的。
最后看下效果,如下:
so,猫叔登场。