轉載:https://blog.csdn.net/Patrick_Boom/article/details/107180717
前段時間總是加班,也沒啥心情和精力去研究新東西,總結一下自己之前做的字帖的功能
先上效果圖:
文章分為幾部分:
(一) 畫圖板實現原理
(二) 畫圖具體實現過程中的核心點
(三) 在畫圖板的基礎上 演變為字帖的思路
· 畫圖板實現原理
畫圖板功能一定要有兩個東西:一個畫布,一個畫筆。
然后你需要知道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坐標
試了很多次的代碼,滿滿干貨 ~ 拿去拿去 (如果父物體及以上的層級有縮放,這里可能還需要修正的參數,這里就不寫了)
-
Vector2 GetUV(Vector2 brushPos)
-
{
-
//獲取圖片在屏幕中的像素位置
-
Vector2 rawImagePos = Vector2.zero;
-
-
//判斷所在畫布的渲染方式,不同渲染方式的位置計算方式不同
-
switch (m_renderMode)
-
{
-
case RenderMode.ScreenSpaceOverlay:
-
rawImagePos = rawImage.rectTransform.position;
-
break;
-
default:
-
rawImagePos = m_uiCamera.WorldToScreenPoint(rawImage.rectTransform.position);
-
break;
-
}
-
-
//換算鼠標在圖片中心點的像素位置
-
Vector2 pos = brushPos - rawImagePos;
-
-
//換算鼠標在圖片中UV坐標
-
Vector2 uv =
new Vector2(pos.x / m_rawImageSizeX +
0.5f, pos.y / m_rawImageSizeY +
0.5f);
-
-
return uv;
-
}
2. Shader 如何把你畫的東西 畫到 RawImage 上
經過1中的一些列的換算,我們知道了落筆位置對應圖板的貼圖的uv坐標了,
下一步就是把uv對應像素及周邊的像素填上你想要的顏色,喏~ 一個點就畫完了。
然后是如何畫線呢? 你會說:簡單,點多了就是線了啊,每幀去打點不就ok了~ 這時候就出問題了,如果畫的太快,一幀的時間過去你的手已經畫出去了好遠。那么就是一些不連續的點,而不是完整的線。
所以,我們需要把當前點和上一個點存起來,兩點之間做填充。
我這里用的方式是:將兩個點為圓心的兩個圓填滿,再將將以兩個點連線為對稱軸,長為兩點之間距離,圓直徑為寬的矩形填滿。(自己算法 不一定好~ 各位大神有高招歡迎討論~ )
具體步驟看一下代碼:
-
Shader
"Hidden/DrawWord"
-
{
-
Properties
-
{
-
_Tex("Texture"
, 2D) = "white" {}
-
_Size("Size",
float) = 0
-
_Color("Color"
, color) = (1,1,1,1)
-
_UV("UV"
, vector) = (0,0,0,0)
-
_LastUV("LastUV"
, vector) = (0,0,0,0)
-
}
-
-
SubShader
-
{
-
-
ZTest
Always Cull Off ZWrite Off Fog{ Mode Off }
-
-
Blend
SrcAlpha OneMinusSrcAlpha
-
-
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;
-
};
-
-
v2f
vert(appdata v)
-
{
-
v2f
o;
-
o.vertex =
UnityObjectToClipPos(v.vertex);
-
o.uv =
v.uv;
-
return
o;
-
}
-
-
sampler2D
_Tex;
-
float
_Size;
-
fixed4
_UV;
-
fixed4
_LastUV;
-
fixed4
_Color;
-
-
fixed4
frag(v2f i) : SV_Target
-
{
-
fixed4
col = tex2D(_Tex, i.uv);
-
-
float
a = _UV.x;
-
float
b = _UV.y;
-
float
c = _LastUV.x;
-
float
d = _LastUV.y;
-
float
AA = d - b;
-
float
BB = a - c;
-
float
CC = b * c - a * d;
-
-
float
x = i.uv.x;
-
float
y = i.uv.y;
-
-
float
sqrDic1 = (x - a) * (x - a) + (y - b) * (y - b);
-
float
sqrDic2 = (x - c) * (x - c) + (y - d) * (y - d);
-
float
sqrDic11 = (AA * x + BB * y + CC) * (AA * x + BB * y + CC) / (AA * AA + BB * BB);
-
float
sqrDic22 = (x - (a + c) / 2) * (x - (a + c) / 2) + (y - (b + d) / 2) * (y - (b + d) / 2);
-
-
float
sqrDicStand1 = _Size/10000 * _Size/10000;
-
float
sqrDicStand2 = ((a - c) * (a - c) + (b - d) * (b - d)) / 4;
-
-
//判斷當前像素是否在被畫的范圍之內
-
if
(sqrDic1 < sqrDicStand1 || sqrDic2 < sqrDicStand1 || (sqrDic11 < sqrDicStand1 && sqrDic22 < sqrDicStand2))
-
{
-
col =
_Color;
-
}
-
-
return
col;
-
}
-
ENDCG
-
}
-
}
-
}
好嘞~ 核心代碼就這些啦,項目貼在最下面。
畫圖的Demo 項目地址:https://github.com/PatrickBoomBoom/board.git
· 在畫圖板的基礎上 演變為字帖的思路
這些已經在公司的項目中實現了,不太方便貼出來,就口述一下吧 ~
畫圖的思路是:找到操作的uv坐標,然后去改變貼圖顏色,想做字帖的話在畫圖的基礎上再加兩個步驟:
1. 規定可塗色的范圍:
准備一張有透明通道的寫好的字的圖片,shader中判斷像素是否在寫字的范圍內的時候同時判斷是否在預先准備好的字(或者對應筆畫)的圖片中,那么就只有(你畫的 && 屬於字內的)像素才會被填色;
2. 規定下筆起點、轉折點、終點、方向:
配好一個漢字的各種配置,然后用一個Gameobject來充當畫筆輸入位置,規定這個obj只能沿着筆畫走,如果寫對了,跳轉下一筆,如果寫錯重寫。