てことは…
cos側はiが残らないグループ
cos同士か、sin同士(iの2乗=-1が掛けられる)の掛け合わせ
sin側はiが残るグループ
cosとsinの掛け合わせ
なので
cos(A+B) = cosA・cosB + -1・sinA・sinB
sin(A+B) = cosA・sinB + sinA・cosB
これで、サクッと思い出せるか。
てことは…
cos側はiが残らないグループ
cos同士か、sin同士(iの2乗=-1が掛けられる)の掛け合わせ
sin側はiが残るグループ
cosとsinの掛け合わせ
なので
cos(A+B) = cosA・cosB + -1・sinA・sinB
sin(A+B) = cosA・sinB + sinA・cosB
これで、サクッと思い出せるか。
前回から続けてthreeStart関数の解説。
コメントをはぶいて、後に続く4行がカメラの作成と設定っす。
こいつね↓
newが付いてるのでわかると思うけど、最初の1行がカメラオブジェクトの作成。
THREE.PerspectiveCameraというクラスのオブジェクトです。
camera = new THREE.PerspectiveCamera( 45 , frame.・・・10000 )
作成されたカメラオブジェクトは、camera = としてるので、以後、変数cameraを使ってカメラオブジェクトにメッセージを送ることができます。これもrendererと同じくvar無しなんだけど、このvarの有無については後述。
var frame = ← varが付く
renderer = ← varが付かないの違いは?
THREE.PerspectiveCamera作成時の付加情報としては
縦側の視野角: 45度
高さに対する横幅の比: frame.clientWidth / frame.clientHeight
手前クリップ深度: 1
奥クリップ深度: 10000
てのを送ってます。
こいつはカメラ越しに見える3D領域と、その光景が投影された2D画面を定義するもので、視野角を広く指定すれば、その分、映り込む3D空間が広がります。
カメラ越しに見える3D領域と、その光景が投影された2D画面
こんな感じで、指定する視野角は投影される2D画面の縦側に利用され、横側は高さに対する横幅の比によって決定されます。で、残りの手前クリップ深度、奥クリップ深度で、カメラ越しに切り取る3D領域の前面部と後面部を、カメラの位置からの距離で指定してる。
興味を持った人は、実際に自分で値を変えて表示がどうなるか試してみるといいでしょう。値を変えるとリアルタイムで画面が変わるページがあったんで、そっちで試してみるのもよし。
ここね↓
次は作ったカメラオブジェクトの位置を指定。
camera.position.set(0, 50, 100);
positionはカメラオブジェクトのプロパティで、3次元空間でのカメラ座標を示す。この3D座標を表現するプロパティ自身もオブジェクトなのでsetメッセージで座標を指定してます。付加情報としてx、y、z座標の順に , で区切って渡してる。
でもって、カメラはpositionプロパティの位置から、lookAtメッセージで指定される座標(注視点)に向くことになり、カメラ位置から注視点への線が、視線となるわけです。こっちは連想配列でx、y、zキーで座標を指定することになるみたい。
camera.lookAt( {x:0, y:0, z:0 } ); ←注視点の指定
じゃ、camera.up.set(0, 1, 0)てのは何だよっていうと、カメラが持つupプロパティの指定ってことになります。upプロパティは2Dの投影画面の上方向が3D空間上ではどっち方向かを指定してるっぽいです。
positionプロパティと同じクラスのオブジェクトなんでsetメッセージで向きを指定する。カメラは、視線が決まっても、それだけだと視線を軸に360度ぐるぐる回転できるので、向きを固定するための情報が必要なんですな。
この場合、与えるx、y、zは座標ではなく方向ってことに注意。
これを、例えばcamera.up.set(1, 1, 0)なんかにすると、カメラを斜めに傾けた映像になります。
注意)今回は、y軸に沿って増加方向を指定したんだけど、後で調べたら、この値はカメラ作成時に初期値として設定される値なんで、別にわざわざcamera.up.set(0, 1, 0)を書かなくても問題なかったんだよね。やれやれ。
ちなみに、ここで語っている3D座標は、右手座標系と言われるものです。
右手座標系↓
親指を回転軸の正の方向に向け、残る4本の指が向いている方向が正の回転方向という決まりだそうです。
高校の幾何だと圧倒的に右手座標系がメジャーですが、左手系てのもあって、3Dでの位置情報や回転情報は、どっちかに決めて話をしないと、話してる方と聞いてる方でイメージがグチャグチャになるんで注意しましょう。
左手座標系↓
混ぜると危険。
カメラが終わったら、次はシーンの作成。
シーンはTHREE.Sceneというクラスのオブジェクトです。
シーンを作ってからカメラ作ってもいいです。ここらへんは順序関係なし。
で、このシーンオブジェクトに光源を配置してるのが
という処理。
今回の光源にはTHREE.PointLightというクラスのオブジェクトを使う。
こいつを作成してシーンに追加してます。光源作成時の付加情報としては
光源の色: FFFFFF
光源の強さ: 2
光源の届く範囲: 300
を順に渡してます。色はRGB三原色を16進表記で2文字ずつ並べて6文字の16進数として指定。
RGB三原色てのは、赤(Red)、緑(Green)、青(Blue)の3色で、この3つの色の強弱を組み合わせると人間が感じとれる色の実用的な範囲は表現できることになる。RGB3色全部最強の明るさにした時は真っ白ね。
three.jsだと色の強さは0〜255の範囲で強さを指定でき、これを16進表記で書くと0〜FFの範囲ってことになる。
16進表記てのは、日頃使ってる0〜9の数字に加えて10〜15の数をアルファベットのA〜Fで表記する方法。これだと日頃使ってる数の表記法である10進表記に比べ、0から15までを1文字で表せることになる。
なので、10進表記が9の次で桁上げ(位を変える)するのに対し
16進表記だと15の次で桁上げ(位を変える)することになる。
同じ47という数を書き表してるのに、10進表記と16進表記じゃ全然表記が違うんで注意しましょう。
なぜにこんな書き方するかというのを語り出すと、なぜに色の強さを0〜255で表現するんだ、0〜100の方がキリがいいじゃんというところから話さないとダメになるんで、詳しくは自分で「2進数 16進数 コンピュータ」あたりでググってください。
とにかく今回のFFFFFFは、赤、緑、青、すべてFF=255の最強値なんで、真っ白の指定ってことになります。
あと、THREE.PointLightは点光源と言われる光源で、光源の位置から放射状に光が広がります。電球をイメージすればいいでしょう。
色の他には、光の強さと、その届く範囲を指定してます。
ちなみに太陽光は平行光源に分類されて、それ専用の光源クラスもある。
太陽も点光源ちゃ点光源だけど、地球との距離があまりに離れてるのでほぼ平行になるて話は小学生の理科で習ったはず。これをまじめに位置計算して点光源として指定してもめんどくさいだけでメリットない(というか計算誤差が大きくなりすぎてしまうのか?)ってんで、太陽を表現する時は平行光源オブジェクトを使うみたい。
とにかく光源オブジェクトを作ったら、カメラと同じくpositionプロパティを設定して位置を決めてシーンに登録。
シーンへのオブジェクトの登録は、登録したいオブジェクトを付加情報に指定したaddメッセージを使います。
light.position.set( 100, 60, 80 ) ←位置決め
scene.add(light) ←登録
その次の
arrengeObjects()
はarrengeObjectsという関数の呼び出しで、この関数で、立方体と球体を作成してシーンに追加してます。
別関数にしたのは、今後、この関数を変更していろいろ実験する予定だから。
関数名のarrengeObjectsは適当に決めた。
立方体と球体、どちらもTHREE.Meshというクラスのオブジェクトです。
THREE.Meshてのは複数の3角形平面で構成された多面体で、この3角形平面の配置を定義しているのが
THREE.CubeGeometry:立方体を表現する複数の3角形平面配置情報クラス
THREE.SphereGeometry:球体を 〃
のオブジェクトっす。THREE.CubeGeometryの場合は付加情報として、立方体の大きさをx、y、zの順に指定。
THREE.SphereGeometryの場合は付加情報として
半径: 20
横分割数: 20分割
縦分割数: 20分割
を順に指定。分割数にあんまり小さい値を指定するとガタガタの球になります。
THREE.Meshオブジェクト作成時は、この配置情報のオブジェクトと、多面体に光が当たった時にどう表示されるかの設定を表現した
THREE.MeshLambertMaterial:素材情報クラス
のオブジェクトを付加情報として渡します。
THREE.MeshLambertMaterialは、例えば木の素材、ガラスの素材、それぞれが持つ固有のざらつきや滑らかさ、反射特性を表現するもんです。
細かく調整することで金属っぽくしたり、紙、プラスティックぽくできます。
今回は単純に色だけ指定してあとはお任せする。いろいろな設定があるんで連想配列での指定になってますな。colorキーで色を指定してます。
こうやって作成したTHREE.Meshオブジェクトは、シーンに登録することで、画面に表示されます。光源オブジェクトと同じくaddメッセージ使います。
scene.add(cube); ←立方体オブジェクトの登録
・・・
scene.add(sphere); ←球体オブジェクトの登録
ここでいよいよvarを付けるつけないの違いが出てきます。例えば変数
scene
にはthreeStart関数でシーンオブジェクトを割り当ててるんですが、もしこれを
scene = new THREE.Scene();
じゃなく
var scene = new THREE.Scene();
としていると、画面には何も表示されないことになります。
なぜかと言うと、関数内での初登場時にvarをつけてる変数は、その関数から戻るときに破棄される決まりだからです。
例えば、今回threeStart関数で用意した変数frameは初登場時にvarをつけてるので、別の関数(仮にanother関数とする)で、同じように初登場時にvarをつけた変数frameを用意しても、これはthreeStart関数側のframeとは別物となります。
じゃあ、関数内で初登場時にvarをつけなかった変数はどうなるかというと、関数外で用意されているものとみなされます。
こいつを一般にグローバル変数といいます。関数内限定で用意する方はローカル変数という。
こんな感じ↓
注意)ただし、varを付けることができるのは、関数内初登場時の 変数名 = ・・・ といった処理や、初登場時にvarを付けた変数名だけを記述する処理 var 変数名; の場合のみ。いずれも変数宣言(後述)を意味する。
なので、さっきのようにthreeStart関数内で作った変数sceneの前にvarをつけると、このsceneは他の関数で利用できなくなっちゃうんですな。
注意)この場合、関数外に明示的に「var scene;」と書いてないし、関数内初登場時のvar無しの scene = ・・・ といった記述もなくなるので、sceneなんて変数は存在しないというエラーになる。
この仕組みがないと、関数内だけで一時的に使いたい変数に対しても、いちいち「この変数別の場所で使ってなかったよな、使ってる変数の値、勝手に変えちゃまずいよな」と確認する必要がでてきて、非常にめんどうくさくなる。
非常に役立つ仕組みなんだけど、varを付ける付けないで、こういったトラップにもなるんで気をつけましょう。
あと、グローバル変数は変数tのように、いちいち関数外に並べておく方が、はっきりしてわかりやすいです。これを変数宣言ていいますが、今回はわざと省略してみますた。JavaScriptちゃんはこれでもガッツで動く。
変数宣言
var t;
初期値の設定付き変数宣言
var t = 0;
ということでvarの説明も終わってあとひといき。
addの後は、cube、sphereのpostionプロパティに座標を指定して、sphereをcubeのちょっと上に配置してます。ちなみに、three.jsが用意してくれてるTHREE.CubeGeometryやTHREE.SphereGeometryはpostionで指定された位置が、自身の中央位置を示していると解釈して移動します。
arrengeObjects関数の後に呼び出しているloop関数は、Web画面への表示を支持する処理。Web画面表示中はアニメーションさせるために繰り返し呼び出す必要がある処理群なんで、独立した関数にしてます。名前は何でもいいけど、繰り返しって事がわかりやすいようにloopて名前にしてる。
このうち、THREE.WebGLRendererオブジェクトにシーンとカメラオブジェクトを付加情報としたrenderメッセージを送ってるところが、Webブラウザの画面に3D空間を描画した2D画面が表示させる処理。
renderer.render(scene, camera); ←cameraでsceneを撮って表示せよ!
renderer、scene、cameraはすべてグローバル変数なんで、threeStart関数で設定した各オブジェクトを指し示してます。
renderメッセージを送る前にやっているcubeのrotationプロパティへの設定は回転量の指定。このプロパティもオブジェクトで、setメッセージでx、y、zの順にそれぞれの軸の回転量を渡すことで設定される。
cube.rotation.set(0, 0, t/100)
今回ならz軸に対して、t/100ラジアンの回転を指定してることになる。
ラジアン単位が0〜2π(π:3.14)で0〜360度を表現する角度の単位てのは高校で習います。2Dや3Dの回転では、サイン・コサイン・タンジェントが大活躍するんでラジアン単位を使うのが基本となっとります。
サイン・コサイン・タンジェントについては、いずれ使うんでその時に。
ここで使ってる変数tはWebに画面が表示されている間は、回転量を保持する必要があり、ローカル変数にして関数が呼び出されて戻るたびに毎回作成・破棄されちゃ困るんで、グローバル変数として変数宣言してます。一番最初の値を0とするために = 0 としてる。
var t = 0; ←こいつね
function loop() {
でloop関数内で
t++;
とやってるので、tはloop関数を呼び出すたびに1増えます。++は変数の値を1つ増やす処理。他の書き方として
t = t + 1;
というのもあります。これはまず = の右側の計算が行われて、その結果を = で結ばれた左側の変数に設定するという意味。
省略記法として
t += 1;
てのもあります。
結果、loop関数が呼ばれるたびに、cubeはz軸に対して
1/100、2/100、3/100、・・・、呼び出された回数/100
ラジアンの回転を与えられることになる。
画面の動きから考えてloop関数が繰り返し呼ばれていることになるけど、この仕組みはloop関数の最後に、windowという変数が示すオブジェクトに送っているrequestAnimationFrameメッセージで実現してます。
window.requestAnimationFrame(loop)
変数windowは、これまでの説明でグローバル変数だというのはわかると思うけど、これはJavaScript側で用意された変数で、現在JavaScriptが動作中のWeb画面を持つウィンドウを表現したオブジェクトを示してます。
注意)同じようにthreeStart関数の初っぱなで使ってる、変数documentもJavaScript側で用意された変数です。こっちは前回説明したとおり。
そのウィンドウに対して、付加情報にloopを指定したrequestAnimationFrameメッセージを送ることで、「ウィンドウさんは、次のアニメーションのための画像更新のタイミングが来たらloop関数を呼び出してね」という指定になる。
試しに // を使ってコメントにしたらアニメーションが止まります。
// window.requestAnimationFrame(loop);
だいたい1/60秒たつと呼び出されるみたいなんで
(1/100ラジアン) / (1/60秒)
の速度で回転することになる。なので
t++
を
t = t + 3
とかすると、1/60秒ごとに3/100ラジアンづつ回転することになって「そんな、通常の3倍の速度で回転している」ってことになるよ。
renderer.clear()
は画面の消去。前の画面を消してから新しい画面を描いてる。なんか省略しても大丈夫っぽいけど、一応やってる。
以上で、これからやる物理シミュレーションの舞台についての基礎的な話はおしまい。
次回は、いよいよ物理シミュレーション自体の基礎的なお話。
やっとだよ。
扱うのは、お約束の重力加速度だ!
ま、それは置いといて、three.jsのサンプルにMikuMikuDanceのローダーがあったんで、そのご紹介。
試しに別のmmdファイルを指定してみたら動きましたわ〜。
名前自体もネタバレ指定にされてる○刃696ちゃん。
キャラ名 MMDモデル
でググれば見つかると思われ。
解凍してできたフォルダをまるごと
three.js-master/examples/models/mmd/
に置いて、その中にある拡張子が.pmxのファイルを
three.js-master/examples/webgl_loader_mmd.html
の
var modelFile
に指定すれば踊る。
var modelFile = 'models/mmd/追加フォルダ名/MMDファイル名.pmx';
て感じ。
すごいっす。
Clothシミュレーションや、頂点モーフ、IKなんかも自前でやってるっぽい。
じゃまた。
three.jsのオブジェクトの公式説明自体は以下です。
前回、よ〜やく
画面にcube-600.htmlファイルの内容が表示されると、JavaScriptで書いたthreeStart関数が呼び出されて実行されるよ。
って話まで進んだわけですよ。
というわけで、今回はJavaScriptちゃんのお話。
まず、threeStart関数でやってることは何かっていうと
1)3D空間にカメラを配置し、その空間に展開される光景(シーン)として、光源、立方体、球体を配置する。
2)カメラから見た光景は、こんな感じですよ〜とWebブラウザの自分が担当する画面領域に表示する。
ことです。
このうち青色枠の処理は、表示中に繰り返し実行(秒間数十回)してます。
秒間数十回、画面を書き直すたびに少しづつ立方体の傾き大きくすることで、まるで本当に回転してるように見せかけてるわけです。アニメーションの基礎ですな。
そこらへんを念頭にthreeStart関数の手順を解説〜。
まず、関数は上から順に書かれている処理を実行することになっていて、threeStart関数では以下の4行が
canvas-frameという識別子から特定できるWebブラウザ上の2D平面を、3D空間を投影する領域として登録する。
という作業になってます。
じゃ、threeStart関数の先頭行は何かと言うと、こいつは処理の記述ではなく単なる覚書です。プログラム業界ではコメントと言います。
JavaScriptは // から改行までがコメントとみなされる仕組みで、文中のどこにでも入れることができます。
コメントは動作に何の影響も及ぼさない(読み込み時にWebブラウザがコメントか処理か判断する分くらいは影響する)んですが、後から見直した時に自分が何やろうとしてたか思い出すのに便利なのでよく使います。
コメントは動作には関係しない。
というのを覚えておきましょう。
なので、threeStart関数の場合、先頭から2つめの行からが処理ってことになるわけですよ。
最初にやってるのはcanvas-frameという識別子を持つ画面領域の特定。
記述中に出てくるdocumentという変数が、このJavaScriptを実行中のHTMLファイルの記述を抽象化した「何か」を指し示してます。
変数は、数学で使う変数とほぼ同じです。
違うところは、数学では変数を
x を 1とおくと、y = x + 1のyには何が入るか?
といったように、主に数を示す代用品として扱うのに対し、JavaScriptでは変数を、数だけじゃなく文字列や「何か」を示す代用品として扱います。
今回なら変数documentは
htmlというブロックにheadブロック、bodyブロックを持つ
bodyブロックにdivブロックを1つ持つ
このdivブロックはcanvas-frameという識別子を持ち縦横600ピクセルの領域
といった情報を持つ「何か」を示すわけです。
こういった抽象化された「何か」をJavaScriptではオブジェクトって言います。
で、documentの後ろに . でつながれたgetElementByIdは、オブジェクトに対する「指示」を意味していて
document.getElementById('canvas-frame')
はdocumentが示すオブジェクトに、getElementById('canvas-frame')という「指示」を送っていることになります。
これをJavaScriptではオブジェクトにメッセージを送ると表現します。
で、今回送ってるgetElementById('canvas-frame')というメッセージは
識別子canvas-frameを持つオブジェクトを取り出す
というもので、getElementByIdの後に続く('canvas-frame')が、識別子の指定となってます。JavaScriptではHTMLで指定した識別子を文字列として扱うんで ’ ’ で囲んで
’canvas-frame’
としてます。これが文字列を指定していますよという合図。ちなみに ” ” で囲んでも同じく文字列の扱いになります。
こんな感じで動作に対する付加情報が必要な場合は、メッセージ名の後ろに続く ( ) の間に、その情報を並べることになってます。
ちなみに、付加情報がない場合は () とだけ書きます。
この決まりは関数の呼び出しでも適用されて、前回のthreeStart関数の呼び出しでは特に渡すべき付加情報がないのでthreeStart()となっとるわけですね。
で、関数やメッセージは処理の結果を戻せるようにもなっていて、例えば数学なら
f(x) = x + 1
と定義された関数f(x)に対し
y = f(1)
と書いたなら、変数yは2と考えるのと同じように
frame = document.getElementById('canvas-frame')
というふうに = を使って結ぶことで、getElementById('canvas-frame')の処理結果が変数frameに設定されるようになってます。getElementById('canvas-frame')の処理結果とは
識別子canvas-frameを持つオブジェクト
なので、frameは識別子canvas-frameを持つオブジェクトを指し示すようになるわけです。
変数の名前は自由に決めてかまいません。frameが嫌ならcanvas_frameでもいいし、fとしてもいい。ただしcanvas-frameとは書けません。引き算と紛らわしい(canvas - frame)んで禁止されてます。
もうすでに使用しているdocumentを指定することも避けた方がいいでしょう。これはframeの手前にあるvarとも関係するスコープという仕組みに絡むことなんで後述。
あと、行の最後にある ; は処理と処理の間に入れる区切りです。JavaScriptの場合、改行も処理と処理の区切りと解釈されるんで、今回のような処理ごとに改行する書き方なら ; は無くても問題ないんですが
変わり者さんが処理と処理を連続で書こうとすると必須となります。
改行しろやヴオケエみたいな。
というわけで、今回の記述なら必要ないんですが、C言語のくせで ; 付けちゃってます。付けても問題は無っしんぐ。
で、次の処理はTHREE.WebGLRendererというオブジェクトの新規作成。新規にオブジェクトを作成する場合newキーワードをつけます。
renderer = new THREE.WebGLRenderer({antialias: true});
これでrendererって変数が、その作成したTHREE.WebGLRendererオブジェクトを示すようになります。
さっきのオブジェクトへのメッセージ送信と紛らわしんですが、こっちは
THREE.WebGLRenderer
がオブジェクトの種類を示してて、「THREE.WebGLRenderer」で1つの種類です。JavaScriptではオブジェクトの種類のことをクラスと呼びます。
今回ならTHREE.WebGLRendererというクラスのオブジェクトを作るってことになります。このオブジェクトが3D描画を担当するオブジェクトさんです。
作成時も、関数呼び出しやメッセージ送信と同じで、付加情報を ( ) で囲んで渡せます。今回渡してる
{antialias: true}
は連想配列と呼ばれるもので { } で囲んだ中に、キーと値のペアを書いて作ります。最初にキーを書いて : を挟んで値を書く。
必要なら , で区切って、複数のキーと値のペアを含ませることができるようになってもいます。
当然だけど、受け手側(今回ならTHREE.WebGLRendererを作る側)が知っているキーでないと意味をもちません。
ここではantialiasというキーにtrueを結びつけることで、作成されるTHREE.WebGLRendererオブジェクトに、3D描画時には時間が多少かかってもいいので、モニター上の文字や斜線などに生じるギザギザを目立たなくしてねとお願いしたことになります。こういったギザギザ除去処理をanti-aliasing(アンタイエリアシングとかアンチエリアシング)といいます。
指定している値は真偽値と呼ばれるもので、trueかfalseの2種類です。
true:真
false:偽
antialiasがtrue(真)だとanti-aliasingをするという意味になり、false(偽)だとanti-aliasingをしないという意味になります。違いに興味ある人はtrueのところをfalseに書き換えて読み込ませてみましょう。
その次の行の
renderer.setSize(frame.clientWidth, frame.clientHeight )
はrendererに対するsetSizeメッセージ送信です。
setSizeはTHREE.WebGLRendererオブジェクトに対して、3Dを表示する2D画面の大きさを指定するもんなんで、付加情報として2D画面の大きさが必要になります。
複数の付加情報は上記の連想配列を使う以外に、こんな感じに , で区切って渡す方法もある。付加情報に何をもらうかや、どういうもらい方をするかはTHREE.WebGLRendererが決めることで、こちら側は、その指定に従うだけです。今回なら、画面の横幅、高さを直接数値で
renderer.setSize(600, 600)
と書いてもいい。
ただ、これだとHTMLのCSSでcanvas-frameの大きさを、例えば100x100に変えても、THREE.WebGLRendererオブジェクト側は600x600のままってことになっちゃうんですよ。
それを避けるためにframe.clientWidthとframe.clientHeightと書いて、frameが示すオブジェクトに設定された横と縦の大きさを指定していることになります。
frame.clientWidthやframe.clientHeightは、どちらもframeに対するメッセージ送信に見えるけど、() がついてないのでメッセージ送信ではなく、オブジェクトの属性の値を意味します。
clientWidthが幅でclientHeightが高さの属性で、HTMLのCSSでcanvas-frameに指定したwidthとheightの値600、600が入ってます。
属性は、オブジェクトごとに保持される変数と考えてもらっていいです。JavaScriptでは属性のことをプロパティと呼びます。
なので、setSizeに渡される付加情報は、frameのプロパティclientWidthとclientHeightの値である600、600という数値ということになります。
ちなみにgetElementByIdが戻すオブジェクトのクラスはElementです。ここらへんはJavaScriptを学習しながら、おいおい調べればいいと思われ。
ここで↓
そして次の行でframeにappendChild(renderer.domElement)メッセージを送り、frameの子分として、rendererのdomElementプロパティが示すオブジェクトを加えます。
frame.appendChild(renderer.domElement)
これでframeが表示される時はTHREE.WebGLRendererオブジェクトが内容を描画する取り決めが成立したことになる。
varについては後述と書いたけど、長くなったので以下次回!