본문 바로가기

Graphics/webgl

[webgl] Hello World

중요한것은 버텍스 쉐이더와 프래그먼트 쉐이더

그리고 GPU는 어떤함수를 호출해서 이 두개의 함수를 호출.

  • 버텍스 쉐이터: 공간좌표
  • 프레그먼트쉐이더: 색상

버텍스 쉐이더 (GLSL) 함수를 작성해보자

결국 GPU가 이것을 호출하니깐,

버텍스 쉐이터

#version 300 es // 이거있어야 WebGL2

// GPU가 버텍스쉐이더를 실행하는데 사용되는 데이터
// (는 버퍼를 통해 받아서 여기에 저장 해놓을거임)
in vec4 a_position; // 대충 점의 위치임

// 모든 쉐이더는 main 함수를 가지고 있습니다.
void main() {

  // gl_Position는 버텍스 쉐이더의 미리정의되어 있는 내장 변수입니다.
  // 여기에 값을 넣어주면 버텍스 쉐이더에서 쓸 준비 끝!
  gl_Position = a_position;
}

프레그먼트 쉐이더

#version 300 es

// 프래그먼트 쉐이더는 기본 정밀도를 가지고 있지 않으므로 선언을 해야함.
// mediump은 기본값으로 적당합니다. "중간 정도 정밀도"를 의미합니다.
precision mediump float;

// 프래그먼트 쉐이더(fragment shader)에서 출력을 선언 해야합니다.
out vec4 outColor;

void main() {
  // 붉은-보라색으로 출력하게 설정합니다.
  outColor = vec4(1, 0, 0.5, 1);
}

걍 변수로 색상을 등록해 놓으면 출력이 된다고? return 도 없고, 내장 함수도 아닌데.. ?

하여튼 된다고 하니 일단 넘어간다.

근데 우리는 자바스크립트에서 사용할거니깐 GLSL를 백틱문자를 이용해 템플릿스트링으로 만들어준다.

var vertexShaderSource = `#version 300 es

in vec4 a_position;

void main() {

  gl_Position = a_position;
}
`;

var fragmentShaderSource = `#version 300 es

precision mediump float;

out vec4 outColor;

void main() {
  outColor = vec4(1, 0, 0.5, 1);
}
`;

그리고 위 코드에 아래의 코드를 추가 하면 완성. 왜이렇게 길어 ㅋㅋㅋ어이없네

function createShader(gl, type, source) {
  var shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) {
    return shader;
  }

  console.log(gl.getShaderInfoLog(shader));  // eslint-disable-line
  gl.deleteShader(shader);
  return undefined;
}

function createProgram(gl, vertexShader, fragmentShader) {
  var program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  var success = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    return program;
  }

  console.log(gl.getProgramInfoLog(program));  // eslint-disable-line
  gl.deleteProgram(program);
  return undefined;
}

function main() {
  // Get A WebGL context
  var canvas = document.getElementById("c");
  var gl = canvas.getContext("webgl2");
  if (!gl) {
    return;
  }

  // create GLSL shaders, upload the GLSL source, compile the shaders
  var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
  var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

  // Link the two shaders into a program
  var program = createProgram(gl, vertexShader, fragmentShader);

  // look up where the vertex data needs to go.
  var positionAttributeLocation = gl.getAttribLocation(program, "a_position");

  // Create a buffer and put three 2d clip space points in it
  var positionBuffer = gl.createBuffer();

  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  var positions = [
    0, 0,
    0, 0.5,
    0.7, 0,
  ];
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

  // Create a vertex array object (attribute state)
  var vao = gl.createVertexArray();

  // and make it the one we're currently working with
  gl.bindVertexArray(vao);

  // Turn on the attribute
  gl.enableVertexAttribArray(positionAttributeLocation);

  // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
  var size = 2;          // 2 components per iteration
  var type = gl.FLOAT;   // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0;        // start at the beginning of the buffer
  gl.vertexAttribPointer(
      positionAttributeLocation, size, type, normalize, stride, offset);

  webglUtils.resizeCanvasToDisplaySize(gl.canvas);

  // Tell WebGL how to convert from clip space to pixels
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  // Clear the canvas
  gl.clearColor(0, 0, 0, 0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  // Tell it to use our program (pair of shaders)
  gl.useProgram(program);

  // Bind the attribute/buffer set we want.
  gl.bindVertexArray(vao);

  // draw
  var primitiveType = gl.TRIANGLES;
  var offset = 0;
  var count = 3;
  gl.drawArrays(primitiveType, offset, count);
}

main();

헬로우 월드 맞음?? 휴 이제 위의 코드를 다시 분석해보겠음

main을 먼저 보면

 var canvas = document.getElementById("c");
 var gl = canvas.getContext("webgl2");

 var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
 var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

 var program = createProgram(gl, vertexShader, fragmentShader);

createShader 부터 보면 될것 같다.
createShader 에서는 GLSL로 작성되었기때문에 컴파일이 필요한 vertexShaderSource랑
그리고 이 GLSL함수를(vertexShaderSource랑) GPU가 호출할수 있도록, 전달해주기 위해 WebGL을 인자로 받는다.

createShader

function createShader(gl, type, source) {
  var shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) {
    return shader;
  }

  console.log(gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
}

쉐이더만들고

쉐이더에 GLSL 소스 담았고, 컴파일하고, 컴파일 완료되면, 쉐이더를 리턴.

이런식으로

var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

두개의 쉐이더를 만들어준다. 하지만 GPU는 프로그램이란 함수만 호출한다.
즉, 프로그램을 만들고 두개의 쉐이더를 프로그램에 넣어줘야한다(연결)

createProgram

function createProgram(gl, vertexShader, fragmentShader) {
  var program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  var success = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    return program;
  }

  console.log(gl.getProgramInfoLog(program));
  gl.deleteProgram(program);
}

Attribute

그리고 이제는 GPU 쪽으로 쉐이더 함수를 넘겼는데, 쉐이더 함수에서 사용하는 데이터는 아직 안넘겼으니깐, 데이터를 제공해보도록 하자.
GLSL 상에 어트리부트가 하나 선언되어있다 (a_position)

먼져 어트리부트의 위치를 WebGL2 API 를 통해 찾아보자 (왜 알아야하지? 일단 스킵)
positionAttrivuteLocation 찍어보니 0 이라고 뜸.

var positionAttributeLocation = gl.getAttribLocation(program, "a_position");

Buffer

a_position이라는 입력 애트리뷰트로 제공할 데이터를 담을 버퍼를 만들고 바인드 해줍니다.

* GPU에 만드는거임

var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

 

WebGL은 내부에 이런 버퍼를 전역 영역에서 여러개 생성하고 관리할 수 있습니다.
그래서 현 시점에 어떤 버퍼를 사용하고 있는지 알려줘야만 합니다. 이때 어떤 버퍼를 사용하고 있다고 가리키는 것이 바로 바인드 포인트(bind point)라고 합니다.
아래 코드처럼 bindBuffer() 함수를 실행하면 바인드 포인트는 positionBuffer를 가리키게 되므로 이후부터 공급되는 데이타는 이 바인드 포인트가 가리키는 버퍼에 입력되게 됩니다.  

 

버퍼에 데이터 넣기

// three 2d points
var positions = [
  0, 0,
  0, 0.5,
  0.7, 0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

자바스크립트 배열을 형식있는 배열로 만들고,

gl.STATIC_DRAW: WebGL에 데이터를 어떻게 사용할 것인지에 대한 힌트입니다

ARRAY_BUFFER: 위에서 이 바인드 포인트로 바인드 했기 떄문에 position buffer를 사용합니다.

gl.bufferData함수는 데이터를 GPU에 있는 positionBuffer에 복사합니다. 

 

이렇게 되면 GPU가 접근할수 있는 버퍼에 데이터를 넣은 거고,

GLSL에 있는 attribute가 데이터를 가져오기 만 하면됨.

데이터를 가져오기위해attribute에게 데이터를 가져오는 방법을 알려줘야함.

그러기 위해 Vertex Array Object라고 불리는 attribute 상태 콜렉션을 생성해야함.. (아.. 할거 왜이렇게 많어..)

 

(나중에... 헬로우월드 한번 하기 힘드네..)

 

참고사이트:

https://www.bsidesoft.com/?p=4023

https://webgl2fundamentals.org/webgl/lessons/ko/webgl-fundamentals.html

 

 

'Graphics > webgl' 카테고리의 다른 글

[webgl] Hello World  (0) 2019.12.29
[webgl] 기초  (0) 2019.12.02