前言

  • 有段时间没有更新博客了,不知道应该写些什么,太简单感觉没有记录的必要,太难自己都没能理解,不知道如何下手。回归初心,记录自己想记录的东西。
  • 需要实现一个白板绘画的功能,可以使用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 DstColorPass{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 100ZTest Always Cull Off ZWrite Off Fog{ Mode Off }Blend One DstColorPass{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 texturefixed4 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> coding2233@163.com </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 initializationprivate 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 frameprivate 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);}
}

截图展示

未完成

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

总结

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

源码链接

https://github.com/coding2233/UnityPaint

Unity实现在白板上绘画涂鸦相关推荐

  1. 使用Unity在材质球上实现绘画:详细解释每一行Shader代码!

    在Unity中实现在材质球上绘画可以使用下面这个步骤: 创建一个基础的材质球:在Unity的项目面板中创建一个新材质球,然后将其分配给您要绘画的对象. 创建一个Shader:为了实现在材质球上绘画,您 ...

  2. Android opengl es 3.0 + ndk 绘画涂鸦项目

    前言 写一个opengl es 3.0 + ndk 的绘画涂鸦项目,命名为白板哈哈哈,记录自己遇到的问题,顺便学到的知识整合一遍,算是对自己一段时间的总结. 项目地址:Whiteboard 如果对你有 ...

  3. 头条白板面试_如何在白板上组织您的想法并粉碎技术面试

    头条白板面试 by Doug Arcuri 由道格·阿库里(Doug Arcuri) 如何在白板上组织您的想法并粉碎技术面试 (How to organize your thoughts on the ...

  4. 【使用Unity开发Windows Phone上的2D游戏】(1)千里之行始于足下

    写在前面的 其实这个名字起得不太欠当,Unity本身是很强大的工具,可以部署到很多个平台,而不仅仅是可以开发Windows Phone上的游戏. 只不过本人是Windows Phone 应用开发出身, ...

  5. 【python】在图片上绘画

    大家好,我是胡亥大魔王.今天介绍python中在图片上绘画 Pillow 的 ImageDraw 模块可以在图像上画线.矩形.圆形或其它简单形状. 绘制形状 下面介绍 ImageDraw 方法在图像上 ...

  6. Unity批量给模型上同一个材质

    Unity批量给模型上同一个材质 第一步:先选择所有要上材质的模型: 第二步:将创建的材质拖到右侧属性栏:

  7. 【软件推荐】身为高级unity工程师,电脑上的软件一览表,从工作顺序带你一览高级unity工程师所用的软件,如果高级是你的目标,这篇博客绝对不会让你失望

    目录 题目 开头 开发之前 钉钉 FeiQ OFFICE 开发中 UnityHub Visual Studio 2019 Everything Notepad++ git TortoiseGit To ...

  8. 如何在网格上绘画【UE4】

    网格绘画是玩家在游戏中的物体上绘画的能力.网格绘画的例子有Super Mario Sunshine中的 goop. Portal 2中的凝胶和Splatoon中的墨水.它可以用作游戏元素,也可以纯粹用 ...

  9. 记号笔写在白板上引起的尴尬而又无奈的事件

    前言 前日晚饭归来,载夕阳而迎明月,微风习习,草木依依,不觉诗兴大起.然才疏学浅,空有气而不得发,悲哉,痛哉.忽忆及古人语:熟读唐诗三百首,不会作诗也会吟.适前日晨起读诗几首,遂选张九龄之感遇以记之. ...

最新文章

  1. CentOS基础命令大全
  2. 基于vue-cli,做个nuxt脚手架~
  3. 任天堂和VR,是要“重新牵手”还是“分道扬镳”?
  4. linux pwm控制蜂鸣器 滴滴_51单片机PWM直流电机PID控制转速源程序
  5. python app mysql_Python 操作 MySQL 的5种方式
  6. Linux下的文件共享全攻略系列之二:NFS快速配置教程与安全策略
  7. pytorch 对特征进行mean_Pytorch的mean和std调查实例
  8. 2021年第十一届MathorCup高校数学建模挑战赛比赛占坑
  9. 天壤联合创始人韩定一:大模型+小样本数据,AI驱动金融数字化转型新范式|量子位·视点分享回顾...
  10. 深度学习算法原理——神经网络的基本原理
  11. DDR2芯片内部终结ODT技术解析
  12. 基于FCM算法的聚类算法
  13. 单片机测量PWM占空比的三种方法
  14. 基于微信小程序的资产管理平台的设计与实现
  15. 如何从零开始学习SEM?
  16. Scala进阶_函数式编程(过滤丶排序丶分组丶聚合)
  17. 5G NR 基础原理与关键技术
  18. Python中文件路径
  19. c html保存为图片格式,【单选题】在 IE 中,若要把整个网页的文字和图片一起保存在一个文件中,则文件的类型应为 。 A. HTM B. HTML C. MHT D. TXT...
  20. 【Android休眠】之PowerKey唤醒源实现

热门文章

  1. mysql phpwind_PHPWind环境搭建(Linux)
  2. Hive 自定义函数编写(UDF,UDAF,UDTF)
  3. 【STM32】数码管显示按键控制舵机转动的角度(二)
  4. (二十七)张量表示定理 —— Cauchy 基本表示定理
  5. rhel配置DNS分析+实验
  6. Leetcode-二分+递归/回溯-1723. 完成所有工作的最短时间
  7. 基于RSA的公钥基础体系下安全通信实战
  8. 计算机和数学专业哪个难,学计算机专业难吗 数学很差能学吗
  9. Android中Vitamio视频框架学习
  10. CVPR 2022|上海交大腾讯优图提出IFRNet:视频插帧新范式新SOTA