Android日常學習:OpenGL 實踐之貝塞爾曲線繪製
說到貝塞爾曲線,大家肯定都不陌生,網上有很多關於介紹和理解貝塞爾曲線的優秀文章和動態圖。
以下兩個是比較經典的動圖了。
二階貝塞爾曲線:
三階貝塞爾曲線:
由於在工作中經常要和貝塞爾曲線打交道,所以簡單說一下自己的理解:
現在假設我們要在座標系中繪製一條直線,直線的方程很簡單,就是 y=x ,很容易得到下圖:
現在我們限制一下 x 的取值範圍為 0~1 的閉區間,那麼可以得出 y 的取值範圍也是 0~1。
而在 0~1 的區間範圍內,x 能取的數有多少個呢?答案當然是無數個了。
同理,y 的取值個數也是有無數個。每一個 x 都有唯一的 y 與之對應,一個 (x,y) 在座標系上就是一個點。
所以最終得到的 0~1 區間的線段,實際上是由無數的點組成的。
那麼這條線段有多長呢?長度是由 x 的取值範圍來決定的,若 x 的取值為 0~2,那麼線段就長了一倍。
另外,如果 x 的取值範圍不是無數個,而是以 0.05 的間距從 0 到 1 之間遞增,那麼得到的就是一串點了。
由於 點 是一個理想狀態下的描述,在數學上點是沒有寬高、沒有面積的。
但是,如果你在草稿紙上繪製一個點,不管你用到是鉛筆、毛筆、水筆還是畫筆,一個點總是要佔面積的。
毛筆畫一個點的面積可能需要鉛筆畫幾十個點了。
在實際生活中,如果要以 0.05 的間距在第一幅座標系圖中畫出 x 在 0~1 區間的一串點,最終結果就和直接畫一條線段沒啥差別了。
這就是現實和理想的差別了。理想一串點,現實一條線。
我們把這個邏輯放到手機螢幕上。
手機螢幕上的最小顯示單位就是畫素了,一個 1920 * 1080 的螢幕指的就是各方向上畫素點的數量。
假如繪製一條和螢幕一樣寬的線段,一個點最小就算一個畫素,最多也就 1080 個點了。
點佔的畫素越多,那麼實際繪製時需要的點的數量越少,這也算是潛在的最佳化項了。
說完直線,再回到貝塞爾曲線上。
曲線和直線都有一個共同點,它們都有各自特定的方程,只不過我們用的直線例子比較簡單,既 y = x ,一眼看出計算結果。
直線方程 y = x,在數學上可以這麼描述:y 是關於 x 的函式,既 y = F(x) ,其中 x 的取值決定了該直線的長度。
根據上面的理解,這個長度的直線實際又是由在 x 的取值範圍內對應的無數個點組成的。
反觀貝塞爾曲線方程以及對應的圖形如下:
- 二階貝塞爾曲線:其中,P0 和 P2 是起始點,P1 是控制點。
- 三階貝塞爾曲線其中,P0 和 P3 是起始點,P1 和 P2 是控制點。
不難理解,假設我們要繪製一條曲線,肯定要有起始和結束點來指定曲線的範圍曲線。
而控制點就是指定該曲線的弧度,或者說指定該曲線的彎曲走向,不同的控制點得出的曲線繪製結果是不一樣的。
另外,可以觀察到,無論是幾階貝塞爾曲線,都會有引數 t 以及 t 的取值範圍限定。
t 在 0~1 範圍的閉區間內,那麼 t 的取值個數實際上就有無數個了,這時的 t 就可以理解成上面介紹直線中講到的 x 。
這樣一來,就可以把起始點、控制點當初固定引數,那麼貝塞爾曲線計算公式就成了 B = F(t) ,B 是關於 t 的函式,而 t 的取值範圍為 0~1 的閉區間。
也就是說貝塞爾曲線,選定了起始點和控制點,照樣可以看成是 t 在 0~1 閉區間內對應的無數個點所組成的。
有了上面的闡述,在工(ban)程(zhuan)的角度上,就不難理解貝塞爾曲線到底怎麼使用了。
Android 繪製貝塞爾曲線
Android 自帶貝塞爾曲線繪製 API ,透過 Path 類的 quadTo 和 cubicTo 方法就可以完成繪製。
1 // 構建 path 路徑,也就是選取 2 path.reset(); 3 path.moveTo(p0x, p0y); 4 // 繪製二階貝塞爾曲線 5 path.quadTo(p1x, p1y, p2x, p2y); 6 path.moveTo(p0x, p0y); 7 path.close(); 8 9 // 最後的繪製操作10 canvas.drawPath(path, paint);
這裡的繪製實際上就是把貝塞爾曲線計算的方程式交給了 Android 系統內部去完成了,引數傳遞上只傳遞了起始點和控制點。
我們可以透過自己的程式碼來計算這個方程式從而對邏輯上獲得更多控制權,也就是把曲線拆分成許多個點組成,如果點的尺寸比較大,甚至可以減少點的個數實現同樣的效果,達到繪製最佳化的目的。
OpenGL 繪製
透過 OpenGL 可以實現我們上述的方案,把曲線拆分成多個點組成。這種方案要求我們在 CPU 上去計算貝塞爾曲線方程,根據 t 的每一個取值,計算出一個貝塞爾點,用 OpenGL 去繪製上這個點。
這個點的繪製可以採用 OpenGL 中畫三角形 GL_TRIANGLES 的形式去繪製,這樣就可以給點帶上紋理效果,不過這裡面的坑略多,起始點和控制點都是執行時動態可變的實現難度會大於固定不變的。
這裡先介紹另一種方案,這種方案實現比較簡單也能達到最佳化效果,我們可以把貝塞爾曲線的計算方程式交給 GPU, 在 OpenGL Shader 中去完成。
這樣一來,我們只要給定起始點和控制點,中間計算貝塞爾曲線去填補點的過程就交給 Shader 去完成了。
另外,透過控制 t 的數量,我們可以控制貝塞爾點填補的疏密。
t 越大,填補的點越多,超過一定閾值後,不會對繪製效果有提升,反而影響效能。
t 越小,那麼貝塞爾曲線就退化成一串點組成了。所以說 t 的取值範圍也能對繪製起到最佳化作用。
繪製效果如下圖所示:
以下就是實際的程式碼部分了,關於 OpenGL 的基礎理論部分可以參考之前寫過的文章和公眾號,就不再闡述了。
在 Shader 中定義一個函式,實現貝塞爾方程:
1vec2 fun(in vec2 p0, in vec2 p1, in vec2 p2, in vec2 p3, in float t){ 2 float tt = (1.0 - t) * (1.0 -t); 3 return tt * (1.0 -t) *p0 4 + 3.0 * t * tt * p1 5 + 3.0 * t *t *(1.0 -t) *p2 6 + t *t *t *p3; 7}
該方程可以利用 Shader 中自帶的函式最佳化一波:
1vec2 fun2(in vec2 p0, in vec2 p1, in vec2 p2, in vec2 p3, in float t) 2{ 3 vec2 q0 = mix(p0, p1, t); 4 vec2 q1 = mix(p1, p2, t); 5 vec2 q2 = mix(p2, p3, t); 6 vec2 r0 = mix(q0, q1, t); 7 vec2 r1 = mix(q1, q2, t); 8 return mix(r0, r1, t); 9}
接下來就是具體的頂點著色器 shader :
1// 對應 t 資料的傳遞 2attribute float aData; 3// 對應起始點和結束點 4uniform vec4 uStartEndData; 5// 對應控制點 6uniform vec4 uControlData; 7// mvp 矩陣 8uniform mat4 u_MVPMatrix; 910void main() {11 vec4 pos;12 pos.w = 1.0;13 // 取出起始點、結束點、控制點14 vec2 p0 = uStartEndData.xy;15 vec2 p3 = uStartEndData.zw;16 vec2 p1 = uControlData.xy;17 vec2 p2 = uControlData.zw;18 // 取出 t 的值19 float t = aData;20 // 計算貝塞爾點的函式呼叫21 vec2 point = fun2(p0, p1, p2, p3, t);22 // 定義點的 x,y 座標23 pos.xy = point;24 // 要繪製的位置25 gl_Position = u_MVPMatrix * pos;26 // 定義點的尺寸大小27 gl_PointSize = 20.0;28}
程式碼中的 uStartEndData 對應起始點和結束點,uControlData 對應兩個控制點。
這兩個變數的資料傳遞透過 glUniform4f 方法就好了:
1 mStartEndHandle = glGetUniformLocation(mProgram, "uStartEndData"); 2 mControlHandle = glGetUniformLocation(mProgram, "uControlData"); 3 // 傳遞資料,作為固定值 4 glUniform4f(mStartEndHandle, 5 mStartEndPoints[0], 6 mStartEndPoints[1], 7 mStartEndPoints[2], 8 mStartEndPoints[3]); 9 glUniform4f(mControlHandle,10 mControlPoints[0],11 mControlPoints[1],12 mControlPoints[2],13 mControlPoints[3]);
另外重要的變數就是 aData 了,它對應的就是 t 在 0~1 閉區間的劃分的數量。
1 private float[] genTData() {2 float[] tData = new float[Const.NUM_POINTS];3 for (int i = 0; i < tData.length; i ++) {4 float t = (float) i / (float) tData.length;5 tData[i] = t;6 }7 return tData;8 }
以上函式就是把 t 在 0~1 閉區間分成 Const.NUM_POINTS 份,每一份的值都存在 tData 陣列中,最後透過 glVertexAttribPointer 函式傳遞給 Shader 。
最後實際繪製時,我們採用 GL_POINTS 的形式繪製就好了。
1 GLES20.glDrawArrays(GLES20.GL_POINTS, 0, Const.NUM_POINTS );
以上就是 OpenGL 繪製貝塞爾曲線的小實踐。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2663876/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- webGL入門-四階貝塞爾曲線繪製Web
- html5中canvas繪製貝塞爾曲線HTMLCanvas
- Android 貝塞爾曲線Android
- html5中canvas貝塞爾曲線繪製菊花HTMLCanvas
- 用canvas繪製一個曲線動畫——深入理解貝塞爾曲線Canvas動畫
- 包教包會-貝塞爾曲線的繪製原理與應用
- Android 自定義貝塞爾曲線工具Android
- Android-貝塞爾曲線實現水波紋動畫Android動畫
- 自定義 View 梳理:用貝塞爾曲線繪製酷炫輪廓背景View
- Android繪圖最終篇之大戰貝塞爾三次曲線Android繪圖
- canvas實現高階貝塞爾曲線Canvas
- 貝塞爾曲線基礎部分
- UIBezierPath貝塞爾曲線UI
- iOS開發之畫圖板(貝塞爾曲線)iOS
- 貝塞爾曲線(Bezier curve)實現節點連線
- 貝塞爾曲線理解與應用
- iOS UIBezierPath 貝塞爾曲線iOSUI
- OpenGL實現Hermite演算法繪製三次曲線MIT演算法
- Flutter 自定義元件之貝塞爾曲線畫波浪球Flutter元件
- 如何理解並應用貝塞爾曲線
- 貝塞爾曲線開發的藝術
- Android教你一步一步從學習貝塞爾曲線到實現波浪進度條Android
- 貝塞爾曲線原理、推導及Matlab實現Matlab
- 簡易製作貝塞爾曲線動畫(JS+css3+canvas)動畫JSCSSS3Canvas
- canvas bezierCurveTo() 三次貝塞爾曲線Canvas
- iOS UIBezierPath貝塞爾曲線常用方法iOSUI
- 使用貝塞爾曲線裁圓優化tableView優化View
- Path從懵逼到精通(2)——貝塞爾曲線
- js控制貝塞爾曲線程式碼例項JS線程
- 安卓自定義 View 進階:貝塞爾曲線安卓View
- canvas繪製貝濟埃曲線程式碼例項Canvas線程
- SVG之Path路徑詳解(二),全面解析貝塞爾曲線SVG
- 從 0 到 1Android 自定義 View(四)貝塞爾曲線AndroidView
- 自定義View合輯(6)-波浪(貝塞爾曲線)View
- canvas 二次貝塞爾曲線quadraticCurveTo()Canvas
- SVG <path> C 指令 三次貝塞爾曲線SVG
- SVG <path> Q指令 二次貝塞爾曲線SVG
- 白話經典貝塞爾曲線及其在 Android 中的應用Android