2026年7月27日
2026年7月27日
WordPressにWebGLで3Dモデルを表示する方法:three.jsとショートコードで実装
WebGL は、ブラウザーで GPU を使って3Dグラフィックスを描画するための標準の API です。直接書くと手間が多いため、実際には three.js のようなライブラリを使うのが一般的です。この記事では、three.js で glTF / GLB 形式の3Dモデルを読み込み、WordPress の記事にショートコードで表示する小さなプラグインを作ります。
コードは WordPress 6.9.4・PHP 8.3・three.js r187(npm の版番号 0.187.0)の環境で、ブラウザーにモデルが表示され、コンソールにエラーが出ないことを確認しています(2026年10月時点)。
作るものと前提
- 記事に
webgl_modelというショートコードを書くと、その位置に3Dモデルが表示され、マウスやタッチで回転・拡大できる。 - three.js は WordPress 6.5 で入ったスクリプトモジュールの仕組み(
wp_register_script_module())で読み込む。そのため WordPress 6.5 以上が必要。 - 3Dモデルは、Blender などから書き出せる glTF / GLB 形式を使う。
ファイル構成は次のとおりです。
wp-content/plugins/my-webgl-viewer/
├── my-webgl-viewer.php
├── view.js
└── models/
└── sample.glb
手順1: プラグイン本体(PHP)を作る
my-webgl-viewer.php を作ります。
<?php
/**
* Plugin Name: My WebGL Viewer
* Description: three.js で glTF / GLB の3Dモデルを表示するショートコード [webgl_model]。
* Version: 1.0.0
* Requires at least: 6.5
* Requires PHP: 7.4
*/
defined( 'ABSPATH' ) || exit;
const MY_WEBGL_THREE_VERSION = '0.187.0';
/**
* three.js 本体とアドオン、表示用スクリプトをスクリプトモジュールとして登録する。
*/
add_action( 'init', function () {
$cdn = 'https://cdn.jsdelivr.net/npm/three@' . MY_WEBGL_THREE_VERSION;
// ID は import 文に書く名前と同じにする(インポートマップに出力される)。
wp_register_script_module( 'three', $cdn . '/build/three.module.js', array(), null );
wp_register_script_module( 'three/addons/loaders/GLTFLoader.js', $cdn . '/examples/jsm/loaders/GLTFLoader.js', array( 'three' ), null );
wp_register_script_module( 'three/addons/controls/OrbitControls.js', $cdn . '/examples/jsm/controls/OrbitControls.js', array( 'three' ), null );
wp_register_script_module(
'my-webgl-viewer',
plugins_url( 'view.js', __FILE__ ),
array( 'three', 'three/addons/loaders/GLTFLoader.js', 'three/addons/controls/OrbitControls.js' ),
'1.0.0'
);
} );
/**
* [webgl_model src="models/sample.glb" height="400" alt="説明"]
*/
add_shortcode( 'webgl_model', function ( $atts ) {
$atts = shortcode_atts(
array(
'src' => 'models/sample.glb',
'height' => 400,
'alt' => '3Dモデル',
),
$atts,
'webgl_model'
);
// プラグイン内の models/ フォルダーにあるファイルだけを許可する。
$file = basename( $atts['src'] );
if ( ! preg_match( '/\.(glb|gltf)$/i', $file ) || ! file_exists( __DIR__ . '/models/' . $file ) ) {
return '';
}
wp_enqueue_script_module( 'my-webgl-viewer' );
return sprintf(
'<div class="my-webgl-viewer" data-src="%1$s" role="img" aria-label="%2$s" style="height:%3$dpx">'
. '<noscript>%4$s(表示には JavaScript と WebGL が必要です)</noscript></div>',
esc_url( plugins_url( 'models/' . $file, __FILE__ ) ),
esc_attr( $atts['alt'] ),
absint( $atts['height'] ),
esc_html( $atts['alt'] )
);
} );
ポイントは次のとおりです。
- スクリプトモジュールの ID: three.js のアドオン(
GLTFLoader.jsなど)は、中でimport ... from 'three'と書かれています。モジュールの ID をthreeにしておくと、WordPress が出力するインポートマップ(<script type="importmap">)に"three": "…/three.module.js"が入り、この import が解決されます。 - バージョン引数
null:wp_register_script_module()の第4引数をnullにすると、URL に?ver=が付きません。false(初期値)だと WordPress のバージョンが付きます。CDN のファイルはバージョンを URL に含めているのでnullにしています。 - 読み込むページを絞る:
wp_enqueue_script_module()をショートコードの中で呼んでいるので、ショートコードがあるページだけで three.js が読み込まれます。クラシックテーマではインポートマップとモジュールがフッターに出力されるため、本文の処理中に追加しても間に合います(検証環境で出力を確認)。 - ファイルの制限:
basename()でフォルダー部分を取り除き、拡張子が.glb/.gltfで、プラグインのmodels/に実在するファイルだけを表示します。記事を書く人が任意の URL を読み込ませないようにするためです。 - 出力のエスケープ: URL は
esc_url()、属性はesc_attr()、高さはabsint()で数値にしています。
古い解説でよく見かける build/three.js や examples/js/loaders/GLTFLoader.js は、現在の three.js の配布物には含まれていません(r187 で確認。現在は ES モジュール版の build/three.module.js と examples/jsm/ を使います)。
手順2: 表示用スクリプト(view.js)を作る
同じフォルダーに view.js を作ります。ページ内の .my-webgl-viewer 要素ごとに、レンダラー・シーン・カメラ・ライトを用意してモデルを読み込みます。
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
function showMessage( container, text ) {
const p = document.createElement( 'p' );
p.className = 'my-webgl-viewer__message';
p.textContent = text;
container.replaceChildren( p );
}
function initViewer( container ) {
let renderer;
try {
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
} catch ( e ) {
// WebGL が使えない環境では代わりの文を出す。
showMessage( container, 'お使いの環境では3D表示に対応していません。' );
return;
}
renderer.setPixelRatio( Math.min( window.devicePixelRatio, 2 ) );
container.replaceChildren( renderer.domElement );
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 45, 1, 0.01, 1000 );
scene.add( new THREE.HemisphereLight( 0xffffff, 0x444444, 2 ) );
const sun = new THREE.DirectionalLight( 0xffffff, 2 );
sun.position.set( 3, 5, 4 );
scene.add( sun );
const controls = new OrbitControls( camera, renderer.domElement );
controls.enableDamping = true;
function resize() {
const width = container.clientWidth;
const height = container.clientHeight;
renderer.setSize( width, height, false );
renderer.domElement.style.width = '100%';
renderer.domElement.style.height = '100%';
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
new ResizeObserver( resize ).observe( container );
resize();
new GLTFLoader().load(
container.dataset.src,
( gltf ) => {
const model = gltf.scene;
scene.add( model );
// モデルの大きさに合わせてカメラの位置を決める。
const box = new THREE.Box3().setFromObject( model );
const size = box.getSize( new THREE.Vector3() ).length();
const center = box.getCenter( new THREE.Vector3() );
controls.target.copy( center );
camera.position.copy( center ).add( new THREE.Vector3( size * 0.6, size * 0.4, size * 0.8 ) );
camera.near = size / 100;
camera.far = size * 10;
camera.updateProjectionMatrix();
controls.update();
},
undefined,
() => {
renderer.setAnimationLoop( null );
showMessage( container, '3Dモデルを読み込めませんでした。' );
}
);
// 画面に見えているときだけ描画する。
let visible = false;
new IntersectionObserver( ( entries ) => {
visible = entries[ 0 ].isIntersecting;
} ).observe( container );
renderer.setAnimationLoop( () => {
if ( ! visible ) {
return;
}
controls.update();
renderer.render( scene, camera );
} );
}
document.querySelectorAll( '.my-webgl-viewer' ).forEach( initViewer );
| 部分 | 役割 |
|---|---|
WebGLRenderer |
WebGL で描画する。WebGL が使えない環境では作成時にエラーになるので、try / catch で代わりの文を出す |
HemisphereLight・DirectionalLight |
glTF のマテリアルは光が無いと真っ暗に見えるため、環境光と平行光源を置く |
OrbitControls |
ドラッグで回転、ホイールやピンチで拡大縮小。enableDamping で動きをなめらかにする |
ResizeObserver |
画面幅が変わったら描画サイズとカメラの縦横比を合わせる |
Box3 |
モデル全体を囲む箱から大きさと中心を求め、モデルが画面に収まる位置にカメラを置く |
IntersectionObserver |
要素が画面外にあるときは描画を止め、CPU・GPU とバッテリーの消費を抑える |
手順3: モデルを置いて有効化し、記事に埋め込む
my-webgl-viewerフォルダーをwp-content/plugins/にアップロードします。- 表示したい
.glbファイルをmy-webgl-viewer/models/に置きます。 - 管理画面の「プラグイン」で「My WebGL Viewer」を有効化します。
- 記事の編集画面で「ショートコード」ブロックを追加し、次のように書きます。
[webgl_model src="sample.glb" height="400" alt="アヒルの3Dモデル"]
src には models/ の中のファイル名、height には表示の高さ(px)、alt には読み上げ用の説明を書きます。幅は記事の本文幅に合わせて自動で決まります。
メディアライブラリにアップロードしない理由
WordPress の初期状態では、.glb と .gltf はアップロードを許可されたファイル形式に含まれていません(検証環境の get_allowed_mime_types() で確認)。そのままメディアライブラリにアップロードしようとしても、エラーになってアップロードできません。この記事ではプラグインのフォルダーにモデルを置くことで、この制限を変えずに済ませています。
.gltf と .glb の違い
.gltf は JSON のテキストファイルで、形状のデータ(.bin)や画像を別ファイルとして参照することがあります。その場合は参照先のファイルも同じフォルダーに置く必要があります。.glb はそれらを1つにまとめたバイナリ形式なので、WordPress で扱うなら .glb のほうが管理しやすくなります。
表示を軽くするための注意
- モデルのサイズ: ポリゴン数やテクスチャ画像の解像度が大きいほど、ダウンロードと描画に時間がかかります。Blender などで書き出すときに不要な部分を削り、テクスチャを必要な大きさに縮めてください。
- 圧縮されたモデル: Draco などで圧縮された glTF を読み込むには、
GLTFLoaderに加えてDRACOLoaderの設定が必要です。この記事のコードのままでは読み込めません。 - ピクセル比: 高解像度の画面では描画する画素数が増えるため、
setPixelRatio()で上限を2にしています。 - 1ページあたりの数: モデルごとに WebGL のコンテキストを作るため、1ページに多数並べるとブラウザーの上限に達することがあります。1ページ数個までにとどめてください。
CDN を使わずに配信する場合
この記事では three.js を jsDelivr(CDN)から読み込んでいます。外部への通信を減らしたい場合や、CDN の障害の影響を避けたい場合は、次のファイルをダウンロードしてプラグイン内に置き、wp_register_script_module() の URL を plugins_url() に置き換えます。
build/three.module.jsと、そこから読み込まれるbuild/three.core.jsexamples/jsm/loaders/GLTFLoader.jsと、そこから相対パスで読み込まれるexamples/jsm/utils/BufferGeometryUtils.js・examples/jsm/utils/SkeletonUtils.jsexamples/jsm/controls/OrbitControls.js
アドオンは相対パスでほかのファイルを読み込むため、build/ と examples/jsm/ のフォルダー構成を保ったまま置いてください。three.js のバージョンを上げるときは、本体とアドオンを同じ版にそろえます。
まとめ
- WordPress で WebGL の3D表示を行うなら、three.js と glTF / GLB 形式のモデルを使うのが手軽です。
- three.js は WordPress 6.5 以降のスクリプトモジュール(
wp_register_script_module())で登録すると、インポートマップが自動で出力され、import ... from 'three'がそのまま使えます。 - ショートコードの中で
wp_enqueue_script_module()を呼べば、3D を使うページだけで読み込まれます。 - WebGL 非対応・読み込み失敗時の代わりの表示、画面外での描画停止、モデルの軽量化を組み込んでおくと、閲覧者の環境に左右されにくくなります。
ショートコードの作り方の基本は「WordPressでショートコードを作る方法」、スクリプトの読み込み方は「wp_enqueue_scriptsの使い方」も参考にしてください。