[Go to “teaching” in Morimoto Lab]
描画と模様について演習していきます。
まず、少しプログラムに慣れるように、線の描画の演習をします。
次に、基礎知識として、
・スコープ(変数の寿命)、
・プログラムの構成、
・関数(functionとも言う)
について学びます。
次回以降では、木の描画、模様の描画、数式を使った描画などをします。
線による描画はCGの基本要素です。
これに絡めた研究を以下にいくつか紹介します。
少し古いものは、最新の研究では計算方法などは置き換わっているものが多いかと思いますが、こういうトピックスがあるんだなと思っていただければ。
ちなみに、CG研究は論文と一緒にデモ&説明動画が添えられていることが多いです。
ここでは動画があれば動画をまず見ることをお勧めします。
(皆さんも最終課題でこのような動画を作ることになるかもしれません)
1. Sketch系インターフェース
描画はCG分野ではスケッチ入力としてよく登場します。
2. 植物や植物柄のモデリング
植物や植物柄のモデリング(形作る)を以下に簡単に行うか、という研究もたくさんあります。
3. Line Neatening & Beautification
描画した線画をきれいに美化する研究もあり、Adobeソフトなどで使われたりします。(そもそもAdobeがこのような研究をしていたり)
4. 検索と合成
少し変わり種?描画した内容を表すような画像合成手法の元祖。
それでは線の描画です。
まず練習で、しましまを描きましょう。
・・・。
次に、for文でしましまを描きましょう。
色や線の間隔/範囲/位置、などを変えてみてどうなるか見て理解してください。
以下のサンプルプログラムの実行結果が以下の画像になります。
実行してみて、幅を変えたり、色や位置を変えたりしてみてください。
[sample code A]
size(500,500); //window size
background(255); //background color
//draw a line
strokeWeight(5); //line weight(sen haba)
stroke(0); //line color
line(0,0,100,100); //line drawing
//start x, y, end x, y
//draw lines by for
stroke(50,200,50); //line color RGB (tree-like color)
for(int i=0 ; i < 200; i++){
line( i*10,100,
i*10,200);
}
//draw lines using different colors
strokeWeight(1);
for(int i=0 ; i < 255; i++){
stroke(50,i,50);//color
line( i,200,
i,300);
}
次に課題です。
以下のような画像を作るため、ステップを分けています。
for文を使いましょう。for文の使い方のリンク
紙などに図を書き、それぞれの線の始点と終点の座標を描き出してみよう。
for文で線の始点と終点の位置を少しずつずらそう。
同じでなくてもよいが、上の右図の場合、外枠上の線の間隔は15 pixである。
↑ヒント
まず、上下反転したものを作る→左右反転→上下左右反転
図とまったく同じでなくてもよい
線を描く位置をパラメータにして、色を変化させる
例えば、色のRGB要素のどれかを線を描画する座標値に変えてみる
滑らかに変化する0~1の値を色の値に乗算すれば、色を滑らかに変化させることができる。
座標を0~1の値にスケーリング(拡大縮小)してみよう
もしx座標をパラメータにするとしたら、x座標値を画像のwidthで割ると0~1の値にできる。
ここまでの話題と少しかわるのですが、今後、アニメーションやインタラクションに対応するため、
GameByBallを思い出してみてください。
少し基礎的な知識を整理します。
何気なく使ってきたintやfloatなどのデータのことを、変数と言います。
その変数は、定義する位置によって、使える範囲が違います。
この変数の使える範囲のことをスコープといいます。
変数(variable) : intやfloatで作ったデータのこと。配列やクラスで作るものも。
ブロック : 中括弧
{}
でまとめられた部分。forやif文にもあるし、setup()やdraw()のあとにもありますね。(その他、自分で作りたいところにブロックは作ってもエラーにはなりません。)スコープ : 変数の有効範囲のこと。ブロックの中で作られた変数はそのブロックの中だけで有効です。
グローバル変数 : ブロックの外で作られた変数のこと。有効範囲はそのプログラム上のすべてになります。
ローカル変数 : ブロックの中で作られた変数のこと。有効範囲はそのブロック内になります。
上の図を見ながら、以下を確認してください。
プログラム実行時の処理の順番は矢印で示した通りです。
グローバル変数はプログラムの一番上にまとめましょう。
変な場所に作ると、わかりにくいプログラムになってしまいます。
グローバル変数aはsetupでもdrawでも使えます。
setup内で定義したbの有効範囲はsetupのブロック内です。
ただし、bを定義するより上の部分では使えないです。
drawのifのブロック内で定義したbはこのブロック内で使えます。
setupにもbを定義していますが、同じ名前でも有効範囲が違うのでOKです。
同じ名前の変数の有効範囲が重なるとエラーになります!!!
Processingのプログラムの構成は大体3つになります。
以下の例を見てみましょう。
//構成する前
int a;
a=0;
a++;
println(a);//a is 1
//構成した後
//★1. usually define global data (variables).
int a;
//★2. only once at first time
void setup()
{
size(300,300); //size() must be here.
a=0;
}
//★3. repeated after setup
void draw()
{
a++;
println(a);//a is 1,2,3,4,5,…
}
前後でプログラムの挙動は変わってしまうので、「こうした方がいい」というものではありません。
(場合によっては前者のsetupやdrawのないプログラムを選択すべきかもしれないです。それも含めて)
ここで言いたいのは、
プログラム実行時、最初に一回だけ実行されるsetup、
その後繰り返し呼ばれるdraw、
どこででも使える変数となるグローバル変数、
の3つのパートを使い分けたいということです。
例えば、size関数はウィンドウを生成するものですので、drawで繰り返し呼ばない方がよいです。
また、もし変数を一つのブロック内でしか使わないのだったら、グローバル変数にするのはエラーの元だしメモリの浪費です。
どちらかの存在を忘れて、同じ名前の変数をグローバルとローカルの両方で作ってエラーが起こるなど。
あまりばんばんグローバル変数は作らない方がよいですね。
Point:
どこに何を書くべきか、判断しましょう。
計算コストやエラー防止、プログラムの可読性(わかりやすさ)などで判断しますが、大体作法は以下です。
グローバル変数:あまり増やさない。この場所(通常setupより上のファイルの最初のところを指す)に処理を書かない。
setup:最初に一回だけ行うべき処理を書く。グローバル変数の初期化など。高みを目指す人へ:読みやすいコードを書くためのおすすめの本
Point:
コードが長くなりすぎて、Processingのプログラムファイル(.pde)一つでは管理が大変になってきたら、ファイルを追加することができます。 (作り方の参考)
この時の注意として、ファイルを追加した場合、それは最初のファイルの続きのような扱いになりますので、異なる別のプログラムにはなりません。
setup()やdraw()、println()なども関数と言いますが、processingの方で元から用意されているものです。
以下のように、オリジナルの関数を作ることもできます。
setup, draw以外は勝手に名前を付けて作りました。
作った関数はsetupやdrawから呼び出して使います。
setup, drawなどと違って実行するタイミングは、それが記述されている箇所のタイミングになります。
関数(function) : 特定の機能や処理をひと固まりにしたもの。
引数(ひきすう, parameter): println(a);などと書いた場合のaのこと。関数に渡す変数、または、値。
同じプログラムでも関数を使うと色々と便利である。
以下を実行してみましょう。
[sample code B]
void setup() {
size(700, 700); //window size
background(255); //background color
noFill();
//顔の描画
strokeWeight(9); //line weight
stroke(50, 200, 50); //line color RGB
rect( 0,0,300, 250);
rect( 50,100,30, 30);//left eye
rect( 220,100,30, 30);//right eye
rect( 80, 180, 150, 20);//mouth
//drawFace();
//drawFace2(300);
}
void drawFace()
{
strokeWeight(9); //line weight
stroke(50, 200, 50); //line color RGB
rect( 0,0,300, 250);
rect( 50,100,30, 30);//left eye
rect( 220,100,30, 30);//right eye
rect( 80, 180, 150, 20);//mouth
}
void drawFace2(float x)
{
strokeWeight(9); //line weight
stroke(50, 200, 50); //line color RGB
rect( x+0,0,300, 250);
rect( x+50,100,30, 30);//left eye
rect( x+220,100,30, 30);//right eye
rect( x+80, 180, 150, 20);//mouth
}
rect()
はProcessingで用意してある四角形を描く関数です。
最初の引数が四角形の左上のx座標、次がy座標、3番目は横幅、4番目が縦幅です。
rectの詳しい使い方はこちら。
–
rect()のあとにコメントアウトで隠れているdrawFace()には、setup関数内にある顔の描画と同じプログラムが書いてあります。
–
では、
setup内の //顔の描画 のあとの6行を削除orコメントアウトし、
drawFace()をアンコメントアウトしてください。
最初と同じ内容が描画されたと思います。
さて、何がうれしいでしょうか?
…もともと6行だったところが1行で書けました!(setup関数内だけ見れば)
見やすくなりましたね!
関数名もdrawFaceなどにすると、更に分かりやすくなります。
が、これだけではまだあまりうれしくありません。
–
このままだと関数の恩恵があまりないので、少し工夫します。
関数には指定した変数を持たせることができます。
このような関数に渡せる数値のことを引数(ひきすう)
と言います。
上記のプログラムにもう一つある関数、drawFace2の(float x)が引数です。
使い方は、例えばsetupで記述する場合、括弧にfloatの値を書くと、xにその値を入れてdrawFace2内のプログラムを実行します。
この関数、中身はほとんどdrawFaceと同じですが、すべてのrectの最初の値に、drawFace2の引数xが足してあります。
drawFace2を案コメントアウトすると以下のように、顔を簡単にずらした位置に書けます。
引数の値を少し変えるだけで、簡単にずらせます。
関数のうま味:
このように関数を使うと、一行で命令を呼び出せます。
見やすいコードを描くのにも便利ですし、
似たようなコードなら引数を工夫すれば一つの関数で色々な処理をカバーすることもできます。
ウィンドウサイズが足りなければsize()の値を変えて大きくするなど。
顔の描画自体を変えてもいいです。
↑顔をたくさん
↑例えば、3つの丸を関数で描画するように変更
↑その関数をforでたくさん呼び出す
drawFace2に引数を追加して、顔等の色を引数で変えれるように変更してください。
drawFace2に引数を追加して、yの位置もずらせるようにしてください。
for文を使ってdrawFace2をたくさん呼び出し、その際にrandomで色と位置を変更して顔等をたくさん表示してください。
上記2のHint: ランダム関数の使い方
random(50) で、0-50の間のfloat型の乱数を返します。
random(-10, 50) などとすると、-10~50の間のfloat型の乱数を返します。
[詳しくはこちらのリンクを参照]
☕Coffee Break「名付けの重要性」
結構大事な話で、オリジナルの関数は
同じ名前の関数を作ってはいけない
元からある関数と同じ名前でもだめ
などの名づけの決まりがあります。
でも、同じ名前の関数でも、引数が異なればOKです。
例えば以下のように関数を並べて定義しても、エラーにはなりません。
void drawFace(){
strokeWeight(5); //line weight(sen haba)
stroke(50,200,50); //line color RGB
for(int i=0 ; i < 200; i++){
line( i*10,100,
i*10,200);
}
}
void drawFace(int offsetY){
strokeWeight(5); //line weight(sen haba)
stroke(50,200,50); //line color RGB
for(int i=0 ; i < 200; i++){
line( i*10,100+offsetY,
i*10,200+offsetY);
}
}
「まぁでもとりあえず同じ名前の関数を作るのはやめとこ」 と思いました?
関数や変数の名前は重要で、これでプログラムの読みやすさが変わってきます。
プログラムの読みやすさは気分の問題とかでなく、プログラムの技術としてとても大事です。
類似する機能を持った関数は、同じ名前を付けた方が分かりやすいかもしれません。
ちなみに、同じ名前で異なる引数の関数を定義することを関数のオーバーロードといいます。
Point: 関数や変数の名前の付け方
これも大事な話です。上記の関数drawFace()が
myFunc()
など、「関数だったらなんでもいい」みたいな名前になっていたら、
setupだけ見たときに何が書いているかわからず、
プログラムの可読性が低くなります。
わかりにくい、読みにくいプログラムということですね。
同じように変数名も
int a;
とするより、
int linenum; //line number = 線の数
などと、意味を持たせた方がわかりやすくなります。