본문 바로가기
FrontEnd/THREE.js

3D Computer Graphics (4)

by oncerun 2023. 4. 15.
반응형

Three.js에서의 포스트 프로세싱을 적용하는 예제 코드를 보면 Three.js에서 제공하는 여러 객체들이 존재했다. 

 

 

FilePass, GlitchPass, OutlinePass 이들의 구현체를 조금 살펴보자.

 

import {
	ShaderMaterial,
	UniformsUtils
} from 'three';
import { Pass, FullScreenQuad } from './Pass.js';
import { FilmShader } from '../shaders/FilmShader.js';

class FilmPass extends Pass {

	constructor( noiseIntensity, scanlinesIntensity, scanlinesCount, grayscale ) {

		super();

		if ( FilmShader === undefined ) console.error( 'THREE.FilmPass relies on FilmShader' );

		const shader = FilmShader;

		this.uniforms = UniformsUtils.clone( shader.uniforms );

		this.material = new ShaderMaterial( {

			uniforms: this.uniforms,
			vertexShader: shader.vertexShader,
			fragmentShader: shader.fragmentShader

		} );

		if ( grayscale !== undefined )	this.uniforms.grayscale.value = grayscale;
		if ( noiseIntensity !== undefined ) this.uniforms.nIntensity.value = noiseIntensity;
		if ( scanlinesIntensity !== undefined ) this.uniforms.sIntensity.value = scanlinesIntensity;
		if ( scanlinesCount !== undefined ) this.uniforms.sCount.value = scanlinesCount;

		this.fsQuad = new FullScreenQuad( this.material );

	}

	render( renderer, writeBuffer, readBuffer, deltaTime /*, maskActive */ ) {

		this.uniforms[ 'tDiffuse' ].value = readBuffer.texture;
		this.uniforms[ 'time' ].value += deltaTime;

		if ( this.renderToScreen ) {

			renderer.setRenderTarget( null );
			this.fsQuad.render( renderer );

		} else {

			renderer.setRenderTarget( writeBuffer );
			if ( this.clear ) renderer.clear();
			this.fsQuad.render( renderer );

		}

	}

	dispose() {

		this.material.dispose();

		this.fsQuad.dispose();

	}

}

export { FilmPass };

 

 

코드 부분을 보면 shader 코드를 설정하는 부분이 있다.

const shader = FilmShader;

this.uniforms = UniformsUtils.clone( shader.uniforms );

this.material = new ShaderMaterial( {

    uniforms: this.uniforms,
    vertexShader: shader.vertexShader,
    fragmentShader: shader.fragmentShader

} );

 

vertexShader, fragmentShader를 glsl로 작성하여 물체의 material을 만들 수 있다는 이야기다. 

이때 필요한 공유 값은 uniforms로 넘겨주어 원하는 대로 만들 수 있다는 점. 

 

 

그냥 3D를 로드하고 3D 객체를 만들고 조작하는 것을 넘어서 화려하고 인터랙티브 한 효과를 주기 위해선

결국 shader라는 것이 필요하다는 것을 인지했다. 

 

 

OpenGL은 그래픽 라이브러리이다.  이는 하드웨어와 운영체제에 제약을 받지 않는 범용적인 API 프로그래밍을 위한 것이다. 

3D에 관심을 가지게 됬다면 DirectX를 들어보았을 것이다. Microsoft에서 독자설루션으로 선보인 게임 그래픽용 그래픽 라이브러리다.  

 

하지만 이러한 그래픽 라이브러리는 API가 매우 방대하고 많은 양의 지식 없이는 사용하기가 까다롭다. 

 

그래서 실제 몇 가지 잘 사용되지 않는 API를 제거한 OpenGl for Embedded Systems가 생겼고 이는 보통 안드로이드, iOS, Web 환경에서 주로 그래픽 가속을 위해 사용된다. 

 

Three.js 구조는 OpenGL ES를 기반으로 만들어진 WebGL을 한 번 더 추상화하여 우리에게 편리한 API를 제공해 준다. 

 

WebGL의 코드를 잠시 살펴보자. 

 

 const container = document.querySelector('#container');
  const canvas = document.createElement('canvas');
  canvas.width = 300;
  canvas.height = 300;

  container.appendChild(canvas);

  const gl = canvas.getContext('webgl');

HTML5 캔버스를 사용하고 캔버스에서 webgl 컨텍스트를 가져오는 것부터 시작한다.

 

GLSL은 OpenGL에서 사용되는 shader 언어이다.

 

과거에는 CPU 기반의 렌더링 방식이 사용되었기에 GLSL과 같은 Shader 언어가 필수적인 기술은 아니었다.

 

하지만 점차 그래픽 처리의 하드웨어 가속화 기술이 발전하면서 그래픽 처리와 성능을 극대화하기 위한 shader 언어가 등장하면서 렌더링 파이프 라인에서 필수적인 부분이 되었다. 

 

OpenGL에서는 GLSL이라는 shader 언어를 지원하기 때문에 이를 위해 우리는 가장 기본적으로 두 가지의 프로그래밍을 해주어야 한다.

 

바로 Vertext Shader와 Fragment Shader이다. 

 

 

  const vertexShader = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(
    vertexShader,
    `
    attribute vec2 position;
    varying vec2 vPosition;

    void main() {
      vec2 newPosition = (position + 1.0) / 2.0; // 0~1
      gl_Position = vec4(position, 0.0, 1.0);

      vPosition = newPosition;
    }
  `
  );
  gl.compileShader(vertexShader);

 

 

clip space까지 책임지는 vertext shader를 프로그래밍해주어야 한다. 

 

  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(
    fragmentShader,
    `
    precision mediump float;
    varying vec2 vPosition;

    void main() {
      gl_FragColor = vec4(vPosition, 0.0, 1.0);
    }
  `
  );
  gl.compileShader(fragmentShader);

 

이제 우리는 Fragment Shader를 작성하고 컴파일한다. 실제 아직도 적응되지 않는 게, 이 모든 것이 병렬프로그래밍의 한 부분이라는 것이다. 한 줄의 코드가 모든 픽셀에 적용된다는 것이 적응하기 힘든 부분이긴 하다. 

 

 

끝이 아니다.

 

const program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);

  gl.linkProgram(program);
  gl.useProgram(program);

 

vertex shader와 fragment shader를 하나의 program으로 만들어 주는 과정이 필요하다. 

 

 

  const vertices = new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]);
  const vertexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

 

그다음으로는 정점 데이터를 넘겨주어야 한다. 32비트를 가지는 부동 소수점 배열을 사용하며 이를 통해 버퍼라는 것을 만들어 해당 버터에 데이터를 넘겨줘야 한다. 

 

OpenGL ES를 살짝 공부할 때 vertex Array에는 수많은 정보가 들어간다고 배웠다. 

예를 들어 정점 좌표, 정점에 대한 노말 벡터, 그리고 텍스처 좌표 등등... 

 

  const position = gl.getAttribLocation(program, 'position');
  gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(position);

넘겨준 정점을 어떻게 해석하고 계산해야 하는지 정보를 주어야 한다. 

 

 

gl.drawArrays(gl.TRIANGLES, 0, 6);

이제 그리면 된다. 

 

 

이렇게 그려진 결과를 Three.js를 사용하면 매우 간단한 코드 몇 줄로 줄일 수 있다. 

 

즉 Three.js은 WebGL을 쉽게 이용할 수 있게 해주는 라이브러리라는 것이다. 

 

여기까지 좋다. 다만 함정이 숨겨져 있다. 

 

우리가 원하는 것은 멋지고 인터랙티브 하고 성능도 좋은 3D 인터랙티브를 원한다.

 

그러면 Three.js API만으로는 근처는 가능하지만 목표에 도달하기는 힘들다. 

 

즉 우리는 shader라는 것을 작성해야 한다. 그래서 Three.js는 shader를 사용하도록 몇 가지 도움을 미리 주었기에

개발자는 각 vertex shader, fragment shaer 코드만 작성하면 된다. 

 

이는 매우 감사한 일이다. 

 

또 다른 함정은 용어, 수학이다. 

 

API 문서를 읽어보았다면 매우 당황했을 수도 있다. 처음에 나는 내가 글을 못 읽는 줄 알았다. 

우선 수학적인 용어가 매우 많고 숨겨져 있다. 왜?라는 질문이 들어가는 순간 수학 공부하고 있는 자신을 발견할 수 있다. 

 

그래도 사용하는 수학적 이론이나 계산식을 별도의 알고리즘을 작성할 것이 아니라면 널리 사용되는 알고리즘을 한 번 정확히 이해하고 다음부터 복사 붙여 넣기 하는 식으로 가는 것이 도움이 될 듯해 보인다. 

 

 

 

취미로 3D 그래픽을 할 것인데 OpenGL부터 시작하고 싶지는 않다. 흥미는 중요하기 때문에 그래서 역방향으로 공부하려고 한다. 

 

Three.js API를 활용하면서 왜? 라는 질문을 하고 점진적으로 low level로 내려가는 것이 올바른 것 같다. 

이를 실제 실무에 조금씩 적용하면서, 이러한 환경이 있음에 감사하면서 발전해 나갈 생각이다. 

 

 

 

 

 

반응형

'FrontEnd > THREE.js' 카테고리의 다른 글

Three.js 라이브러리를 확장한 객체지향 구조  (0) 2023.04.26
물리공간 구축  (0) 2023.04.24
Post Processing  (0) 2023.04.13
Setting  (0) 2023.04.11
추적하기.  (0) 2023.03.22

댓글