cc.Graphics
畫線也能加紋理了?文末附送完整代碼。
初步實現
在 初探精靈中的網格渲染模式 ! 中簡單分析了 Sprite
組件的渲染模式 Mesh
。
這次,我們應用 Sprite
渲染模式 Mesh
和 cc.Graphics
,實現畫線紋理的操作。
先看看效果如何。
先在場景創建一個 cc.Graphics
節點。並添加一個子節點 cc.Sprite
,渲染模式改為 Mesh
。
因為 Mesh
中計算的坐標是從左上角,而 Graphics
畫圖是從中心開始畫的。
所以 cc.Sprite
節點, Scale
調整為 (1,-1)
, Anchor
調整為 (0,1)
。
為了使紋理超出邊界后可以重復填充,這個紋理大小得是 2
的 n
次方,並設為 Repeat
。
畫紋理肯定需要坐標位置信息。
來一起看看,Graphics
的 webgl
實現。
Graphics
中有一個 _impl
變量。
這個 _impl
里有一個 _paths
變量,記錄了所有畫線路徑,和對應的畫線的點。
而 lineTo
和 moveTo
都會往 _paths
塞入畫線的點數據。
對於 circle
和 arc
以及 rect
等接口,最終還是調用 lineTo
和 moveTo
。
所以有了這個 _paths
我們畫紋理的時候,可以先把點遍歷出來。
for (let index = 0; index < _impl._paths.length; index++) {
const path = _impl._paths[index];
const pathPoints = path.points;
if (pathPoints.length < 2) continue;
for (let index2 = 1; index2 < pathPoints.length; index2++) {
// 當前點
const p = cc.v2(pathPoints[index2].x, pathPoints[index2].y);
// 上一個點
const p_pre = cc.v2(pathPoints[index2 - 1].x, pathPoints[index2 - 1].y);
}
}
如何畫紋理呢?
先考慮相鄰的兩個點,再根據線寬 w
畫一個長方形。長方形有四個點,我們要求出這四個點的坐標。
先算出這兩個點的方向。
const dir = p.sub(p_pre); //方向
接着求出一個垂直方向的向量(根據向量內積為0求出),長度為線寬一半。
const cross_dir = (dir.y == 0 ? cc.v2(0, 1) : cc.v2(1, -dir.x / dir.y).normalize()).mulSelf(w / 2); //垂直方向
根據兩個點和垂直方向可以求出這個長方形的四個頂點。
const p_r_t = p.add(cross_dir); //右上
const p_r_b = p.sub(cross_dir); // 右下
const p_l_t = p_pre.add(cross_dir); // 左上
const p_l_b = p_pre.sub(cross_dir); // 左下
最后根據四個點填充 sprite.spriteFrame
中的數據 vertices
,如果不理解的話,可以參考初探精靈中的網格渲染模式 !
對於 uv
紋理坐標,這邊就直接使用頂點坐標的縮放一個系數。參考代碼如下。
const uv_mul = 50;
const i_offset = vertices.x.length;
vertices.x.push(p_r_t.x, p_r_b.x, p_l_t.x, p_l_b.x);
vertices.y.push(p_r_t.y, p_r_b.y, p_l_t.y, p_l_b.y);
vertices.nu.push(p_r_t.x / uv_mul, p_r_b.x / uv_mul, p_l_t.x / uv_mul, p_l_b.x / uv_mul);
vertices.nv.push(p_r_t.y / uv_mul, p_r_b.y / uv_mul, p_l_t.y / uv_mul, p_l_b.y / uv_mul);
vertices.triangles.push(i_offset + 0);
vertices.triangles.push(i_offset + 1);
vertices.triangles.push(i_offset + 2);
vertices.triangles.push(i_offset + 1);
vertices.triangles.push(i_offset + 2);
vertices.triangles.push(i_offset + 3);
這么畫長方形存在一個問題,對於畫圓弧,如果分隔太大,或者線寬比較大,會出現分割的問題。
怎么解決這個問題呢?
一是可以參考源碼,把連接處補上。
另一種方式是直接用 GraphicsAssembler
中的 buffers
數據,重新組織一下。
當然,這些等我研究出來再分享給大家(日常挖坑)!
以上為白玉無冰使用 Cocos Creator v2.3.3
關於 "畫線紋理的一種簡單實現!"
的技術分享。如果對你有點幫助,歡迎分享給身邊的朋友。
畫線紋理之連接優化
對轉角處加一層處理,就可以更加平滑了。。。。
先看看效果。
在 畫線紋理的一種簡單實現! 中介紹了可以使用 Sprite
渲染模式 Mesh
和 cc.Graphics
,實現畫線紋理。
不過在連接處存在縫隙。
那么怎么處理這個縫隙呢?
只需要在連接點畫一個圓,這樣縫隙就能去掉了。
那么怎么畫圓呢?可以把圓看成是正多邊形,根據半徑和圓心的關系,可以確認位置坐標。可參考 shader 動畫之 loading 特效!這篇文章。
半徑剛好就是畫線寬度的一半,某個圓上的坐標轉成代碼如下。
// 角度
const r = 2 * Math.PI * index3 / count;
// 先算方向向量,在加上圓心坐標就是,圓上的點。
const pos_circle = cc.v2(w / 2 * Math.cos(r), w / 2 * Math.sin(r)).addSelf(p);
怎么確定頂點索引呢?
其實可以按照圓心走,畫一個個三角形就行啦。
當然這是其中一種索引方式,轉成代碼如下。
//畫圓
const count = 12;
i_offset = vertices.x.length;
vertices.x.push(p.x);
vertices.y.push(p.y);
vertices.nu.push(p.x / uv_mul);
vertices.nv.push(p.y / uv_mul);
for (let index3 = 0; index3 < count; index3++) {
const r = 2 * Math.PI * index3 / count;
const pos_circle = cc.v2(w / 2 * Math.cos(r), w / 2 * Math.sin(r)).addSelf(p);
vertices.x.push(pos_circle.x);
vertices.y.push(pos_circle.y);
vertices.nu.push(pos_circle.x / uv_mul);
vertices.nv.push(pos_circle.y / uv_mul);
if (index3 === 0) {
// 0 - count -1
vertices.triangles.push(i_offset, i_offset + 1 + index3, i_offset + count);
} else {
// 0 - index3 - (index3-1)
vertices.triangles.push(i_offset, i_offset + 1 + index3, i_offset + index3);
}
}
以上只是實現簡單畫線紋理的效果,如果要實現繩子這種效果,那就需要重新計算紋理坐標,和位置/方向/長度等有關系。
這個暫時還沒想好,留給大家討論吧哈哈~
以上為白玉無冰使用 Cocos Creator v2.3.3
關於 "畫線紋理之連接優化!"
的技術分享。如果對你有點幫助,歡迎分享給身邊的朋友。
畫線紋理之繩子
為繩子任意方向的拖動添加紋理~
效果預覽
前置教程
這次的紋理是使用 Sprite
組件的渲染模式 Mesh
,前文 初探精靈中的網格渲染模式 ! 介紹了這個用法。
繪制的數據要用到 _poins
來畫長方形,前文 畫線紋理之簡單實現 中有介紹。
在連接處畫個圓達到平衡效果,前文 畫線紋理之連接優化 中有講到處理方法。
回顧一下這三篇文章有助於本文的理解哦~
實現原理
前幾篇已經實現了畫線紋理,這次主要的目標是計算正確的 uv
坐標。
因為這個線有方向,有長度,都會影響紋理坐標的計算。
這里想到的一個思路是,把所有的線段拉成一條直線,並放到一個方向
。
為了使這個紋理能從尾部帶動頭部的效果,拉直后,最后一個點作為紋理的起始點。
所以遍歷這個點的時候,要從尾部開始,並記錄一下每節的長度。
紋理坐標 v
的兩個點是 0
和 1
。 紋理坐標 u
(水平方向) 根據繩子的長度去推算。
// 從最后一點開始計算
for (let index2 = pathPoints.length - 1; index2 > 0; index2--) {
// 省略部分代碼
vertices.x.push(p_r_t.x, p_r_b.x, p_l_t.x, p_l_b.x);
vertices.y.push(p_r_t.y, p_r_b.y, p_l_t.y, p_l_b.y);
// 計算uv
vertices.nu.push(offsetX.x * uv_mul, offsetX.x * uv_mul, (offsetX.x + dirLen) * uv_mul, (offsetX.x + dirLen) * uv_mul);
vertices.nv.push(1, 0, 1, 0);
// 省略部分代碼
offsetX.addSelf(cc.v2(dirLen, 0)); // 記錄已經畫的長度長度
}
這么倒着遍歷會出現一個問題,就是尾巴的紋理會被頭覆蓋。
所以計算長方形的頂點索引后,要整體反轉,讓他從頭開始畫。主要代碼如下。
let trianglesCache: number[][] = [];
for (let index2 = pathPoints.length - 1; index2 > 0; index2--) {
// 省略部分代碼
triangles.push(i_offset + 0);
triangles.push(i_offset + 1);
triangles.push(i_offset + 2);
triangles.push(i_offset + 1);
triangles.push(i_offset + 2);
triangles.push(i_offset + 3);
trianglesCache.push(triangles);
}
trianglesCache.reverse(); // 頂點索引反轉
trianglesCache.forEach(v => {
// 真正的頂點索引順序
vertices.triangles.push(...v)
})
反轉后,繩子的紋理就正確了。
對於連接處畫圓(實際是多邊形),需要注意每個點都要旋轉,這樣才能讓圓的紋理方向正確。
參考代碼如下。
//畫圓
const dir_angle = dir.signAngle(cc.v2(-1, 0));//與x軸的負方向的夾角
const count = 12;
i_offset = vertices.x.length;
// 這里是圓心
vertices.x.push(p.x);
vertices.y.push(p.y);
vertices.nu.push(offsetX.x * uv_mul);
vertices.nv.push(0.5);
for (let index3 = 0; index3 < count; index3++) {
const r = 2 * Math.PI * index3 / count;
// 圓心到各個邊的向量
const pos_circle = cc.v2(w / 2 * Math.cos(r), w / 2 * Math.sin(r));
vertices.x.push(pos_circle.add(p).x);
vertices.y.push(pos_circle.add(p).y);
// 對於圓的uv需要旋轉
vertices.nu.push((pos_circle.rotate(dir_angle).x + offsetX.x) * uv_mul);
vertices.nv.push(pos_circle.rotate(dir_angle).y / w + 0.5);
if (index3 === 0) {
triangles.push(i_offset, i_offset + 1 + index3, i_offset + count);
} else {
triangles.push(i_offset, i_offset + 1 + index3, i_offset + index3);
}
}
最后,給大家畫個星吧~
小結
把彎的掰直!
這個繩子紋理的整個思路就是把所有彎的線,都轉化成直的后,再計算紋理坐標。
以上為白玉無冰使用 Cocos Creator v2.3.3
關於 "畫線紋理之繩子!"
的技術分享。如果對你有點幫助,歡迎分享給身邊的朋友。