Home
JavaScript
Three.jsテクスチャをGLSLでbacground-size: cover;のようにする

Three.jsテクスチャをGLSLでbacground-size: cover;のようにする

Three.jsテクスチャをGLSLでbacground-size: cover;のようにする

どうも!かけちまるです!

Three.jsで画像をテクスチャとして使うときにGLSLでテクスチャをbackground-size: cover;のような見た目にする方法を解説します。

背景画像をテクスチャにしたい時などに使えそうですね。
これでテクスチャをレスポンシブに対応させることができます。

この記事では、

  • ・テクスチャをbackground-size: cover;のようにする方法

がわかります。

今回のテンプレート

次のコードを元に実装していきます。

ディレクトリ構成
開く&閉じるコピー
index.html css/ -style.css js/ -main.js
index.html
開く&閉じるコピー
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" type="text/css" href="./css/style.css"> <script async src="https://unpkg.com/es-module-shims@1.5.8/dist/es-module-shims.js"></script> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.142.0/build/three.module.js" } } </script> <script src="./js/main.js" type="module"></script> </head> <body> <script id="v-shader" type="x-shader/x-vertex"> </script> <script id="f-shader" type="x-shader/x-fragment"> </script> </body> </html>
style.css
開く&閉じるコピー
*{ margin: 0; padding: 0; box-sizing: border-box; } html, body{ height: 100%; } canvas{ position: fixed; top: 0; left: 0; z-index: -1; }
main.js
開く&閉じるコピー
import * as THREE from 'three'; /** * シーン・カメラ・レンダラーを用意する */ const fov = 50; const fovRad = (fov / 2) * (Math.PI / 180); // 視野角をラジアンに変換 let distance = (window.innerHeight / 2) / Math.tan(fovRad); // カメラ距離を求める const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( fov, window.innerWidth / window.innerHeight, 0.1, 2000 ); camera.position.z = distance; const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.body.appendChild(renderer.domElement); /** * ジオメトリ+マテリアル=メッシュを用意する */ const planeGeometry = new THREE.PlaneGeometry(100, 100); const shaderMaterial = new THREE.ShaderMaterial(); let mesh = new THREE.Mesh(planeGeometry, shaderMaterial); scene.add(mesh); // アニメーション function animate() { renderer.render(scene, camera); requestAnimationFrame(animate); } animate(); // ブラウザのリサイズに対応させる window.addEventListener('resize', onWindowResize); function onWindowResize() { renderer.setSize(window.innerWidth, window.innerHeight); camera.aspect = window.innerWidth / window.innerHeight; distance = (window.innerHeight / 2) / Math.tan(fovRad); camera.position.z = distance; camera.updateProjectionMatrix(); }

ここまで次のようになっていればオッケーです。

テンプレート

テンプレートの捕捉

今回はShaderを使うのでindex.htmlでは、Shaderを記述する準備をしています。(19、23行目)
main.jsでは、ジオメトリはライトがいらないPlaneGeometry、マテリアルはShaderMaterialを使用しています。(27、28行目)

ここまで何をやっているかよくわからない人はこちらの記事が参考になると思います。

6〜8行目はwindowサイズとカメラの画角を合わせメッシュ位置の指定などをpxで指定できるようにカメラの距離を求めています。
詳細は次の記事で解説しています。

テクスチャをbackground-size: cover;のようにする方法

今回は、Shaderを使うので少し複雑になります。
なので、順序だてて説明していきます。

1

vertexShaderfragmentShaderを読み込む

まずは、Shaderを読み込んでみます。
index.htmlでShaderを用意します。(3~5、9~11行目)
main.jsのShaderMaterialの引数でShaderを読み込みます。(3、4行目)

ここまでで次のようになっていればOKです。
これでShaderで読み込み確認ができました。

Shader読み込み
index.html
開く&閉じるコピー
・・・ <script id="v-shader" type="x-shader/x-vertex"> void main() { gl_Position = vec4( position, 1.0 ); } </script> <script id="f-shader" type="x-shader/x-fragment"> void main() { gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 ); } </script> ・・・
main.js
開く&閉じるコピー
・・・ const shaderMaterial = new THREE.ShaderMaterial({ vertexShader: document.querySelector('#v-shader').textContent, fragmentShader: document.querySelector('#f-shader').textContent, }); let mesh = new THREE.Mesh(planeGeometry, shaderMaterial); scene.add(mesh); ・・・
2

テクスチャを表示させる

まずは、main.jsでTextureLoaderを用意し、任意の画像を読み込みます。(5、6行目)
※画像はunsplashから拝借しています。
そして、Shaderにtextureを渡します。(10、12行目)

index.htmlでは、textureを受け取り画像の座標をマッピングします。

ここまでで次のようになっていればOKですが、画像比率がおかしなことになっています。

main.js
開く&閉じるコピー
・・・ /** * ジオメトリ+マテリアル=メッシュを用意する */ const loader = new THREE.TextureLoader(); const texture = loader.load('https://source.unsplash.com/9orSC4PLJr4/'); const planeGeometry = new THREE.PlaneGeometry(2, 2); const shaderMaterial = new THREE.ShaderMaterial({ uniforms: { uTexture: { value: texture } }, vertexShader: document.querySelector('#v-shader').textContent, fragmentShader: document.querySelector('#f-shader').textContent, }); let mesh = new THREE.Mesh(planeGeometry, shaderMaterial); scene.add(mesh); ・・・
index.html
開く&閉じるコピー
・・・ <script id="v-shader" type="x-shader/x-vertex"> varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4( position, 1.0 ); } </script> <script id="f-shader" type="x-shader/x-fragment"> varying vec2 vUv; uniform sampler2D uTexture; void main() { vec3 color = texture2D( uTexture, vUv ).rgb; gl_FragColor = vec4( color, 1.0 ); } </script> ・・・
3

background-size: cover;のようにする

まずは、main.jsでY座標用のアスペクト比とX座標用のアスペクト比をShaderに渡します。(11、12行目)

index.htmlでは、Y座標用のアスペクト比とX座標用のアスペクト比を受け取ります。(4、5行目)
14行目vUv = uv;をコメントアウトし、アスペクト比を補正する処理を書きます。(9~12行目)

ここまででbackground-size: cover;のようになりましたがリサイズに対応していません。

main.js
開く&閉じるコピー
・・・ /** * ジオメトリ+マテリアル=メッシュを用意する */ const loader = new THREE.TextureLoader(); const texture = loader.load('https://source.unsplash.com/9orSC4PLJr4/'); const planeGeometry = new THREE.PlaneGeometry(2, 2); const shaderMaterial = new THREE.ShaderMaterial({ uniforms: { uYAspect: { value: window.innerHeight / window.innerWidth }, uXAspect: { value: window.innerWidth / window.innerHeight }, uTexture: { value: texture } }, vertexShader: document.querySelector('#v-shader').textContent, fragmentShader: document.querySelector('#f-shader').textContent, }); let mesh = new THREE.Mesh(planeGeometry, shaderMaterial); scene.add(mesh); ・・・
index.html
開く&閉じるコピー
・・・ <script id="v-shader" type="x-shader/x-vertex"> varying vec2 vUv; uniform float uYAspect; uniform float uXAspect; void main() { vUv = uv - .5; vUv.y *= min(uYAspect, 1.0); vUv.x *= min(uXAspect, 1.0); vUv += .5; //vUv = uv; gl_Position = vec4( position, 1.0 ); } </script> ・・・
4

リサイズ処理を追加する

リサイズ処理はY座標用のアスペクト比とX座標用のアスペクト比を計算しなおすだけでOKです。(5、6行目)

これで完成です。

main.js
開く&閉じるコピー
// ブラウザのリサイズに対応させる window.addEventListener('resize', onWindowResize); function onWindowResize() { renderer.setSize(window.innerWidth, window.innerHeight); shaderMaterial.uniforms.uYAspect.value = window.innerHeight / window.innerWidth; shaderMaterial.uniforms.uXAspect.value = window.innerWidth / window.innerHeight; camera.aspect = window.innerWidth / window.innerHeight; distance = (window.innerHeight / 2) / Math.tan(fovRad); camera.position.z = distance; camera.updateProjectionMatrix(); }

おわり

かけちまる
かけちまる
Webエンジニアをしています。
HTML/CSS/JavaScript/jQuery/PHPができます。
WEB制作を中心に日々学びになったこと、興味が沸いたことについて初心者の方でもわかりやすいようにアウトプットしていくブログです。
Xserver

関連記事

【CSSのみ】背景のグラデーションを変化させるアニメーション

【CSSのみ】背景のグラデーションを変化させるアニメーション

jQueryでheaderとfooterを共通化する【load()メソッド】

jQueryでheaderとfooterを共通化する【load()メソッド】

【Three.js】スクロールにあわせてメッシュを動かす方法

【Three.js】スクロールにあわせてメッシュを動かす方法

【CSSのみ】指定行数を超えたテキストを非表示にする方法

【CSSのみ】指定行数を超えたテキストを非表示にする方法

【Three.js基礎】概念の理解と立方体の実装

【Three.js基礎】概念の理解と立方体の実装

react-scrollでページ内スクロールが効かない時と複数ページの対応

react-scrollでページ内スクロールが効かない時と複数ページの対応