光の反射と屈折は、幾何光学の最も基本的な現象です。光が異なる媒質の境界に到達したとき、一部は反射し、一部は屈折して透過します。これらの法則はフェルマーの原理という単一の原理から導出でき、光ファイバーからカメラレンズまで幅広い応用の基盤となります。
本記事の内容
- フェルマーの原理
- 反射の法則の導出
- スネルの法則(屈折の法則)の導出
- 全反射と臨界角
- 分散とプリズム
- Pythonでの可視化
前提知識
この記事を読む前に、以下の知識があると理解が深まります。
- 微分の基礎(極値条件)
- 三角関数の基本的な性質
フェルマーの原理
フェルマーの原理(Fermat’s principle)は次のように述べられます。
光は、ある点から別の点へ進むとき、光路長(optical path length)が停留値をとる経路を通る。
光路長 $L$ は、屈折率 $n$ と幾何学的経路長 $ds$ の積分で定義されます。
$$ L = \int n \, ds $$
均一媒質中では光速は $v = c / n$ ですから、光路長は光が進むのに要する時間に比例します。つまり、フェルマーの原理は「光は最短時間の経路を通る」と言い換えることもできます(より正確には停留値)。
反射の法則の導出
平面鏡での反射を考えます。点 A から出た光が鏡面上の点 P で反射して点 B に到達するとします。
A の座標を $(0, h_1)$、B の座標を $(d, h_2)$、P の座標を $(x, 0)$ とします。媒質の屈折率は一様に $n$ とします。
光路長は:
$$ L(x) = n\left(\sqrt{x^2 + h_1^2} + \sqrt{(d-x)^2 + h_2^2}\right) $$
フェルマーの原理より、$L(x)$ が停留値をとる条件は:
$$ \frac{dL}{dx} = 0 $$
微分を計算します。
$$ \begin{align} \frac{dL}{dx} &= n\left(\frac{x}{\sqrt{x^2 + h_1^2}} + \frac{-(d-x)}{\sqrt{(d-x)^2 + h_2^2}}\right) \\ &= n(\sin\theta_i – \sin\theta_r) = 0 \end{align} $$
ここで $\theta_i$ は入射角、$\theta_r$ は反射角です。したがって:
$$ \boxed{\theta_i = \theta_r} $$
これが反射の法則です。入射角と反射角は等しくなります。
スネルの法則(屈折の法則)の導出
屈折率 $n_1$ の媒質1と屈折率 $n_2$ の媒質2の境界面を考えます。点 A $(0, h_1)$ が媒質1に、点 B $(d, -h_2)$ が媒質2にあり、境界面上の点 P $(x, 0)$ を光が通過するとします。
光路長は:
$$ L(x) = n_1 \sqrt{x^2 + h_1^2} + n_2 \sqrt{(d-x)^2 + h_2^2} $$
フェルマーの原理より:
$$ \frac{dL}{dx} = \frac{n_1 x}{\sqrt{x^2 + h_1^2}} – \frac{n_2 (d-x)}{\sqrt{(d-x)^2 + h_2^2}} = 0 $$
入射角 $\theta_1$、屈折角 $\theta_2$ を用いると:
$$ \frac{n_1 x}{\sqrt{x^2 + h_1^2}} = n_1 \sin\theta_1, \quad \frac{n_2 (d-x)}{\sqrt{(d-x)^2 + h_2^2}} = n_2 \sin\theta_2 $$
よって:
$$ \boxed{n_1 \sin\theta_1 = n_2 \sin\theta_2} $$
これがスネルの法則(Snell’s law)です。
全反射と臨界角
光が光学的に密な媒質($n_1 > n_2$)から疎な媒質へ進む場合、スネルの法則より:
$$ \sin\theta_2 = \frac{n_1}{n_2}\sin\theta_1 $$
$n_1 > n_2$ のとき $n_1/n_2 > 1$ なので、$\theta_1$ がある角度を超えると $\sin\theta_2 > 1$ となり、屈折光が存在しなくなります。この臨界的な入射角を臨界角 $\theta_c$ といいます。
$$ \sin\theta_c = \frac{n_2}{n_1} $$
$$ \boxed{\theta_c = \arcsin\left(\frac{n_2}{n_1}\right)} $$
$\theta_1 > \theta_c$ のとき、光は全て反射されます。これが全反射(total internal reflection)です。光ファイバーはこの原理を利用して光を閉じ込め、長距離を伝搬させます。
具体例
ガラス($n_1 = 1.50$)から空気($n_2 = 1.00$)への場合:
$$ \theta_c = \arcsin\left(\frac{1.00}{1.50}\right) = \arcsin(0.667) \approx 41.8° $$
分散(プリズム)
屈折率 $n$ は一般に波長 $\lambda$ に依存します。これを分散(dispersion)といいます。コーシーの分散公式は:
$$ n(\lambda) = A + \frac{B}{\lambda^2} + \frac{C}{\lambda^4} $$
短い波長(青色光)ほど屈折率が大きいため、プリズムを通過した白色光は虹色に分離されます。
Pythonでの実装
スネルの法則の可視化
import numpy as np
import matplotlib.pyplot as plt
# --- スネルの法則: 屈折の可視化 ---
n1 = 1.0 # 空気
n2 = 1.5 # ガラス
theta1_deg = np.linspace(0, 89, 200)
theta1 = np.radians(theta1_deg)
sin_theta2 = (n1 / n2) * np.sin(theta1)
theta2 = np.arcsin(sin_theta2)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 左: 入射角 vs 屈折角
axes[0].plot(theta1_deg, np.degrees(theta2), 'b-', linewidth=2)
axes[0].plot([0, 89], [0, 89], 'k--', alpha=0.3, label=r'$\theta_1 = \theta_2$')
axes[0].set_xlabel(r'Incident angle $\theta_1$ [deg]', fontsize=12)
axes[0].set_ylabel(r'Refracted angle $\theta_2$ [deg]', fontsize=12)
axes[0].set_title(f'Snell\'s law: $n_1={n1}$ to $n_2={n2}$', fontsize=13)
axes[0].legend(fontsize=11)
axes[0].grid(True, alpha=0.3)
# 右: 屈折の光線図
theta_i = np.radians(45)
theta_r = np.arcsin((n1 / n2) * np.sin(theta_i))
# 境界面
axes[1].axhline(y=0, color='gray', linewidth=2)
axes[1].axvline(x=0, color='gray', linewidth=1, linestyle='--', alpha=0.5)
axes[1].fill_between([-3, 3], [-3, -3], [0, 0], alpha=0.1, color='blue', label=f'Medium 2 (n={n2})')
axes[1].fill_between([-3, 3], [0, 0], [3, 3], alpha=0.1, color='yellow', label=f'Medium 1 (n={n1})')
# 入射光
length = 2.5
x_in = -length * np.sin(theta_i)
y_in = length * np.cos(theta_i)
axes[1].annotate('', xy=(0, 0), xytext=(x_in, y_in),
arrowprops=dict(arrowstyle='->', color='red', lw=2))
# 屈折光
x_out = length * np.sin(theta_r)
y_out = -length * np.cos(theta_r)
axes[1].annotate('', xy=(x_out, y_out), xytext=(0, 0),
arrowprops=dict(arrowstyle='->', color='blue', lw=2))
# 反射光
x_ref = -length * np.sin(theta_i)
y_ref_end = length * np.cos(theta_i)
axes[1].annotate('', xy=(-x_in, y_in), xytext=(0, 0),
arrowprops=dict(arrowstyle='->', color='green', lw=2, ls='--'))
axes[1].set_xlim(-3, 3)
axes[1].set_ylim(-3, 3)
axes[1].set_aspect('equal')
axes[1].set_title('Ray diagram (θ_i = 45°)', fontsize=13)
axes[1].legend(fontsize=10, loc='lower right')
plt.tight_layout()
plt.show()
全反射の臨界角
import numpy as np
import matplotlib.pyplot as plt
# --- 全反射と臨界角 ---
n1 = 1.5 # ガラス
n2 = 1.0 # 空気
theta_c = np.degrees(np.arcsin(n2 / n1))
print(f"臨界角: {theta_c:.1f}°")
theta1_deg = np.linspace(0, 89, 500)
theta1 = np.radians(theta1_deg)
sin_theta2 = (n1 / n2) * np.sin(theta1)
# 全反射領域と屈折領域を分離
mask_refract = sin_theta2 <= 1.0
mask_total = sin_theta2 > 1.0
fig, ax = plt.subplots(figsize=(8, 5))
# 屈折領域
theta2_refract = np.degrees(np.arcsin(sin_theta2[mask_refract]))
ax.plot(theta1_deg[mask_refract], theta2_refract, 'b-', linewidth=2, label='Refracted angle')
# 全反射領域
ax.axvspan(theta_c, 90, alpha=0.2, color='red', label='Total internal reflection')
ax.axvline(x=theta_c, color='red', linestyle='--', linewidth=1.5,
label=f'Critical angle = {theta_c:.1f}°')
ax.set_xlabel(r'Incident angle $\theta_1$ [deg]', fontsize=12)
ax.set_ylabel(r'Refracted angle $\theta_2$ [deg]', fontsize=12)
ax.set_title(f'Total internal reflection ($n_1={n1}$ → $n_2={n2}$)', fontsize=13)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)
ax.set_xlim(0, 90)
ax.set_ylim(0, 100)
plt.tight_layout()
plt.show()
プリズムによる分散
import numpy as np
import matplotlib.pyplot as plt
# --- プリズムの分散 ---
# コーシーの分散公式(BK7ガラスの近似値)
A, B = 1.5046, 0.00420 # 簡略化した係数(μm単位)
wavelengths = np.array([0.400, 0.450, 0.500, 0.550, 0.600, 0.650, 0.700]) # μm
colors_name = ['violet', 'blue', 'cyan', 'green', 'orange', 'red', 'darkred']
n_values = A + B / wavelengths**2
apex_angle = np.radians(60) # プリズム頂角
fig, ax = plt.subplots(figsize=(10, 6))
for wl, n, col in zip(wavelengths, n_values, colors_name):
# 最小偏角条件: 入射角
theta_i = np.arcsin(n * np.sin(apex_angle / 2))
# 最小偏角
delta_min = 2 * theta_i - apex_angle
# プリズム入射面での屈折
theta_r1 = np.arcsin(np.sin(theta_i) / n)
# 簡易光線描画
x_start = -2
y_start = 0.5
# 入射光
ax.plot([x_start, 0], [y_start, 0], color=col, linewidth=1.5)
# 出射光(偏角を反映)
x_end = 2 * np.cos(np.degrees(delta_min) * np.pi / 180 * 0.3)
y_end = -2 * np.sin(delta_min)
ax.plot([0, 2], [0, -np.degrees(delta_min) * 0.04], color=col, linewidth=2,
label=f'{wl*1000:.0f} nm (n={n:.4f})')
ax.set_xlim(-2.5, 2.5)
ax.set_ylim(-2, 2)
ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('y', fontsize=12)
ax.set_title('Dispersion by prism (Cauchy model)', fontsize=13)
ax.legend(fontsize=9, loc='upper right')
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
まとめ
本記事では、光の反射と屈折について解説しました。
- フェルマーの原理: 光は光路長が停留値をとる経路を通る
- 反射の法則: 入射角と反射角は等しい($\theta_i = \theta_r$)
- スネルの法則: $n_1 \sin\theta_1 = n_2 \sin\theta_2$
- 全反射: $\theta_1 > \theta_c = \arcsin(n_2/n_1)$ で全ての光が反射される
- 分散: 屈折率の波長依存性により、白色光がスペクトルに分離される
次のステップとして、以下の記事も参考にしてください。