yamicha.com's Blog - Presented by yamicha.com
Blog yamicha.com's Blog - 2018/10 の記事
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21] [22] [23] [24] [25] [26] [27] [28] [29] [30] [31]

yamicha.com's Blog
 諸事情により、現在更新休止中。ご了承ください。もし今後ブログを再開することがあるとすれば、その際にはこのブログスクリプトではなく、新しく開発したものによるかもしれません。
 当ブログ管理者についてはこちらをご参照。
開発魔法(737)
社会問題(733)
お知らせ(11)
質問・バトン回答(15)
ゲスト出演(8)
経済・知的財産(150)
ゲーム開発(182)
[Ada] 勝手に補足
- Note
- 金配りの次の一手


- Endless Ultimate Diary
- 銃世界

漢字バトン
- うるる雑記帳
- 漢字接力棒

ツキアイゲノムバトン
- ブログ@うにうに画像倉庫
- あぶ内閣

縺イ縺セ縺、縺カ縺励ヰ繝医Φ
- 月夜のボヤキ
- 騎士サーラバトン
パスワードを使う
名無し (2012/02/27)


開発者解放裁判
yamicha.com (2010/03/14)
Winnyに関しては、私も「純白」とまでは考えておりませんし、使用し..

開発者解放裁判
通りすがり (2010/03/08)
winnyに関しては「ダウンロードソフト板」なんてところを拠点に開発..

新型インフルエンザの恐怖
いげ太 (2009/11/03)
> C#などの「int Some(int , int)」は、F#では「(int * int) ->..

時効に関する思考
yamicha.com (2009/08/31)
>いげ太さんコメントありがとうございます。手元にドキュメントが少..
Homepage
Blog Top
Normal View
List View
Search
Calendar
Comment List
Trackback List
Blog RSS
Send Trackback
Phone Mode
Administrator
yamicha.com
Blog
るううるる。
Source
法令データ提供システム
FindLaw
Development
Java2 Platform SE 6
Java EE 6 API
MySQL Developer Zone
PHP Reference
MSDN Library
Ada Reference Manual
Objective Caml
Python Documentation
Erlang
Prolog Documents
漢字乱用無き様御願い申し上げ候
2007/09/21(Fri)22:01:12
 そのうち十分に取り上げたかった教育問題に関して。
 まず、東京都足立区が学力テストによる予算配分をやめる方針です。例の「学力テスト前の模擬テスト」「誤答指差し」「成績の振るわない可能性のある生徒を勝手に欠席」などの問題を受けたもので、日本でも英国と同様の問題が発生することを知らしめた事件でした。なお、再三取り上げているように、英国ではこれと似たような制度を導入した結果、授業が「知識を得るためではなくテストのための勉強」となったり、成績の悪い生徒を強引に試験に欠席させるなどの問題が表面化し、状況次第では廃止も検討されているようです。
 テストを基準に予算配分しようものなら、このような結果になることは火を見るより明らかです。にもかかわらず、こうした制度を導入した足立区が間違っていたのであり、廃止するのは当然でしょう。せめて、区内の学校同士が「テストの点戦争」を開始し、前述の問題が完全に教育現場を埋め尽くす前に廃止できて良かったと考えるべきでしょうか。
 これは全国学力テストについても同様です。学校ごとのテストの成績が公表され、ランク付けされるようなことがあれば、似たような争いが発生することは否定できません。特に学校選択制と組み合わせた時に問題が大きくなります。学校選択制自体はいじめ回避や柔軟な通学を可能にするための役立つ制度なのですが、「100マス計算を行う先生がいる小学校に希望者殺到」などという頭の悪い親連中の行動パターンからすれば、成績が上位の学校に希望者が殺到する反面、成績の振るわない学校では希望者が激減するといったことも起こりかねません。
 しかし、話は少々ずれますが、100マス計算の一体どこが優れているのでしょうか。私がもし100マス計算などというものをやらされていれば、算数なるものが大嫌いになっていたに違いありません。プログラムを書くのに重要なのは算数ですから、きっと永久にプログラムを打つこともなかったでしょう。この100マス計算も学力テストの成績も本質は同じであり、これらがどのような中身であれ、例えば学力テストなどは不正に点数が底上げされていたとしても、「100マス計算による計算力向上」「学力テストの成績上位」といった見た目だけに判断して飛びついているのでしょう。これでは子どもだけに学力向上を図っても無駄なような気がしなくもありません。
 さて、私は安倍氏辞任を知ってほっとしましたが、その最も大きな理由は「教育バウチャー」です。これまた再三申し上げている通りですが、学力テストの結果公表と学校選択制というところまでなら、入学者確保の争いが起こる可能性はあるにしても、まだマシというものです。しかし、バウチャーによって入学者数と予算配分をリンクさせるようなことがあればどうなるか。もう結果はお分かりでしょう。日本の教育をあっという間に崩壊させることができます。
 つまり、学力による予算配分、学力テストの学校ごとの結果公表、教育バウチャーなどの策は導入すべきではありません。果たして日本は、それを英国と足立区から学ぶことができるでしょうか。

 何やらたびたび行われているらしい国語世論調査。この調査からは興味深いことが3つ分かります。まず、20代のほとんどが分からない漢字を携帯電話で調べており、20〜30代に関しては携帯電話が紙の辞書を圧倒しています。また、日本語変換システムのおかげで難読漢字を多く使う傾向が見られ、手書きなら「憂うつ」と書くところを変換では「憂鬱」とする人が多いようです。こうしたことから、PC時代において漢字の使用はむしろ広がっているともいえ、政府は「読めるべきだが、書ける必要はない」という「準常用漢字」なるカテゴリの導入を検討しているようです。
 さらに、慣用句について誤った意味を覚えている人が多いことも明らかになりました。といっても、これに関しては今回の調査に限ったものではありません。
 では1つずつ検証してみましょう。まずは携帯電話の変換に頼ることについて。最近は変換システムもハードウェアもかなり進化しており、小型コンピュータでもそれなりの変換結果を得ることができるようになりました。こうした点を考えれば、携帯電話で漢字を調べることは必ずしも悪いとはいえません。同音異義語には十分気をつける必要はありますが、PCの変換システムの中には同音異義語の意味を表示してくれるものも存在し、この点もある程度は解消できます。
 ただし、変換システムはあくまで漢字変換の利便のためにあることを忘れてはいけません。例えば携帯電話の中には「るな」と打って「月」と出るものがあるようですが(当方未確認。真実性は保証しません)、もちろん「月」を「るな」とする読み方は地球上のどこにも存在しません。したがって、変換は必ずしも万能ではありません。「るなと打ったら月が出た。だから月はるなと読む」というのは、「すうがくと打ったらルート記号が出た。だからルート記号はすうがくと読む」と定義するほど愚かなことです。
 2つ目、難読漢字について。少々難しい漢字を使うことは別に構いませんが、最近どうも「難読漢字の乱用」がなされている気がしてなりません。何でもかんでも変換すれば済むというものではありません。はっきり言って無駄に漢字が使われている文章は非常に見苦しいですし、小説にも難読漢字や常用外漢字、旧字のようなものが乱用される傾向があります。
 例えば以下の文章をご覧いただきましょう。

 大変申し訳御座いませんが、或る理由に依って本日の会議は延期とさせて頂きます。次回日程は前以って御連絡差し上げた通りです。御理解下さいます様宜しくお願い申し上げます。

 無駄に漢字を使っています。紙の文章なら「或る理由に依って」や「前以って」などと書くことはあまりありません。漢字を使わないような書き方(例「ねんきん事業機構」「さいたま市」「あっぷる市」「うきは市」)がまぬけなのは見ての通りですが、漢字をやたら使うのも見苦しいのです。
 小説では、有名な例として「博士の愛した数式」を挙げておきましょう。この小説内では「馘」なる漢字がいくつか用いられています。そのまま「クビ」という意味なのですが、やはり常用するような漢字ではないためか、わざわざ「くび」というルビ付きで表示してあります。しかし、文章の流れからしても、このような妙に難しい漢字を使う必要があるようには見えませんでした。それから、この小説で目立ったのが「〜をしまった」(収納の意味)の表記ゆれです。「しまった」と「仕舞った」がかなりの頻度で混在しているのです。おそらく「変換したら何となく出てきた」というレベルなのでしょうが、この点が変換システムの弱点です。何でも変換すれば済むというものではないのです。
 こうしたことから、無駄な漢字変換は慎むべきでしょう。ただ、漢字変換も時と場合のようで、例えば新聞はおおむね常用漢字のみを使用していますが、それでは「ら致」「ねつ造」といった表記が生じ、これを見苦しいと考える人も少なくないようです。毎日新聞の校閲に関するコラムでは、校閲者自身が「純真無く」という意味が反対になりそうな表記について語っていたことがありました。最近は良く用いられる漢字をルビ付き(ただし非常に常用される場合はルビなし)で表記することが多くなっているようです。
 では結局どうすべきかといいますと、基本的に「読み手の立場に立ち、読みづらいカナ混ぜ、やたら難しい漢字表記は避ける」というスタンスなら問題ないでしょう。難読変換が自己満足に陥っていたり、常用漢字の使用が完全にマニュアル化していたりと、それらを読み手より優先しているからこのような状況になるのです。
 そして3つ目、慣用句について。これは「誤用」が少ないうちはともかく、大半が誤用となったらそちらを新しい意味として定着させるべきでしょう。最も有名な誤用語の例としては「確信犯」があります。これは本来「それを正しいと考えて行動する」という意味なのですが、現代においては「故意犯」の意味として用いられています。マスコミも後者の意味を用いており、もはやこの言葉の表す意味は一定しています。結果、この言葉を安全に用いることができます。
 問題は意味が一定していない言葉です。例えば「役不足」、これは本来「人物に対して役が小さすぎる」意味なのですが、現代では半分の人が「役に対して人物が小さすぎる」ものと考えています。結果、ほめ言葉として用いるのも侮蔑として用いるのも危険であり、「この言葉を使わない」ことが唯一解となってしまいます。つまり、せっかく存在する言葉が使用できないばかりか、場合によっては言葉が死ぬ恐れすらあります。
 したがって、明らかに「誤用」が定着している言葉はそれを定義とすべきです。そうでなければその言葉を用いることができません。「言葉の伝統が失われる」といった類の反論も出そうですが、その言葉を封印せざるを得ないよりは幾分かマシです。せっかくの慣用句にもかかわらず、意味が間違って伝わっては何の意味もありません。
 常用漢字の使用に伴って読み方や意味が変化した単語もあります。例えば「世論」は「よろん」とも「せろん」とも読みますが、もともとは「輿論」が正しい表記です。しかし、今となっては「世論」と表記するのが一般的であり、これは「せろん」とも読みますので、これを「せろん」と読んだ人に「誤りだ。よろんと読むべき」と指摘しても怒りを買うだけです。
 他にも「洗浄」はもともと「洗滌」と書き、「せんでき」と読むものでした(変換もできます)。それが「洗浄」とされ、「せんじょう」と読むようになりました。しかし、これを誤りとする人はいないでしょう。他には「義援金」も本来「義捐金」が正しいのですが、今では「義援金」の方が違和感なく用いられています。「誤用」を悪とするなら、これらすべてを元に戻さなければなりません。他には、「独壇場」はもともと「どくせんじょう」、「早急」は今では「さっきゅう」とも「そうきゅう」とも読めますし、「一生懸命」が「一所懸命」であったのは有名な話です。
 元が外国生まれの言葉では、「沈黙は金、雄弁は銀」がたびたび誤用されます。雄弁はベターであり、沈黙がベストであるという意味合いで使われることが多いようです。この言葉が生まれた際には、金より銀の方が値打ちがあったとも知らずに。つまり「沈黙は上策であるが、雄弁はさらに上策である」が本来の意味なのでしょう。
 こうしたことから、「言葉は基本的に変化するものであり、言葉を使えなくするなら意味を変えても存続させるのが上策」とするのが私の基本スタンスです。言葉に関しては、どちらかといえば革新派となるでしょうか。しかし、例外が1つあります。ここまでで取り上げてきた問題とも微妙に関係するのですが、名前(命名)に関してのみは保守的な方が適当です。
 最近は奇をてらった意味不明な名前が色々と登場していますが、あれは「言葉の進化」の意味を履き違えているとしか考えられません。中には「そのうちこのような名前が当たり前になる」と屁理屈をこねる者もいますが、現代で多少変な名前が数十年後に当たり前になっている確率はせいぜい数割程度でしょうし、あまりに変な名前が当たり前になることはまずありません。「後に当たり前になる」などという妄想が外れたら、一体誰が責任を取るのでしょうか。
 そして現実問題、学校では名前によるいじめが発生することがあり、本人が受ける不利益は計り知れません。「名前が普通でもいじめは起こる」という言い訳も出そうですが、いじめ被害の確率は明らかに上がります。いじめ以外でも、大人になって改名するにしても裁判が必要ですし、しないなら一生十字架を背負わなければなりません。そして、変な名前をつけられた子が大人になった時、その名前が当たり前になっているかといえば、そのような保証は全く存在しません。現代においても明らかに変な名前に関しては、数十年後も当たり前になっていない可能性の方が高いです。反面、現代において当たり前の名前が今後100年の間に当たり前でなくなる可能性は極めて低いです。どちらが有効かは説明するまでもないでしょう。小学校レベルの「確率」の問題さえ解ければ、答えは明らかです。

 国語ばかりでなく算数も取り上げておきましょう。小学校で台形の面積を求める項目が復活するようです。以前のように「(上辺 + 下辺) / 2 * 高さ」の公式は盛り込まないものの、三角形に分割して教えるのだとか。しかし、この公式は「発展的記述」でも構わないので残しておいて欲しいところです。というのは、算数においてどうしてそうなるのかを考えるのは極めて重要なのです。
 まず台形の面積の求め方ですが、上記の公式を使う他に、三角形に分割する、台の切れ込み部分を埋めて長方形にする、台形を地盤にして大きな三角形を作るなど、様々な方法が考えられます。こうしたことを考えるだけでも効果的です。具体的には、

Q.上辺4cm、下辺6cm、高さ3cmの台形の面積を求めよ。いかなる方法で求めても構わない。

A1.公式によれば (4 + 6) / 2 * 3 = 15 であるから、答えは15cm^2。

A2.底辺4cm、高さ3cmの三角形と底辺6cm、高さ3cmの三角形に分割する。
4 * 3 / 2 + 6 * 3 / 2 = 15 であるから、答えは15cm^2。

A3.横6cm、高さ3cmの長方形として考える。長方形にするためには底辺2cm、高さ3cmの三角形が必要。
6 * 3 - 2 * 3 / 2 = 15 であるから、答えは15cm^2。

A4.台形の上に三角形をくっつけ、大きな三角形にする。台形を1つの三角形にするには、その上に底辺4cm、高さ6cmの三角形が必要。台形と組み合わせた大きな三角形の大きさは、底辺6cm、高さ9cmである。
6 * 9 / 2 - 4 * 6 / 2 = 15 であるから、答えは15cm^2。

 A2の方法が台形の公式の基になっていることはお分かりでしょう。「上辺 * 高さ / 2 + 下辺 * 高さ / 2」は「(上辺 + 下辺) / 2 * 高さ」に変形できます。これを自力で見つけることができれば、公式を叩き込むのではなく、習得することができます。
 機械が発展した現代、何らかの計算問題をひたすら解いて、計算スピードが上がったところで、メリットはほとんどありません。どれほど優れた暗算能力を持った人間でも、何千件というデータを数秒で処理することはできません。ところが、データベースや表計算はそれを苦もなくやってのけます。いくら計算能力を鍛えても、機械の計算能力には絶対にかなわないのです。ですから、特に小学校では「算数問題をひたすら解く」よりも「算数の理解」と「算数嫌いにしないこと」に重点を置く方が効率が良いのです。極端な話、足し算が遅くても方法が分かれば電卓で計算できますが、算数嫌いで足し算の方法が分からなければ、電卓で計算することすらできないのです。
 その点、台形の面積の求め方が復活するのは良いことです。求め方さえ分かっていれば、台形のデータと電卓を使って簡単に計算が可能です。ところが、求め方が分からなければ、いかに高性能な電卓があっても面積を求めることはできません。
 どの教科にしてもそうですが、こと算数に関しては、バカげた問題をひたすら解くばかりのくだらない詰め込みは一切無意味ですし(「読み書きそろばん」の時代であれば、暗算能力を鍛えるために数をこなす意味はあったのでしょうが)、かといって教える内容をひたすら減らせば解決する問題でもありません。ゆとりの意味を履き違えている代表的な例であり、それを是正する意味でもこの動きは歓迎できます。

 ベクトル。方向性を示すアレです。実は私、ベクトルの概念に関してはこれまでも使っているはずなのですが、ベクトル自体を使ったことはありません。足し算でも引き算でも。それでも力技で3D処理を書くことはできましたが、そろそろ照明などにベクトル概念が登場するようになりましたので、この辺りで習得してみることにしました。
 基礎すら知らない以上、入門から必要でしょう。まずはGoogleで調べてみたところ、分かりやすいサイトが色々と存在するではありませんか。何でもベクトルとは特定の座標までの向きを示したもので、ベクトル同士の演算も可能なのだとか。ちなみに初等数学だそうです。そういえば文系の高校でも習うと聞いたことがあるような。
 私は3Dベクトルを必要としているのですが、まずは2Dからです。2Dということは、座標が2つ存在しますので、ベクトルには数値が2つ存在します。ベクトルは2つの点を結ぶものとして表すことができ、A点からB点へのベクトルに関しては、高校数学ではABの上に矢印を書いて表示し、通常の数学では何らかの変数として表記(V = AからBへのベクトル、など)することが多いようです。
 ではまずベクトル同士の足し算から。これは非常に簡単で、それぞれの要素を単純に足すだけです。例えばベクトルVがX1,Y0、UがX0,Y1を表しているなら、これらを足し合わせたベクトルは「X = 1 + 0 = 1 , Y = 0 + 1 = 1」であり、新しいベクトルは「X1,Y1」です。単純な足し算ですから、U+VでもV+Uでも同じ結果となります。これは、基準点からベクトルVが表す分だけ進み、次いでベクトルUが表す分だけ進んだ位置と同じになるとのこと。
 次は引き算ですが、これまた簡単です。こちらも単にそれぞれの要素の値を引くだけです。前述のベクトルを用いるなら、「X = 1 - 0 = 1 , Y = 0 - 1 = -1」であり、新しいベクトルは「X1,Y-1」です。これは普通の引き算と全く同じであり、例えば「10 - 4 = 6」は「4の立場から見れば、10は自分より6つ上にある」と言い換えることができますが、ベクトル「V - U = T」に関しても「Uから見ればVはTだけ先の位置にある」と言い換えられます。
 次はベクトル内積です。これまた大変簡単であり、例えばVがX10,Y5、UがX3,Y4であるなら、「10 * 3 + 5 * 4 = 50」です。これだけと言うなかれ、本当にこれだけです。当然のことながら、式には掛け算を使用していますので、直角のベクトル同士なら内積はゼロになりますし、双方の符号が違えばマイナスになります。
 この計算の重要な点として、返すのはベクトルではなく数値であるということがあります。ベクトルの足し算や引き算では新しいベクトルを求めていましたが、こちらは数値を求めます。なお、ベクトル内積を式にすると「V.U」や「V・U」などとなり、ドットで表すため「ドット積」などとも呼ばれます(正式には中ドットが正しいのでしょうが、半角で表記する都合上、ここでは今後「V.U」を用います)。
 ベクトルの特殊な表記としては「|V|」なるものがあります。これはベクトルの長さを意味するもので、式にすれば「SQRT(X^2 + Y^2)」だそうな。hypot(ピタゴラスの定理)ではありませんか。3Dを作る時には大変お世話になりました。ちなみに3Dでも「SQRT(X^2 + Y^2 + Z^2)」あるいは「SQRT(SQRT(X^2 + Y^2)^2 + Z^2)」という式が使えます。
 なお、上記の「ベクトル内積を求める式」ですが、正式には「|V| |U| cos(Theta)」だそうです。|V| 及び |U|は各ベクトルの長さであり、ThetaはVとUの角度の差です。例えばVをX7.07...,Y7.07...、UをX10,Y0として計算すれば、前述の式なら「10 * 7.07... + 0 * 7.07... = 70.7...」であり、コサインを使った式なら「10 * 10 * cos(PI / 4) = 70.7...」となりますので、結果が等しいことが分かります。
 また、これはもう1つ重要な性質を持っています。「V.x * U.x + V.y * U.y」と「|V| |U| cos(Theta)」が等しいのですから、VとUのベクトルのみ分かっているような場合、まず前者の方法で内積を求めることができます。そしてそれを「|V| |U|」で割るとしましょう。するとcos(Theta)部分だけが残るわけですから、これをacosにかけてやれば、UとVの角度の差が分かってしまいます。2Dならatan2を使えば足りますが、3Dになると重要です。
 なお、こうした計算はそれなりに頻繁になされ、毎回|V|を割ったり掛けたりするのはわずらわしいため、「ベクトルの正規化」という手法が良く用いられます。これはベクトルを長さ1の線とみなすものであり、言い換えれば「|V| = 1」にする行為です。いかなる大きさのベクトルであっても、この手法によって方向情報のみを表現するものに変換することができます。計算方法も実に簡単で、「X = V.x / |V| , Y = V.y / |V|」とするだけです。これで新しいX,Yは正規化されたものとなっています。これは位置ではなく方向だけを足し合わせたい場合などに便利です。

 と、ここまでは高校で習うとのことですから、ご存知の方も多いのではないでしょうか。しかし、当サイトで数学をやるからには、高校レベルの問題だけ取り上げてはいおしまい、とはいきません。私にとっては目新しいものではありますが、ベクトルを習った経験のある人からすれば、おそらく空き缶の1つも飛んでくるような内容でしょう。
 そういうわけで、大学数学の分野に入るらしいベクトルの外積と参りましょう。これはどうやら3D固有の概念らしく、ベクトルを積算して新しいベクトルを返します。内積が「V.U」「V・U」の表記を使っていたのに比べ、こちらは「V*U」「VxU」といった具合に掛け算記号を用います(本来は掛け算の算術記号が正しいのですが、ここでは今後「V*U」を用います)。掛け算記号を使っていることから「クロス積」とも呼ばれます。
 ではベクトルの外積とは。おおむね次のように説明されているようです。「V*Uとは、右ネジをVからUの方向に向かって回した時に、ネジが入っていく向き」。何のことやら。これだけではさっぱり意味が分からないため、次のような説明を考えてみました。
 まず、ベクトルの起点(基本的に0,0,0)をS、演算するベクトルを「V*U」とします。まずVとUの間をロープで結びます。その後、VとUを結んだロープの中間点に新しいロープを結び、それをSまで伸ばします。VとUを軸とし、Sまで伸びたロープを90度回転させれば、それがベクトル外積です。したがって、ベクトル外積はV、U、Sのすべてのベクトルから90度の関係にあります
 上記の例ではS,V,Uが三角形を形成していますが、これは実に三角形の向き、すなわち法線ベクトルそのものです。これであっという間に法線ベクトルを求めることができてしまいます。法線ベクトルは照明効果その他の基本ですから、これをあっという間に求めることができるのは非常に重要なのです。
 肝心の外積の求め方ですが、VとUを3次元として「(V.y * U.z - V.z * U.y) + (V.z * U.x - V.x - U.z) + (V.x * U.y - V.y - U.x)」という簡単さ。言い換えるなら、X,Y,Zの各軸に対して「NewVector.axis = V.cosAxis * U.sinAxis - V.sinAxis * U.cosAxis」を実行しているのと同じです。
 ここまで分かればプログラムも書けるでしょう。PHPで書くとしましょうか。
class Vector{
	private $vector;

	const X = 0;
	const Y = 1;
	const Z = 2;

	function __construct($s , $t = null){
		$v = array();

		if($t){
			for($i = 0; $i < 3; $i++){
				$v[] = $t[$i] - $s[$i];
			}
		}else{
			$v = $s;
		}

		$this->vector = $v;
	}

	function set($a , $v){
		$this->vector[$a] = $v;
	}
	function get($a){
		return $this->vector[$a];
	}

	// ベクトル同士の加算
	function plus(Vector $v){
		$a = array();
		for($i = 0; $i < 3; $i++){
			$a[] = $this->get($i) + $v->get($i);
		}

		return new Vector($a);
	}
	// ベクトル同士の減算
	function minus(Vector $v){
		$a = array();
		for($i = 0; $i < 3; $i++){
			$a[] = $this->get($i) - $v->get($i);
		}

		return new Vector($a);
	}
	// ドット積
	function dotProduct(Vector $v){
		$a = 0;
		for($i = 0; $i < 3; $i++){
			$a += $this->get($i) * $v->get($i);
		}
		return $a;
	}
	// クロス積
	function crossProduct(Vector $v){
		$a = array();
		for($i = 0; $i < 3; $i++){
			$cos = ($i + 1) % 3;
			$sin = ($i + 2) % 3;

			$a[] = $this->get($cos) * $v->get($sin) - 
				$this->get($sin) * $v->get($cos);
		}

		return new Vector($a);
	}
	// 全要素に対して指定した数値を掛ける
	function scale($v){
		$a = array();
		for($i = 0; $i < 3; $i++){
			$a[] = $this->get($i) * $v;
		}

		return new Vector($a);
	}
	// ベクトルの長さ
	function hypot(){
		$hypot = 0;
		for($i = 0; $i < 3; $i++){
			$hypot += $this->get($i) * $this->get($i);
		}
		return sqrt($hypot);
	}
	// 正規化
	function normalize(){
		$hypot = $this->hypot();

		$a = array();
		for($i = 0; $i < 3; $i++){
			$a[] = $this->get($i) / $hypot;
		}

		return new Vector($a);
	}
	// シータ算出
	function getTheta(Vector $v){
		return acos($this->dotProduct($v) / 
			($this->hypot() * $v->hypot()));
	}
	function __toString(){
		return "X:" . $this->get(self::X) . "\n" . 
			"Y:" . $this->get(self::Y) . "\n" . 
			"Z:" . $this->get(self::Z);
	}
}
 この通り、さほど難しい記述は必要ありません。実際にこのクラスを使う場合にも、ひたすらメソッドを組み合わせていくだけで計算ができてしまいます。しかし、それでもベクトルは非常に強力なのです。
 コンストラクタは引数を1つまたは2つ取りますが、1つ渡した場合は0から引数の方向へのベクトルであるとみなします。2つ渡すと最初の引数を始点、2つ目の引数を終点とみなしたベクトルを生成します。
 手始めに三角形の法線ベクトルを考えてみましょう。ただし、奥に行くほどZ座標がプラスであり、かつ三角形の頂点は時計回りであるものとします。
$a = array(0 , 100 , 1000);
$b = array(60 , 0 , 1020);
$c = array(-60 , 0 , 970);

// A を始点としたベクトルを生成
$u = new Vector($a , $b);
$v = new Vector($a , $c);

// 正規化
$u = $u->normalize();
$v = $v->normalize();

// 法線ベクトルを算出
print $u->crossProduct($v)->normalize();

// 結果
X:0.384206388403
Y:0.0461047666084
Z:-0.922095332168
 つまり、この三角形はおおむね手前を向いており、やや右向き、少しだけ上を向いていることが分かります。
 首尾よく法線を求められたら、まず反射光を考えてみます。ある方向から光が差し込み、何らかの面に当たって反射したとしましょう。光はどちらの方向に反射するでしょうか。これを算出しなければ反射光の実装は不可能です。現実にも、例えば鏡であったり、光の反射といったものは、同様に求められるはずです。
 調べてみたところ、光の反射は次のような式になるとのこと。
2N(L.N)-L

N : 光が当たる物体の法線ベクトル
L : 光が当たった部分から見た光の向きのベクトル
ただし、両者とも正規化しておく
 私はこのような式が非常に苦手なのです。まず「2N」は「2 * N」であるとして、数とベクトルを掛けるわけですから、クロス積ではなく単なる掛け算ですか。「(L.N)」はドット積として、ドット積の返り値は数ですから、「2N * (L.N)」はベクトルNに2を掛け、さらにドット積の結果を掛けると考えるべきでしょう。最後にベクトルLを減算していますが、これはベクトル同士ですからベクトルの引き算と考えられます。
 そういうわけで、実際に光の反射を求めてみます。法線ベクトルを求めるところまでは先のコードと同じです(三角形の座標のみ変更しています)。
// 三角形の定義
$a = array(0 , 100 , 0);
$b = array(100 , 0 , 0);
$c = array(0 , 0 , 0);

// ベクトルを求める
$u = new Vector($a , $b);
$v = new Vector($a , $c);

// 正規化
$u = $u->normalize();
$v = $v->normalize();

// 法線ベクトル(N)
$n = $u->crossProduct($v)->normalize();

// 光が当たった点から見た光のベクトル(L)
// 今回は右手前から当てている
// なお、もし「光から見た点の方向ベクトル」を使うのであれば
// $l->scale(-1) として座標を反転すればよい
$l = new Vector(array(1 , 0 , -0.707));
$l = $l->normalize();

// 2N(L.N)-L
// N * 2 * (L.N) - L の順番に計算している
$specular = $n->scale(2)->scale($l->dotProduct($n))->minus($l);

print $specular->normalize();

// 結果
X:-0.816537681025
Y:0
Z:-0.577292140485
 なるほど、その方向に反射するわけですか。ベクトルを使えば非常にお手軽に求められます。ご興味がおありなら、三角形や光の方向を変えて色々試してみてください。
 これを使えば、同様に「すべり」も簡単に求められます。「すべり」といいますのは、何かが物体に衝突した際に、それが物体の表面をすべる現象です。斜めにした板に水を落とす状態を想像していただければわかりやすいでしょうか。その性質上、真正面からぶつかってきたものに対するすべりを求めることはできません。
// 三角形から法線を求める
$a = array(0 , 100 , 0);
$b = array(100 , 0 , 50);
$c = array(0 , 0 , 50);

$u = new Vector($a , $b);
$v = new Vector($a , $c);

$u = $u->normalize();
$v = $v->normalize();

$n = $u->crossProduct($v)->normalize();

// 衝突点から見て、衝突させるものが飛んでくる方向(L)
$l = new Vector(array(1 , 0 , -1));
$l = $l->normalize();

// N(L.N)-N
$slide = $n->scale($l->dotProduct($n))->minus($l);
print $slide->normalize();

// 結果
X:-0.912870929175
Y:-0.36514837167
Z:0.182574185835
 簡単です。
 次は屈折を試してみましょう。これには「スネルの法則」というものを使用します。スネルの法則とは、人間の根性の屈折率、問題の屈折率から、問題を目の当たりにした人間がどの程度すねるのかを算出するためのものです。人間の根性と問題の屈折率がおおむね一致していれば、あまり大きな効果は現れませんが、そうでない場合は大きな効果が現れます。
 ではなく、物体1と物体2の間を光が行き来する際、どの程度の屈折が現れるのかを調べるものです。物体には固有の屈折率が存在し、Wikipediaによれば0度1気圧の空気が1.000292、20度の水で1.3334、水晶では1.5443だそうです。定義上、真空が1となっています。
スネルの法則
-N(1-(n1/n2)(1-(L.N)^2))^(1/2)-(n1/n2)(L-(L.N)N)

N : 法線
L : 屈折点からの光の方向
n1 : 入射元の屈折率
n2 : 入射先の屈折率
 無論、ベクトルを使えば屈折を求めることができます。ここでは、完全に水平で上向きの水(20度)に対し、0度1気圧の空気で満たされた空間から、X1,Y1の角度(45度)より光が進入してきた時の屈折を求めてみます。
// 屈折
$n = new Vector(array(0 , 1 , 0));	// 法線
$n = $n->normalize();

$l = new Vector(array(1 , 1 , 0));	// 光の方向
$l = $l->normalize();

// $n1 : 入射元の物体の屈折率
// $n2 : 入射先の物体の屈折率
$n1 = 1.000292;	// 空気
$n2 = 1.3334;	// 水

// n1/n2 を求める
$nd = $n1 / $n2;

// (1-(n1/n2)(1-L.N)^2)^(1/2)
$t1_a = sqrt(1 - $nd * (1 - pow($l->dotProduct($n) , 2)));
// -N($t1_a)
$t1_b = $n->scale(-1)->scale($t1_a);

// (L-(L.N)N)
$t2_a = $l->minus($n->scale($l->dotProduct($n)));
// (n1/n2)($t2_a)
$t2_b = $t2_a->scale($nd);

// ($t1_b)-($t2_b)
$t = $t1_b->minus($t2_b);

// 正規化
$t = $t->normalize();

print $t;

// ついでに角度も求めてみる
print("\n" . atan2($t->get(Vector::Y) , $t->get(Vector::X)) * 180 / M_PI);

// 結果
X:-0.55720686903
Y:-0.830373714123
Z:0
-123.862853365
 つまり、光が斜め45度から水平の水に入った場合、空気が0度1気圧で水が20度であれば、光は-123.86285...度の方向に進むということです。プログラムを見る限り、案外簡単ではないでしょうか。なお、水から空気中に光が出る場合には、$n1と$n2を逆転させた計算が必要です。
 反射に戻ります。光の反射については分かりましたが、まだ光の反射と視点との関係が明確ではありません。光がいくら反射しても、それが視点に飛び込んでこない限り、物体は光っているようには見えないのです。これを求めてみるとしましょう。
 まずは「Beckmann 分布」と呼ばれる方法から。反射光を生成するにはかなり正確なモデルだそうです。なお、以下の分布式の多くはWikipedia:鏡面ハイライトを参考にしています。
Beckmann 分布
((1/(4m^2cos(N,H)^4))E^(-((tan(N,H)/m)^2)))
N : 法線
H : 光と視点の半角(ハーフベクトルとも。これらの中間の角)
E : EXP(1)(自然対数の底。2.71828...)
m : 係数(表面によって使い分け。大きくするほど拡散する。通常0-1の範囲)
 ここで初登場したハーフベクトルですが、何のことはありません。2つのベクトルの中間の角を求めるのは非常に簡単です。正規化したベクトル同士を足せば良いのです。実際に適当なベクトルを作って実践してみれば、これでハーフベクトル化できることが分かるでしょう。
 それでは算出してみます。係数は何でも構いませんが、これはいわば材質を表すものであり、数値が大きいほど光が拡散し、小さいほど一箇所が光ります。法線や光源に関してはもう説明は不要でしょう。今回は「視点」が加わっていますが、指定の方法は光源と基本的に同じですから、迷うこともないでしょう。
$m = 0.5;	// 係数
$n = new Vector(array(0 , 1 , -1));	// 法線
$c = new Vector(array(0.3 , 0 , -1));	// 光を受けた点から視点への方向
$l = new Vector(array(0 , 1 , 0));	// 光を受けた点から光源への方向

$n = $n->normalize();
$c = $c->normalize();
$l = $l->normalize();

$h = $c->plus($l)->normalize();	// 光と視点との半角

// 法線と半角のシータ (N,H)
$nh = acos($n->dotProduct($h) / ($n->hypot() * $h->hypot()));

// $mpow = -((tan(N,H)/m)^2)
$mpow = pow(tan($nh) / $m , 2) * -1;

// $e = E^($mpow)
$e = pow(M_E , $mpow);

// (1/(4m^2cos(N,H)^4))($e) は ($e)/(4m^2cos(N,H)^4) に変換できる
$k = $e / ($m * $m * 4 * pow(cos($nh) , 4));

print $k;

// 結果
0.914904709908
 係数が小さいほど、また視点と反射方向が異なるほど、かなりの勢いで減耗していくのが分かります。視点の位置によって光り方が全く違ってくるのが反射光の特徴です。
 Beckmann分布は金属などをかなり正確に表現することはできますが、髪の毛などの光沢を入れるのには向きません。こうした用途には「Heidrich-Seidel 分布」が使えます。これは髪の毛の他、溝が大量にある金属など、線状のものに向いているようです。計算方法は比較的簡単で、
Heidrich-Seidel 分布
(sin(L,T)sin(V,T)-cos(L,T)cos(V,T))^n

L : 光の方向
V : 視点の方向
T : 溝が入っている方向(髪の毛ならおおむね Y1 か)
n : phong 指数(通常は1以上を取る変数)
 とのこと。指数は大きければ大きいほど光沢が失われます。かなり強力な光沢が出るため、あまり小さな値にしないようにしましょう。
$p = 16.0;	// phong 指数
$c = new Vector(array(0 , -1 , -1.5));	// マテリアルから視点への方向
$l = new Vector(array(0 , 1 , -1));	// マテリアルから光源への方向
$t = new Vector(array(0 , 1 , 0));	// 溝の方向

$c = $c->normalize();
$l = $l->normalize();
$t = $t->normalize();

$s_lt = sin($l->getTheta($t));
$s_ct = sin($c->getTheta($t));
$c_lt = cos($l->getTheta($t));
$c_ct = cos($c->getTheta($t));
$k = pow($s_lt * $s_ct - $c_lt * $c_ct , $p);

print $k;

// 結果
0.730690205002
 これは「光源が上空45度手前、糸や髪の毛、細かい溝のある金属などの糸や溝の向きが完全にY方向であり、それを手前斜め下およそ34度から見た場合の光沢」です。もう少し具体的な例を挙げれば、上空手前45度から糸の束や髪の毛に光を当て、それを下1メートル、手前1.5メートルの位置から見ている状態です。光沢は視点や光の位置の他、phong指数によっても色々変わってきます。
 さて、こうしたアルゴリズムでも反射光を再現することはできるのですが、しばらくGoogleなどで検索してみた結果、何でもクック・トランスを適用しなければ不自然とのこと。この式についてもWikipediaで紹介されていますが、とにかく複雑です。私の手に負えるようなものではありません。
Cook-Torrance モデル
(DFG)/(E.N)

D : 先に算出した Beckmann 分布
F : フレネル項
G : 幾何学減衰
E : 視点へのベクトル
N : 法線ベクトル
 このうちEとNの数値はすぐに求められますし、Dに関しても先ほどの式を流用できます。しかし、FとGについては作らなければなりません。そのうちGに関しては
幾何学減衰
min(1 , (2(H.N)(E.N))/(E.H) , 2(H.N)(L.N)/(E.H))

N : 法線ベクトル
E : 視点ベクトル
L : 光へのベクトル
H : EとLのハーフベクトル
 で求められることが記述してありますが、F(フレネル項)についてはそうはいきません。仕方がありませんので、Googleで様々なサイトを渡り歩いて調べてみました。
 結果、フレネル項とは問題の屈折率を変数とし、この問題に触れるとどの程度の人が寝てしまうのかを算出するものであることが分かりました。どうやら電磁波に関する式らしく、レム波とノンレム波に分けて算出するようです。ではなく、屈折率からp波とs波の反射を求めるものであるようです。式にするとこうなります。
フレネル項
(1/2)((g-L.H)^2/(g+L.H)^2)((((L.H)(g+L.H)-1)^2/((L.H)(g-L.H)+1)^2)+1)

g : 屈折率に関する変数
g^2 = n^2-1+(L.H)^2
すなわち
g = sqrt(n^2-1+(L.H)^2)

n : 屈折率
 複雑です。ひたすら複雑です。ちなみにこの式、「真空状態から特定の屈折率の物体にぶつかって反射した光」を求めるものであるそうです。真空状態ではなく、空気や水などから特定の物体にぶつかった場合の反射を算出する式も知りたかったのですが、これしか見つかりませんでした。Wikipediaのフレネル項の記述を見る限り、上記の式のどこかにn2を盛り込めば良さそうなのですが。残念ながら同項目には反射光を処理するための式自体は記載されていません。いずれもう少し検証してみるとしましょうか。
 式が分かったところで、実際に作成してみましょう。手に負えないと言いつつ作成してしまうのもどうかという気がしますが。
$m = 0.2;	// 係数
$f_n = 1.5;	// 物体の屈折率

$n = new Vector(array(0 , 1 , -1));	// 法線
$c = new Vector(array(0 , 0 , -1));	// マテリアルから視点への方向
$l = new Vector(array(0 , 1 , 0));	// マテリアルから光源への方向

$n = $n->normalize();
$c = $c->normalize();
$l = $l->normalize();

// Beckmann 分布(D)を算出
// 光と視点との半角
$h = $c->plus($l)->normalize();

// 法線と半角のシータ (N,H)
$nh = acos($n->dotProduct($h) / ($n->hypot() * $h->hypot()));

// $mpow = -((tan(N,H)/m)^2)
$mpow = pow(tan($nh) / $m , 2) * -1;

// $e = E^($mpow)
$e = pow(M_E , $mpow);

$d = $e / ($m * $m * 4 * pow(cos($nh) , 4));

// フレネル項(F)を算出
// L.H
$f_c = $l->dotProduct($h);

// g = sqrt(n^2-1+(L.H)^2) = sqrt(n*n+(L.H)*(L.H)-1)
$f_g = sqrt($f_n * $f_n + $f_c * $f_c - 1.0);

// (1/2)(g-L.H)^2/(g+L.H)^2) = 1(g-L.H)^2/2(g+L.H)^2
$f_1 = (1 * pow($f_g - $f_c , 2)) / (2 * pow($f_g + $f_c , 2));

// (((L.H)(g+L.H)-1)^2/((L.H)(g-L.H)+1)^2)+1
// = 1+(L.H(g+L.H)-1)^2/(L.H(g-L.H)+1)^2
$f_2 = 1 + pow($f_c * ($f_g + $f_c) - 1 , 2) / 
	pow($f_c * ($f_g - $f_c) + 1 , 2);

// ($f_1)($_2)
$f = $f_1 * $f_2;

// ジオメトリック(G)
// (2(H.N)(E.N))/(E.H)
$g_a = 2.0 * $h->dotProduct($n) * $c->dotProduct($n) / $c->dotProduct($h);

// (2(H.N)(L.N)/(E.H))
$g_b = 2.0 * $h->dotProduct($n) * $l->dotProduct($n) / $c->dotProduct($h);

// min(1 , ($g_a) , ($g_b))
$g = min(1 , min($g_a , $g_b));

// (DFG)/(E.N)
$k = ($d * $f * $g) / $c->dotProduct($n);

print $k;

// 結果
0.444062272037
 今回は屈折率によってかなり結果が変わってきます。なお、「真空中から何らかの物体に光が当たった場合」ということで反射を算出してありますので、物体の屈折率まで1.0(真空状態)に設定されていると一切反射は起こりません。
 それで、これらにどの程度の効果があるのか。数字を見るだけでは分かりづらいものではあります。ここはひとつ、実物を見ていただきましょう。Cook-TorranceアルゴリズムをDualKnightに組み込んだ結果です。


 球体にポイント光源で光を当てた例。光の位置は右上やや手前です。今回のマテリアル自体のカラーは真っ黒に設定しており、マテリアル特性と光だけでマテリアルを光らせています。ちなみに光自体は白色なのですが、マテリアル特性で拡散光に対して青以外の光を吸収するようにしているため、物体は青く見えます。反射光についてはすべての光を反射するよう設定しているため、反射部分は白く見えています。


 球体にスポットライト光源で光を当てた例。光は手前側やや上から球体の中心部に向かって放射しています。これまた光は白色なのですが、マテリアル特性によって赤く光っています。シータ要素をかなり大きめに設定したため、スポットライトの半径による減耗は出ていないようです。


 球体にディレクショナル光源で光を当てた例。しつこいようですが、光の色は白色です。こちらは左下手前からの光となっています。ディレクショナルには減耗がないからか、視点との相性がたまたま良かったのかは知りませんが、反射光がそれなりに強く出ています。

 しかし反射光は意外と使いづらいです。視点によっては全く効果が出なかったりします。さらに、面に向かって光を当てると派手に光ってしまったりするため、球体以外に用いるのは設定が非常に困難です。リアリズムを無視してエフェクトを派手にしたいなら、たまには派手にギラギラ光らせるのも面白いでしょうが。
 以上、ベクトルでありました。しかし、ベクトル入門から一気にCook-Torranceモデルまで実装してしまうのは、ブログの構成からしていかがなものでしょう。このブログ自体がそういうブログですから別に良いのですが。私自身が正真正銘ベクトルを知らなかったところから書いているため、さほど難しい内容にはなっていないはずですが、ブログで数学など取り上げるべきかどうか。
 最後に一応注意事項を。これらのプログラムは一応「それっぽく」動きますが、なにぶん私はベクトルを習得したばかりであり、さらに難解な方程式らしき式を読むのが非常に苦手であるため、計算を間違っていないとは言い切れません。もしこれらを何かのプログラムに利用される際には、そのプログラムに一定以上の精密さが要求されるのであれば、しっかりご自分で調査されることをおすすめします。
カテゴリ [開発魔法][社会問題] [トラックバック 0][コメント 0]
<- 前の記事を参照 次の記事を参照 ->

- Blog by yamicha.com -