WebGLを用いた5つの点の表示


はじめに、ブログ記事投稿に伴い、情報を提供してくださった方に、心より御礼申し上げます。
誠にありがとうございます。

<br>


=====*以下、WebGL、GLSLを用いて5つの点を表示するまでです。*=====

**同一ディレクトリ内に以下五つのファイルを作成いたします**
-htmlファイル-
・canvas.html

-cssファイル-
・style.css

-JavaScriptファイル-
・script.js

-シェーダファイル(拡張子を必要といたしません。)-
・vertex
・fragment
<br>
<br>

-**実行結果**-
![74231e26c9a6e912090e3502fa939336.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703383/eb36a892-c7cf-6c07-9f6d-19de39f8bc6d.png)




```html:canvas.html
<!DOCTYPE html>
<html>
  <head>
    <script src='./script.js'></script>
    <link rel='stylesheet' href='./style.css'>
  </head>
  <body>
    <canvas id='train'></canvas><!-- canvas要素 -->
  </body>
</html>
```


```css:style.css
@charset "utf-8";

* {
    margin: 0px;
    padding: 0px;
}

html, body {
    text-align: center;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

canvas {
    margin: 0px auto;
}
```


```:vertex
attribute vec3 position; //頂点毎固有のデータを指定できる修飾子attribute
attribute vec4 color;
varying   vec4 vColor; //フラグメントシェーダに変数を渡したい時に用いる修飾子varying

void main(){
  vColor = color;

  gl_Position  = vec4(position, 1.0);
  gl_PointSize = 16.0;
}
```


```:fragment
precision mediump float;

varying vec4 vColor;  //頂点シェーダと同じ変数名でなけ

void main(){
  gl_FragColor = vColor;
}
```

```JavaScript:script.js
window.addEventListener('DOMContentLoaded', () => {
  const webgl = new WebGLFrame();//インスタンスを生成
  webgl.init(train);//コンテキスト取得の関数
  webgl.load()//load関数で非同期処理によるファイルの読み込みが終わった後に、.thenが実行されます。
    .then(() => {
    webgl.setup();//変数に値を配列として代入する記述のある関数
    webgl.render();//GPUに描画命令を行うための記述がある関数
  });
}, false);

class WebGLFrame{

  constructor(){
    this.canvas = null;//初期化
    this.gl     = null;
    this.render = this.render.bind(this);
  }

  init(canvas){
    if(canvas instanceof HTMLCanvasElement === true){
      this.canvas = canvas;//canvas要素が送られてきた場合、そのまま代入。
    }else if(Object.prototype.toString.call(canvas) === '[object String]'){
      //文字列で送られてきた場合
      const c = document.querySelector('#${canvas}');
      //canvas要素を取得した後に代入
      if(c instanceof HTMLCanvasElement === true){
        this.canvas = c;
      }
    }
    if(this.canvas == null){throw new Error('invalid argument');}
    this.gl = this.canvas.getContext('webgl');//コンテキストの取得//WebGLの組み込み関数を扱うことができるようになる。
    if(this.gl == null){throw new Error('webgl not supported');}
  }
  
  load(){
    this.program     = null;//初期化
    this.attLocation = null;
    this.attStride   = null;

    return new Promise((r) => {
      this.loadShader([//非同期でシェーダファイルの読み込み
        './vertex',
        './fragment',
      ])
      .then((shaders) => {//loadShader関数が実行された後に実行される
        const gl = this.gl;
        //ファイルを元にシェーダの生成
        const vs = this.createShader(shaders[0], gl.VERTEX_SHADER);//頂点(vertex)シェーダ
        const fs = this.createShader(shaders[1], gl.FRAGMENT_SHADER);//フラグメントシェーダ
        this.program = this.createProgram(vs, fs);//プログラムの生成とシェーダのリンクを行い不整合がないことを確かめる。

        this.attLocation = [
          gl.getAttribLocation(this.program, 'position'),//Locationの取得//C言語のポインタに似ている//シェーダファイルの変数に関する番地の取得
          gl.getAttribLocation(this.program, 'color'),
        ];

        this.attStride = [//要素数の指定
          3,//配列の要素を幾つで区切るかを指定positionの場合(x, y, z)の３つになるので3という記述
          4,
        ];

        r();//Promiseオブジェクトの引数の関数呼び出し
      })
    })
  }

  setup(){
    const gl = this.gl;
    
    this.position = [
       0.0,  0.0,  0.0,//x, y, z
       0.5,  0.5,  0.0,//座標の指定//VBOの原点は中央
      -0.5,  0.5,  0.0,
      -0.5, -0.5,  0.0,
       0.5, -0.5,  0.0,
    ];

    this.color = [
      1.0, 1.0, 1.0, 1.0,//r, g, b, a
      0.5, 1.0, 1.0, 1.0,
      1.0, 0.5, 1.0, 1.0,
      1.0, 1.0, 0.5, 1.0,
      0.5, 0.5, 0.5, 1.0, 
    ];

    this.vbo = [
      this.createVbo(this.position),//頂点属性を格納するVertex Buffer Objectの生成
      this.createVbo(this.color),
    ];

    gl.clearColor(0.1, 0.1, 0.1, 1.0);//色の初期化設定
  }

  render(){
    const gl = this.gl;
    
    this.canvas.width  = window.innerWidth;//canvasの横幅指定
    this.canvas.height = window.innerHeight;//canvasの高さ指定
    gl.viewport(0, 0, this.canvas.width, this.canvas.height);
    
    gl.clear(gl.COLOR_BUFFER_BIT);//canvasの色の初期化
    gl.useProgram(this.program);//描画に使う、頂点シェーダとフラグメントシェーダがリンクされているprogramの指定
    this.setAttribute(this.vbo, this.attLocation, this.attStride);
    gl.drawArrays(gl.POINTS, 0, this.position.length / 3);
    //プリミティブ型を指定することで頂点をどのように描写するのかを指定
    //gl.drawArraysでGPUに描画することをお願いする。
    //描画を行うのはGPU
    //CPUの記述はJavaScript
    //GPUの記述はGLSL
    //CPUとGPUの橋渡しがWebGL
  }


  loadShader(pathArray){
    if(Array.isArray(pathArray) !== true){throw new Error('invalid argument');}
    //送られてきた引数が配列オブジェクトではなかった場合のエラーハンドリング
    const promises = pathArray.map((path) => {//.mapで一つずつ関数を呼び出し変数pathに配列としていれる
      return fetch(path).then((response) => {return response.text();})
    });//ここの理解は乏しいです。
    return Promise.all(promises);
  }

  createShader(source, type){
    const gl =this.gl;
    const shader = gl.createShader(type);//type毎に頂点シェーダとフラグメントシェーダの生成

    gl.shaderSource(shader, source);//シェーダに非同期で読み込んだファイルを割り当てる
    gl.compileShader(shader);//シェーダファイルを読み込めるようにコンパイルを行う。
    if(gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
      return shader;//コンパイルが正常に行われた時に限り、シェーダを返す。
    }else{
      alert(gl.getShaderInfoLog(shader));//エラーを表示
      return null;
    }
  }

  createProgram(vs, fs){
    const gl = this.gl;
    const program = gl.createProgram();//programの生成

    gl.attachShader(program, vs);//programにシェーダを割り当てます。
    gl.attachShader(program, fs);
    gl.linkProgram(program);//vs と fsを結びつけます。
    if(gl.getProgramParameter(program, gl.LINK_STATUS)){
      gl.useProgram(program);//リンクが正常に行われた際に、扱うprogramを指定し、
      return program;//返り値として返します。
    }else{
      alert(gl.getProgramInfoLog());//エラーハンドリング
      return null;
    }
  }

  createVbo(data){
    const gl = this.gl;
    const vbo = gl.createBuffer();//bufferを格納するためのオブジェクトの生成
    
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);//gl.ARRAY_BUFFERがvbo, bindを行うことでデータをセットすることが出来ます。
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);//計算誤差込のデータ型、浮動小数点数の指定
    gl.bindBuffer(gl.ARRAY_BUFFER, null);//bindを外すための記述
    
    return vbo;
  }

  setAttribute(vbo, attL, attS){
    const gl = this.gl;
    
    vbo.forEach((v, index) => {//繰り返し処理
      gl.bindBuffer(gl.ARRAY_BUFFER, v);
      gl.enableVertexAttribArray(attL[index]);//Locationを有効にします
      gl.vertexAttribPointer(attL[index], attS[index], gl.FLOAT, false, 0, 0);//この記述でCPUからGPUにデータを送ります。//描画命令の前に行わなければならない記述です。
      //CPUで指定されたピクセルをGPUが塗るためです。
    });
  }
}
```


=====*以上、WebGLとGLSLを用いて5つの点を描画するまでの記述です。*=====

最後までご覧頂き、誠にありがとうございました。
認識が誤っている点、知識が不足している部分についてご指摘願えれば、御幸甚に尽きます。