본문 바로가기
Web Development/ThreeJS

[Three.js / Javascript] TextGeometry 글자 사이즈 변경(update)하기(feat. dat-gui)

by 감자맹고우 2022. 6. 13.
728x90
반응형

Three.js로 프로젝트를 진행하는 도중, 텍스트를 출력해야하는 일이 생겼다.

텍스트 출력 자체는 Three.js 의 example이나 레퍼런스들이 꽤 있어서 구현하기가 어렵지 않았는데, 폰트 크기를 사용자가 조절할 수 있도록 UI 기능으로 제공하려고 하니 관련 기능을 찾기가 쉽지 않았다.

 

우선 시도해본 것은 TextGeometry의 size 속성값을 찾아 변경해보는 것이었는데, 콘솔로 찍어 발견한 geometry.parameters.parameters.size 속성 값을 변경해보았지만, 값이 변하더라도 크기가 변하는 그런 쉬운 일은 일어나지 않았다.

 

구글링을 해보고 별 짓을 다 해보았지만, TextGeometry의 size를 update 시켜 줄 기존 기능이나 방안은 없어보였다...

구글링으로 나온 것은 Troika JS / Aframe / Text sprite 등 으로 해보라였는데... 모듈 환경(NodeJS)으로만 제공되어 imports 를 통해 해결해보려고 했음에도 필요한 패키지 문제나 동작하지 않는 이유로 결국 적용에 실패했다.

 

결국 마지막으로 미뤄두었던, 비용이 많이 발생하는 방법을 택하기로 했다.

 

 

[ 해결 방법 ]

 

구글링 결과 ThreeJS는 TextGeometry의 Update 기능이 없다고 하였다.

그래서 geometry를 지우고 다시 생성해주어야 하는데, 정보가 맞는지 확인을 위해 ThreeJS의 Text 예제들을 확인해보니 정말 글자의 변경을 모두 새로 그려주고 그룹에서 지우는 방식으로 구현하고 있었다.

 

나는 TextGeometry 자체를 현재는 ThreeJS 그룹에 추가해서 사용하지 않도록 구현해두었기에, 일단은 Javascript 배열을 사용해서 여러 개의 TextGeometry가 같은 사이즈 처리를 받도록 구현하기로 했다.

 

그리고, dat-gui 라이브러리를 적용해서 UI로 사이즈를 조절할 수 있도록 구현하고, WireframeGeomtry를 통해 삼각형 폴리곤을 보이도록 구현할 예정이다.

 

최종 결과물은 다음과 같다.

 

 

반응형

 

/** 3d-loader.js(자체 구현) **/
import * as THREE from 'three'; //(Spring에서 import 사용은 코드블럭 하단 p.s 참고)
import * as dat from 'dat-gui-master/build/dat';
import {FontLoader} from 'threejs/examples/jsm/loaders/FontLoader.js';

//textGeometry를 담을 배열
let textGroup = new Array();

//GUI 사용을 위해 선언
const FontData = function() {
    this.FONT_SIZE = 100;
}

window.onload = function() {

	//scene, renderer 추가
    const scene = new THREE.Scene();
    const renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true
    });
    
    //renderer 설정
    renderer.setSize(window.innerWidth, window.innerHeight);
    (~~~ 기타 설정 중략 ~~~)
    
    //그려줄 container 설정, JSP 등 HTML에 ID 속성이 container인 곳에 ThreeJS 캔버스 추가됨
    const container = document.querySelector('#container');
    container.appendChild(renderer.domElement);
    
    //카메라 추가
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    scene.add(camera);

	//조명 추가
	const light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.x = 5;
    light.position.y = 10000;
    light.position.z = 5;
    scene.add(light);
    
    //GUI 추가
    const gui = new dat.GUI();
    const fontData = new FontData();
    
    gui
        .add(fontData, 'FONT_SIZE')
        .min(10)
        .max(100)
        .step(10)
        .name('폰트 크기')
        .onChange(
            () => {
                if(textGroup.length > 0) {
                    textGroup.map(e => {
                        refreshText(fontData.FONT_SIZE, scene);
                    });
                }
            }
        );
    
    //Text 추가
    createText("테스트", fontData.FONT_SIZE, scene);
    
    //draw
    const draw = () => {
        renderer.render(scene, camera);
        renderer.setAnimationLoop(draw);  //지속적으로 draw 호출
    }
    
    draw();
}

//Text 생성
const createText = (message, size, scene, color = 0x006699) => {
    const fontLoader = new FontLoader();
    
    async function loadFont() {
        const url = './static/js/~~~/fonts/사용할 폰트.json'; //사용할 폰트 json 파일 위치(예제는 이순신 돋움)
        
        //폰트를 load 후 처리하도록 async await 사용
        const font = await new Promise( (resolve, reject) => {
            fontLoader.load(url, resolve, undefined, reject);
        });
        
        const geometry = new THREE.TextGeometry(message, {
            font, 
            size, 
            height: 1, 
            curveSegments: 4, 
            bevelEnabled: false
        });
        
        //글자 채울 Material 설정
        const fillMaterial = new THREE.MeshPhongMaterial({color});
        const cube = new THREE.Mesh(geometry, fillMaterial);
        
        //폴리곤 보여줄 LineMaterial 설정
        const lineMaterial = new THREE.LineBasicMaterial({color:0xffff00});
        const line = new THREE.LineSegments(
            new THREE.WireframeGeometry(geometry), lineMaterial
        );
        
        //글자와 폴리곤을 그룹으로 묶어줌
        const group = new THREE.Group();
        group.add(cube);
        group.add(line);
        
        scene.add(group);
        
        textGroup.push(group);
    }
    
    loadFont();
}

//Text 재생성
const refreshText = (size, scene, color = 0x006699) => {
    //예외처리
    if(textGroup.length < 1) return;
    
    //기존 개체 index 범위 저장
    const index = textGroup.length;
    
    //기존 textGroup 개체를 새로 생성
    textGroup.map(e => createText(e.children[0].geometry.parameters.text, size, scene, color));
    
    //기존 textGroup 개체만 scene에서 삭제
    for(let i = 0; i < index; i++){
        scene.remove(textGroup[i]);
    }
    
    //textGroup 배열에서 기존 개체 제거
    textGroup.splice(0, index);
}

 

이렇게 구현하면, TextGeometry 재생성을 통해 글자 사이즈가 변경되는 것처럼 보이게 할 수 있다!

필자는 오브젝트도 로드한 후 구현하였기에, 환경 설정에 따라 위의 코드가 정확하게 작동하지 않을 수 있다.

그렇기 때문에, createText와 refreshText 메소드 위주로 참고하면 좋을 것 같다.

추후 오브젝트와 연동하는 기능 구현도 글을 작성할 예정이니 양해 부탁드린다.

 

 

p.s.

Spring 환경에서 JS import 사용은 다음 글을 참고하면 사용할 수 있다.

 

 

[Javascript / Spring Legacy / JSP] Spring Framework에서 자바스크립트 모듈 import 하는 방법 (without NodeJS)

오늘도 어김없이 스프링 프로젝트를 진행하는 도중, Javascript 라이브러리(three.js)를 쓸 일이 생겼다. 최근 Javascript 라이브러리는 NodeJS를 기본사양으로 하기 때문에 import/export로 모듈화하여 사용

devlifetestcase.tistory.com

 

하지만, Three.js는 jsm폴더와 js폴더로 module환경과 기존 환경을 구분하여 제공하고 있다.

그러니 굳이 module 방식을 사용할 필요는 없고, jsm 대신 js폴더의 파일을 jsp에 외부 참조하여 구현하면 된다.

 

p.s.s.

폰트 json 파일은, 하단 링크에서 폰트파일을 선택하여 json 파일로 쉽게 전환할 수 있다.

 

Facetype.js

 

gero3.github.io

 

 

[ 주요 참고 자료 ]

1. https://www.youtube.com/watch?v=CR1csmsBWWI&list=PLe6NQuuFBu7HqxY10b6gNu6iisT2-rZv-&index=5

2. http://www.gisdeveloper.co.kr/?p=10962

3. https://github.com/mrdoob/three.js/blob/master/examples/webgl_geometry_text.html

 

🤞 도움이 되셨기를 바랍니다. 한 번의 클릭과 댓글은 어딘가의 누군가에게 진실로 큰 힘이 됩니다. 🐱‍🏍

 

728x90
반응형

댓글