ykrods note

[覚え書き] css での奥行き表現 (perspective)

目的

三次元画像処理を本格的にはやらないけどある程度は知っておきたいくらいの知識をまとめる

基本知識

css で三次元的な遠近感を表現するには perspective, perspective-origin, transform などのプロパティを用いる。

perspective

視点と z=0 の距離

perspective-origin

消失点(視点の位置)

transform

z方向を含む、要素の座標変換

  • 例えば z方向への平行移動は translateZ() が用いられる

ブラウザはこれらのパラメータから最終的なスクリーンへの描画を行う。描画の手法としては 透視投影 (perspective projection) が用いられる 1

透視投影の概念図

透視投影の概念図 (消失点が左上に設定された場合)

透視投影では三次元の空間にあるオブジェクトを平面上に投影する。css においては z=0 の xy平面が投影面となり、perspective を指定した要素の子要素がそれぞれのz座標を持つようになる。子要素(オブジェクト)のz座標が大きいほど(視点に近づくほど)、大きく描画される。

視点と z=0 座標の距離が 100px の空間上に、一辺 50px の箱を z=0px, z=50px, z=75px の位置にそれぞれ配置し、どのように描画されるかを確認する。

<!DOCTYPE html>
<html>
<head>
  <title>perspective ex1</title>
  <meta charset="utf-8" />
  <style>
    /* 以下のスタイルは見た目の調整 */
    body {
      margin: 100px;
    }
    .plane {
      background-color: #E5E5E5;
      position: relative;
      width: 240px;
      height: 200px;
    }
    .box {
      position: absolute;
      top: 0;
      left: 10px;
      width: 50px;
      height: 50px;
      background-color: rgba(255, 0, 0, 0.2);
      font-size: 9px;
      display: flex;
      justify-content: center;
      align-items: center;
    }
  </style>
</head>
<body>
  <div class="plane" style="perspective: 100px; perspective-origin: left top;">
    <div class="box" style="transform: translateZ(0px);">0px</div>
    <div class="box" style="transform: translateZ(50px);">50px</div>
    <div class="box" style="transform: translateZ(75px);">75px</div>
  </div>
</body>
</html>

表示結果

ex1.html の表示結果
  • z=0px では 一辺が 50px のまま

  • z=50px (視点からの距離は1/2)では、一辺が 100px (2倍) になる

  • z=75px (視点からの距離は1/4)では、一辺が 200px (4倍) になる

描画される長さが距離に反比例しているのがわかる 2

perspective プロパティと perspective() の違い

perspective プロパティの他、transform に与える perspective() 関数でも視点からの距離を設定できる。

style="transform: perspective(200px) translateZ(100px);"
  • perspective は親要素に指定するが、 perspective() は変換対象に指定する

  • perspective() は perspective-origin でなく transform-origin を消失点として変換処理を行う

  • perspective() はあくまで3次元座標の変換処理なので、最終的なスクリーンへの描画の際に親要素の perspective (および perspective-origin) の影響を受ける

計算がわけわからなくなるので少なくともどちらか一方を使うのが良いように思える。

perspective プロパティでは視点(カメラ)の傾きがないので、傾きを付けたい場合には親要素に transform: perspective() rotateX() を指定する、というような使い方だろうか(下の練習ではそのような使い方をしているが、一般的かは不明)。

練習

上の例では立体感がないので、2点パースと3点パースのよくある図を描いてみる。

立体感を出すため、 rotateY() でオブジェクトを横回転する。また3点パースのカメラの縦方向の回転に rotateX() を用いる。

<!DOCTYPE html>
<html>
<head>
  <title>2 and 3 point perspective</title>
  <meta charset="utf-8" />
  <style>
    body {
      margin: 50px;
    }

    .pers {
      width: 100px;
      height: 100px;
      margin: 50px;
      transform-style: preserve-3d;
    }

    .two-point {
      perspective: 160px;
      perspective-origin: center 75%;
    }

    .three-point {
      transform: perspective(160px) rotateX(15deg);
    }

    .face {
      position: absolute;
      width: 100px;
      height: 100px;
    }

    .left {
      background-color: green;
      transform: rotateY(-45deg) translate3d(0, 0, 50px);
    }

    .right {
      background-color: limegreen;
      transform: rotateY(45deg) translate3d(0, 0, 50px);
    }
  </style>
</head>
<body>
  <h1>2 and 3 point perspective example</h1>

  <h2>2-point</h2>
  <div class="two-point pers">
    <div class="face right"></div>
    <div class="face left"></div>
  </div>

  <h2>3-point</h2>
  <div class="three-point pers">
    <div class="face right"></div>
    <div class="face left"></div>
  </div>
</body>
</html>

表示結果

上の html の表示結果

(追記)スクロール

perspective を指定した要素に overflow: scroll を指定して3次元の描画領域をスクロール可能にした場合、スクロールに応じて子要素が再描画(アニメーション)される。パララックススクロールのcssによる実装では、この仕組みが使われているようだ。

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <style>
      body {
        margin:0;
      }
      .container {
        width: 300px;
        height: 300px;
        overflow: scroll;
        perspective: 100px;
      }
      .bg {
        width: 500px;
        height: 500px;
        background-color: lightgray;
      }
      .hurdle {
        transform: rotateY(90deg);
        background-color: red;
        width: 100px;
        height: 100px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="bg"></div>
      <div class="hurdle" style="position: absolute; top: 100px; left: 100px;"></div>
      <div class="hurdle" style="position: absolute; top: 100px; left: 150px;"></div>
      <div class="hurdle" style="position: absolute; top: 100px; left: 200px;"></div>
      <div class="hurdle" style="position: absolute; top: 100px; left: 250px;"></div>
    </div>
  </body>
</html>

表示結果

上の html の表示結果(動く)

その他メモ

  • perspective: none; (初期値)の場合は無限遠という扱いなようなので、 z座標が 100 だろうが 10000 だろうが描画される大きさに影響しない、ということらしい

参考

Footnotes

1

CSS Transforms Module Level 2 や MDN の解説では z=0 の平面は plane, drawing plane などと表記されているがまぁ projection plane (投影面) ということでよいだろう

2

これは人間の目に近いとされるがより近い描画方法があるのかなどは知らない

CSS perspective 3d-image

[覚え書き] css での奥行き表現 (perspective) — ykrods note
https://www.ykrods.net/posts/2021/06/26/css-perspective-intro/

Comments