以前、VRモードに対応する形で作ったので、そのままVRモードでつくりますが、VRモードじゃなくても要点は変わりません。
❶ Canvasを作る
❷ TextureにCanvasを設定する
❸ MaterialにTextureを設定する
❹ MeshにMaterialと適当なGeometryを設定する
❺ Meshのmaterial.map.needsUpdateにtrueを渡す
という流れです。
注意事項としては❺は1度だけでなく、Textureを更新したいタイミングで設定しなければなりません。
毎ループ更新したかったら、毎ループ設定する必要があります。
また、VRモードでは何故かrequestAnimationが止まってしまうようなので、そこは気をつける必要があります。
未検証ですが、XRSession.requestAnimationFrameを使えば動くのかもしれません。
JavaScript
import * as THREE from './build/three.module.js'; import { OrbitControls } from './jsm/controls/OrbitControls.js'; import { GLTFLoader } from './jsm/loaders/GLTFLoader.js'; import { BoxLineGeometry } from './jsm/geometries/BoxLineGeometry.js'; import { VRButton } from './jsm/webxr/VRButton.js'; const canvas = document.createElement('canvas'); document.body.appendChild(canvas); const scene = new THREE.Scene(); const width = window.innerWidth; const height = window.innerHeight; const renderer = new THREE.WebGLRenderer({ canvas, antialias: true }); renderer.setSize(width, height); renderer.vr.enabled = true; const camera = new THREE.PerspectiveCamera(45, width / height, 1, 100); camera.position.set(0, 1, 10); const controls = new OrbitControls(camera, renderer.domElement); const light = new THREE.DirectionalLight(0xFFFFFF); light.position.set(10, 10, 10); scene.add(light); const room = new THREE.LineSegments( new BoxLineGeometry(8, 8, 8, 10, 10, 10).translate(0, 2, 0), new THREE.LineBasicMaterial({ color: 0xFFFFFF }) ); scene.add(room); document.body.appendChild(VRButton.createButton(renderer)); const plane = (() => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const fontSize = 24; canvas.width = 320; canvas.height = 180; const geometry = new THREE.PlaneGeometry(canvas.width / 100, canvas.height / 100); const texture = new THREE.CanvasTexture(canvas); const material = new THREE.MeshBasicMaterial({ map: texture }); (function update() { ctx.fillStyle = 'white'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'black'; ctx.font = `${fontSize}px sans-serif`; ctx.fillText(Date.now(), 0, fontSize); material.map.needsUpdate = true; requestAnimationFrame(update); })(); return new THREE.Mesh(geometry, material); })(); plane.position.y = 2; plane.position.z = -2; scene.add(plane); renderer.setAnimationLoop(() => { controls.update(); renderer.render(scene, camera); });
すごくざっくり書くとこんな感じです。
毎フレームDate.now()の結果が更新されます。
ただ、このコードだと、VRモードに入るとDate.nowの更新が止まってしまいます。
理由を調べたのですが、requestAnimationFrameが止まってしまうようです。
なので、updateを一緒にリターンし、renderer.setAnimationLoop内で実行するように改修します。
JavaScript
import * as THREE from './build/three.module.js'; import { OrbitControls } from './jsm/controls/OrbitControls.js'; import { GLTFLoader } from './jsm/loaders/GLTFLoader.js'; import { BoxLineGeometry } from './jsm/geometries/BoxLineGeometry.js'; import { VRButton } from './jsm/webxr/VRButton.js'; const canvas = document.createElement('canvas'); document.body.appendChild(canvas); const scene = new THREE.Scene(); const width = window.innerWidth; const height = window.innerHeight; const renderer = new THREE.WebGLRenderer({ canvas, antialias: true }); renderer.setSize(width, height); renderer.vr.enabled = true; const camera = new THREE.PerspectiveCamera(45, width / height, 1, 100); camera.position.set(0, 1, 10); const controls = new OrbitControls(camera, renderer.domElement); const light = new THREE.DirectionalLight(0xFFFFFF); light.position.set(10, 10, 10); scene.add(light); const room = new THREE.LineSegments( new BoxLineGeometry(8, 8, 8, 10, 10, 10).translate(0, 2, 0), new THREE.LineBasicMaterial({ color: 0xFFFFFF }) ); scene.add(room); document.body.appendChild(VRButton.createButton(renderer)); const plane = (() => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const fontSize = 24; canvas.width = 320 canvas.height = 180; const geometry = new THREE.PlaneGeometry(canvas.width / 100, canvas.height / 100); const texture = new THREE.CanvasTexture(canvas); const material = new THREE.MeshBasicMaterial({ map: texture }); function update() { ctx.fillStyle = 'white'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'black'; ctx.font = `${fontSize}px sans-serif`; ctx.fillText(Date.now(), 0, fontSize); ctx.fillText(x, 0, fontSize * 2); material.map.needsUpdate = true; } const plane = new THREE.Mesh(geometry, material); plane.update = update; return plane; })(); plane.position.y = 2; plane.position.z = -2; scene.add(plane); renderer.setAnimationLoop(() => { controls.update(); plane.update(); renderer.render(scene, camera); });
こんな感じです。
plane.updateという名前がバッティングしそうで若干気になりますが、いまのところこれでVRモードでも動作します。
また、
JavaScript
import * as THREE from './build/three.module.js'; import { OrbitControls } from './jsm/controls/OrbitControls.js'; import { GLTFLoader } from './jsm/loaders/GLTFLoader.js'; import { BoxLineGeometry } from './jsm/geometries/BoxLineGeometry.js'; import { VRButton } from './jsm/webxr/VRButton.js'; const canvas = document.createElement('canvas'); document.body.appendChild(canvas); const scene = new THREE.Scene(); const width = window.innerWidth; const height = window.innerHeight; const renderer = new THREE.WebGLRenderer({ canvas, antialias: true }); renderer.setSize(width, height); renderer.vr.enabled = true; const camera = new THREE.PerspectiveCamera(45, width / height, 1, 100); camera.position.set(0, 1, 10); const controls = new OrbitControls(camera, renderer.domElement); const light = new THREE.DirectionalLight(0xFFFFFF); light.position.set(10, 10, 10); scene.add(light); const room = new THREE.LineSegments( new BoxLineGeometry(8, 8, 8, 10, 10, 10).translate(0, 2, 0), new THREE.LineBasicMaterial({ color: 0xFFFFFF }) ); scene.add(room); document.body.appendChild(VRButton.createButton(renderer)); const plane = (() => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const fontSize = 24; canvas.width = 320 canvas.height = 180; const geometry = new THREE.PlaneGeometry(canvas.width / 100, canvas.height / 100); const texture = new THREE.CanvasTexture(canvas); const material = new THREE.MeshBasicMaterial({ map: texture }); setInterval(() => { ctx.fillStyle = 'white'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'black'; ctx.font = `${fontSize}px sans-serif`; ctx.fillText(Date.now(), 0, fontSize); material.map.needsUpdate = true; }, 1000 / 24); return new THREE.Mesh(geometry, material); })(); plane.position.y = 2; plane.position.z = -2; scene.add(plane); renderer.setAnimationLoop(() => { controls.update(); renderer.render(scene, camera); });
といった感じで、requestAnimationFrameをやめて、setIntervalにするだけでも動くのでこっちの方が手軽かもしれません。
Textureの更新タイミングとアプリケーションの更新タイミングを別にしたい場合は、この実装でも良い気がします。