こんにちは。フロントエンドエンジニアのぼこです。
今回はContentfulというHeadlessCMSに、3Dモデルを入稿してブラウザで表示します。
目次
今回作ったもの
今回はこんな感じのデモサイトを作ってみました(1ページのみで、リンクなどはすべてダミーです)。
CMSから3Dモデルを3つ投稿し、それを切り替えながら表示しています。
なんとなくそれっぽいアニメーションを自己満で付けていますが、今回見た目や動きの解説まではしていないです、すみません。ただリポジトリは公開しておりますので、もし気になる方は見てみてください(全然綺麗なコードじゃないですが……)。
また、それ以外の実装もポイントを抜粋して説明していますので、こちらも詳細はリポジトリを見ていただけますと幸いです。
主な使用技術
Contentful
HeadlessCMSはContentfulを使ってみました。Contentfulを選んだ理由としては、メジャーなHeadlessCMSのなかでも無料プランでも3Dデータを入稿できるからです。
ただし、無料プランではスペースを1つしか作成できないみたいなのでご注意ください(2022年10月時点)。
Next.js
CMS連携や静的ジェネレートがとても簡単なのでNext.jsを使っています。おそらくこの手の構成では現状一番採用率が高いのではないでしょうか。
React Three Fiber
Reactを使うということで、WebGLについてはおなじみReact Three Fiber(以降 r3f)です。僕のブログでも何度か紹介させていただいていますが、本当に手軽で便利なので今回も採用しています。
もちろん通常のThree.jsでも今回のデモの再現は可能です。
パッケージのバージョン
react: 18.2.0
next: 12.2.5
three: r144
@react-three/fiber: 9.32.1
@react-three/drei: 8.8.3
Contentfulに3Dデータを入稿する
まずはContentfulで記事を作成していきます。
Contentfulでは「スペース」という単位でプロジェクトを管理するみたいなので、スペースを新規作成します。もしくは無料プランで初めて使ってみる場合は、ユーザー登録を済ませると自動的に最初のスペースが作られていると思います。
コンテンツモデルの作成
空のスペースができたので、コンテンツモデルを作成します。
コンテンツモデルというのは、例えば
- 記事タイトル
- アイキャッチ画像
- 本文
のような、記事の骨組みとなる構造にあたります。
画面上部のメニューより Content Model を選択し、新しいモデルを作成していきます。
初めての作成なので、コンテンツタイプの作成から始まります。
今回は「クライアントの製品を3Dで紹介する」みたいなイメージで作るので、「製品/product」というコンテンツタイプにします。Nameは表示名なので日本語可で、Api Identefierはこの後APIリクエストの際にidとして使うのでわかりやすい文字列にしておきましょう。
コンテンツタイプの作成ができたら、次はフィールドの追加です。
フィールドというのは、例えば
- テキスト
- リッチテキスト
- 画像
など、データの形式にあたります。
今回は3Dモデルをgltf(glb)ファイルで扱いたいので、「Media」を選択して表示名とIDを設定します。
また、その他に今回は「商品タイトル」と「説明文」をテキストで追加しています。
フィールドが完成したら、画面右上からsaveをしておきましょう(セーブせずに画面遷移しようとするとセーブを促してくれるみたいです)。
記事の投稿
準備ができたので、実際に記事を投稿していきます。画面上部のメニューよりContentを選択し、新しい記事を作成します。
先ほど作成した3DモデルデータフィールドのFile部分にglbファイルをアップロードします。
メディアのアップロードや記事の作成をした後は、画面右のpublishボタンを押して公開します。
Next.jsでContentfulからデータを取得
ここからは実装の説明に移ります。お好きな方法でNext.jsのプロジェクトを作成してください。
Contentfulのクライアント作成
プロジェクトが作成できたら、Contentfulのクライアント用パッケージをインストールします。
npm i contentful
Contentfulからデータを取得するためのクライアントを生成する処理を別ファイルに作成します。
// src/lib/cms.js
import { createClient } from 'contentful';
export const client = createClient({
space: process.env.CF_SPACE_ID,
accessToken: process.env.CF_ACCESS_TOKEN,
});
ここで扱っている2つのパラメータはContentful側で設定するものになります。
Contentfulの管理画面で Settings → API Keys を開くと、デフォルトでBlank1という名前のAPIが設定されていると思います(運用する際には適宜名前を変えると良いと思います)。
APIを選択すると詳細が見られると思いますので、
- Space ID
- Content Delivery API – access token
を控えておきます。
開発用のエディタに戻って、プロジェクトルートに.env(.env.local)ファイルを作成します。
# .env
CF_SPACE_ID=xxxxxxx(Space ID を入れる)
CF_ACCESS_TOKEN=xxxxxxx(access token を入れる)
getStaticPropsで投稿を取得
Next.jsの getStaticProps で、先ほど作成したクライアントの関数を呼び出してデータを取得します。
// src/pages/index.jsx
import { client } from '~/lib/cms';
import { PageIndex } from '~/components/PageIndex';
export default function Index(props) {
return <PageIndex {...props} />;
}
export const getStaticProps = async () => {
const data = await client.getEntries({ content_type: 'product' });
return {
props: { posts: data.items },
};
};
これで投稿の取得が完了し、PageIndexコンポーネント内でデータを受け取れるようになったかと思います。
次からr3fを使って表示していきます。
React Three Fiberで3Dモデルを表示
パッケージをインストールします。
npm i three @react-three/fiber @react-three/drei
以下のようにThree.jsのコンポーネントを作成します。
// src/components/webgl/WebGLCanvas.jsx
import { Suspense } from 'react';
import { Canvas } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
import { ModelWrapper } from '~/components/webgl/ModelWrapper';
export const WebGLCanvas = ({ models }) => {
return (
<div>
<Canvas>
<directionalLight />
<ambientLight intensity={0.5} />
<OrbitControls />
<Suspense>
<ModelWrapper models={models} />
</Suspense>
</Canvas>
</div>
);
};
// src/components/webgl/ModelWrapper.jsx
import { Model } from '~/components/webgl/Model';
export const ModelWrapper = ({ models }) => {
// 省略
return models.map((model, i) => (
<Model
key={model.id}
url={model.url}
/>
));
};
// src/components/webgl/Model.jsx
import { useGLTF } from '@react-three/drei';
export const Model = ({ url }) => {
const { scene } = useGLTF(url);
return <primitive object={scene} />;
};
こんな感じで @react-three/drei が提供する useGLTF に3DモデルのURLを渡せばモデルがロードできます。
また、canvasのコンポーネントで models というデータを受け取ってModelWrapperコンポーネントに渡していますが、データの中身についても説明しておきます。
Contentfulで設定したメディアを取得する際、データの階層がかなり深くなっているので、canvasコンポーネントの呼び出し側でデータをあらかじめ整形しておくと扱いやすいと思います。
// src/components/PageIndex.jsx
export const PageIndex = ({ posts }) => {
const models = posts.map((post) => ({
id: post.sys.id,
url: post.fields.model.fields.file.url, // ← 階層が深い
}));
// 省略
return (
// 省略
<WebGLCanvas models={models} />
);
};
実装の解説は以上です。Next.jsやReact Three Fiberがとても便利なので、3Dデータの取得から表示まではとてもシンプルなコードで実装できました。
実際のリポジトリはコードがかなり複雑になっていますが、ほとんどがアニメーションのコードですね……(笑)。
おわりに
今回はContentfulとNext.jsで3Dモデルを扱ってみました。
まだ実務での実装経験はありませんが、例えばお客さんの製品を3Dで見せたい、といったシーンがあれば使えるかもしれませんね……!
LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。