SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

ActionScriptによるWebの3Dグラフィックス再入門

ActionScriptによるWebの3Dグラフィックス再入門 (2) - シェーディングでもっと3Dらしく

フラットシェーディングの仕組みと、ActionScriptにおけるパッケージ化


  • X ポスト
  • このエントリーをはてなブックマークに追加

シェーディングの下準備

 フラットシェーディングを実装するために、ベクトル関係のメソッドをいくつか説明しておきます。まずはこのステップのプログラムから新しくVector3クラスに加わっているメソッドからです。今までのVector3クラスは主に頂点座標を表すために使ってきましたが、今回からベクトルらしくなってきます。

 dotメソッドは、ベクトルvとの内積を計算し、結果を返すメソッドです。

Vector3.as
public function dot(v:Vector3):Number {
    return x * v.x + y * v.y + z * v.z;
}

 計算はとても単純で、x、y、z同士を掛け合わせたものを足すだけです。こんな計算で何ができるか疑問ですが、ベクトルの長さを返すlengthプロパティでは内積が役に立っています。また、両方のベクトルの長さが1のときの内積は特別な意味を持ちます。長さが1のベクトルとベクトルの間の角度をθとすると、cosθは内積と等しくなります。数式で内積を表すときには、ドット(・)を用います(例:a・b)。

 crossメソッドは、ベクトルvとの外積を計算し、結果を返すメソッドです。

Vector3.as
public function cross(v:Vector3):Vector3 {
    return new Vector3(
        y * v.z - z * v.y,
        z * v.x - x * v.z,
        x * v.y - y * v.x
    );
}

 この計算は少し複雑ですが、「ベクトル 外積」といったキーワードで検索すれば解説が見つかるかと思います。この外積を使うと、三角形の頂点座標を使って、三角形に垂直な方向のベクトル、つまり法線を計算することができます。数式で外積を表すときには、×を用います(例:a×b)。余談ですが、英語では内積をdot product、外積をcross productと呼びます。内積と外積を表す数式の記号は、まさにこれを表していると言えます。

 lengthプロパティは、ベクトルの長さを返すプロパティです。

Vector3.as
public function get length():Number {
    return Math.sqrt(dot(this));
}

 自分自身との内積の平方根がベクトルの長さとなります。なお、function get XXX()という書き方をすると、メソッドではなくプロパティとなります。プロパティは、メソッド呼び出しと違って次のように()を使わずに呼び出すことができます。今回は出てきませんが、getの反対でsetもあります。

プロパティの呼び出し
var v:Vector3 = new Vector3(1, 2, 3);
var len:Number = v.length;

 Vector3クラスの最後、normalプロパティは、正規化されたベクトルを返すプロパティです。

Vector3.as
public function get normal():Vector3 {
    return new Vector3(x / length, y / length, z / length);
}

 正規化とは、ベクトルの長さを1にすることです。lengthプロパティで各要素を割ることで正規化しています。ベクトルの正規化はいろいろな所で出てきます。

 Triangleクラスにはnormalプロパティが追加されています。

Triangle.as
public function get normal():Vector3 {
    // a: v0からv1へ向かうベクトル
    var a:Vector3 = v1.sub(v0);
    // b: v0からv2へ向かうベクトル
    var b:Vector3 = v2.sub(v0);
    // 法線 = a×b
    return a.cross(b).normal;
}

 このnormalは「法線」という意味で、直前に出てきたVector3normalの「正規化」とは意味が異なります。法線は外積のところでちらっと説明しましたが、面に垂直なベクトルのことです。プログラム中に出てくる頂点やベクトルを図にまとめました。

三角形と法線
三角形と法線

 Triangleクラスの説明でも書きましたが、三角形を表から見ると、それぞれの頂点は反時計回りにv0、v1、v2の順で並んでいます。これらの頂点を使い、三角形の辺を意味するベクトルaとbを計算します。

 ある点からある点へ向かうベクトルは、終点の座標から始点の座標を引くことで計算することができます。そのため、aとbは次のように計算できます。

a = v1 - v0
b = v2 - v0

 求まったaとbの外積a×bが、三角形の法線となります。a、b、そして外積a×bの関係は、前回も出てきたように右手の指を使うと簡単に説明できます。右手の親指をベクトルa、人差し指をベクトルbとしたとき、ベクトルの外積a×bは、中指の方向を向きます。外積は、aとb両方に対して垂直です。これが外積の幾何学的な意味です。外積の計算自体は複雑ですが、ベクトル同士の関係で考えると案外すっきりするものです。

 なお、外積は可換ではないので、b×aは別の値になります。親指をb、人差し指をaと考えると、外積b×aである中指は下を向くと思います。紛らわしいですが、ここを間違えてしまうと、後ろを向いているはずの面が描画されてしまうなどの問題が発生してしまいます。

 以上、少し複雑でしたが、外積を使うことによって三角形の頂点座標から法線を計算することができます。

フラットシェーディングの実装

 いよいよランバートの法則を使ったフラットシェーディングの実装です。プログラムでは、Triangleクラスのrenderメソッドがそれに当ります。

Triangle.as
public function render(graphics:Graphics):void {
    var brightness:Number = normal.dot(new Vector3(0, 0, 1));
    if (brightness <= 0) {
        return;
    }
    
    // RGBの値を0から255の範囲で適当に設定してみてください。
    var r:uint = 127;
    var g:uint = 127;
    var b:uint = 255;
    // 0xRRGGBB形式にする
    var color:uint = (uint(r * brightness) << 16) |
        (uint(g * brightness) << 8) | uint(b * brightness);
    
    // 省略
}

 まずnormalnew Vector(0, 0, 1)の内積を計算し、結果をbrightnessとしています。実はランバートの法則はこの1行だけです。ランバートの法則を図で表すと、次のようになります。

ランバートの法則
ランバートの法則

 光の指す方向へ向かうベクトルがl、面の法線ベクトルがnです。今回は光は常に視点側から当たっていることにするので、lは(0, 0, 1)で固定です。面の法線ベクトルはTriangleクラスのnormalプロパティです。これらのベクトルの間の角度をθとします。面の明るさは、面が光の方向を向いているとき、つまりθが0度のときに最大となり、θが90度になると真っ暗になります。これをコサインで表すのがランバートの法則です。

ランバートの法則
面の明るさ = cosθ

 コサインを使うことで、θが0度のときには明るさが1、90度(-90度でもOK)の時には明るさが0となります。コサインがとりうる値は-1~1ですが、0以下になってしまうになってしまうようなケース(面が真横を向いていたり後ろを向いていたり)では面を表示しません。

 ここでθが何度なのか計算しなければならなさそうですが、そうではありません。Vector3#dotメソッドで軽く触れたとおり、内積を使うことでcosθを計算することができます。ベクトルlとnが正規化されていれば、lとnの内積はcosθに等しくなります。

コサインと内積の関係
面の明るさ = cosθ = l・n  (lとnの長さは1)

 以上のことから、光源へ向かうベクトルと三角形の法線の内積が、面の明るさとなります。なお、明るさの計算結果が0以下であれば、何もせずに終了します。

 次に、求めた明るさの値を使って、実際に塗りつぶす色を決定します。まずrgbに最も明るいときのRGB値を設定します。そしてそれらの要素にbrightnessをかけ、ビット演算で組み合わせます。後はこの色を使って以前と同様に三角形を塗りつぶせば、フラットシェーディングした4面体を描くことができます。

完成図
完成図

 マウスやキーボードを使った操作は前回と同じです。プログラムのほうはTriangleクラスのメソッドを使った方法に書き換えてあります。

まとめ

 ランバートの法則を使ってフラットシェーディングすることで、前回のワイヤーフレームから一歩進みました。これでそこそこ3Dらしくなったのではないでしょうか。ただし、まだ問題もあります。Triangleクラスをたくさん使ってみると、前後関係がおかしくなることがあります。これはZソートという手法で改善されるのですが、今後の課題としておきます。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
ActionScriptによるWebの3Dグラフィックス再入門連載記事一覧
この記事の著者

rch850/りちゃ(リチャ)

気になった技術をいろいろとつまみ食いしてます。覚えた技術をうまく組み合わせて面白いものができたらいいなと思います。twitter: @rch850

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/2235 2008/09/05 11:02

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング