Unity 中实现截图画笔橡皮擦工具

Unity 中实现截图画笔橡皮擦工具

好久没写博客了,随着疫情的缓和,工作也忙了起来 ,写博客也成了忙里偷闲的一项娱乐活动了,不以娱乐为目的技术博客写手不是一个好的厨师(我一向自称自己是码农界最帅的厨子)~
今天就来分享下之前项目中用shader实现的一个画笔橡皮擦功能,当时是一位老师提出要在软件里做笔记的一个需求,希望可以在软件使用过程中划重点,做笔记,再截图保存,方便学生复习,真不愧是一位一切以学生为中心的好老师~
刚接到这个需求,我还在UnityAssetsStore上找了找相关插件,想着直接拿来用多好,找了一些,觉得都不太方便集成到项目, 索性自己用shader写一个吧,也复习一下之前的shader技术。
实现思路:在UI最上层添加一个填充满屏幕的画布RenderTexture, 运用 屏幕后处理技术Graphics.Blit,对当前的画布上的RenderTexture进行笔刷采样处理,画笔效果如下:在这里插入图片描述
核心代码:

    private void Paint(Vector2 point)
    {
    
        if (point.x < _leftDownConnerPoint.x || point.x > _rightUpConnerPoint.x || point.y < _leftDownConnerPoint.y || point.y > _rightUpConnerPoint.y)
            return;
        Vector2 uv = new Vector2(point.x / (float)_screenWidth,
            point.y / (float)_screenHeight);
        if (_eraserFlag)
        {
            _eraserBrushMat.SetVector("_UV", uv);
            Graphics.Blit(_blitRenderTexture, _blitRenderTexture, _eraserBrushMat);
        }
        else
        {
            _paintBrushMat.SetVector("_UV", uv);
            Graphics.Blit(_blitRenderTexture, _blitRenderTexture, _paintBrushMat);
        }

    }

point即为我们当前鼠标屏幕坐标,第一句if是我限制了截图可画的区域,重要的是下面的代码,先算出当前采样纹理UV值,然后传入_paintBrushMat的画笔材质shader,在用 Graphics.Blit 去做效果叠加处理。接下来我们看下核心的_paintBrushMat的画笔材质shader代码:

Shader "Unlit/PaintBrush"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_BrushTex("Brush Texture",2D)= "white" {}
		_Color("Color",Color)=(1,1,1,1)
		_UV("UV",Vector)=(0,0,0,0)
		_Size("Size",Range(1,1000))=1

		_SizeY("SizeY",Range(1,1000))=1
	}
	SubShader
	{
		Tags { "RenderType"="Transparent" }
		LOD 100
		ZTest Always Cull Off ZWrite Off Fog{ Mode Off }
		Blend SrcAlpha OneMinusSrcAlpha
		//Blend One DstColor
		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;
			sampler2D _BrushTex;
			fixed4 _UV;
			float _Size;
			float _SizeY;
			fixed4 _Color;

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				// sample the texture

		

		       float size = _Size;
               float2 uv = i.uv + (0.5f / size);
               uv = uv - _UV.xy;
               uv *= size;

			   float sizeY = _SizeY;
			   uv.y=uv.y*sizeY/size;

			   fixed4 col = tex2D(_BrushTex,uv);
			
				col.rgb = 1;
				col *= _Color;
				return col;
			}
			ENDCG
		}
	}
}

_BrushTex就是我们的笔刷图标图片,就是个效果图中的那个小黑点图片,_Color就是笔刷颜色,_UV就是我们当前要采样的笔刷点在画布上的UV值,_Size就是我们的画布像素与笔刷图片的像素比例大小,_SizeY是画布高度与画布跨度的比例,计算方式 float sizeY = ((float)Screen.height / (float)Screen.width) * size;(其实不需要size,shader里就不要在size了)因为我们最终的采样结果应该是一个规则圆形,而非宽高不一样的椭圆,我们主要看片源着色器的代码,这里
float2 uv = i.uv + (0.5f / size);
uv = uv - _UV.xy;
uv = size;
其实是做了一个从画布每个UV值到采样图片UV值的一个坐标映射计算,
float sizeY = _SizeY;
uv.y=uv.y
sizeY/size;
这里就是我们上面说的要调整uv的x,y采样比例,无论屏幕宽高比例如何,我们都输出圆形。
注意我们要开启深度测试为Always,避免片元被舍弃,关闭深度写入 ,alpha混合设置为Blend SrcAlpha OneMinusSrcAlpha;

我们再来看看橡皮擦得效果,如图(别问我为啥用这次绿色展示,大家都不喜欢绿色,可不得擦掉它嘛,哈哈):
在这里插入图片描述
代码如下:

Shader "Unlit/EraserBrush"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_BrushTex("Brush Texture",2D)= "white" {}
		_Color("Color",Color)=(1,1,1,1)
		_UV("UV",Vector)=(0,0,0,0)
		_Size("Size",Range(1,1000))=1

		
		_SizeY("SizeY",Range(1,1000))=1
	}
	SubShader
	{
		Tags { "RenderType"="Transparent" }
		LOD 100
		ZTest Always Cull Off ZWrite Off Fog{ Mode Off }
		Blend Off
		//Blend One DstColor
		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;
			sampler2D _BrushTex;
			fixed4 _UV;
			float _Size;
			float _SizeY;
			fixed4 _Color;

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				// sample the texture
		       float size = _Size;
               float2 uv = i.uv + (0.5f / size);
               uv = uv - _UV.xy;
               uv *= size;





			   float sizeY = _SizeY;
			   uv.y=uv.y*sizeY/size;

			   	float cirle =  pow(uv.x-0.5f, 2)+pow(uv.y-0.5f*sizeY/size, 2);
				if(cirle>0.25f)
				   discard;
				fixed4 col = tex2D(_BrushTex,uv);
				col.rgb = 1;
				col *= _Color;

				return col;
			}
			ENDCG
		}
	}
}

橡皮擦,我们采用透明空心圆圈的采样图片, 在片元着色器代码处理颜色时不同, float cirle = pow(uv.x-0.5f, 2)+pow(uv.y-0.5f*sizeY/size, 2);
if(cirle>0.25f)
discard;
我们关闭了alpha blend ,在需要擦除的区域之外的片元直接舍弃,这样圆圈空心区域的颜色就直接成了透明,实现了擦除的效果(在shader里用if语句不太好,有待改进)。
需要注意的就是我们在对笔刷采样应该增大它的采样频率,让它在每一帧多采样几次。
本人在shader编程方面的知识也是比较薄弱,欢迎留言指正,谢谢~
附上工程源码:
工程源码:https://github.com/ivoslove/ScreenShotPainter

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值