본문 바로가기

WebGL

[WebGL + Three.js] GLSL - colors

 

 

Intro.

음.. glsl에 대해 검색하시는 분들이 어떤 용도로 glsl에 대해 찾아볼까 생각하다가 (학술, 실무 etc..) 결론적으로 어찌되었든 그래픽 관련된 이슈 때문이겠지요? 그래서 가장 첫 주제로는 color에 대해 이야기 해 보려합니다. 참고로 테스팅 환경은 Three.js를 기반으로 사용했습니다.

아래 링크들을 통해 테스팅 환경을 구현해 보세요!

https://threejs.org/docs/#api/en/materials/ShaderMaterial

 

three.js docs

 

threejs.org

https://gist.github.com/kylemcdonald/9593057

 

Minimal three.js shader example.

Minimal three.js shader example. GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

다른건 자유입니다만, 저는 아래와 같이 mesh를 짰습니다.

const material = new THREE.ShaderMaterial({
      uniforms: {
      },
      vertexShader: await vertexShader.text(), // vertexShader 파일
      fragmentShader: await fragmentShader.text() // fragmentShader파일
    });

    const geometry = new THREE.PlaneGeometry(1, 1);
    const plane = new THREE.Mesh(geometry, material);
    plane.position.set(0.5, 0.5, 0);
    scene.add(plane)

 

Default Properties

먼저 가장 간단한 vertex Shader와 Fragment Shader를 만들어 보겠습니다.

// vertex_shader.glsl
void main(){
  vec4 localPosition=vec4(position,1.);
  gl_Position=projectionMatrix*modelViewMatrix*localPosition;
}
// fragment_shader.glsl
void main(){
  gl_FragColor = vec4(0,0,0,1.0);
}

 

이상한점을 느끼셨나요? gl_FragColor , position , gl_Position , projectionMatrix , modelViewMatrix 에 대한 선언이 없는데 잘 실행되고 있습니다.

 

이 피쳐는, 사실 Three.js가 템플릿처럼 디폴트로 Shader matrial에 각각의 Shader를 제공 시, 기본적으로 속성들을 넣어준 것입니다.

기본적으로 제공되는 속성들은 아래 링크에서 볼 수 있습니다.

https://threejs.org/docs/index.html#api/en/renderers/webgl/WebGLProgram

 

three.js docs

 

threejs.org

 

 

여기서 기본적으로 많이 쓰는것들에 대해 이야기 해보자면,

  • position - bufferGeometry 속성을 통해 제공받는 3D vector attribute
  • uv - bufferGeometry를 통해 제공받는 텍스쳐 디멘션에 관한 2D vector attribute 
  • gl_position - OpenGL 고유 변수, 여기에 할당되는 데이터가 screen space의 포지션이 됩니다.
  • projection,view,model Matrix - 변환 행렬로, 반.드.시 Projection * view * model * (custom pos) 순으로 연산되어야 합니다. (혹은 Projection * modelView 도 가능)
  • gl_FragColor - 최종 픽셀 색상

정도가 있겠습니다.

 

 

gl_FragColor

자 그럼, 본격적으로 색상을 바꿔 볼까요?

당연하지만 색상을 바꿀거니, vertex shader은 무시하고 fragment shader를 조작해봅시다.

void main(){
  gl_FragColor=vec4(1,0,0,1);
}

 

빨간 화면이 나왔습니다.

조금 프론트엔드를 해보았다~ 하면 직관적으로 알겠지만 gl_FragColor의 4차원 벡터의 4가지 구성요소는 순서대로 rgba와 일치합니다.

단, 그 범위는 정규화되어 0~1까지지만요

glsl color system = (rgb value / 255.0)과 같이 생각하시면 됩니다.

 

여기까지는 뭐 별거 없네 생각하실 수 있습니다만, shader의 모든 코드는 각 픽셀에 대하여 실행됩니다.

 

그렇다는 것은, vertex shader를 통해 위치 정보 혹은 시간 정보를 넘겨 받고, 그에 따라 색상을 다르게 해볼 수 도 있습니다.

 

1. varying

먼저 varying 키워드를 통해 위치 정보를 넘겨 받겠습니다.

일반적으로 varying 변수는 소문자 v를 앞에 붙여서 이름을 짓습니다.

 

//vertex
// uv : 텍스쳐 정보가 0 ~ 1로 매핑된 2차원 벡터 데이터
void main(){
  vec4 localPosition=vec4(position,1.);
  vUvs=uv;
  gl_Position=projectionMatrix*modelViewMatrix*localPosition;
}
varying vec2 vUvs;

void main(){
  
  gl_FragColor=vec4(vUvs,0,1);
 
}

 

물체에 대한 uv정보를 알기에, 위치에 따라 interpolate된 색상을 얻을 수 있습니다.

 

참고로 화면을 보시다시피, 웹에서의 y축이 뒤집어진 영상 좌표계가 아니라 저희가 일반적으로 생각하는 데카르트 좌표계입니다.

2. uniform

vertex , fragment shader에서 전역 변수처럼 활용하기 위해 uniform을 통해 값을 넘겨 색상값을 생성할 수 있습니다.

단, uniform은 I-value이기에, shader내에서 재할당은 불가합니다.

    //main.js
    
    const material = new THREE.ShaderMaterial({
      uniforms: {
        color1: {
          value : new THREE.Vector4(1,1,0,1)
        },
        color2: {
          value : new THREE.Vector4(0,1,1,1)
        }
      },
      vertexShader: await vertexShadder.text(),
      fragmentShader: await fragmentShader.text()
    });

먼저 Shader material에 uniforms 속성을 정의하고 실제 shader에서 쓸 이름을 key로하여 값을 정의합니다.

 

색상 값을 uniform value로 삼았기에, 바로 fragment shader에서 직접 사용합니다.

varying vec2 vUvs;
uniform vec4 color1;
uniform vec4 color2;

void main(){

  gl_FragColor=vec4(
    mix(
      color1,
      color2,
      vUvs.x
    )
  );
}

 

여기서 mix함수의 경우 built-in 함수인데, 기본적으로 mix(a,b,t)라 할 시, a와 b를 비율 t로 섞은 값을 반환합니다.

 

ex) mix(10 , 20 , 0) => 10

ex) mix(0 , 100 , 0.5) => 50

 

저 같은 경우, 노랑색과 cyan 색을 uv의 x좌표에 따라 보간시켰습니다.

그러면 화면 가장 좌측은, x=0일 테니, 순수 노랑색일테고

화면 가장 우측은, x=1 일테니, 순수 cyan 색일겁니다.

 

2. attribute

속성값의 경우, Three.js에선 해당 mesh를 이루는 geometry에 속성을 추가하여 사용가능합니다.

 

먼저 사용할 값을 정의하겠습니다. 다만, Three.js에서 attribute는 bufferAttribute의 인스턴스이기에, 먼저 전처리를 해놓겠습니다.

const colors = [
      new THREE.Color(0xFF0000),
      new THREE.Color(0x00FF00),
      new THREE.Color(0x0000FF),
      new THREE.Color(0x00FFFF),
    ]
    const colorFloats = colors.map(c => c.toArray()).flat();

 

이제 plane을 이루는 planeGeometry인스턴스에 속성을 추가해줍시다.

 r,g,b 3개 묶음이 곧 색상값 1개이므로 Float32BufferAttribute 생성 시, 2번째 인자로 3을 넣어줍시다.

const geometry = new THREE.PlaneGeometry(1, 1);
    geometry.setAttribute('dovigod',
    new THREE.Float32BufferAttribute(colorFloats, 3))

 

이제 vertex shader에서 attribute값을 받아 varying을 통해 fragment shader로 전달합니다.

PlaneGeometry는 디폴트로, width/height segment = 1 , 즉, vertex가 총 4개이니,

Float32BufferAttribute를 순회하며, 각 vertex마다 3개 씩 끊어서 색상정보를 생성할겁니다.

attribute vec3 dovigod;
varying vec3 vColor;

void main(){
  vec4 localPosition=vec4(position,1.);
  vColor=dovigod;
  gl_Position=projectionMatrix*modelViewMatrix*localPosition;
}
varying vec3 vColor;

void main(){
  gl_FragColor=vec4(
    vColor,
    1.
  );
}

 

 

 

 

*P.S) 스크립트 파일에서 길이가 12인 배열을 보냈는데(각 색상 데이터 4개 * 색상별 값 3 = 12), Vertex Shader에서 vec3로 데이터를 받는것은, Three.js 에서 제공하는 BufferAttribute의 경우, 인자로 받는 데이터 크기 만큼 ArrayBuffer를 순차적으로 잘라 각 정점 데이터로 사용할 수 있게 해줍니다. 공식 문서에도 명시 되어있지만, BufferAttribute의 element 개수는 

 

itemSize * numVertices

 

만큼 존재해야합니다. 따라서, 예시의 배열 요소 12개는 BufferAttribute 의 데이터 사이즈 3으로 커팅되어 각 정점에 입력됩니다.

 

따라서, attribute의 데이터 타입이 vec3인 것입니다.

'WebGL' 카테고리의 다른 글

[WebGL] Hemisphere Lighting  (0) 2023.07.09
[WebGL] Ambient light  (0) 2023.07.09
[WebGL + Three] Textures (텍스쳐 다루기)  (0) 2023.07.02
[Graphics] Transformation Pipeline (local ~ screen 좌표계)  (0) 2023.03.19
[WebGL] GLSL - 기초  (2) 2023.03.16