本稿は、「Getting started with WebGL in p5」ページに書かれている内容を基本に再構成したもので、内容を一部改変しています。WebGL(ウェブジーエル)は、ウェブブラウザで3次元コンピュータグラフィックスを表示させるための標準仕様を言います(ウィキペディア「WebGL」)
以降のチュートリアルは、Processing 2.0のp3D入門記事を参考にしています。
目次
p5.jsを使ったWebGL入門
p5.jsには、デフォルトのレンダラーであるp2Dと、WEBGLの2つのレンダリングモードがあります。両方ともhtmlのキャンバス要素を利用しますが、キャンバスのWEBGL”コンテキスト”を有効にすることで、2Dと3D両方が描画できるようになります。WEBGLを有効にするには、createCanvas()関数の第3パラメータとして次のように指定するだけです。
function setup() {
createCanvas(200, 200, WEBGL);
}
3D座標システム
ある程度p5.jsでコードを記述して来た方なら、おそらく直交座標の0, 0 (x,y)が描画キャンバスの左上隅にあることをご存知でしょう。WEBGLモードにはこれに3つめの次元Zが加わります。ではこのz座標はどのように処理するのでしょう? z座標は、画面からみなさんに向かう方向を指す軸です。p5.js(WEBGL)の軸がどの方向を指すのか思い出すときに役立つ記憶術に、”左手の”法則があります。左手の人差し指を右に向け、中指を下に向けると、親指が自動的に自分の方を指します。この3つの指の方向が3つの軸を指しています。点0,0,0 (x, y ,z)はキャンバスの真ん中に位置します。
簡単な例を見てみましょう。
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
}
function draw() {
background(255);
box();
}
これにより、ボックスが、ブラウザのウィンドウ一杯に広がったキャンバスのセンターに描画されることになります。
リファレンスメモ
createCanvas()
キャンバス要素をドキュメントに作成し、そのサイズをピクセル単位で設定する。この関数はセットアップの開始時に一度だけ呼び出されるべき。複数回の呼び出しは予期できない振る舞いをもたらす結果に陥る。描画キャンバスが複数欲しい場合には、createGraphics()が使用できる(デフォルトでは隠されるが表示できる)。
システム変数のwidthとheightは、この関数に渡されるパラメータで設定される。 createCanvas()を使用しない場合には、100 x 100ピクセルのデフォルトサイズでウィンドウが与えられる。
シンタックス
createCanvas(w, h, [renderer])
パラメータ
w 数値: キャンバスの幅
h 数値: キャンバスの高さ
renderer 定数: P2DかWEBGL
戻り
p5.Renderer:
box()
与えられた幅と高さ、深度でボックスを描画する。
シンタックス
box([width], [Height], [depth], [detailX], [detailY])
移動、回転
オブジェクトをデフォルトでセンターに配置するのは、3D空間について考える開始点として理にかなっていると思われ、少数の幾何プリミティブを描画したい場合には手早い方法ですが、原点を2Dモードと同じ左上隅に戻したい場合には、負の幅と高さの移動を適用します。
function draw() {
background(255);
// 描画の原点を左上隅に移動する
translate(-width / 2, -height / 2, 0);
box();
}
リファレンスメモ
translate()は「5_1:移動(translate) p5.js JavaScript」を参照。
translate(x,y,z)の呼び出しは、専門的な言い方で言うと、変換をモデル行列(Model Matrix)に適用しますが、行っているのは、描画の原点座標の移動です。次のコードを書いたとします。
box();
translate(100, 100, -100);
box();
このコードは、ボックスを描画し、その後モデル行列を右に100単位、下に100単位、ビューワー(見ている者)から離れる位置に100単位移動して、その移動した新しい位置を原点として別のボックスを描画します。ここには重要なポイントが2つあります。1つは、translate()は、それ以降に呼び出される描画関数に適用されるということです。もう1つは、”ピクセル”でなく”単位”という一般名称を使っていることです。というのも、実際の移動距離は仮想カメラの視野の影響を大きく受けるからです。実際に認識される移動距離は、モデル行列とビュー行列(View Matrix、つまりカメラ)によって作成されます。
次のセクションでは仮想カメラを詳しく見ていきますが、その前に回転も見ておきましょう。rotate()は3Dにおけるモデル行列変換のまた別のタイプで、WEBGLモードでは、4つの回転関数があります。
rotate(angle, [x, y, z]);
rotateX(angle);
rotateY(angle);
rotateZ(angle);
どんな回転にどの関数を使うかは、”____軸周りの回転”と覚えておくと役立ちます。たとえばX軸周りにバレルロール(横に回転)したい場合には、次のコードを記述します。
rotateX(radians(45));
回転関数はすべて、数値のパラメータをラジアン単位で取ります。
リファレンスメモ
rotate()は「5_2:回転(rotate) p5.js JavaScript」を参照。
次のコードは、3Dボックスの移動と回転、3Dカメラの位置設定の例です。
function setup() {
createCanvas(500, 500, WEBGL);
angleMode(DEGREES);
const cam = createCamera();
// カメラを上に100、手前に500の位置に設定 --(4)
cam.setPosition(0, -100, 500);
// ノーマルマテリアルを適用
normalMaterial();
// 3Dボックスを作成
box(50);
// 座標システムの原点を右に100、手前に100移動 --(1)
translate(100, 0, 200);
// 座標システムをX軸周りに45度回転 --(2)
rotateX(45);
// 座標システムをY軸周りに-10度回転 --(3)
rotateY(10);
// 3Dボックスを作成
box(50);
}
下図は左から、上記コードの(1)を、上記コードの(1)と(2)を、上記コードの(1)と(2)と(3)を、上記コードの(1)と(2)と(3)と(4)を実行した結果です。
(1)では、座標システムの原点を右に100、手前に100移動しているので、右の3Dボックスの側面が見えています。(2)では、(1)の後、座標システムをX軸周りに45度回転しているので、右の3Dボックスの底面が見えています。(3)ではさらに座標システムをY軸周りに-10度回転しています。(4)では、カメラを上に100、手前に500の位置に設定しているので、かなり離れた位置から見下ろすことになります。
リファレンスメモ
createCamera()
新しいp5.Cameraオブジェクトを作成し、レンダラーにこのカメラを使うよう伝える。p5.Cameraオブジェクトを返す。
p5.Camera.setPosition()
現在のカメラの向きを維持しつつ、ワールド空間のカメラ位置を設定する
カメラとビュー
現在WEBGLモードには、3つのカメラ関数があります。
camera(x, y, z)
perspective(fovy, aspect, near, far)
ortho(left, right, bottom, top, near, far)
WEBGLモードでのデフォルトのカメラビューは、60度の視野(field of view)を持つ透視投影です。これは、createCanvas()でWEBGLモードを初期化するときに作成されます。デフォルトのカメラオプションの上書きには、perspective()かortho()を呼び出すだけです。透視投影では、z平面にある見る人に近い物が、それより遠くにある物より大きく見えます。平行投影(ortho())では、同じサイズの物はたとえz平面で遠くにある物でも同じ大きさに見えます。
下図は3D描画アプリのBlenderの画面で、透視投影と平行投影を比較したものです。
リファレンスメモ
camera()
3Dスケッチ用のカメラ位置を設定する。この関数のパラメータは、カメラの位置とスケッチのセンター(カメラがどこを向くか)、上方向(カメラの向き)を定義する。
引数なしで呼び出されると、この関数は、camera(0, 0, (height/2.0) / tan(PI*30.0 / 180.0), 0, 0, 0, 0, 1, 0)と同じデフォルトカメラを作成する。
シンタックス
camera([x], [y], [z], [centerX], [centerY], [centerZ], [upX], [upY], [upZ])
perspective()
3Dスケッチでのカメラの透視投影(perspective projection)を設定する。この投影は、奥行きを縮めることで遠近を表す。つまり、カメラに近い物は実際のサイズで見え、カメラからより遠い物はより小さく見える。この関数のパラメータは、垂直方向の視野を通る視錐台(ピラミッドの上部を切り取ったような形をした領域を言い、カメラはその中にあるオブジェクトを見ることができる)とアスペクト比(通常は幅/高さ)、ニアとファーのクリッピング平面を定義する。
引数なしで呼び出されると、perspective(PI/3.0, width/height, eyeZ/10.0, eyeZ10.0)と同じ命令がデフォルトで与えられる。eyeZは((height/2.0) / tan(PI60.0/360.0))と同じ。
シンタックス
perspective([fovy], [aspect], [near], [far])
パラメータ
fovy Number: カメラの視錐台の垂直方向の視野角度(下から上へ)、angleMode単位。
aspect Number: カメラの視錐台のアスペクト比
near Number: 視錐台のニア平面までの距離
far Number: 視錐台のファー平面までの距離
ortho()
3Dスケッチでカメラの平行投影を設定し、オブジェクトを表示するボックス型の視錐台を定義する。この投影では、同じサイズのオブジェクトは、カメラからの遠近に関係なく、どれも同じサイズに見える。この関数のパラメータは、leftが最小のx値、rightが最大のx値、topが最小のy値、bottomが最大のy値、nearが最小のz値、farが最大のy値である視錐台を指定する。パラメータを与えない場合には、デフォルトでortho(-width/2, width/2, -height/2, height/2)が使用される。
シンタックス
ortho([left], [right], [bottom], [top], [near], [far])
パラメータ
left 数値: カメラの視錐台の左平面
right 数値: カメラの視錐台の右平面
bottom 数値: カメラの視錐台の下平面
top 数値: カメラの視錐台の上平面
near 数値: カメラの視錐台のニア平面
far 数値: カメラの視錐台のファー平面
次のコードはperspective()とortho()関数の簡単な使用例です。orbitControl()関数を使用すると、キャンバス上をマウスでぐりぐりできます。
function setup() {
createCanvas(500, 500, WEBGL);
// ノーマルマテリアルを適用
normalMaterial();
// キャンバス左上隅にラジオボタンを作成
const radio = createRadio();
radio.option('perspective');
radio.option('ortho');
radio.position(10, 10);
// ラジオボタンの選択変更で透視投影と平行投影を切り替える
radio.changed(() => {
const val = radio.value();
if (val === 'perspective') {
perspective(PI / 3.0, width / height, 0.1, 1000);
}
else if (val === 'ortho') {
ortho(-width, width, height, -height / 2, 0.1, 1000);
}
});
radio.value('perspective');
}
function draw() {
background(200);
// キャンバス上をマウスでぐりぐりできる
orbitControl();
box(100);
translate(100, 100, -100);
rotate(PI / 4, [1, 1, 0]);
box(100);
}
リファレンスメモ
orbitControl()
マウスやトラックパッドを使って3Dスケッチが回転できる。左クリックしてドラッグすると、スケッチセンターを中心にカメラが回転し、右栗生してドラッグすると、回転せずにカメラ位置がパンできる。マウスホイールをスクロールすると、カメラをスケッチの中心に近づけたり遠ざけたりできる。この関数は、X軸とY軸に沿ったマウス移動の感度を示すパラメータで呼び出すこともできる。パラメータを指定しない呼び出しはorbitControl(1,1)と同じ。軸の移動方向を反転するには、感度に負数を入力する。
シンタックス
orbitControl([sensitivityX], [sensitivityY], [sensitivityZ])
パラメータ
sensitivityX 数値: X軸に沿ったマウス移動に対する感度(オプション)
sensitivityY 数値: Y軸に沿ったマウス移動に対する感度(オプション)
sensitivityZ 数値: Z軸に沿ったマウス移動に対する感度(オプション)
3Dプリミティブシェイプ
p5.jsには、7つの異なる3Dジオメトリプリミティブがあります。
box()
plane()
sphere()
ellipsoid()
cone()
cylinder()
torus()
各プリミティブはサイズのパラメータだけを取り、位置のパラメータは取りません。
box(10,20,30); // 幅が10、高さが10、奥行きが30のボックスを描画
cone(40, 100, 100);// 半径が40、高さが100、細かさ(セグメント)が100のコーンを描画
“細かさって何?”と思われるでしょう。webglモードでは、曲線や線をどれだけ滑らかに描画するかが指定できます。大きな値ほど曲線が滑らかになりますが、グラフィックレンダラーの負荷が増えます。一般的には、プリミティブの描画にはデフォルトの細かさで十分です。
cone(40,100);// 半径が40、高さが100、細かさがデフォルトの24のコーンを描画
プリミティブの描画の、3Dと2Dに置ける重要な違いの1つは、3Dプリミティブはサイズのパラメータは取るが、位置のパラメータは取らないということです。3Dプリミティブの位置を変えるには、上のセクションで述べているように、translate(x,y,z)を呼び出します。
テクスチャ
本稿執筆時点で、p5.jsがWEBGLモードのテクスチャとしてサポートするのは、ビデオとイメージ、オフスクリーン2Dレンダラです。テクスチャは、3Dジオメトリを包む”皮膚”のようなもので、たとえば、静止画像をボックスに”テクスチャ”したい場合には、コードは次のようになります。
let img;
function preload() {
img = loadImage('path/to/img.jpg');
}
function setup() {
createCanvas(300, 300, WEBGL);
}
function draw() {
background(200);
orbitControl();
texture(img);
box(45);
}
上記コードの実行例。背景が透明のPNG画像を使っているので、背景が透けます。orbitControl()によって画面がグリグリできるので、3Dであることが分かります。
テクスチャに使用するイメージをpreload()内でロードするのは一般的なベストプラクティスですが、ビデオを扱うときには、ビデオファイルは通常、静的イメージより大きく、ロードにより時間がかかるので、preload()内でのロードは特に役立ちます。
beginShape()を使って作成するグラフィックにテクスチャを貼り付けるには、UV座標を渡す必要があります。これらの座標は適用されるテクスチャにマッピングされます。textureMode(NORMAL)を用いると、これらの値を0と1の間に正規化するよう、p5.jsに伝えることができます。詳細はtextureMode()を参照してください。
リファレンスメモ
texture()
ジオメトリにテクスチャを貼り付ける。
シンタックス
texture(tex)
パラメータ
tex p5.Image|p5.MediaElement|p5.Graphics: テクスチャとしてレンダリングする2次元グラフィック
textureMode()
テクスチャマッピングの座標空間を設定する。デフォルトモードは、イメージの実際の座標を参照するIMAGEモード。NORMALモードは、0から1の範囲に正規化された値の空間を参照する。この関数はWEBGLモードでのみ動作する。
100 x 200ピクセルのイメージ場合、IMAGEモードでは、イメージを矩形全体のサイズにマッピングするには、(0,0)と(100, 0)、(100,200)、(0,200)が必要になる。NORMALモードでは、(0,0)、(1,0)、(1,1)、(0,1)になる。
シンタックス
textureMode(mode)
パラメータ
mode 定数: IMAGEかNORMAL
テキスト
webglモードでテキストを扱うには、2つの選択肢があります。2Dのp5.js APIを使ってオフスクリーンイメージにレンダリングする方法と、ネイティブのwebgl text()関数を使う方法です。両方に以下に述べるメリットとデメリットがあります。
オフスクリーンレンダラ
テキストをオフスクリーンレンダラに描画してから、それをテクスチャとして使用します。
メリット:
- ブラウザのビルトインフォント(たとえば’mono’や’sans’など)も含む、2Dのp5.js APIで使用できる同じフォントが使用できる
- stroke()を使って、グリフの周囲に輪郭が描画できる
- 特に、テキストをsetup()内で1回だけレンダリングし、それをdraw()内で再利用できる場合には、パフォーマンスが(webgl text()関数の方法よりも)良くなる可能性がある
デメリット
- ズームしたときのピクセレーションや、テキストイメージを傾けたときのアンチエイリアスによる不正確さのため、テキストの忠実度はtext()ほど高くない可能性がある
次のコードは、テキストの描画にオフスクリーンレンダラを使う例です。
let pg;
let myFont;
function preload() {
myFont = loadFont('assets/AvenirNextLTPro-Demi.otf');
}
function setup() {
createCanvas(300, 300, WEBGL);
pg = createGraphics(256, 256);
pg.fill('#ED225D');
pg.textFont(myFont);
pg.textSize(60);
}
function draw() {
background(200);
orbitControl();
pg.background(200);
pg.text('p5*js', 10, 80);
// グラフィックをテクスチャとして渡す
texture(pg);
plane(150);
}
orbitControl()を使っているので、画面がグリグリできます。
webgl text()関数
text()関数のwebgl版は2D版とよく似ていますが、いくつか違いがあります。
デメリット
- 使用できるのは、preload()内でloadFont()を使ってロードしたOpenTypeかTrueTypeフォントのみ。フォントファイルはスケッチからアクセスできる場所に配置するか、CORS互換のWeb URLを使う必要がある
- 現時点でstroke()はサポートされていない
メリット
- レンダリングされたテキストの忠実度は、特にズームや傾けた場合でも良好のはず
- テキストが定期的に変化し、前述の方法で用いたオフスクリーンイメージがキャッシュできないときは特に、パフォーマンスは良くなる
次のコードは、OpenTypeフォントをロードし、webgl text()を使ってテキストを描画する例です。
let myFont;
function preload() {
myFont = loadFont('assets/AvenirNextLTPro-Demi.otf');
}
function setup() {
createCanvas(300, 300, WEBGL);
fill('#ED225D');
textFont(myFont);
textSize(36);
}
function draw() {
background(200);
orbitControl();
text('p5*js', 10, 50);
}
orbitControl()を使っているので、画面がグリグリできます。
ライトとマテリアル
ライト
ライトは、p5.jsスケッチに奥行きと現実性を与える、単純でしかし強力な方法です。本稿執筆時点では、次の3つのタイプのライト関数があります。
ambientLight()
directionalLight()
pointLight()
以下では次のコードをベースに使っています。
function setup() {
createCanvas(300, 300, WEBGL);
// 3Dジオメトリの線をなくす
//noStroke();
}
function draw() {
background(200);
rotateZ(frameCount * 0.01);
rotateX(frameCount * 0.01);
rotateY(frameCount * 0.01);
sphere(60);
}
ambientLight()は3つの中で最も単純な関数で、それ以降に描画されるオブジェクトに、均一の(全方向の)環境光を提供します。パラメータには、p5.Colorやr,g,bの数値を取ります。
// 環境光の色をグレーに
ambientLight(50);
sphere(60);
directionalLight()の光線は指定された方向に照らしますが、特定の光源を持たないので、ジオメトリに近づけたり遠ざけたりする位置取りはできません。directionalLight()のベクトルは、光がジオメトリに当たる角度と見なすこともできます。したがって、負のy値はジオメトリを下から照らすことになります。
// 太陽光が下から当たる
directionalLight(255, 255, 0, 0, -1, 0);
// 環境光の色をグレーに
ambientLight(50);
sphere(60);
pointLight()はパラメータとしてカラーと位置を取ります。directionalLight()との大きな違いは、pointLight()の光は特定の光源から照らすことにあり、そのため遠いオブジェクトと近いオブジェクトで反射が異なります。
// 太陽光が下から当たる
directionalLight(255, 255, 0, 0, -1, 0);
// 赤い点光源が左から当たる
pointLight(255, 0, 0, -200, 0, 0);
// 環境光の色をグレーに
ambientLight(50);
sphere(60);
リファレンスメモ
環境光をカラーで作成する。環境光は、キャンバス上のどこからでも来る光で、特定の光源を持たない。
シンタックス
ambientLight(v1, v2, v3, [alpha])
ambientLight(value)
ambientLight(gray, [alpha])
ambientLight(values)
ambientLight(color)
パラメータ
v1 数値: 現在のカラー範囲にもとづく赤か色相の値
v2 数値: 現在のカラー範囲にもとづく緑か彩度の値
v3 数値: 現在のカラー範囲にもとづく青か明度の値
alpha 数値: アルファ値(オプション)
value 文字列: カラー文字列
gray 数値: グレー値
values 数値[]: カラーの赤緑青とアルファ成分を含んだ配列
color p5.Color: 環境光のカラー
directionalLight()
カラーと方向を持つ指向性ライトを作成する。1度に最大5個の指向性ライトを有効にできる。
シンタックス
directionalLight(v1, v2, v3, position)
directionalLight(color, x, y, z)
directionalLight(color, position)
directionalLight(v1, v2, v3, x, y, z)
パラメータ
v1 数値: 赤か色相の値(その時点でのカラーモードによる)
v2 数値: 緑か彩度の値
v3 数値: 青か明度の値
position p5.Vector: ライトの方向
color 数値[]|文字列|p5.Color: カラー配列、CSSカラー文字列、p5.Color値
x Number: X軸方向
y Number: Y軸方向
z Number: Z軸方向
pointLight()
カラーと光の位置を持つ点光源ライトを作成する。1度に最大5個の点光源ライトを有効にできる。
シンタックス
pointLight(v1, v2, v3, x, y, z)
pointLight(v1, v2, v3, position)
pointLight(color, x, y, z)
pointLight(color, position)
パラメータ
v1 数値: 赤か色相の値(その時点でのカラーモードによる)
v2 数値: 緑か彩度の値
v3 数値: 青か明度の値
x Number: X軸方向
y Number: Y軸方向
z Number: Z軸方向
position p5.Vector: ライトの位置
color 数値[]|文字列|p5.Color: カラー配列、CSSカラー文字列、p5.Color値
マテリアル
実際の世界の光の反射は、オブジェクトに対する反射角度やオブジェクトの材質(マテリアル)によってさまざまに異なります。本稿執筆時点でp5.jsには次の4つのタイプのマテリアルがあります。
normalMaterial()
ambientMaterial()
specularMaterial()
normalMaterial()はパラメータを取らず、ジオメトリの法線ベクトルをRGBカラーに自動的にマッピングします。ジオメトリの法線については、ウィキペディアの「Normal (geometry)」項が参考になります。
(以前は、以降のジオメトリをカラーで塗り、ライト関数の影響を受けないbasicMaterial()関数もありましたが、fill()関数と同じなので削除されました。fill()は”基本マテリアル”機能としてWEBGLで使用できます)
ambientMaterial()はfill()と似ていますが、全体の色が、それより前にあるライト関数の影響を受けます。
// 太陽光が手前上から当たる
directionalLight(255, 255, 255, 0, 1, -1);
// 黄色のマテリアル
ambientMaterial(255, 255, 0);
specularMaterial()は最もリアリスティックなマテリアルです。スペキュラマテリアルは、光を1方向に反射するマテリアルを表す方法で、ガラスや水滴、ビリヤードボールなどのように見える効果があります。
ambientLight(100);
pointLight(255, 255, 255, -40, -40, 40);
specularMaterial(255, 0, 0);
リファレンスメモ
normalMaterial()
ライトの影響を受けないマテリアルで反射しない。多くの場合デバッグ用のプレースホルダ(暫定的な)マテリアルに用いられる。X軸に向いているサーフェイスは赤に、Y軸に向いているサーフェイスは緑に、Z軸に向いているサーフェイスは青になる。使用できるすべてのマテリアルはこのサンプルで見ることができる。
ambientMaterial()
指定されたカラーを持つ環境光マテリアル。オプジェクトがすべてのライト下で反射するカラーを定義する。たとえば、環境光マテリアルが純粋な赤で、環境光が緑の場合、オブジェクトはどのライトも反射しない。使用できるすべてのマテリアルはこのサンプルで見ることができる。
シンタックス
ambientMaterial(v1, [v2], [v3])
ambientMaterial(color)
パラメータ
v1 数値: 赤か色相の値(その時点でのカラーモードによる)
v2 数値: 緑か彩度の値(オプション)
v3 数値: 青か明度の値(オプション)
color 数値[]|文字列|p5.Color: カラー配列、CSSカラー文字列、p5.Color値
specularMaterial()
指定されたカラーを持つ鏡面光マテリアルで、反射する。環境光マテリアルと同様、環境光下でオブジェクトが反射するカラーを定義する。たとえば、オブジェクトの鏡面光マテリアルが純粋な赤で、環境光が緑の場合、オブジェクトはどのライトも反射しない。ほかの点光源ライトや指向性のライトの場合、鏡面光マテリアルは光源のカラーを反射する。使用できるすべてのマテリアルはこのサンプルで見ることができる。
シンタックス
specularMaterial(v1, [v2], [v3])
specularMaterial(color)
パラメータ
v1 数値: 赤か色相の値(その時点でのカラーモードによる)
v2 数値: 緑か彩度の値(オプション)
v3 数値: 青か明度の値(オプション)
color 数値[]|文字列|p5.Color: カラー配列、CSSカラー文字列、p5.Color値
おまけ:Blenderモデルのロードと描画
Blenderで作成した3Dモデルを.obj形式で書き出し、loadModel()関数でモデルをスケッチに読み込んで、model()関数でモデルを呼び出すと、3Dモデルが描画できます。
let monkey;
function preload() {
monkey = loadModel('assets/monkey.obj');
}
function setup() {
createCanvas(300, 300, WEBGL);
angleMode(DEGREES);
}
function draw() {
background(200);
directionalLight(255, 255, 0, 0, -1, -1);
ambientLight(50);
scale(80);
rotateZ(180)
rotateY(frameCount * 0.5);
model(monkey);
}