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 |