https://webglfundamentals.org/webgl/lessons/webgl-shaders-and-glsl.html
https://github.com/Myoungmin/WebGL_Fundamentals
WebGL Shaders and GLSL
vertex shader와 fragment shader는 함께 program으로 연결된다.
일반적인 WebGL 앱은 많은 program을 가진다.
Vertex Shader
vertex shader의 역할은 클립 공간 좌표를 생성하는 것이다
shader는 vertex마다 한 번씩 호출된다.
호출될 때마다 특수 전역 변수, gl_Position을 특정한 클립 공간 좌표로 설정해야 한다.
vertex shader가 데이터를 가져오는 3가지 방법.
- Attributes : buffer에서 가져온 데이터
- Uniforms : single draw가 호출될 때 모든 vertex에서 동일하게 유지되는 값, 전역변수 처럼 사용한다.
- Textures : pixels/texels의 데이터
1. Attributes
vertex shader가 데이터를 가져오는 가장 일반적인 방법은 buffer와 attribute를 통하는 것이다.
1) 먼저 buffer를 생성한다.
var buf = gl.createBuffer();
2) buffer에 데이터를 넣는다.
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, someData, gl.STATIC_DRAW);
3) shader program을 통해 초기화 시 attribute 위치를 찾는다.
var positionLoc = gl.getAttribLocation(someShaderProgram, "a_position");
4) Rendering할 때 해당 buffer에서 attribute로 데이터를 어떻게 가져올지 WebGL에 지시
// 이 속성의 버퍼에서 데이터 가져오기 활성화
gl.enableVertexAttribArray(positionLoc);
var numComponents = 3; // (x, y, z)
var type = gl.FLOAT; // 32비트 부동 소수점 값
var normalize = false; // 값 원본 그대로 유지
var offset = 0; // 버퍼의 처음부터 시작
var stride = 0; // 다음 정점으로 가기 위해 이동하는 바이트 수
// 0 = `type`과 `numComponents`에 맞는 스트라이드 사용
gl.vertexAttribPointer(positionLoc, numComponents, type, false, stride, offset);
5) 클립 공간 vertex를 buffer에 넣으면, attribute를 통하여 vertex shader에 정상적인 위치에 대한 데이터를 넘겨줄 수 있다.
2. Uniforms
그리기 호출의 모든 vertex에 대해 동일하게 유지되며 shader에 전달되는 값.
1) 오프셋 값을 추가하기 위해 vertex shader에서 uniform 선언
attribute vec4 a_position;
uniform vec4 u_offset;
void main() {
gl_Position = a_position + u_offset;
}
2) 초기화 시 uniform의 위치를 찾는다.
var offsetLoc = gl.getUniformLocation(someProgram, "u_offset");
3) 그리기 전에 uniform을 설정한다.
gl.uniform4fv(offsetLoc, [1, 0, 0, 0]); // 화면 오른쪽 절반으로 오프셋
uniform은 개별 shader program에 속한다.
만약 이름이 같은 uniform을 가진 shader program이 여러 개 있다면,
두 uniform 모두 고유한 위치와 값을 가진다.
그래서 gl.uniform???을 호출하면 현재 program의 uniform만 설정한다.
현재 program은 gl.useProgram에 전달한 마지막 프로그램이다.
4) uniform은 여러 타입을 가질 수 있고, 각 타입마다 설정을 위해 gl.uniform???으로 해당하는 함수를 호출해야 한다.
bool, bvec2, bvec3, bvec4 타입도 있고, gl.uniform?f? 또는 gl.uniform?i? 함수를 사용한다.
5) 배열의 경우 배열의 모든 uniform을 한번에 설정할 수 있다.
// 셰이더
uniform vec2 u_someVec2[3];
// 초기화 시 자바스크립트
var someVec2Loc = gl.getUniformLocation(someProgram, "u_someVec2");
// 렌더링할 때
gl.uniform2fv(someVec2Loc, [1, 2, 3, 4, 5, 6]); // u_someVec2의 전체 배열 설정
6) 배열의 개별 요소를 설정하고 싶다면 각 요소의 위치를 개별적으로 찾아야 한다.
// 초기화 시 자바스크립트
var someVec2Element0Loc = gl.getUniformLocation(someProgram, "u_someVec2[0]");
var someVec2Element1Loc = gl.getUniformLocation(someProgram, "u_someVec2[1]");
var someVec2Element2Loc = gl.getUniformLocation(someProgram, "u_someVec2[2]");
// 렌더링할 때
gl.uniform2fv(someVec2Element0Loc, [1, 2]); // 요소 0 설정
gl.uniform2fv(someVec2Element1Loc, [3, 4]); // 요소 1 설정
gl.uniform2fv(someVec2Element2Loc, [5, 6]); // 요소 2 설정
7) 구조체를 생성하면, 각 필드를 개별적으로 찾아야 한다.
struct SomeStruct {
bool active;
vec2 someVec2;
};
uniform SomeStruct u_someThing;
개별적으로 위치 확인
var someThingActiveLoc = gl.getUniformLocation(someProgram, "u_someThing.active");
var someThingSomeVec2Loc = gl.getUniformLocation(someProgram, "u_someThing.someVec2");
Fragment Shader
fragment shader의 역할은 래스터화되는 현재 픽셀의 색상을 제공하는 것이다.
fragment shader는 각 픽셀마다 한 번씩 호출된다.
호출될 때마다 특수 전역 변수, gl_FragColor를 어떤 색상으로 설정해줘야 한다.
fragment shader가 데이터를 가져오는 3가지 방법.
- Uniforms : single draw가 호출될 때 모든 vertex에서 동일하게 유지되는 값, 전역변수 처럼 사용한다.
- Textures : pixels/texels의 데이터
- Varyings : vertex shader에서 전달되고 보간된 데이터
1. Textures in Fragment Shaders
1) shader의 texture에서 값을 가져오면 sampler2D uniform을 생성하고, 값을 추출하기 위해 GLSL 함수 texture2D를 사용한다.
precision mediump float;
uniform sampler2D u_texture;
void main() {
vec2 texcoord = vec2(0.5, 0.5) // 텍스처 중앙에서 값 가져오기
gl_FragColor = texture2D(u_texture, texcoord);
}
2) texture에서 나오는 데이터는 수많은 설정에 따라 달라진다.
아래는 최소한의 texture 데이터를 생성하고 넣는 예시이다.
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
var level = 0;
var width = 2;
var height = 1;
var data = new Uint8Array([
255, 0, 0, 255, // 빨강 픽셀
0, 255, 0, 255, // 초록 픽셀
]);
gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
3) 초기화 시 shader program의 uniform 위치를 찾는다.
var someSamplerLoc = gl.getUniformLocation(someProgram, "u_texture");
4) Rendering할 때 texture unit에 texture를 바인딩한다.
var unit = 5; // 텍스처 유닛 선택
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_2D, tex);
5) texture를 바인딩한 unit이 무엇인지 shader에 알려준다.
gl.uniform1i(someSamplerLoc, unit);
2. Varyings
vertex shader에서 fragment shader로 값을 전달하는 방법.
varying을 사용하려면 vertex shader와 fragment shader 양쪽에 서로 동일한 varying을 선언해야 한다.
각 vertex마다 vertex shader의 varying에 특정 값으로 설정하고, WebGL이 픽셀을 그릴 때 이 설정한 값들 사이를 보간한다.
fragment shader에서 보간의 결과값이 대응되는 varying으로 전달된다.
GLSL
- Graphics Library Shader Language의 약자로 shader가 작성되는 언어이다.
- 그래픽 래스터화에 일반적으로 필요한 수학적 계산을 수행하도록 설계되었다.
- 예를 들어 각각 2개의 값, 3개의 값, 4개의 값을 나타내는 vec2, vec3, vec4 같은 타입들이 내장되어 있다.
- 마찬가지로 2x2, 3x3, 4x4 행렬을 나타내는 mat2, mat3, mat4가 있다.
https://myoungmin.github.io/WebGL_Fundamentals/