アニメーション
JS(WebGL)で作るガラスの中に浮かぶ写真の3Dクリスタル
2026/02/02
2026/1/28
写真をただ平面として表示するのではなく、
ガラスの中に閉じ込められた立体物として見せることができたら、
写真の印象は大きく変わります。
本デモでは、WebGLを使って透明なガラスオブジェクトを構築し、
その内部に写真が浮かんでいるように見える
3Dクリスタル表現を実装しました。
反射や屈折、環境光の映り込みによって、
写真は単なるテクスチャではなく、
光を受けて存在している“物体”として感じられる構成になっています。
Preview プレビュー
Code コード
body {
margin: 0;
overflow: hidden;
background-color: #020202;
}
canvas {
display: block;
}
import * as THREE from 'https://esm.sh/three@0.170.0';
import { OrbitControls } from 'https://esm.sh/three@0.170.0/examples/jsm/controls/OrbitControls.js';
import { RoundedBoxGeometry } from 'https://esm.sh/three@0.170.0/examples/jsm/geometries/RoundedBoxGeometry.js';
import { RGBELoader } from 'https://esm.sh/three@0.170.0/examples/jsm/loaders/RGBELoader.js';
// ==========================================
// 設定
// ==========================================
// 1. 中央に表示する写真のURL
const PHOTO_URL = 'https://kumonosu.net/wp-content/uploads/2026/01/IMG_1298.jpg.webp';
// 2. 反射させる環境マップ
const ENV_URL = 'https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/soliltude_1k.hdr';
const settings = {
photoScale: 1.2,
envIntensity: 1.5, // 反射の明るさ
rotationSpeed: 0.5
};
// --- SCENE SETUP ---
const scene = new THREE.Scene();
scene.background = new THREE.Color('#020202');
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 4);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// --- 360° ENVIRONMENT (Reflections) ---
const rgbeLoader = new RGBELoader();
rgbeLoader.load(ENV_URL, (texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
scene.environmentIntensity = settings.envIntensity;
});
// --- STARK LIGHTS ---
const arcBlue = new THREE.PointLight(0x00ffff, 8, 15);
arcBlue.position.set(2, 3, 4);
scene.add(arcBlue);
const ironRed = new THREE.PointLight(0xff0000, 6, 15);
ironRed.position.set(-3, -2, 3);
scene.add(ironRed);
const group = new THREE.Group();
scene.add(group);
// --- PHOTO OBJECT ---
const photoMat = new THREE.MeshStandardMaterial({
side: THREE.DoubleSide,
roughness: 0.2,
metalness: 0.2
});
const photoMesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), photoMat);
group.add(photoMesh);
const textureLoader = new THREE.TextureLoader();
textureLoader.load(PHOTO_URL, (tex) => {
tex.colorSpace = THREE.SRGBColorSpace;
photoMat.map = tex;
const aspect = tex.image.width / tex.image.height;
const s = settings.photoScale;
if (aspect > 1) {
photoMesh.scale.set(s, s / aspect, 1);
} else {
photoMesh.scale.set(s * aspect, s, 1);
}
});
// --- GLASS OBJECT ---
const glassMat = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
transmission: 1.0,
thickness: 1.0,
ior: 1.5,
roughness: 0.0,
metalness: 0.1,
transparent: true,
envMapIntensity: 2.2
});
const glassMesh = new THREE.Mesh(new RoundedBoxGeometry(2.1, 2.1, 0.8, 32, 0.2), glassMat);
group.add(glassMesh);
// --- ANIMATION LOOP ---
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const time = clock.getElapsedTime();
group.rotation.y += 0.005 * settings.rotationSpeed;
group.rotation.x = Math.sin(time * 0.4) * 0.1;
arcBlue.intensity = 6 + Math.sin(time * 2) * 2;
controls.update();
renderer.render(scene, camera);
}
animate();
window.onresize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
Explanation 詳しい説明
仕様
本デモは、Three.jsを用いてWebGLシーンを構築しています。
シーン内には写真用のプレーンと、その前面に配置したガラスオブジェクトを重ねています。
写真はテクスチャとして読み込み、ガラスには MeshPhysicalMaterial を使用することで、透明度、屈折、反射といった物理的な性質を再現しています。
環境マップにはHDRIを使用し、周囲の光景がガラスに映り込むことで、平面的になりがちな透明表現に奥行きを与えています。
光と反射の設計
反射表現を強調するため、環境光に加えて複数のポイントライトを配置しています。
ライトの色と位置を調整することで、ガラス表面に現れる反射が単調にならず、素材としての存在感が感じられるよう設計しています。
オブジェクトをゆっくり回転させることで、反射や屈折の変化が自然に見える点も特徴です。
カスタマイズ
以下の項目を変更することで、見た目を調整できます。
- 写真の差し替え
PHOTO_URLを変更することで、任意の画像を表示できます。 - ガラスの質感
transmission、thickness、ior、roughnessを調整すると、透明感や屈折の強さが変わります。 - 反射の強さ
環境マップの強度やenvMapIntensityを変更することで、映り込みの印象を調整できます。 - 回転速度
回転量を調整することで、静的な展示にも動きのある演出にも対応できます。
注意点
透明マテリアルとHDRIを使用しているため、端末やGPU性能によっては描画負荷が高くなる場合があります。
また、反射表現は環境マップに強く依存するため、HDRIの種類によって印象が大きく変わります。
用途に応じた環境マップの選定が重要です。
まとめ
写真を単なる画像として扱うのではなく、光と素材を持つオブジェクトとして見せることで、表現の幅は大きく広がります。
本デモは、WebGLを使った質感表現の一例として、写真表現を一段引き上げるための実装例です。