JavaScript WebGL 設定顏色

XXHolic發表於2022-01-01

引子

JavaScript WebGL 繪製一個面之後想著可以嘗試複雜一點的了,沒想到設定顏色的時候又出現問題了。

設定顏色

在之前的示例中,都是設定單一的顏色,但每個頂點都可以擁有各自的顏色資訊。

基於繪製三角形主要有下面幾方面變化:

  • 資料
  • 頂點著色器
  • 片元著色器
  • 緩衝顏色資料

資料

顏色資料有 4 個分量:R、G、B、A 。

  let colors = [
    1.0, 0.0, 0.0, 1.0, // red
    0.0, 1.0, 0.0, 1.0, // green
    0.0, 0.0, 1.0, 1.0, // blue
  ];

頂點著色器

之前都是隻提供了位置變數,對於顏色需要提供額外的顏色變數進行儲存。此外還需要輸出對應的顏色到下一個階段。

  const source = `
    attribute vec3 vertexPos;
    attribute vec4 vertexColor;

    varying vec4 vColor;
    void main(void){
      gl_Position = vec4(vertexPos, 1);
      vColor = vertexColor;
    }
  `;

這裡面多了一個 varying 型別的變數,這是一種頂點著色器給片斷著色器傳值的方式。

片元著色器

片元著色器接受對應的變數也要進行宣告。

  const fragmentSource = `
    precision highp float;
    varying vec4 vColor;
    void main(void){
      gl_FragColor = vColor;
    }
  `;

這裡出現了變數,需要用 precision highp float 設定片元著色器的浮點數精度。頂點著色器有預設的精度可以不用顯式設定。

緩衝顏色資料

頂點位置資料進行了緩衝,顏色資料也要進行緩衝。

  /**
   * 緩衝顏色資料
   * @param {*} gl WebGL 上下文
   * @param {*} shaderProgram 著色器程式
   * @param {*} colorData 顏色資料
   */
  function setColorBuffers(gl, shaderProgram, colorData) {
    // 建立空白的緩衝物件
    const buffer = gl.createBuffer();
    // 繫結目標
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    // WebGL 不支援直接使用 JavaScript 原始陣列型別,需要轉換
    const dataFormat = new Float32Array(colorData);
    // 初始化資料儲存
    gl.bufferData(gl.ARRAY_BUFFER, dataFormat, gl.DYNAMIC_DRAW);

    // 獲取對應資料索引,變數跟頂點著色器裡面對應
    const vertexPos = gl.getAttribLocation(shaderProgram, "vertexColor");
    // 解析頂點資料
    gl.vertexAttribPointer(vertexPos, 4, gl.FLOAT, false, 0, 0);
    // 啟用頂點屬性,頂點屬性預設是禁用的。
    gl.enableVertexAttribArray(vertexPos);
  }

效果

這是示例,效果如下:

95-result

發現顏色漸變發散開來了,這個是因為在光柵化過程中,轉變為畫素時對顏色進行了插值。

在程式中只定義了三個頂點的顏色,它們之間畫素的顏色會隨著畫素位置變化,相鄰畫素之間同一種單色的差值是定值。如果不想要這樣的效果,可以在片元著色器中自定義。

動態自定義示例

這是示例,片元著色器主要變化:

  const fragmentSource = `
    precision highp float;
    varying vec4 vColor;

    int findMax(float r, float g, float b) {
        if (r > g && r > b) {
            return 0;
        }
        if (g > r && g > b) {
            return 1;
        }
        return 2;
    }

    void main(void){
      float red = vColor.r;
      float green = vColor.g;
      float blue = vColor.b;
      int max = findMax(red, green, blue);
      vec4 finalColor = vColor;
      if (max == 0) {
          finalColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
      else if (max == 1) {
          finalColor = vec4(0.0, 1.0, 0.0, 1.0);
      }
      else if (max == 2) {
          finalColor = vec4(0.0, 0.0, 1.0, 1.0);
      }
      gl_FragColor = finalColor;
    }
  `;

findMax 方法會對每個畫素的顏色的分量進行比較,將最終顏色設定為最大的一個分量。下面是效果:

95-custom

參考資料

相關文章