LoginSignup
11
2

More than 1 year has passed since last update.

【クソアプリ】部屋とテレビの大きさを比較するサービスをWebGLでごりごり作っていく

Posted at

この記事は「クソアプリ Advent Calendar 2022 2」の19日目の記事です。

概要

テレビを買うときって、何インチとか何型とか何Vとかいろいろあって良くわからないですよね。
インチをcmに直して横縦のサイズを一覧してくれているサイトもいろいろありますが、数値だけ書かれても実際どのくらいの大きさなのか良くわからないですよね。
何かと比べてどのくらいって言ってくれれば比較しやすいのになと思いますよね。
畳と比較してくれれば大体わかるのになって思いますよね。

そういうのを作っていきましょう。

出来たもの

image.png

部屋のサイズ(4~12畳)とテレビのサイズ(30~100型)を選ぶと比較が表示されます。
緑のところが畳で黒いところがテレビです。

意外に小さく見えちゃっていますが、家具があったりドアがあったり自分はこの部屋の中にいるんだというところを脳内で補っていくと、さすがに8畳に90型は大きすぎるだろうということがひと目でわかりますね。よかったですね。

WebGLで作る

壁2つと床1つとテレビを平面で書けばいいだけなので、モデリングツールは使わずに直接ごりごりとWebGLを書いていってみます。
Three.jsなんか使ってしまうと一瞬で終わってしまうので、勉強がてらイチから書いていってみます。

参考資料を探す

このような記事を熟読しつつコードをコピーしてきて実行してみたりしていきます。
そしてまずはサンプル通りにキューブを出力して喜んでいきます。

image.png

頂点を用意する

練習は済んだので早速とりかかっていきます。
頂点はこんな感じです。
あとで部屋の大きさは動的に変更したいので、予め変数化しておきます。

function setGeometry(gl) {
  // size
  const roomHeight = 2.4 / 2;
  const roomUnit   = 6;
  const roomWidth  = 0.9 * 4 / 2;
  const roomLength = 0.9 * 3 / 2;
  var positions = transpose4(new Float32Array([
    // west wall
    -roomWidth, -roomHeight,   roomLength,
    -roomWidth,  roomHeight,   roomLength,
     roomWidth, -roomHeight,   roomLength,
    -roomWidth,  roomHeight,   roomLength,
     roomWidth,  roomHeight,   roomLength,
     roomWidth, -roomHeight,   roomLength,

    // south wall
    -roomWidth,  -roomHeight,  -roomLength,
    -roomWidth,   roomHeight,   roomLength,
    -roomWidth,  -roomHeight,   roomLength,
    -roomWidth,  -roomHeight,  -roomLength,
    -roomWidth,   roomHeight,  -roomLength,
    -roomWidth,   roomHeight,   roomLength,

     // floor
    -roomWidth,  roomHeight,  -roomLength,
     roomWidth,  roomHeight,  -roomLength,
     roomWidth,  roomHeight,   roomLength,
    -roomWidth,  roomHeight,  -roomLength,
     roomWidth,  roomHeight,   roomLength,
    -roomWidth,  roomHeight,   roomLength,
  ]));
  gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
}

畳1枚は大体180cm×90cmで2:1になっているとのことだったので、90cm×90cmを何×何に並べていくかで計算できそうですね。
6畳間の場合は90cm×90cmが4×3、8畳間なら4×4といった感じです。

参考イメージ

image.png

とりあえず描画

これに法線と頂点カラーも付けて描画するとこんな感じです。

image.png

形がよくわからないので・・・
さすがにライトは要りますね。

ライトを付けていきましょう。

参考資料を探す

https://dev.to/ndesmic/webgl-engine-from-scratch-part-7-diffuse-lighting-3in8
https://developer.mozilla.org/ja/docs/Web/API/WebGL_API/Tutorial/Lighting_in_WebGL
https://scrapbox.io/teamlab-frontend/Three.js%E3%80%803D%E5%85%A5%E9%96%80%E7%B7%A8_~%E3%83%A9%E3%82%A4%E3%83%88%E3%81%A8%E3%83%9E%E3%83%86%E3%83%AA%E3%82%A2%E3%83%AB~

よくわからないけど、面の法線と光源の内積(dot)を取ってあげると同じ面でも光源との距離に応じて違う値を得られるのでその値をハイライトに利用したり減衰に利用したりできるっていう感じですかね。
なるほどねー

image.png
https://webglfundamentals.org/webgl/lessons/ja/webgl-3d-lighting-point.html

一番シンプルなシェーダーだとこんな感じ。
なるほどねー

//fragment shader
void main() {
    mediump float light = dot(vNormal, uLight1[1].xyz);
    gl_FragColor = vColor * vec4(light, light, light, 1);
}

描画してみる

いろいろ試してみて、
スポットライトのソースも借りてきて使ってみたらこんな感じ。

image.png

まあまあいけそうなので、これでよしとします。
(シェーダーはお借りしたものほぼそのままなので載せないでおきます。計算も意味わかってないですし。)

テレビを置く

テレビを置いていきます。
奥側の壁と同じ頂点の並びと法線を使えばすぐできます。
サイズの方は、入力値がインチなので、cmに直してmに直して16:9三角形のsinΘとcosΘから縦横サイズに変換します。

function setGeometry(gl) {
  // size
  const roomHeight = 2.4 / 2;
  const roomUnit   = 6;
  const roomWidth  = 0.9 * 4 / 2;
  const roomLength = 0.9 * 3 / 2;
  const tvUnit = 100;
  const tvWidth = tvUnit * 2.54 / 100 * 0.87 / 2;
  const tvHeight = tvUnit * 2.54 / 100 * 0.49 / 2;
  const tvLenght = roomLength - 5;
  var positions = transpose4(new Float32Array([
    // west wall
    -roomWidth, -roomHeight,   roomLength,
    -roomWidth,  roomHeight,   roomLength,
     roomWidth, -roomHeight,   roomLength,
    -roomWidth,  roomHeight,   roomLength,
     roomWidth,  roomHeight,   roomLength,
     roomWidth, -roomHeight,   roomLength,

    // south wall
    -roomWidth,  -roomHeight,  -roomLength,
    -roomWidth,   roomHeight,   roomLength,
    -roomWidth,  -roomHeight,   roomLength,
    -roomWidth,  -roomHeight,  -roomLength,
    -roomWidth,   roomHeight,  -roomLength,
    -roomWidth,   roomHeight,   roomLength,

    // tv
    -tvWidth, -tvHeight,   tvLenght,
    -tvWidth,  tvHeight,   tvLenght,
     tvWidth, -tvHeight,   tvLenght,
    -tvWidth,  tvHeight,   tvLenght,
     tvWidth,  tvHeight,   tvLenght,
     tvWidth, -tvHeight,   tvLenght,

     // floor
    -roomWidth,  roomHeight,  -roomLength,
     roomWidth,  roomHeight,  -roomLength,
     roomWidth,  roomHeight,   roomLength,
    -roomWidth,  roomHeight,  -roomLength,
     roomWidth,  roomHeight,   roomLength,
    -roomWidth,  roomHeight,   roomLength,
  ]);
  gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
}

描画するとこんな感じ。

image.png

だんだん出来てきましたね。
どんどんいきましょう。

畳を貼る

「畳の大きさと比較すればテレビがどのくらいの大きさなのか分かる」ということで始めたのだから、畳のテクスチャは必須ですよね。

でも、めんどかったんですよね。

畳1枚のテクスチャだけ用意すると、それを敷き詰めるには床の平面をもっと分割していかないといけないし、
畳を敷き詰めたテクスチャを用意して一枚の平面に貼るとすると、頂点はそのままでいいけど部屋のバリエーション分の畳を敷き詰めたテクスチャを用意しないといけないので、どっちにしても大変。

ということで閃きました。

グリッドを引けばいいじゃない。90cm×90cmで。

ついでに、テレビの横幅が畳のどこに当たっているかわかるようにテレビ側からも補助線を引いていきましょう。

グリッド用のシェーダーを用意する

線を引く用のシンプルなシェーダー。
グリッドは光源の影響を受けなくてよいので何も計算しなくてよい。
線の太さも色も今回は固定とします。

  //vertex-shader
  attribute vec4 a_position;
  uniform mat4 u_worldViewProjection;
  
  void main() {
    // Multiply the position by the matrix.
    gl_Position = u_worldViewProjection * a_position;

    gl_PointSize = 10.0;
  }
  //fragment-shader
  void main() {
    gl_FragColor = vec4(vec3(1.0), 1);
  }
  </script>

グリッド用の頂点を用意する

gl.LINESで線を引くため、始点と終点のペアを必要数分用意する。
TVの左右の補助線用の頂点ペアにも用意する。

function setGrid(gl) {
  const gridSize = 0.9;

  var _axis = [];
  for (var ii = 0; ii < roomWidthUnit + 1; ii += 1) {
    _axis.push(-roomWidth + ( ii * gridSize ), roomHeight,  roomLength);
    _axis.push(-roomWidth + ( ii * gridSize ), roomHeight, -roomLength);
  }
  for (var jj = 0; jj < roomLengthUnit + 1; jj += 1) {
    _axis.push( roomWidth, roomHeight, -roomLength + ( jj * gridSize ));
    _axis.push(-roomWidth, roomHeight, -roomLength + ( jj * gridSize ));
  }

  var axis = transpose4(new Float32Array([
    ..._axis,

    // tv
    -tvWidth,  roomHeight, roomLength,
    -tvWidth, -roomHeight, roomLength,

     tvWidth,  roomHeight, roomLength,
     tvWidth, -roomHeight, roomLength,
  ]));
  gl.bufferData(gl.ARRAY_BUFFER, axis, gl.STATIC_DRAW);
}

シェーダーを切り替えてグリッドを引く

単純に、前の描画処理の後にuseProgramでシェーダーを切り替えて次の描画を開始すればよさそうでした。
worldViewProjectionMatrixは使いまわします。

  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  // 部屋とテレビ用のシェーダー
  gl.useProgram(program);
  // 部屋用の前処理
  ...省略
  // 部屋を描画
  gl.drawArrays(primitiveType, offset, count);

  // シェーダーを切り替える
  gl.useProgram(program_grid);
  gl.enableVertexAttribArray(positionLocation_grid);
  gl.bindBuffer(gl.ARRAY_BUFFER, axisBuffer);
  gl.vertexAttribPointer(
    positionLocation_grid, size, type, normalize, stride, offset);
  gl.uniformMatrix4fv(worldViewProjectionLocation_grid, false, worldViewProjectionMatrix);

  // 直線を描画
  var primitiveType = gl.LINES;
  var offset = 0;
  var count = ( roomWidthUnit + 1 + roomLengthUnit + 1 + 2) * 2;
  gl.drawArrays(primitiveType, offset, count);

描画する

image.png

いい感じですね。
補助線のおかげで大体の長さも分かりますし。

コントロールをつけてスタイルを調整して完成

image.png

出来ました :clap: :clap:

Netlifyにアップして完成です。

:clap: :clap:

終わり

お疲れ様でした。
絶妙に役に立たないものが出来て満足です。

11
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
2