ノーマルマップと物理ベースライティングで写真を立体的に表示
平面の写真に、光の理由を与える回です。明度から法線を起こし、Lambert+Specularと環境マップで質感を作る。強すぎない設定が肝。
作ったもの
JPG画像を、ノーマルマップと物理ベースライティングで立体的に表示するWebアプリです。
平面の写真に、法線情報を与える。光の当たり方で、凹凸が見える。まるで物理的な厚みがあるかのように。
環境マップも使って、周囲の光を反射させています。リアルな質感表現ができる。
まずは、ライティングの効果を見てみましょう:
環境光と平行光を調整すると、見た目が大きく変わる。光の方向を変えれば、凹凸の見え方も変わります。
なぜ作ったか
平面の写真に、立体感を与えたかった。
デジタル写真は平坦。でも、印刷写真には微妙な凹凸がある。紙の厚み、インクの盛り上がり。
その質感を、WebGLで再現できないか。そう考えました。
ノーマルマップは、3Dモデルでよく使われる。平面に法線情報を与えて、立体的に見せる技術。これを写真に応用したら面白いのではないか。
ノーマルマップの生成
写真から、ノーマルマップを生成します。
画像の明度を高さとして扱う。明るい部分は高く、暗い部分は低い。その勾配から、法線ベクトルを計算:
// 周囲のピクセルから高さ差を計算
float heightL = texture2D(photo, uv + vec2(-0.001, 0.0)).r;
float heightR = texture2D(photo, uv + vec2(0.001, 0.0)).r;
float heightD = texture2D(photo, uv + vec2(0.0, -0.001)).r;
float heightU = texture2D(photo, uv + vec2(0.0, 0.001)).r;
// 法線を計算
vec3 normal = normalize(vec3(
heightL - heightR,
heightD - heightU,
1.0
));
X方向とY方向の高さ差から、法線を算出。これで凹凸の情報が得られます。
物理ベースライティング
ノーマルマップができたら、ライティングを計算。
Lambert反射とSpecular反射を組み合わせます:
// Lambert反射(拡散反射)
float diffuse = max(dot(normal, lightDir), 0.0);
// Specular反射(鏡面反射)
vec3 viewDir = normalize(cameraPos - fragPos);
vec3 halfDir = normalize(lightDir + viewDir);
float specular = pow(max(dot(normal, halfDir), 0.0), shininess);
// 合成
vec3 result = ambient + diffuse * lightColor + specular * lightColor;
Lambert項で、光の当たり方を計算。Specular項で、ハイライトを追加。
これで、物理的に正しいライティングができる。
環境マップの統合
環境マップで、周囲の光を反射させます。
写真の背景画像を、環境マップとして使用:
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
scene.environment = envMap;
環境光が写真に反射して、リアルな質感が出る。
最初、環境マップなしで試した。平坦に見えた。
環境マップを追加したら、立体感が増した。意外でした。
パラメータの調整
ノーマルマップ強度
ノーマルマップの強度を調整できます。
0.0だと平面、2.0だと凹凸が強すぎる。0.7がちょうど良かった。
写真によって最適な値が変わる。風景写真は0.5、ポートレートは0.8くらいが自然。
環境マップ強度
環境マップの反射も調整可能。
1.0だと控えめ、2.0だと反射が強い。1.2で、適度な反射感が出た。
試してみた結果
風景写真
風景写真を読み込んだ。
空の部分が滑らか、山の部分に凹凸が見える。光の方向を変えると、陰影が変化する。
立体的に見える。これが作りたかったもの。
ポートレート
人物写真も試した。
肌の質感が立体的になる。光の当たり方で、顔の凹凸が強調される。
ノーマルマップ強度を0.8にしたら、自然な立体感が出た。
モノクロ写真
モノクロ写真で、ノーマルマップの効果が一番分かりやすい。
明暗のコントラストが、そのまま高さ情報になる。光を当てると、凹凸がはっきり見える。
使ってみて
実際に試してみてください:
旧サイトのデモを見る
ノーマルマップと物理ベースライティングで、平面の写真に立体感を与えることができました。
ポイントは以下の3つ:
- ノーマルマップ生成:画像の明度を高さとして扱い、勾配から法線を計算
- 物理ベースライティング:Lambert反射とSpecular反射を組み合わせ、リアルな光の計算
- 環境マップ統合:周囲の光を反射、立体感が大幅に向上
最初は環境マップなしで試して平坦に見えましたが、環境マップを追加したら立体感が増す。ノーマルマップ強度0.7、環境マップ強度1.2が最適でした。
写真に物理的な質感を与えたい方の参考になれば嬉しいです。
まとめ
今回は、ノーマルマップと物理ベースライティングで写真を立体的に表示する実装をしました。
ポイントは以下の4つ:
- ノーマルマップ:明度から高さを推定、勾配から法線を計算、立体情報を生成
- 物理ベースライティング:Lambert項(拡散反射)+ Specular項(鏡面反射)で正確な光の計算
- 環境マップ:周囲の光を反射させることで、リアルな質感を実現
- パラメータ調整:ノーマルマップ強度0.7、環境マップ強度1.2で自然な立体感
平面の写真に、物理的な厚みを与える試みでした。環境マップの効果が予想以上に大きく、立体感が大幅に向上しました。
写真の新しい表現方法を探している方の参考になれば嬉しいです。
参考にした資料
この記事で興味を持った方におすすめのリンク:
自分の関連記事:
最後まで読んでくださり、ありがとうございました。