首页 > 其他分享 >Unity实现在白板上绘画涂鸦

Unity实现在白板上绘画涂鸦

时间:2022-12-13 15:03:21浏览次数:83  
标签:涂鸦 lastPoint point 笔刷 白板 uv private Unity Vector2


前言

  • 有段时间没有更新博客了,不知道应该写些什么,太简单感觉没有记录的必要,太难自己都没能理解,不知道如何下手。回归初心,记录自己想记录的东西。
  • 需要实现一个白板绘画的功能,可以使用LineRenderer或者GL,但是都被我舍弃了,我想同时实现笔刷功能,以上两种方法都不合适,于是我选择了用材质渲染到RenderTexture上,用来记录绘画的痕迹。
  • 之前已经在ue4中,实现了一个类似的功能,现在准备在unity上画在一个白板上,如果想在3D物体上涂鸦,就参考之前的博客:​​UE4快速实现涂鸦功能​​

思路

有之前的demo作为参考,我们基本上已经确定了实现白板绘画的可能性。我们需要做的就是利用
​​​Graphics.Blit​​函数,将笔刷纹理、颜色绘制到一张RenderTexture保存下来,并重复利用,就能完整保存下来自己的绘画痕迹。

笔刷Shader

除了上面的​​Graphics.Blit​​函数,最核心的就是这个shader了,里面就是将之前的Texture与最新的笔刷已经纹理再混合成一张新的图片。注释写得比较随意,看看就好。

Shader "Unlit/PaintBrush"
{
Properties
{
//之前的Texture
_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
}
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;
fixed4 _Color;

v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, 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->缩小纹理
uv *= size;
fixed4 col = tex2D(_BrushTex,uv);
//去掉原来的颜色
//我这里基本上都是取rng图片做的笔刷
col.rgb = 1;
//*上笔刷的颜色
col *= _Color;
return col;
}
ENDCG
}
}
}

功能实现

我们在一个白板上去画线,比在模型上用射线取模型uv的值应该更好理解了,我们只需要获取鼠标的位置计算与屏幕宽高的占比就是对应了图片的uv值。

//画点
private void Paint(Vector2 point)
{
if (point.x < 0 || point.x > _screenWidth || point.y < 0 || point.y > _screenHeight)
return;

Vector2 uv = new Vector2(point.x / (float)_screenWidth,
point.y / (float)_screenHeight);
_paintBrushMat.SetVector("_UV", uv);
Graphics.Blit(_renderTex, _renderTex, _paintBrushMat);
}

注意事项

  • 如果你在update获取的鼠标移动过快,两个点的距离太大会导致绘画不连续,这里就需要插值绘制,我这里的做法不太严谨,有需要可以自己重新写插值算法。
//插点
private void LerpPaint(Vector2 point)
{
Paint(point);

if (_lastPoint == Vector2.zero)
{
_lastPoint = point;
return;
}

float dis = Vector2.Distance(point, _lastPoint);
if (dis > _brushLerpSize)
{
Vector2 dir = (point - _lastPoint).normalized;
int num = (int)(dis / _brushLerpSize);
for (int i = 0; i < num; i++)
{
Vector2 newPoint = _lastPoint + dir * (i + 1) * _brushLerpSize;
Paint(newPoint);
}
}
_lastPoint = point;
}
  • 因为我们使用到了RenderTexture,unity好像会将RenderTexture缓存下来以便下次的快速调用,但是这就有一个新的问题,每次我们重新运行的时候,RenderTexture可能还会保留上次的内容,这时候,我们就可以在最开始的时候,将RenderTexture的内容全部清除掉。
Shader "Unlit/ClearBrush"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
ZTest Always Cull Off ZWrite Off Fog{ Mode 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;

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
fixed4 col = tex2D(_MainTex, i.uv);
col = 0;
return col;
}
ENDCG
}
}
}

完整代码

//-----------------------------------------------------------------------
// <copyright file="Test.cs" company="Codingworks Game Development">
// Copyright (c) codingworks. All rights reserved.
// </copyright>
// <author> codingworks </author>
// <email> [email protected] </email>
// <time> #CREATETIME# </time>
//-----------------------------------------------------------------------

using UnityEngine;
using UnityEngine.UI;

public class Paint : MonoBehaviour
{
private Vector2 _lastPoint;

[SerializeField] private Material _clearBrushMat;

[SerializeField] private Material _paintBrushMat;

private RenderTexture _renderTex;

private int ScreenWidth, ScreenHeight;

[SerializeField] private RawImage _rawImage;

private float _paintLerpSize;


// Use this for initialization
private void Start()
{
ScreenWidth = Screen.width;
ScreenHeight = Screen.height;

var brushSize = _paintBrushMat.GetFloat("_Size");
float brushTexWidth = _paintBrushMat.GetTexture("_BrushTex").width;
_paintLerpSize = brushTexWidth / brushSize;

_renderTex = RenderTexture.GetTemporary(ScreenWidth, ScreenHeight, 24);

Graphics.Blit(null, _renderTex, _clearBrushMat);

_rawImage.texture = _renderTex;
}

// Update is called once per frame
private void Update()
{
if (_renderTex && _paintBrushMat)
{
if (Input.GetMouseButton(0))
LerpPaint(Input.mousePosition);

if (Input.GetMouseButtonUp(0))
_lastPoint = Vector2.zero;
}
}


private void LerpPaint(Vector2 point)
{
Paint(point);

if (_lastPoint == Vector2.zero)
{
_lastPoint = point;
return;
}

var dis = Vector2.Distance(point, _lastPoint);
if (dis > _paintLerpSize)
{
var dir = (point - _lastPoint).normalized;
var num = (int) (dis / _paintLerpSize);
for (var i = 0; i < num; i++)
{
var newPoint = _lastPoint + dir * (i + 1) * _paintLerpSize;
Paint(newPoint);
}
}
_lastPoint = point;
}

/// <summary>
/// 绘画
/// </summary>
/// <param name="point">鼠标的位置</param>
private void Paint(Vector2 point)
{
if (point.x < 0 || point.x > ScreenWidth || point.y < 0 || point.y > ScreenHeight)
return;

var uv = new Vector2(point.x / ScreenWidth,
point.y / ScreenHeight);
_paintBrushMat.SetVector("_UV", uv);
Graphics.Blit(_renderTex, _renderTex, _paintBrushMat);
}
}

截图展示

Unity实现在白板上绘画涂鸦_绘画

未完成

  1. 橡皮擦还没做,最好参照笔刷shader,再单独写一个橡皮擦的shader
  2. 颜色如果能做成一个面板,能随意选择颜色
  3. 本来就只是一个demo,就不要求太多…

总结

  1. 作为一个程序员,绘画和写字就不要吐槽了
  2. 整篇博客下来,自己都有一种不知所云的感觉,思维太飘了
  3. 有兴趣的同学,直接看整个工程吧

源码链接

​https://github.com/coding2233/UnityPaint​


标签:涂鸦,lastPoint,point,笔刷,白板,uv,private,Unity,Vector2
From: https://blog.51cto.com/u_15911199/5934151

相关文章

  • Unity 使用拖尾渲染器模拟简易的管道流体
    关于管道流体关于管道流体,最佳的解决方案肯定是UV动画无疑,在网上看过很多例子几乎都是这样的,毕竟用实时流体计算的话开销确实太大,用粒子系统的话又苦于难以表现出流体的粘稠......
  • Unity UGUI图文混排源码(三) -- 动态表情
    这里是根据图文混排源码(二)进一步修改的,其他链接也不贴了,就贴一个链接就好了,第一次看这文章的同学可以先去看看其他几篇文章1.首先来一个好消息,在最新版本的图文混排中,终于......
  • Unity 简易的UI背景昼夜轮替效果
    在UI背景上实现一个简易的有光影照射的昼夜轮替效果,往往比一个死板的UI背景看起来更加形象生动,比较传统的方式是多图轮流替换的序列帧动画,不过要达到整个UI背景大图的所有地......
  • 手动build unity3d的docker镜像
    手动buildunity3d的docker镜像参考资料docker官方文档:​​DockerDocumentation|DockerDocumentation​​unity3dlinux版的论坛链接,在这里能找到各个版本,以及需要安装......
  • Unity UGUI
    超详细的基础教程传送门:(持续更新中)UnityUGUI的教程好少,幸亏找到一个UGUI的Demo,看了几个例子,以下是一些简单的学习笔记:1.导入UI图片资源2.设置参数:            ......
  • unity shaderlab Blend操作
    原文链接: ​​http://www.tiankengblog.com/?p=84​​Blend混合操作是作用于在所有计算之后,是Shader渲染的最后一步,进行Blend操作后就可以显示在屏幕上。shader的计算步骤......
  • 【Unity】超级坦克大战(二)游戏流程
    更新日期:2020年7月9日。项目源码:在终章发布索引​​本章最佳实践​​​​正式开始​​​​登录流程​​​​准备流程​​​​关卡选择流程​​​​闯关流程​​​​启用所......
  • Unity UGUI图文混排(五) -- 一张图集对应多个Text
    继上一篇说的更新了一张图集对应多个Text的功能,为了节省资源嘛这里,但是也没有舍弃之前的一个Text一个图集,因为我感觉应该两个都有用,于是我重新写了一个脚本1.其实大体跟前面......
  • 【Unity】超级坦克大战(三)登录界面
    更新日期:2020年7月9日。项目源码:在终章发布索引​​本章最佳实践​​​​正式开始​​​​创建UI编辑场景​​​​创建登录界面UI实体​​​​创建登录界面UI逻辑类​​​......
  • 【Unity】超级坦克大战(一)搭建项目、导入框架、前期开发准备
    更新日期:2020年7月9日。项目源码:在终章发布免责声明:超级坦克大战使用的图片、音频等所有素材均有可能来自互联网,本专栏所有文章仅做学习和教程目的,不会将任何素材用于任何......