クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

拡散反射のシェーディング

このレッスンのあとで: 法線と光の向きから、拡散反射の明るさを計算できる。

90分のレッスンコマンド2件コンピュータビジョンラボ:JupyterLab でピクセル、しきい値、物の数え方

光の向きで、表面の明るさが決まる

シェーディングとは、物の表面の明るさを、光の向きから計算して、立体に見せることです。同じ球でも、光が当たる側は明るく、反対側は暗くなります。

このレッスンでは、法線と光の向きの角度から、拡散反射の明るさを求めます。

このレッスンのゴール

メモ

法線と光の向きから、拡散反射の明るさを計算できる。

キーワード

ことばいみ
法線表面に垂直に立つ矢印
単位ベクトル長さが 1 の矢印
正規化矢印の長さを 1 にそろえること
拡散反射光が表面で、いろいろな向きに散る反射
内積対応する数をかけて、足した値
光の向き表面の点から光源へ向かう向き

学生が入力するセルは、このプレビューでは空白です。

法線は、表面に垂直な向きの矢印

法線は、表面の1点で、表面に垂直に立つ矢印です。球では、中心からその点へ向かう向きが法線になります。

点 p の法線は、(p − 中心) を半径で割った矢印で、長さは 1 です。長さが 1 の矢印を、単位ベクトルと呼びます。

中心 (0, 0, 0)、半径 5 の球の法線

球の上の点 p(p − 中心) ÷ 半径法線
(5, 0, 0)(5, 0, 0) ÷ 5(1, 0, 0)
(0, 3, 4)(0, 3, 4) ÷ 5(0, 0.6, 0.8)
(0, 0, -5)(0, 0, -5) ÷ 5(0, 0, -1)

学生が入力するセルは、このプレビューでは空白です。

長さを 1 にそろえるには、長さで割る

ベクトルの長さは、x、y、z の2乗を足して、平方根をとった値です。(3, 0, 4) の長さは、9 + 16 の平方根で 5 です。

長さを 1 にするには、各数を長さで割ります。(3, 0, 4) は (0.6, 0, 0.8) になります。この操作を、正規化と呼びます。

拡散反射は、法線と光の向きの内積で決まる

拡散反射の明るさは、光が表面にどれだけ正面から当たるかで決まります。法線と光の向きが同じなら最も明るく、直角なら暗くなります。

2つを単位ベクトルにそろえて内積をとると、その値が 0 から 1 の明るさの割合です。負になったら、光が裏から当たっているので 0 にします。

法線と光の角度と、明るさの関係

角度内積明るさ (最大 255)
0 度1.0255
60 度0.5約 128
90 度00
120 度-0.5 を 0 にする0

学生が入力するセルは、このプレビューでは空白です。

内積から明るさを求める

コード: python
normal = [0, 1, 0]
light = [0, 5, 12]
length = (light[0] ** 2 + light[1] ** 2 + light[2] ** 2) ** 0.5
unit_light = [light[0] / length, light[1] / length, light[2] / length]
cosine = normal[0] * unit_light[0] + normal[1] * unit_light[1] + normal[2] * unit_light[2]
if cosine < 0:
    cosine = 0
print(round(cosine, 2), round(255 * cosine))
# light を [0, -5, 12] に変えて、もう一度実行します。
# 明るさは、いくつになりますか。

numpy で内積と明るさを確かめる

次のコマンドは、法線 (0, 0, 1) と光の向き (3, 0, 4) で、内積と明るさを求めます。光の向きを 5 で割って、長さを 1 にそろえてから、内積をとります。

出力の 0.8 は内積です。204 は、255 に 0.8 をかけて四捨五入した明るさです。

内積 0.8 の明るさを求める

コマンド
docker exec jupyter python3 -c "import numpy as np; n = np.array([0, 0, 1]); l = np.array([3, 0, 4]) / 5; print(n @ l, round(float(255 * max(0, n @ l))))"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

球の1点の明るさは、3つの手順で求まる

球の1点の明るさは、3つの手順で求まります。法線を求め、光の向きと一緒に正規化し、内積から明るさを出します。

レイが球に当たった点に、この手順をくり返すと、すべてのピクセルの明るさが決まります。当たらなかったピクセルは、背景の色です。

光を当てた球を、画像にする

次のコマンドは、半径 50 の球に、光の向き (-1, -1, 2) から光を当て、灰色の画像 sphere.png に保存します。画面では y が下向きなので、光は左上の手前から来ます。

sphere.png を開くと、黒い地に球が浮かび、左上がいちばん明るく、右下の縁は暗くなって、背景の黒に溶けます。表示は、中心、左上寄りの (20, 20)、右下寄りの (80, 80)、角の (0, 0) の明るさです。

光を当てた球を sphere.png に保存

コマンド
docker exec jupyter python3 -c "import numpy as np; from PIL import Image; y, x = np.mgrid[-50:50, -50:50]; rest = 2500 - x * x - y * y; z = np.sqrt(np.maximum(rest, 0)); light = np.array([-1, -1, 2]) / np.sqrt(6); level = np.maximum(0, (x * light[0] + y * light[1] + z * light[2]) / 50); picture = np.where(rest > 0, level * 255, 0).astype('uint8'); Image.fromarray(picture).save('sphere.png'); print(picture[50, 50], picture[20, 20], picture[80, 80], picture[0, 0])"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

よくある間違い

注意

光に近いほど明るくなると考えがちです。拡散反射は、距離ではなく、法線と光の向きの角度で決まります。向きを正規化せずに内積をとると、255 を超えるなど、正しくない明るさになります。

このラボとソースについて: コンピュータビジョンラボ:JupyterLab でピクセル、しきい値、物の数え方

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。