Three.jsライブリーを使った3Dグラフィックスを始めた頃から、球体をガラスまたは鏡のように見せる手法があるのは知っていた。
今回、作成したので整理を兼ねて記録しておく
球体が背景画像を反射しているように見せる6枚の画像を貼り付け、疑似的に表現。
from PIL import Image
import os
# Open the uploaded image
img = Image.open("data/P8178284_01.jpg")
# Assuming the panorama is a horizontal strip, we will divide it into four equal parts for front, back, left, right
# and create two additional parts for top and bottom with a solid color.
# Calculate the width of each part for the horizontal strip
part_width = img.width // 4
part_height = img.width // 4
# Define the parts for the cube map
parts = {
'front': img.crop((part_width, part_height, 2 * part_width, 2 * part_height)),
'back': img.crop((3 * part_width, part_height, 4 * part_height, 2 * part_height)),
'left': img.crop((0, part_height, part_width, 2 * part_height)),
'right': img.crop((2 * part_width, part_height, 3 * part_width, 2 * part_height)),
'top': img.crop((part_width, 0 , 2 * part_width, part_height)),
'bottom' : img.crop((part_width, 2 * part_height, 2 * part_width, 3 * part_height))
}
# Save the parts to the disk
saved_paths = {}
output_dir = 'data/cubemap/'
if not os.path.exists(output_dir):
os.makedirs(output_dir)
for part_name, part_img in parts.items():
part_path = f"{output_dir}{part_name}.jpg"
part_img.save(part_path)
saved_paths[part_name] = part_path
saved_paths
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="keywords" content="3D glass shere" />
<meta name="description" content="Three js glass shere" />
<titleɯD glass shere</title>
<link rel="shortcut icon" href="https://redrb.heteml.net/parts/akaicon.ico">
<link rel="stylesheet" href="css/kl.css" />
<style type="text/css">
<!--
h1 {
color:lime;
text-align:center;
}
#canvas_container {
width:80%;
margin:50px auto;
}
#canvas3dglass {
width:100%;
margin:0px auto;
}
-->
</style>
<script src="https://unpkg.com/stats.js@0.17.0/build/stats.min.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.152.2/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.152.2/examples/jsm/"
}
}
</script>
</head>
<body>
<h1>町田市薬師池公園</h1>
<div id="canvas_container">
<canvas id="canvas3dglass"></canvas>
<script type="module" src ="js/glass.js"> </script>
</div>
//----------------- 2023.12.27
import * as THREE from "three";
import { FontLoader } from "./FontLoader.js";
import { TextGeometry } from "./TextGeometry.js";
import * as BufferGeometryUtils from "three/addons/utils/BufferGeometryUtils.js";
// canvas size
const canvasElement = document.querySelector("#canvas3dglass");
const width = canvasElement.clientWidth;
const height = canvasElement.clientHeight;
// renderer
const renderer = new THREE.WebGLRenderer({
canvas: canvasElement, alpha: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
// renderer.setClearColor(0x000040);
// scene
const scene = new THREE.Scene();
// camera
const camera = new THREE.PerspectiveCamera(45, width / height);
// camera.position.set(0, 0, 1000);
var radiuscamera = width ; // camera radius
var anglecamera = 0; // camera angle
// light
const ambientLight = new THREE.AmbientLight(0xffffff);
scene.add(ambientLight);
// 1辺あたりに配置するオブジェクトの個数
const CELL_NUM = 30;
// 結合用のジオメトリを格納する配列
const boxes = [];
for (let i = 0; i < CELL_NUM; i++) {
for (let j = 0; j < CELL_NUM; j++) {
for (let k = 0; k < CELL_NUM; k++) {
// 立方体個別の要素を作成
const geometrySphere = new THREE.SphereGeometry(2, 2, 2);
// 座標調整
const geometryTranslated = geometrySphere.translate(
100 * (i - CELL_NUM / 2),
100 * (j - CELL_NUM / 2),
100 * (k - CELL_NUM / 2)
);
// ジオメトリを保存
boxes.push(geometryTranslated);
}
}
}
// ジオメトリを生成
const geometry = BufferGeometryUtils.mergeGeometries(boxes);
// マテリアルを作成
const material = new THREE.MeshStandardMaterial({ color: 0xffffff, wireframe: true });
// メッシュを作成
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// ---- sphere
const path = "glassimg";
var bgimg = "P8178284.jpg";
document.getElementById('canvas_container').style.backgroundImage = "url(" + path + "/" + bgimg +")";
document.getElementById('canvas_container').style.backgroundRepeat = "no-repeat";
document.getElementById('canvas_container').style.backgroundPosition = "50% 50%"; // x y
document.getElementById('canvas_container').style.backgroundSize = 100 + "%";
const urls = [
path + '/' + 'right.jpg',
path + '/' + 'left.jpg',
path + '/' + 'top.jpg',
path + '/' + 'bottom.jpg',
path + '/' + 'front.jpg',
path + '/' + 'back.jpg'
];
var cubeTextureLoader = new THREE.CubeTextureLoader();
var textureMirror = cubeTextureLoader.load(urls);
// Mirror -----------
var mirrorGeometry = new THREE.SphereGeometry(200, 200, 30);
var mirrorMaterial = new THREE.MeshPhongMaterial({
envMap: textureMirror,
reflectivity: 1.2
});
var mesh1 = new THREE.Mesh(mirrorGeometry, mirrorMaterial);
scene.add(mesh1);
// text define
const fsize = 50;
const text = 'Yakushiike Park';
const loadert = new FontLoader()
loadert.load('fonts/optimer_regular.typeface.json', function (font) {
const textGeometry = new TextGeometry(text, {
font: font,
size: fsize,
height: 1,
});
const textMaterial = new THREE.MeshPhongMaterial({ color: 0xffd700 });
var textMesh = new THREE.Mesh(textGeometry, textMaterial);
scene.add(textMesh);
textGeometry.center(); // text on center position set
textMesh.position.set(0, -300, 0);
});
tick();
// 毎フレーム時に実行されるループイベントです
function tick() {
// camera move
camera.position.x = radiuscamera * Math.cos(Math.PI / 180 * anglecamera);
camera.position.y = radiuscamera * Math.sin(Math.PI / 180 * anglecamera);
camera.position.z = 1000 + radiuscamera * Math.sin(Math.PI / 180 * anglecamera);
camera.lookAt(new THREE.Vector3(0, 0, 0)); // 中央に向ける
anglecamera += 0.2;
// レンダリング
renderer.render(scene, camera);
requestAnimationFrame(tick);
}; // tick end
// resize
window.addEventListener('resize', onResize);
function onResize() {
camera.aspect = canvasElement.clientWidth / canvasElement.clientHeight;
camera.updateProjectionMatrix();
// --- end
鏡やガラスでなくても、球体に画像を貼り付けるときは出来上がりを意識して原画像を注意して選ぶ。 例えば建物であれば前後左右上下に意識して撮影するとよいと思うが、現実には難しい。疑似的に使えるような写真を意識して撮るのも必要であろう。