webgl-3d-object について
Three.jsでWebレイアウトに実際の3Dオブジェクトを配置するためのデザインスキルです。ジオメトリ、マテリアル、ライティング、動き、後処理の指針を示します。
主な機能
- 01Three.jsのジオメトリと透視カメラ
- 02PBRマテリアルとライティング設定
- 03控えめなトランスフォームアニメーション
- 04リサイズと後処理の指針
活用シーン
- 01ヒーロー領域への3Dオブジェクト追加
- 02多面体による製品機能の表示
- 03実際のライティングを使った浮遊オブジェクト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mengto/ skills --skill webgl-3d-objectインストール後、Claude Code で/webgl-3d-objectを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
demo/index.htmlPROMPT.mdsource.json詳細
WebGL 3D オブジェクト
使用する場面
- ヒーロー、機能紹介ブロック、または製品を印象づける箇所に、存在感のある 3D オブジェクトが必要なとき。
- 実際のジオメトリ、ライティング、ハイライト、エッジを表現したいとき。
- ファセット加工されたメッシュを、ウェブレイアウト内で浮遊またはゆっくり回転させたいとき。
- CSS 変形、SVG による錯視、フラットなグラデーションでは不十分なとき。
ルール
- 実際の 3D ジオメトリを使用します。
IcosahedronGeometry、DodecahedronGeometry、BoxGeometry、カスタムのBufferGeometry、または glTF メッシュを使います。 - オブジェクトに奥行きとスケール感を与えるため、透視カメラを使用します。
- PBR マテリアルを使用します。
MeshStandardMaterialまたはMeshPhysicalMaterialを使います。 - ブランドの雰囲気に合わせて、
metalness、roughness、emissiveを調整します。 - 少なくとも 1 つのディレクショナルライトに、アンビエントライトまたはヘミスフィアライトによる補助光を加えて照明します。
- 変形のみをアニメーションさせます。わずかな回転、上下運動、または視差にとどめます。
- リサイズに対応し、クリーンアップ時にはジオメトリ、マテリアル、レンダラーを破棄します。
HTML と CSS
<div class="webgl-object-shell">
<canvas class="webgl-object-canvas" data-webgl-3d-object></canvas>
</div>
.webgl-object-shell {
position: relative;
width: min(100%, 720px);
aspect-ratio: 1 / 1;
}
.webgl-object-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
}
Three.js オブジェクトの作成手順
import * as THREE from "three";
function initWebGL3DObject(canvas, options = {}) {
if (!canvas) return () => {};
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
alpha: true,
});
renderer.setClearColor(0x000000, 0);
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, options.maxDpr || 1.75));
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = options.exposure || 1.05;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(38, 1, 0.1, 100);
camera.position.set(0, 0.15, 5.2);
const geometry = new THREE.IcosahedronGeometry(options.radius || 1.35, options.detail || 1);
const material = new THREE.MeshStandardMaterial({
color: options.color || 0x8aa4ff,
metalness: options.metalness ?? 0.48,
roughness: options.roughness ?? 0.34,
emissive: options.emissive || 0x101833,
emissiveIntensity: options.emissiveIntensity ?? 0.22,
flatShading: true,
});
const object = new THREE.Mesh(geometry, material);
object.castShadow = true;
object.receiveShadow = true;
scene.add(object);
const ambient = new THREE.AmbientLight(0xffffff, 0.38);
scene.add(ambient);
const key = new THREE.DirectionalLight(0xffffff, 2.15);
key.position.set(3.4, 4.2, 4.8);
key.castShadow = true;
key.shadow.mapSize.set(1024, 1024);
scene.add(key);
const rim = new THREE.DirectionalLight(options.rimColor || 0x7dd3fc, 0.82);
rim.position.set(-4.2, 1.2, -2.8);
scene.add(rim);
const shadowPlane = new THREE.Mesh(
new THREE.PlaneGeometry(5.2, 5.2),
new THREE.ShadowMaterial({ opacity: 0.18 })
);
shadowPlane.position.set(0, -1.65, 0);
shadowPlane.rotation.x = -Math.PI / 2;
shadowPlane.receiveShadow = true;
scene.add(shadowPlane);
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
let rafId = 0;
function resize() {
const width = Math.max(1, canvas.clientWidth);
const height = Math.max(1, canvas.clientHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, options.maxDpr || 1.75));
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
function render(time = 0) {
const t = time * 0.001;
object.rotation.x = -0.16 + Math.sin(t * 0.45) * 0.06;
object.rotation.y = t * 0.28;
object.rotation.z = Math.sin(t * 0.32) * 0.08;
object.position.y = reduceMotion ? 0 : Math.sin(t * 0.8) * 0.08;
renderer.render(scene, camera);
if (!reduceMotion) rafId = requestAnimationFrame(render);
}
function handleResize() {
cancelAnimationFrame(rafId);
resize();
render();
}
resize();
render();
window.addEventListener("resize", handleResize);
return () => {
cancelAnimationFrame(rafId);
window.removeEventListener("resize", handleResize);
geometry.dispose();
material.dispose();
shadowPlane.geometry.dispose();
shadowPlane.material.dispose();
renderer.dispose();
};
}
const cleanupObject = initWebGL3DObject(
document.querySelector("[data-webgl-3d-object]"),
{
color: 0x8aa4ff,
rimColor: 0x7dd3fc,
metalness: 0.48,
roughness: 0.34,
emissive: 0x101833,
}
);
マテリアルの初期値
- 高級感のある金属:
metalness: 0.45-0.7、roughness: 0.25-0.45。 - ソフトなセラミック:
metalness: 0.0-0.15、roughness: 0.38-0.62。 - 発光色をまとったテック系オブジェクト:低めの
emissiveとemissiveIntensity: 0.12-0.35を設定します。 - ファセット加工されたオブジェクト:
flatShading: trueを設定します。滑らかな製品オブジェクトではfalseを設定します。
ライティングの初期値
- キーライト:ディレクショナルライトを前面寄りの高い角度に配置し、最も強い光源にします。
- アンビエントライト:影が見える程度の低い強度にします。
- リムライト:ブランドカラーまたは背後からの寒色系の光で、エッジを際立たせます。
- 影:オブジェクトに接地感のある奥行きが必要な場合にのみ有効にし、シャドウマップのサイズは適度に保ちます。
モーションの初期値
- 回転:ゆっくりと途切れなく動かし、ページコンテンツより目立たないようにします。
- 浮遊:Y 軸上で
0.04~0.12単位にします。 - 動きを減らす設定:静止フレームを描画するか、直接操作のみを許可します。
- オブジェクトが主なインタラクション対象でない限り、カメラの移動は避けます。
避けること
- WebGL を使っているように見せかける CSS 3D 変形。
- 実際のライティングと奥行きが求められている場面で、ライティングされないマテリアルを使うこと。
- 実際のジオメトリの代わりに、グラデーションを使った平面を使うこと。
- 形状が見えにくくなるほど強いブルームやパーティクル。
- モバイルで高すぎる DPR、大きすぎるシャドウマップ、または多すぎるライトを使うこと。
- オブジェクトを前景のコピーや CTA と競合させること。
簡易チェック
- オブジェクトの形状、エッジ、ハイライト、影が見えます。
- マテリアルで
metalness、roughness、必要に応じてemissiveが使われています。 - ディレクショナルライトとアンビエントライトの両方が用意されています。
- カメラは意図せず正投影になっておらず、透視投影になっています。
- リサイズしてもオブジェクトが引き伸ばされません。
- ジオメトリ、マテリアル、イベントリスナー、RAF、レンダラーがクリーンアップされます。
コメント
Markdown 対応