在Unity中实现画图/字帖功能

摘要:
具体而言,获取RawImage组件上的RenderTexture作为源贴图,也作为目标贴图,然后使用自己的材质处理RenderTextture。处理结果仍保存回RenderTexture。图形.Blit;材质处理逻辑写入着色器。然后,以下问题变成如下:1。着色器如何知道您的“笔位置”2。着色器如何将您的绘画绘制到RawImage上?绘画1的具体实现过程中的核心点。着色器如何知道您的“笔位置”?每个人都知道着色器中计算的坐标是贴图的uv坐标,那么你如何知道笔的位置?

转载:https://blog.csdn.net/Patrick_Boom/article/details/107180717

前段时间总是加班,也没啥心情和精力去研究新东西,总结一下自己之前做的字帖的功能

先上效果图:

在Unity中实现画图/字帖功能第1张

文章分为几部分:

(一) 画图板实现原理

(二) 画图具体实现过程中的核心点

(三) 在画图板的基础上 演变为字帖的思路

· 画图板实现原理

画图板功能一定要有两个东西:一个画布,一个画笔。

然后你需要知道Unity中有这样一个函数:
        public static void Blit (Texture source, RenderTexture dest, Material mat) ;

这个函数的官方解释是:“Copies source texture into destination render texture with a shader”

我个人的理解就是: 把source贴图上的信息,通过一个材质上的shader里的处理方法,赋给dest贴图。

这就是画板的实现原理的支撑,Material 是画笔,RawImage 是画布。

具体点说,获取 RawImage 组件上的 RenderTexture 作为 source贴图,同时也作为dest贴图,然后用自己的Material去对RenderTexture做处理,处理结果还是保存回这个RenderTexture。

            Graphics.Blit(m_renderTex, m_renderTex, brushMat);

材质的处理逻辑是写在shader上的。

那么接下来的问题变成了如下 :

1.Shader 如何知道你的“落笔位置”

2.Shader 如何把你画的东西 画到 RawImage 上

· 画图具体实现过程中的核心点

1. Shader 如何知道你的“落笔位置”

大家都知道shader中计算的坐标是贴图的uv坐标

所以如何知道你的落笔位置呢?这就需要一系列比较恶心的换算了~ 

(1) 得到画板中心在屏幕中的中心位置

(2) 得到鼠标/手指触碰位置在屏幕中的位置

(3) 计算鼠标/手指触碰位置 与 画板中心的相对位置

(4) 计算鼠标/手指触碰位置 相对于贴图的uv坐标

试了很多次的代码,满满干货 ~  拿去拿去 (如果父物体及以上的层级有缩放,这里可能还需要修正的参数,这里就不写了)

  1. Vector2 GetUV(Vector2 brushPos)
  2. {
  3. //获取图片在屏幕中的像素位置
  4. Vector2 rawImagePos = Vector2.zero;
  5. //判断所在画布的渲染方式,不同渲染方式的位置计算方式不同
  6. switch (m_renderMode)
  7. {
  8. case RenderMode.ScreenSpaceOverlay:
  9. rawImagePos = rawImage.rectTransform.position;
  10. break;
  11. default:
  12. rawImagePos = m_uiCamera.WorldToScreenPoint(rawImage.rectTransform.position);
  13. break;
  14. }
  15. //换算鼠标在图片中心点的像素位置
  16. Vector2 pos = brushPos - rawImagePos;
  17. //换算鼠标在图片中UV坐标
  18. Vector2 uv = new Vector2(pos.x / m_rawImageSizeX + 0.5f, pos.y / m_rawImageSizeY + 0.5f);
  19. return uv;
  20. }

2. Shader 如何把你画的东西 画到 RawImage 上

经过1中的一些列的换算,我们知道了落笔位置对应图板的贴图的uv坐标了,

下一步就是把uv对应像素及周边的像素填上你想要的颜色,喏~ 一个点就画完了。

然后是如何画线呢? 你会说:简单,点多了就是线了啊,每帧去打点不就ok了~ 这时候就出问题了,如果画的太快,一帧的时间过去你的手已经画出去了好远。那么就是一些不连续的点,而不是完整的线。

所以,我们需要把当前点和上一个点存起来,两点之间做填充。

我这里用的方式是:将两个点为圆心的两个圆填满,再将将以两个点连线为对称轴,长为两点之间距离,圆直径为宽的矩形填满。(自己算法 不一定好~ 各位大神有高招欢迎讨论~ )

具体步骤看一下代码:

  1. Shader "Hidden/DrawWord"
  2. {
  3. Properties
  4. {
  5. _Tex("Texture" , 2D) = "white" {}
  6. _Size("Size", float) = 0
  7. _Color("Color" , color) = (1,1,1,1)
  8. _UV("UV" , vector) = (0,0,0,0)
  9. _LastUV("LastUV" , vector) = (0,0,0,0)
  10. }
  11. SubShader
  12. {
  13. ZTest Always Cull Off ZWrite Off Fog{ Mode Off }
  14. Blend SrcAlpha OneMinusSrcAlpha
  15. Pass
  16. {
  17. CGPROGRAM
  18. #pragma vertex vert
  19. #pragma fragment frag
  20. #include "UnityCG.cginc"
  21. struct appdata
  22. {
  23. float4 vertex : POSITION;
  24. float2 uv : TEXCOORD0;
  25. };
  26. struct v2f
  27. {
  28. float2 uv : TEXCOORD0;
  29. float4 vertex : SV_POSITION;
  30. };
  31. v2f vert(appdata v)
  32. {
  33. v2f o;
  34. o.vertex = UnityObjectToClipPos(v.vertex);
  35. o.uv = v.uv;
  36. return o;
  37. }
  38. sampler2D _Tex;
  39. float _Size;
  40. fixed4 _UV;
  41. fixed4 _LastUV;
  42. fixed4 _Color;
  43. fixed4 frag(v2f i) : SV_Target
  44. {
  45. fixed4 col = tex2D(_Tex, i.uv);
  46. float a = _UV.x;
  47. float b = _UV.y;
  48. float c = _LastUV.x;
  49. float d = _LastUV.y;
  50. float AA = d - b;
  51. float BB = a - c;
  52. float CC = b * c - a * d;
  53. float x = i.uv.x;
  54. float y = i.uv.y;
  55. float sqrDic1 = (x - a) * (x - a) + (y - b) * (y - b);
  56. float sqrDic2 = (x - c) * (x - c) + (y - d) * (y - d);
  57. float sqrDic11 = (AA * x + BB * y + CC) * (AA * x + BB * y + CC) / (AA * AA + BB * BB);
  58. float sqrDic22 = (x - (a + c) / 2) * (x - (a + c) / 2) + (y - (b + d) / 2) * (y - (b + d) / 2);
  59. float sqrDicStand1 = _Size/10000 * _Size/10000;
  60. float sqrDicStand2 = ((a - c) * (a - c) + (b - d) * (b - d)) / 4;
  61. //判断当前像素是否在被画的范围之内
  62. if (sqrDic1 < sqrDicStand1 || sqrDic2 < sqrDicStand1 || (sqrDic11 < sqrDicStand1 && sqrDic22 < sqrDicStand2))
  63. {
  64. col = _Color;
  65. }
  66. return col;
  67. }
  68. ENDCG
  69. }
  70. }
  71. }

好嘞~ 核心代码就这些啦,项目贴在最下面。

画图的Demo 项目地址:https://github.com/PatrickBoomBoom/board.git

 

· 在画图板的基础上 演变为字帖的思路

这些已经在公司的项目中实现了,不太方便贴出来,就口述一下吧 ~ 

画图的思路是:找到操作的uv坐标,然后去改变贴图颜色,想做字帖的话在画图的基础上再加两个步骤:

1. 规定可涂色的范围:

准备一张有透明通道的写好的字的图片,shader中判断像素是否在写字的范围内的时候同时判断是否在预先准备好的字(或者对应笔画)的图片中,那么就只有(你画的 && 属于字内的)像素才会被填色;

2. 规定下笔起点、转折点、终点、方向:

配好一个汉字的各种配置,然后用一个Gameobject来充当画笔输入位置,规定这个obj只能沿着笔画走,如果写对了,跳转下一笔,如果写错重写。 

免责声明:文章转载自《在Unity中实现画图/字帖功能》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇内核旁路、共享内存的零拷贝问题Linux SO_KEEPALIVE属性,心跳下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

针对高通BMS的研究 高通电量计

点击打开链接 高通8064 8974 8926等pm芯片都集成了电量计,估计后续芯片都会一直存在,现在许多项目UI状态栏电池都有百分比显示,所以需要深入分析BMS有助于解决电量方面的BUG。 一: SOC(荷电状态)计算方法 名词: FCC  Full-charge capacity       UC     Remaining capacityCC...

Unity3D特效-场景淡入淡出

        最近公司开始搞Unity3D..整个游戏..特效需求还是比较多的.关于UI部分的特效淡入淡出.看网上用的方法都是用个黑东东遮挡然后设置alpha这么搞....本大神感觉非常的low.而且很渣.故奋笔疾书借此文鄙视那些low方式. 关于这种处理用shader配合Material非常简单的.先来介绍下使用了哪些东东. 1.肯定是需要一个脚本的....

UnityShader快速上手指南(二)

简介 前一篇介绍了如果编写最基本的shader,接下来本文将会简单的深入一下,我们先来看下效果吧 呃,gif效果不好,实际效果是很平滑的动态过渡 实现思路 1.首先我们要实现一个彩色方块 2.让色彩动起来 over 实现一个RGB CUBE 先看代码吧: Shader "LT/Lesson2" { Properties { _OffsetX (...

Unity3d Shader

Unity3d Shader 预览Surface Shader主要用来实现光照相关处理,可能更简洁。Vertex and Fragment Shader如果不与光照交互, 则可以用这个shader,更录活fixed function shaders固定shader主要用于老旧卡ShaderLab不管写哪种shader,最终通过shaderLab实现,其组织结...

使用GLSL实现雾化的效果

1 为什么需要在GLSL中实现雾的效果?   D3D10已经不再支持固定管线的绘制了,所有的绘制都得使用着色器语言。OpenGL虽说仍然支持固定管线,但以后难说。因为趋势如此,所以学习没有坏处。 另外,我的场景使用Shader写的,固定管线的Fog对其无效,自然得自己用着色器写雾的效果了。 2 如何进行呢?-----〉当然是上网查资料了。   网上的htt...

Unity3D教程宝典之Shader篇

教程目录基础讲:Shader学习方法基础讲:基础知识特别讲:常见问题解答特别讲:CG函数第一讲: Shader总篇第二讲: Fixed Function Shader 第三讲: Vertex&Fragment Shader基础 第四讲: 制作一个美丽的地球第五讲:LOGO闪光效果 第六讲:TexGen第七讲:流程图第八讲:Why CG?第九讲:R...