跳到主要內容
黯羽輕揚每天積累一點點

attribute 變量與頂點著色器_WebGL 筆記 2

免費2015-12-22#JS#WebGL#webgl attribute变量#js向webgl传值#顶点着色器的attribute变量#顶点着色器#片元着色器#片元

通過 attribute 變量向頂點著色器中傳值,我們就可以動態修改點的位置了

寫在前面

[上一篇筆記](/articles/用 webgl 繪製一個矩形-webgl 筆記 1/) 中在頂點著色器源程序中寫死了 gl_Position,想要修改點的位置就得修改著色器源程序,這當然沒什麼意義,我們需要一種足夠靈活的方式,比如 attribute 變量

一。封裝 util

[上一篇筆記](/articles/用 webgl 繪製一個矩形-webgl 筆記 1/) 中我們為了繪製一個假矩形(一個略大的點),寫了 70 多行代碼,還只包含了錯誤檢查,兼容性處理還沒有做。每一步都在調用 WebGL 原生 API,雖然流程很清晰,但實在太麻煩,1 個點 70 行,畫兩個點 140 行可不行,所以,先動手封裝 util

開始之前,需要先注意幾個問題:

###1.getContext 的兼容性問題

getContext('webgl') 是標準方式,但在低版本瀏覽器中需要傳入不同的字符串,如下:

/**
 * Creates a webgl context.
 * @param {!Canvas} canvas The canvas tag to get context
 *     from. If one is not passed in one will be created.
 * @return {!WebGLContext} The created context.
 */
var create3DContext = function(canvas, opt_attribs) {
  var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
  var context = null;
  for (var ii = 0; ii < names.length; ++ii) {
    try {
      context = canvas.getContext(names[ii], opt_attribs);
    } catch(e) {}
    if (context) {
      break;
    }
  }
  return context;
}

以上代碼摘自 google 團隊的 webgl-utils.js,除 create3DContext 外還提供了 requestAnimationFrame 的兼容版本,後面動畫也用得到,所以我們的 util 就直接 include webgl-utils.js 了

###2. 保留 program 對象的引用

gl.createProgram() 返回的 program 對象不僅在初始化著色器的過程中會用到,在我們即將要進行的 attribute 變量傳值中也要用,program 對象是 js 和著色器內部通信的橋樑,但問題是WebGL 沒有提供 gl.getProgram() 這樣的 getter,所以在封裝 util 時一定要保留 program 對象的引用,供外部使用,例如:

function initShaders(vsSrc, fsSrc) {
    var program = _createProgram(vsSrc, fsSrc);
    
    if (!program) {
        log('Failed to create program');
        return false;
    }
    gl.useProgram(program);

    // Save program object
    //!!! because there is no getter like gl.getProgram()
    glUtil.program = program;

    return true;
}

以上代碼摘自筆者的 gl-util.js,需要的話可以直接拿去用,接口說明如下:

// 全局變量:glUtil
return {
    program: null,  // runtime assignment

    getContext: getContext,
    initShaders: initShaders,

    debug: function(onOrOff) {
        if (typeof onOrOff === 'boolean') {
            debug = onOrOff;
        }
    }
};

二。術語及 API 解釋

###1. 著色器

著色器分為頂點著色器和片元著色器:

  • 頂點著色器

用來描述頂點特性(如位置、顏色等)的程序。頂點是指二維或三維空間中的一個點,比如二維或三維圖形的端點或交點

  • 片元著色器

進行逐片元處理過程(如光照)的程序。片元是一個 WebGL 術語,可以簡單理解為像素(圖像的單元)

P.S. 實際上片元表示顯示在屏幕上的一個像素,還包括這個像素點的位置、顏色和其他信息

###2. 頂點著色器源程序

用 GLSL ES 寫的程序,以字符串形式傳入 WebGL 系統內部

// 頂點著色器源程序
var vsSrc = 'void main() {' +
    'gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' + // 設置坐標
    'gl_PointSize = 200.0;' +                   // 設置尺寸
'}';

其中,給 gl_Position 賦值是必須的,否則著色器無法正常工作,gl_Positionvec4 類型的,即四維向量(齊次坐標,最後一個分量一般取 1.0)

gl_PointSize 有預設值 1.0,可以不必賦值,gl_PointSizefloat 類型的,不接受3f(C 語言中 3f 表示浮點數 3)這樣的形式,而且只在繪製孤立點時生效(什麼叫繪製孤立點,我們後面再說)

###3. 片元著色器源程序

和頂點著色器源程序一樣,但控制的是片元著色器

// 片元著色器源程序
var fsSrc = 'void main() {' +
    'gl_FragColor = vec4(1.0, 0.0, 1.0, 0.75);' + // 設置顏色
'}';

gl_FragColor唯一的內置變量,也是 vec4 類型,但表示 rgba 色值,rgba 的取值都是 0~1 的,不同於 CSS 中 rgb 是 0~255 的

###4. 繪製操作

繪製操作只有一個接口,但功能很強大:

// 繪製矩形(一個點,但點的尺寸略大)
gl.drawArrays(gl.POINTS, 0, 1);

API 具體信息如下:

gl.drawArrays(mode, first, count)

mode 指定繪製方式,接受常量:
---
gl.POINTS           孤立點(v0, v1...)
gl.LINES            孤立線段(v0v1, v2v3...)
gl.LINE_STRIP       連續線段(v0v1, v1v2...)
gl.LINE_LOOP        連續線段連成圈(v0v1, v1v2...vnv0)
gl.TRIANGLES        孤立三角形(v0v1v2, v3v4v5...)
gl.TRIANGLE_STRIP   連續三角帶(v0v1v2, v2v1v3, v2v3v4...),用來構造複雜模型,如球、樹
                    注意:第二個三角形是 v2v1v3,不是 v1v2v3,這是為了保證第二個三角形的繪製也按照逆時針順序
gl.TRIANGLE_FAN     三角扇(v0v1v2, v0v2v3, v0v3v4),也可以用來構造複雜模型,但在實際應用中不常見,因為需要求出所有三角形的公共頂點 v0

first 指定從哪個頂點開始繪製
---
0 表示從第一個開始,配合緩衝區對象使用(緩衝區對象中可以存放多個頂點信息)

count 指定需要繪製點的個數,同樣配合緩衝區對象使用
---
調用 gl.drawArray 時,頂點著色器會被執行 count 次,每次處理一個頂點,頂點著色器執行完畢後片元著色器開始執行,給片元塗色(中間還有光柵化的過程)

特別注意:頂點著色器逐頂點執行,片元著色器逐片元執行,並不是只執行一次

三。attribute 變量

在著色器源程序中聲明一個變量,然後 js 給這個變量傳值,再繪製出來,就實現了動態修改點的位置,簡單地說就是這樣

###1. 聲明 attribute 變量

// 頂點著色器源程序
var vsSrc = 'attribute vec4 a_Position;' +
    'void main() {' +
    'gl_Position = a_Position;' +   // 設置坐標
    'gl_PointSize = 200.0;' +       // 設置尺寸
'}';
// 片元著色器源程序
//... 不變

類似於 C 語言的變量聲明方式,attribute 叫存儲限定符,vec4 是變量類型。變量名 a_Positiona_ 前綴表示變量是 attribute 變量,當然這個只是習慣,建議採用

P.S. 為什麼在頂點著色器源程序中聲明 attribute 變量,而不是在片元... 因為我們想要動態修改點的坐標,坐標信息來自頂點著色器源程序中的 gl_Position,和片元著色器沒關係

注意:只有頂點著色器才能使用 attribute 變量,片元著色器中需要使用 uniform 變量,此外還有 varying 變量,具體區別在 GLSL ES 筆記中再詳細說明

###2. 初始化著色器

直接調用 util,1 行代碼就能完成剩下的 7 件事(從 createShaderuseProgram 的 7 個步驟)

// 1. 初始化著色器
glUtil.initShaders(vsSrc, fsSrc);

###3. 給 attribute 變量賦值

變量賦值分為 2 步,先獲取存儲位置,再賦值

// 2. 給 attribute 變量賦值
// 獲取 attribute 變量的存儲位置
var a_Position = gl.getAttribLocation(glUtil.program, 'a_Position');
if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return;
}
// 把頂點位置傳遞給 attribute 變量
gl.vertexAttrib3f(a_Position, 0.5, 0.0, 0.0);

###4. 繪製

// 繪製點
gl.drawArrays(gl.POINTS, 0, 1);

第一個參數傳入 gl.POINTS 表示繪製孤立點,還可以繪製線段、三角形等等,以後再說

四。DEMO

包含上述代碼的完整的例子,請查看:http://www.ayqy.net/temp/webgl/attribute/index.html

五。總結

現在我們已經可以動態修改頂點坐標了,當然,這只是很簡單的一步,後面還有:

  • 如何在鼠標點擊的位置繪製一個點?

答案沒有想象的那麼簡單,canvas 和 WebGL 坐標系不同,需要轉換坐標

  • 如何修改點的顏色?

用 uniform 變量就可以了

  • 如何繪製多個點

循環 gl.drawArrays(gl.POINTS, 0, 1);?可以嗎?

這些都是後面的筆記要討論的問題,先不要著急

參考資料

  • 《WebGL 編程指南》

評論

暫無評論,快來發表你的看法吧

提交評論