老實說 比起寫Kinect的學習筆記
還是做些純粹與長頸鹿有關的事物比較合我喜好
所以有想看Kinect V2下一篇的...再等等吧(微笑
這篇起緣於我的同事常常用程式去畫一些有趣的東西(妖怪吃小朋友之類的
因為覺得有趣,就試著也來玩玩看純粹用程式做最基本的繪圖
也就是從單純的幾何圖形與曲線去構成圖案
我的一開始的作品並沒有畫長頸鹿...
在貼出來後就不只一個人問我「長頸鹿勒?」
所以...來看Demo吧!
還是做些純粹與長頸鹿有關的事物比較合我喜好
所以有想看Kinect V2下一篇的...再等等吧(微笑
這篇起緣於我的同事常常用程式去畫一些有趣的東西(妖怪吃小朋友之類的
因為覺得有趣,就試著也來玩玩看純粹用程式做最基本的繪圖
也就是從單純的幾何圖形與曲線去構成圖案
我的一開始的作品並沒有畫長頸鹿...
在貼出來後就不只一個人問我「長頸鹿勒?」
所以...來看Demo吧!
舌頭是重點!!!
其實這個Demo並沒有完全發揮用程式畫圖所帶來的優勢
包含像是各部位的形變與到變色這些才是程式的強項
But...這個Demo我覺得這樣比較可愛所以就這樣!!
=========分隔線========
整個長頸鹿只用到了三種東西去完成:
Circle、Line、Bezier Curve
相信前兩者應該不用我多做介紹
在OF中就只要在draw下使用ofCircle以及ofLine這兩個function就足夠
Bezier Curve(貝茲曲線)相信有使用繪圖軟體或對電腦圖學稍微有涉略的人應該都有聽過
最初是由Paul de Casteljau在1959年根據de Casteljau演算法所計算出的結果
1962時法國工程師Pierre Bézier在汽車設計方面發揚光大 (from wiki)
竟然是數值分析領域的領域,那肯定就有滿滿的數學式
看到這裡不用擔心,筆者完全沒有打算在此去探討原理
要使用Bezier Curve,最直覺的方式就是看例子
(以下感謝wiki提供的完美例子)
Quadratic Bezier Curve(二次貝茲曲線):
二次貝茲曲線有一個控制點
P0是出發點、P2是終點、P1是控制點
假設在t=0.5的情況
首先取得P0→P1的中點A與P1→P2中點B
再取得A→B的中點,就是這個位置的點
只要t從0~1就能得到Bezier Curve
Cubic Bezier Curve(三次貝茲曲線):
同理,三次貝茲曲線有兩個控制點
※想看更高次方的範例的就請直接參考wiki
無論是OF還是Processing
一般來說都是提供二次與三次的Bezier Curve
因此若要畫出更複雜的曲線
可以用多個Bezier Curve來兜出想要的結果
=====================================
比較底層的部分解釋完畢後
再來就要告訴大家怎麼畫長頸鹿了!!
由於這次是比較屬於個人各憑想像力去發揮
所以接下來的做法完全可以根據個人喜好去改變喔
首先是長頸鹿成品:
筆者根據畫的順序將長頸鹿分成四個部分
- 輪廓
- 眼睛
- 舌頭
- 斑紋
以下分別針對這四個部分進行說明
=輪廓=
整個輪廓是由三條直線配上三個Bezier Curve組成
紅色框是嘴巴,綠色框是角,橘色是耳朵
以下提供用到的幾個主要點的大概位置:
筆者這裡是使用OF提供的ofPath去做繪製
程式碼的部份就留在後面一起介紹
=眼睛=
沒甚麼特別的就是一個ofCircle...
=舌頭=
由一組Bezier Curve所繪製
比較有趣的部份就在於開頭跟結尾的位置要怎麼得到
由於舌頭是連在嘴巴的Beizer Curve上
因此這兩點就得從此得來
OF提供的ofBezierPoint(),可以得到某個Bezier Curve上的特定點
控制點的部分可以參考上圖
=斑紋=
兩個斑紋各由一組Bezier Curve所繪製
找開頭跟結尾的問題在這裡同樣要處裡
這次是連在脖子的直線上,只要透過getInterpolate()的方式就可以得到直線上的點
詳細作法請參考程式碼的部分
==========================
以下是繪製長頸鹿的部分程式碼
首先是setup()的部分:
1: void setupGiraffe(float fScale = 200)
2: {
3: _fScale = fScale;
4:
5: _Outline.clear();
6: _Outline.setFilled(false);
7: _Outline.setStrokeWidth(3.0);
8: _Outline.setStrokeColor(ofColor(255));
9:
10: _DrawStartPos.set(-0.15 * fScale, -(fScale * 0.95));
11: ofPoint StartPos_ = _DrawStartPos;
12:
13: _Outline.moveTo(StartPos_);
14:
15: //Mouse
16: _Outline.bezierTo( StartPos_.x - (0.4 * fScale), StartPos_.y,
17: StartPos_.x - (0.4 * fScale), StartPos_.y - (0.1 * fScale),
18: StartPos_.x, StartPos_.y - (0.2 * fScale));
19:
20:
21: //Tongue
22: _TongueStart = ofBezierPoint( StartPos_,
23: ofPoint(StartPos_.x - (0.4 * fScale), StartPos_.y),
24: ofPoint(StartPos_.x - (0.4 * fScale), StartPos_.y - (0.1 * fScale)),
25: ofPoint(StartPos_.x, StartPos_.y - (0.2 * fScale)),
26: 0.2
27: );
28:
29: _TongueEnd = ofBezierPoint( StartPos_,
30: ofPoint(StartPos_.x - (0.4 * fScale), StartPos_.y),
31: ofPoint(StartPos_.x - (0.4 * fScale), StartPos_.y - (0.1 * fScale)),
32: ofPoint(StartPos_.x, StartPos_.y - (0.2 * fScale)),
33: 0.3
34: );
35:
36: //Horn
37: StartPos_.set(StartPos_.x, StartPos_.y - (0.2 * fScale));
38: _Outline.bezierTo( StartPos_.x + (0.05 * fScale), StartPos_.y - (0.15 * fScale),
39: StartPos_.x + (0.125 * fScale), StartPos_.y - (0.175 * fScale),
40: StartPos_.x + (0.125 * fScale), StartPos_.y);
41:
42: //Ear
43: StartPos_.set(StartPos_.x + (0.125 * fScale), StartPos_.y);
44: _Outline.bezierTo( StartPos_.x + (0.15 * fScale), StartPos_.y,
45: StartPos_.x + (0.15 * fScale), StartPos_.y + (0.15 * fScale),
46: StartPos_.x, StartPos_.y + (0.15 * fScale));
47:
48: //Neck
49: _Outline.lineTo( StartPos_.x + (0.175 * fScale), StartPos_.y + fScale);
50: _Outline.lineTo( StartPos_.x - (0.125 * fScale), StartPos_.y + fScale);
51: _Outline.close();
52:
53: //Spot
54: ofVec2f LinePos_;
55: LinePos_.set(StartPos_.x, StartPos_.y + (0.15 * fScale));
56: _SpotsStart1 = LinePos_.getInterpolated(ofVec2f(StartPos_.x + (0.175 * fScale), StartPos_.y + fScale), 0.2);
57: _SpotsEnd1 = LinePos_.getInterpolated(ofVec2f(StartPos_.x + (0.175 * fScale), StartPos_.y + fScale), 0.5);
58:
59: LinePos_ = _DrawStartPos;
60: _SpotsStart2 = LinePos_.getInterpolated(ofVec2f(StartPos_.x - (0.125 * fScale), StartPos_.y + fScale), 0.6);
61: _SpotsEnd2 = LinePos_.getInterpolated(ofVec2f(StartPos_.x - (0.125 * fScale), StartPos_.y + fScale), 0.9);
62: }
3:紀錄比例
為了達到之後對長頸鹿放大縮小時樣子比例不會跑掉
這裡以脖子的長度作為控制整體比例的數值
也就是說,之後每個控制點都會是根據這個值去算實際的位置
當然,在第一次畫的時候可以開心地直接給數字就好
之後再調整即可
5:8:設定輪廓的參數
ofPath的顏色與填滿與否似乎不受常用的ofSetColor()與ofSetFill()所控制
因此得直接針對物件本身設定
※特別注意,ofPath預設的Stroke Width = 0喔!
10:13:設置輪廓起始點
筆者將嘴巴下緣作為繪畫的起始點,為了之後繪製上的方便
而整個座標的中心點則是在脖子下方的線的中間
21:34:取得舌頭的起點與終點
承如剛剛說明所提到,舌頭的起點與終點是在嘴巴的Bezier Curve上
因此這裡就透過ofBezierPoint去取得比例在0.2與0.3的點作為起點與終點
49:51:繪製脖子並封閉輪廓
可以看到雖然脖子一共由三條線段組成,但這裡卻只有兩個lineTo
原因在於ofPath最後可以呼叫close()將最後到達的點與起點相連
這點跟許多繪圖軟體的作法是類似的
53:61:取得斑紋的起點與終點
要取得脖子線上的點,不像Bezier Curve這麼直覺地用一個function就解決
假設一條直線的兩端點為S, E
OF提供了以下的用法來取得此直線上特定比例t的點:
Result = S.getInterpolated(E, t);
========================================
再來是draw()的部份:
1: void drawGiraffe()
2: {
3: ofSetColor(255);
4:
5: _Outline.draw();
6:
7: //Tongue
8: ofBezier( _TongueStart.x, _TongueStart.y,
9: _TongueStart.x - (0.075 * _fScale), _TongueStart.y + (0.05 * _fScale),
10: _TongueEnd.x - (0.075 * _fScale), _TongueEnd.y + (0.05 * _fScale),
11: _TongueEnd.x, _TongueEnd.y
12: );
13:
14: //Eyes
15: ofCircle(_DrawStartPos.x, _DrawStartPos.y - (0.1 * _fScale), (0.025 * _fScale));
16:
17: //Spots
18: ofBezier( _SpotsStart1.x, _SpotsStart1.y,
19: _SpotsStart1.x - (0.1 * _fScale), _SpotsStart1.y,
20: _SpotsEnd1.x - (0.1 * _fScale), _SpotsEnd1.y,
21: _SpotsEnd1.x, _SpotsEnd1.y
22: );
23:
24: ofBezier( _SpotsStart2.x, _SpotsStart2.y,
25: _SpotsStart2.x + (0.1 * _fScale), _SpotsStart2.y,
26: _SpotsEnd2.x + (0.1 * _fScale), _SpotsEnd2.y,
27: _SpotsEnd2.x, _SpotsEnd2.y
28: );
29: }
5:繪製輪廓
7:28:繪製舌頭、眼睛、斑紋
這裡筆者並沒有全部都用ofPath存下整個Bezier Curve
因此直接使用ofBezier來作繪製
=========分隔線========
本篇的簡單介紹了Bezier Curve,並說明如何運用它畫出自己作品
關於Openframeworks所提供的繪圖功能,其實還有很多有趣的地方可以寫
但這篇的對象畢竟不是針對完全OF新手的角度
如果對上面的程式碼有任何疑問都歡迎留言提問
至於有興趣想了解開頭的旋轉長頸鹿的
主要的程式碼就在下方,歡迎參考喔
========================================
旋轉吧!長頸鹿!
1: void ofApp::setup()
2: {
3: ofBackground(85, 143, 216);
4: ofSetVerticalSync(true);
5:
6: _Giraffe.setupGiraffe(200);
7: _Giraffe2.setupGiraffe(200);
8: _fRotateVel = 60.0;
9: _fTimer = ofGetElapsedTimef();
10: _fNowDegree = 0.0;
11: _iNumGiraffe = 1;
12:
13: _AnimGiraffeSize.setCurve(AnimCurve::EASE_IN_EASE_OUT);
14: _AnimGiraffeSize.setDuration(2.0);
15: _AnimGiraffeSize.setRepeatType(AnimRepeat::LOOP_BACK_AND_FORTH);
16:
17: }
18:
19: //--------------------------------------------------------------
20: void ofApp::update()
21: {
22: float fDelta_ = ofGetElapsedTimef() - _fTimer;
23: _fTimer += fDelta_;
24:
25: _fNowDegree += fDelta_ * _fRotateVel;
26: if(_fNowDegree >= 360.0)
27: {
28: _fNowDegree -= 360.0;
29: }
30:
31: _AnimGiraffeSize.update(fDelta_);
32: if(_AnimGiraffeSize.isAnimating())
33: {
34: _Giraffe.setupGiraffe(_AnimGiraffeSize.getCurrentValue());
35: _Giraffe2.setupGiraffe((200 - _AnimGiraffeSize.getCurrentValue()) + 100);
36: }
37: }
38:
39: //--------------------------------------------------------------
40: void ofApp::draw()
41: {
42: ofSetColor(255);
43:
44: float fDiffDegree_ = 360.0/_iNumGiraffe;
45:
46: ofPushMatrix();
47: {
48: ofTranslate(ofGetWindowWidth()/2, ofGetWindowHeight()/2);
49:
50: for(int idx_ = 0; idx_ < _iNumGiraffe; ++idx_)
51: {
52: ofPushMatrix();
53: {
54: ofRotateZ(-(_fNowDegree + idx_ * fDiffDegree_));
55:
56: if(idx_ % 2 == 0)
57: {
58: _Giraffe.drawGiraffe();
59: }
60: else
61: {
62: _Giraffe2.drawGiraffe();
63: }
64: }
65: ofPopMatrix();
66: }
67:
68: }
69: ofPopMatrix();
70: }
沒有留言:
張貼留言