📜

Vite+TypeScriptでWebGLを使ってみる

5 min read

環境

  • Node.js: 14.18.1
  • npm: 6.14.15

始め方

まずはnpm initでプロジェクト作成

https://ja.vitejs.dev/guide/#最初の-vite-プロジェクトを生成する
Project Nameは適当に。Select a frameworkになったらvanilla->vanilla-tsを選ぶ。
npm run devして以下の画面が出ればOK

rootディレクトリにshaderフォルダを作り、

  • vertex_shader.vert
  • fragment_shader.frag

の2つのファイルを作成。

後は以下の通りにコードを書いていく。
(参考にしたソースコードのブログ↓)

https://blog.shogonir.jp/entry/2020/01/20/005229
vertex_shader.vert
#version 300 es

in vec3 vertex_position;
in vec4 color;

out vec4 v_color;

void main() {
    v_color = color;
    gl_Position = vec4(vertex_position, 1.0);
}
fragment_shader.frag
#version 300 es

precision highp float;

in vec4 v_color;
out vec4 fragment_color;

void main() {
    fragment_color = v_color;
}
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <canvas id="canvas" width="512" height="512"></canvas>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
main.ts
import vertex_source from '../shader/vertex_shader.vert?raw';
import fragment_source from '../shader/fragment_shader.frag?raw';

function assert_is_defined<T>(val: T): asserts val is NonNullable<T> {
  if (val === undefined || val === null) {
    throw new Error(
      `Expected 'val' to be defined, but received ${val}`
    );
  }
}

const maybeCanvas = document.getElementById('canvas');
assert_is_defined(maybeCanvas);

const canvas: HTMLCanvasElement = maybeCanvas as HTMLCanvasElement;

const maybe_context = canvas.getContext('webgl2');
assert_is_defined(maybe_context);

const context: WebGL2RenderingContext = maybe_context;

const vertex_shader = context.createShader(context.VERTEX_SHADER);
assert_is_defined(vertex_shader);
context.shaderSource(vertex_shader, vertex_source);
context.compileShader(vertex_shader);

const vertex_shader_compile_status = context.getShaderParameter(vertex_shader, context.COMPILE_STATUS);
if (!vertex_shader_compile_status) {
  const info = context.getShaderInfoLog(vertex_shader);
  console.warn(info);
}

const fragment_shader = context.createShader(context.FRAGMENT_SHADER);
assert_is_defined(fragment_shader);
context.shaderSource(fragment_shader, fragment_source);
context.compileShader(fragment_shader);

const fragment_shader_compile_status = context.getShaderParameter(fragment_shader, context.COMPILE_STATUS);
if (!fragment_shader_compile_status) {
  const info = context.getShaderInfoLog(fragment_shader);
  console.warn(info);
}

const program = context.createProgram();
assert_is_defined(program);
context.attachShader(program, vertex_shader);
context.attachShader(program, fragment_shader)
context.linkProgram(program);

const link_status = context.getProgramParameter(program, context.LINK_STATUS);
if (!link_status) {
  const info = context.getProgramInfoLog(program);
  console.warn(info);
}

context.useProgram(program);

const vertex_buffer = context.createBuffer();
const color_buffer = context.createBuffer();

const vertex_attrib_location = context.getAttribLocation(program, 'vertex_position');
const color_attrib_location = context.getAttribLocation(program, 'color');

const VERTEX_SIZE = 3;
const COLOR_SIZE = 4;


context.bindBuffer(context.ARRAY_BUFFER, vertex_buffer);
context.enableVertexAttribArray(vertex_attrib_location);
context.vertexAttribPointer(vertex_attrib_location, VERTEX_SIZE, context.FLOAT, false, 0, 0);

context.bindBuffer(context.ARRAY_BUFFER, color_buffer);
context.enableVertexAttribArray(color_attrib_location);
context.vertexAttribPointer(color_attrib_location, COLOR_SIZE, context.FLOAT, false, 0, 0);

const halfsize = 0.5;
const vertices = new Float32Array([
  -halfsize, halfsize, 0.0,
  -halfsize, -halfsize, 0.0,
  halfsize, halfsize, 0.0,
  -halfsize, -halfsize, 0.0,
  halfsize, -halfsize, 0.0,
  halfsize, halfsize, 0.0
]);

const colors = new Float32Array([
  1.0, 0.0, 0.0, 1.0,
  0.0, 1.0, 0.0, 1.0,
  0.0, 0.0, 1.0, 1.0,
  0.0, 1.0, 0.0, 1.0,
  0.0, 0.0, 0.0, 1.0,
  0.0, 0.0, 1.0, 1.0
]);

context.bindBuffer(context.ARRAY_BUFFER, vertex_buffer);
context.bufferData(context.ARRAY_BUFFER, vertices, context.STATIC_DRAW);

context.bindBuffer(context.ARRAY_BUFFER, color_buffer);
context.bufferData(context.ARRAY_BUFFER, colors, context.STATIC_DRAW);

const VERTEX_NUMS = 6;
context.drawArrays(context.TRIANGLES, 0, VERTEX_NUMS);

context.flush();

補足

  • Viteでは末尾に"?raw"を付けることでファイルを文字列として読み込める

https://ja.vitejs.dev/guide/assets.html
  • nullチェック用に以下のブログのassertIsDefined関数を参考にした

https://dev.classmethod.jp/articles/assertion-functions-of-typescript-3-7/

npm run devしてブラウザで以下の画面が出れば完成。

Discussion

ログインするとコメントできます