Three.js 최적화 기법

dotoritos·2024년 11월 9일
0
post-thumbnail

최적화 방법들

webgl이나 다른 그래픽을 다루는 대부분의 그래픽스 개발에선 아래와 같은 방법들로 최적화 함
그래픽을 다루는 렌더러들은 drawCall 횟수가 많아지면 많아질수록 병목현상이 생겨 프레임저하와 렉이 발생됨

  • 객체 수 줄이기
  • 간단한 기하학, 재료 사용
  • LOD
  • lazy 로딩
  • 각종 하드웨어 api 사용
  • 셰이더
  • 성능 모니터링 사용
  • 인스턴싱

Mesh * Material = drawCall 횟수
위와 같은 간단한 공식으로 횟수를 구할 수 있음.

let prevTime = performance.now();
let frames = 0;
renderer.info.autoReset = true;
renderer.setAnimationLoop(() => {
    ...

    frames++;
    const time = performance.now();
    if (time >= prevTime + 1000) {
        window._fps = (frames * 1000) / (time - prevTime);
        prevTime = time;
        frames = 0;
    }
});

const updateStats = () => {
    setTimeout(updateStats, 500);
    const info = renderer.info;
    stats.innerText = `
		FPS: ${Math.round(window._fps||0)}
		[Memory]
		Geometries: ${info.memory.geometries}
		Textures: ${info.memory.textures}
		[Render]
		Frame: ${info.render.frame}
		Calls: ${info.render.calls}
		Triangles: ${info.render.triangles}
		Points: ${info.render.points}
		Lines: ${info.render.lines}
	`;
};
updateStats();

출처: get-draw-call-counts

아니면 이렇게 코드 짜서 모니터링 해도 되고.

1. 인스턴싱

같은 MeshrenderState를 바꿔 많은 똑같은 객체를 복사하고 다른위치에 둠. 그리고 drawCall을 그 오브젝트만 발생하게됨
Three.js 에선 InstancedBufferAttribute, 버퍼에 데이터를 넣고 렌더링 시키면 적용된다.

const mesh = new THREE.InstancedMesh( geometry, material, count );

그럼 이런식으로 많은 객체를 생성해서 렌더링 시켜도 프레임 유지가 가능하다.

아 물론 MeshrenderState를 바꿔 많은 똑같은 객체를 복사하고 다른위치에 둔거니 그렇다고 너무 많이 만들면 똑같이 렉은 걸린다.
상식 선 안에서 만들면 되고 디바이스 성능에 따라 백만개 이상의 인스턴싱된 오브젝트들이 무리없이 돌아간다.

자세한 내용은 아래의 링크를 가서 읽어보는것을 추천한다.

webgl-instanced-drawing
rendering-100k-spheres-instantianing-and-draw-calls

2. 배칭, 머지 등의 오브젝트 병합

정적으로 가만히 있을 오브젝트들을 한개의 오브젝트로 만들어서 렌더링 하는 방법
차피 가만히 냅둘건데 합쳐서 그려주는게 훨씬 리소스를 덜 잡아먹는다.

Three.js에서는 Merging으로 부르고 따른데선 뭐 다르게 부르던데 대충 눈치만 보자

하지만 이 방법은 움직이지 않는 물체에 대해 효율적임을 다시 강조하겠음.
움직이는 물체가 합쳐지면 그 물체를 어떻게 렌더링할지 다시 계산하여 버퍼들을 하나로 합치는 연산이 매 프레임마다 이루어지게 된다.

그 외에 합칠 만큼의 메모리도 필요하고 합쳐진 오브젝트들은 Culling이 어려우니
눈치껏 좋은곳에다 쓰면 좋은 방법

Culling:
렌더링 과정에서 렌더링하지 않아도 된다고 판단된 오브젝트들을 제외함
cpu, gpu의 부담을 덜어줄 수 있기에 퍼포먼스적 이득

3. LOD(Level Of Detail)

다들 게임하면서 겪은거다.
멀리있으면 찱흙이 가까이오면 세밀하게 표현되는거

이걸 Three.js에서 쓰는 상황이 생긴다는거 자체가 넌센스긴 하다.

생각보다 간단하다.

const lod = new THREE.LOD();

for( let i = 0; i < 3; i++ ) {
    const geometry = new THREE.IcosahedronGeometry( 10, 3 - i )
    const mesh = new THREE.Mesh( geometry, material );
    lod.addLevel( mesh, i * 10 );
}
scene.add( lod );

Three.js에선 이런식으로 사용하면 된다.

4. lazy loading

오랜 시간동안 로딩이 필요한 지오메트리나 텍스쳐를 천천히 로딩하도록 하는 방법이다.

먼저 저해상도의 텍스쳐를 미리 불러오고 그 뒤에 고해상도 텍스쳐를 가져와 교체하는 방식을 사용하면 된다.

지오메트리 lazy loading은 해상도 낮추는 식의 간단한 방법으론 안되니 Three.js에서 제공하는 SimplifyModifier 를 사용하면 좀 더 단순한 지오메트리를 얻을 수 있다.
네이버에서 제공하는 https://github.com/naver/mesh-simplifier 를 사용해도 괜찮다.

5. 쉐이더 사용하기

Three.js에선 GPU에서 실행되는 GLSL로 작성된 프로그램을 돌려 셰이더를 사용하면 됨.
Three.js에선 RawShaderMaterial, ShaderMaterial를 사용하며
vertexShader, fragmentShader 코드를 작성하여 렌더링 시켜야 되고 C++로 작성하면 됨.

이건 진짜 자유롭게 코드짜서 하는거라 작성을 "잘" 하면 되는데 이건.... 여기서 다루지 않겠습니다.

https://learnopengl.com/Getting-started/Hello-Triangle

여기서 vertexShader, fragmentShader에 대해 기본적인 메커니즘을 얻을 수 있음.

6. webgpu 사용

최근들어 개발자들 사이에선 뜨거운 감자인 webgpu
AI에다 써먹겠다고 난리지만 사실 그래픽스에도 사용 가능하다.

문서: WebGPU_API

three.js 에선 유틸리티 같은 개념으로 지원해주며 three/examples/jsm/misc/GPUComputationRenderer.js 위치에 존재함.

GPUComputationRenderer github

호환성에 관한 이슈가 존재하니 아직은 옵션으로만 사용하자.

그 외 팁

간편하게 결과물을 볼 수 있게 만들어진 애드온들이 존재하는데

  • OrbitControls
  • MapControls

보고있는 물체 혹은 카메라의 방향등을 간편하게 조작 가능하도록 만들어진 애드온
위에서 평면으로 보거나 고정된 방향을 보면서 돌아다니는건 MapControls를 추천하고 그 외엔 OrbitControls를 추천함.
그리고 OrbitControls는 구버전에서 터치줌이나 마우스 줌에 버그나 불편함이 존재함.

webgl에선 line 혹은 linesegment의 굵기는 1px로 무조건 고정됨.
굵은 선이나 스타일이 적용된 선을 원하면 그냥 원통을 그리던가 인스턴스메시 활용하여 작성해야함.

profile
졸려

0개의 댓글