javascript
[js] Three.js를 이용한 웹 환경에서 glb 파일 렌더링 / 상호작용
TTTGGG
2024. 9. 12. 16:10
728x90
반응형
SMALL
// 모달 내에 div를 만들어 sample.glb 파일을 렌더링
// 렌더링한 3D 모델을 마우스 드래그를 통해 회전
// 렌더링한 3D 모델에 더블 클릭을 통해 클릭한 위치 표시 / 표시 제거
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
<script>
let renderer; // 전역 변수로 렌더러를 선언
let scene, camera, model; // 전역 변수로 씬, 카메라, 모델을 선언
const circles = [];
const circleRadius = 0.1; // 원의 반지름 설정
fnTestRenderReady = async function (e) {
$('#TestRender').on('show.bs.modal', function (e) {
$('#TestRender_form')[0].reset(); // 폼 리셋
});
// 모달이 완전히 열린 후 렌더링 초기화
$('#TestRender').on('shown.bs.modal', function () {
initGLBRenderer();
});
// 모달이 닫힐 때 렌더러 제거
$('#TestRender').on('hidden.bs.modal', function () {
if (renderer) {
renderer.dispose(); // 렌더러 자원 해제
renderer.domElement.remove(); // 렌더러 DOM 요소 제거
renderer = null; // 렌더러를 null로 설정
}
});
}
function initGLBRenderer() {
const container = document.getElementById('glbRenderer');
if (renderer) {
console.log('Renderer already exists, skipping initialization.');
return;
}
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild(renderer.domElement);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
camera.position.set(0, 0, 5);
camera.lookAt(0, 0, 0); // 카메라가 모델의 중심을 바라보도록 설정
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5).normalize();
scene.add(light);
const loader = new THREE.GLTFLoader();
loader.load('/Content/img/sample.glb', function (gltf) {
model = gltf.scene;
model.scale.set(3, 3, 3);
model.position.set(0, 0, 0); // 모델 위치 설정
const meshList = [];
model.traverse((child) => {
if (child.type === 'Mesh') {
child.geometry.computeBoundingBox();
child.geometry.computeBoundingSphere();
meshList.push(child); // Mesh만 교차 검사 대상에 추가
}
});
scene.add(model);
console.log("Model Loaded: ", model);
console.log("Mesh List for Raycaster: ", meshList);
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
let angleX = 0, angleY = 0, angleZ = 0;
container.addEventListener('mousedown', function (event) {
isDragging = true;
previousMousePosition = { x: event.clientX, y: event.clientY };
});
container.addEventListener('mousemove', function (event) {
if (isDragging) {
const deltaX = event.clientX - previousMousePosition.x;
const deltaY = event.clientY - previousMousePosition.y;
angleX += deltaY * 0.5;
angleY += deltaX * 0.5;
angleZ += deltaX * 0.25;
model.rotation.x = THREE.MathUtils.degToRad(angleX);
model.rotation.y = THREE.MathUtils.degToRad(angleY);
model.rotation.z = THREE.MathUtils.degToRad(angleZ);
previousMousePosition = { x: event.clientX, y: event.clientY };
}
});
container.addEventListener('mouseup', function () { isDragging = false; });
container.addEventListener('mouseleave', function () { isDragging = false; });
container.addEventListener('dblclick', function (event) {
// 컨테이너의 위치 및 크기 얻기
const rect = container.getBoundingClientRect();
// 마우스 위치를 화면 좌표에서 WebGL 좌표로 변환
const mouse = new THREE.Vector2();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
// Raycaster 생성 및 설정
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera); // 마우스 위치에 따라 광선 설정
// 모든 Mesh 객체들과 circles 배열의 객체들에 대해 교차 검사 수행
const intersects = raycaster.intersectObjects([...meshList, ...circles], true);
if (intersects.length > 0) {
const intersect = intersects[0];
const point = intersect.point;
// 클릭된 위치에 기존 원이 있는지 확인
const existingCircleIndex = circles.findIndex(circle =>
circle.position.distanceTo(point) <= (0.1 + 0.05) // 원의 반지름이 0.1이므로 허용 오차를 반지름 + 여유 범위로 설정
);
if (existingCircleIndex !== -1) {
console.log('Removing existing circle at:', circles[existingCircleIndex].position);
scene.remove(circles[existingCircleIndex]);
circles.splice(existingCircleIndex, 1);
} else {
console.log('Adding new circle at:', point);
const circleGeometry = new THREE.CircleGeometry(0.1, 64); // 원의 반지름을 0.1로 설정
const circleMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000,
depthTest: false, // 다른 객체와의 깊이 검사 비활성화
depthWrite: true // 깊이 쓰기 활성화
});
const circle = new THREE.Mesh(circleGeometry, circleMaterial);
circle.position.copy(point);
circle.position.z += 1.0; // Z 좌표를 모델 앞쪽으로 설정
circle.renderOrder = 10000; // 매우 높은 렌더 순서 설정
scene.add(circle);
circles.push(circle);
}
} else {
console.log('No intersections found.');
}
});
requestAnimationFrame(animate);
}, undefined, function (error) {
console.error('An error occurred while loading the GLB file:', error);
});
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
window.addEventListener('resize', function () {
const width = container.clientWidth;
const height = container.clientHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
});
}
</script>
<div class="modal-body">
<!-- 3D 모델을 렌더링할 div 추가 -->
<div id="glbRenderer" style="width: 100%; height: 300px;"></div>
</div>
결과
![]() |
![]() |
728x90
반응형
LIST