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: モデルを置いて有効化し、記事に埋め込む

  1. my-webgl-viewer フォルダーを wp-content/plugins/ にアップロードします。
  2. 表示したい .glb ファイルを my-webgl-viewer/models/ に置きます。
  3. 管理画面の「プラグイン」で「My WebGL Viewer」を有効化します。
  4. 記事の編集画面で「ショートコード」ブロックを追加し、次のように書きます。
[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.js
  • examples/jsm/loaders/GLTFLoader.js と、そこから相対パスで読み込まれる examples/jsm/utils/BufferGeometryUtils.js・examples/jsm/utils/SkeletonUtils.js
  • examples/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の使い方」も参考にしてください。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲