WebGL學習(2) - 3D場景


原文地址:WebGL學習(2) - 3D場景

經過前面WebGL學習(1) - 三角形的學習,我們已經掌握了webGL的基礎知識,也已經能夠畫出最基本的圖形,比如點,線,三角形,矩形等。有了2D繪圖的基礎,現在終於可以進入精彩的3D世界了,來看一下這一節要實現的3D的效果吧。
實際效果:webGL3D場景

webGL渲染流程

重溫一下webGL的渲染流程,這一節在第3、4、5、6步驟需要學習新的內容。其中寫入數據交叉存放緩沖區,設置隱藏面消除,清空深度緩沖都是比較簡單的部分。重點和難點是在3D變換的環節,在理解了矩陣的原理基礎上,這次使用了《WebGL編程指南》提供的矩陣操作庫。

  1. 獲取webGL繪圖上下文
  2. 初始化着色器
  3. 創建、綁定緩沖區對象
  4. 3D變換
  5. 向頂點着色器和片元着色器寫入數據(數據交叉存放緩沖區)
  6. 設置canvas背景色,設置隱藏面消除
  7. 清空canvas|清空深度緩沖
  8. 繪制

着色器

着色器代碼修改為下面,我們現在需要為每個頂點都使用不同的顏色,所以使用到了varying限定符的變量,這個變量目的就是連接頂點和片元着色器,把頂點信息和顏色信息結合起來。看到頂點着色器和片元着色器都有的v_color變量了嗎?其實就是通過全局變量傳遞。
頂點着色器

<script type="x-shader/x-vertex" id="vs">
attribute vec4 a_Position; //頂點
uniform mat4 u_MvpMatrix;//模型視點投影矩陣
attribute vec4 a_Color;
varying vec4 v_color;// 連接片元着色器
void main() { 
  	gl_Position = u_MvpMatrix * a_Position;
  	v_color=a_Color;//傳遞給片元着色器變量
} 
</script>

片元着色器

<script type="x-shader/x-fragment" id="fs">
precision mediump float; // 精度限定
varying vec4 v_color; //從頂點着色器接收
void main() {
  	gl_FragColor = v_color;
}
</script>

3D坐標系

第1、2、3步驟前面文章已經介紹,現在我們直接進入3D的環節。3D比2D主要就是多了深度信息,用坐標系來描述就是,除xy軸外,還多了z軸。webGL的坐標系跟我們web的坐標系是不一樣的,首先它原點不是在左上角而是位於中間,xyz方向也不同。

視點和視線

接着引入一個概念,視點,也就是定義觀察者的位置,觀察者能看多遠,觀察者的方向,直接看圖吧

上方向就是觀測者從哪個方向看,(0,1,0)是正常的Y軸正方向,(1,0,0)就相當於物體向左旋90度,等於我們把頭打橫看物體。通過定義視點矩陣,我們看到的圖形的形狀會產生變化的,就和我們實際環境不同的角度位置觀察同一物體是一樣一樣的。我們調用矩陣庫中的方法,會產生出一個4X4的矩陣,具體中間的產生過程,可以看源代碼。

// (視點,觀察目標點,上方向)
setLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ)

投影可視空間

只有指定了可視空間,webGL才會去繪制圖形,有兩種類型:
一是盒狀可視空間,由正射投影產生,它產生的圖形,前后物體沒有大小區別,都是一樣高寬。

調用矩陣庫的setOrtho方法,產生矩陣
// 正視投影 (left,right,bottom,top,near,far), 組成一個正方體的可視空間 
setOrtho(left, right, bottom, top, near, far);

二是四棱錐可視空間,由透視投影產生,透視投影產生的3D場景更加真實自然,它產生的圖形具有近大遠小的透視效果,當然性能消耗相對正射投影高一些。

調用setPerspective方法,同理生成矩陣
// 投影矩陣(fov可視空間底面和頂面夾角<大於0>,近裁截面寬高比,近裁截面位置<大於0>,遠裁截面位置<大於0> )
setPerspective(fovy, aspect, near, far)

數據交叉存放緩沖區

我們既可以給不同的信息分別創建單獨緩沖區,也可以給不同的信息創建同一塊合用的緩沖區,前者適合數據量小的情況,我們現在實現第二種情況:給不同的信息創建一塊緩沖區,並交叉存放。首先用一個數組同時存放頂點信息和頂點對應的顏色信息,接着創建緩沖區后調用gl.vertexAttribPointer(),該方法有定義每個分量的個數,每一行的個數以及偏移數,當然相鄰頂點數和偏移量要乘以單位字節,具體看代碼的注釋。

/**
 * 混合緩沖區(包括頂點,顏色),每一行前3個是頂點信息,后3個是顏色信息
 */
var verticeColors=new Float32Array([
  0.0,  1.0,  -2.0,  0.3,  1.0,  0.4, 
  -0.5, -1.0,  -2.0,  0.3,  1.0,  0.4,
  0.5, -1.0,  -2.0,  1.0,  0.4,  0.4, 

  0.0,  1.0,  -1.0,  1.0,  1.0,  0.4, 
  -0.5, -1.0,  -1.0,  1.0,  1.0,  0.4,
  0.5, -1.0,  -1.0,  1.0,  0.4,  0.4, 

  0.0,  1.0,   0.0,  0.4,  0.4,  1.0, 
  -0.5, -1.0,   0.0,  0.4,  0.4,  1.0,
  0.5, -1.0,   0.0,  1.0,  0.4,  0.4, 
]);
// 創建緩沖區
if(!createBuffer(verticeColors)){
  console.log('Failed to create the buffer object');
  return;
}

// 每個元素的字節
var FSIZE = verticeColors.BYTES_PER_ELEMENT;
// 獲取頂點位置
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
// (地址,每個頂點分量的個數<1-4>,數據類型<整形,符點等>,是否歸一化,指定相鄰兩個頂點間字節數<默認0>,指定緩沖區對象偏移字節數量<默認0>)
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 6*FSIZE, 0);
// Enable the assignment to a_Position variable
gl.enableVertexAttribArray(a_Position);

// 獲取a_Color變量的存儲地址並賦值
var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 6*FSIZE, 2*FSIZE);
gl.enableVertexAttribArray(a_Color);

3D相關的其他設置

開啟隱藏面消除可以減少渲染量,提高性能,同時還可以避免順序不一致時,后面的圖形蓋住前面的圖形。而多邊形偏移,可以避免深度很接近的兩個圖形產生沖突。當然每次重新渲染的時候,在清屏的同時清除深度緩沖,具體實現請看代碼。

// 開啟隱藏面消除
gl.enable(gl.DEPTH_TEST);
//清屏|清深度緩沖
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 啟用多邊形偏移,避免深度沖突
gl.enable(gl.POLYGON_OFFSET_FILL);
//設置偏移量
gl.polygonOffset(1.0, 1.0);

執行動畫

來看一下我們執行動畫的部分,首先設置好用於位移旋轉的模型矩陣,然后依次產生視點矩陣,投影矩陣,接着把它們相乘產生出mvp矩陣,然后傳入變量,最后繪圖。在繪制完第一組圖形的時候,將前面的mvp矩陣再左移2個單位,再繪制一遍,於是就產生出了第二組圖形。具體的邏輯情況代碼注釋。

var angle=0;
// 執行動畫
(function animate(){
		// 旋轉位移 等於繞原點Y旋轉
		modelMatrix.setRotate((angle++)%360,0,1,0);
		modelMatrix.translate(1, 0, 1);
		// (視點,觀察目標點,上方向)
		viewMatrix.setLookAt(-0.25, -0.25, 5, 0, 0, -100, 0, 1, 0);
		// 投影矩陣(fov可視空間底面和頂面夾角<大於0>,近裁截面寬高比,近裁截面位置<大於0>,遠裁截面位置<大於0> )
		projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
		// 矩陣相乘
		mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
		// 賦值
		gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);


		//清屏|清深度緩沖
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		// 啟用多邊形偏移,避免深度沖突
		gl.enable(gl.POLYGON_OFFSET_FILL);

		// (基本圖形,第幾個頂點,執行幾次),修改基本圖形項可以生成點,線,三角形,矩形,扇形等
		gl.drawArrays(gl.TRIANGLES, 0, 9);

		//位移后,再將前面3個三角形重新繪制
		modelMatrix.translate(-2, 0, 0);
		mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
		gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);

		//設置偏移量
		gl.polygonOffset(1.0, 1.0);
		gl.drawArrays(gl.TRIANGLES, 0, 9); 

		requestAnimationFrame(animate);
}());

總結

學習完3D場景后,我們又再一次領略到了線性代數中矩陣在圖形學中的重要作用。3D的矩陣轉換才是需要空間思維和深入理解的部分,其他地方說實話就是學習如何調用api。
最后,獻上主體的全部代碼

var	canvas=document.getElementById('canvas'),
    gl=get3DContext(canvas,true);

function main() {
    if (!gl) {
      console.log('Failed to get the rendering context for WebGL');
      return;
    }

    if (!createShaders(gl, 'fs', 'vs')) {
      console.log('Failed to intialize shaders.');
      return;
    }

    /**
       * 混合緩沖區(包括頂點,顏色)
       */
    var verticeColors=new Float32Array([
      0.0,  1.0,  -2.0,  0.3,  1.0,  0.4,
      -0.5, -1.0,  -2.0,  0.3,  1.0,  0.4,
      0.5, -1.0,  -2.0,  1.0,  0.4,  0.4, 

      0.0,  1.0,  -1.0,  1.0,  1.0,  0.4,
      -0.5, -1.0,  -1.0,  1.0,  1.0,  0.4,
      0.5, -1.0,  -1.0,  1.0,  0.4,  0.4, 

      0.0,  1.0,   0.0,  0.4,  0.4,  1.0,
      -0.5, -1.0,   0.0,  0.4,  0.4,  1.0,
      0.5, -1.0,   0.0,  1.0,  0.4,  0.4, 
    ]);
    // 創建緩沖區
    if(!createBuffer(verticeColors)){
      console.log('Failed to create the buffer object');
      return;
    }

    // 每個元素的字節
    var FSIZE = verticeColors.BYTES_PER_ELEMENT;
    // 獲取頂點位置
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    // (地址,每個頂點分量的個數<1-4>,數據類型<整形,符點等>,是否歸一化,指定相鄰兩個頂點間字節數<默認0>,指定緩沖區對象偏移字節數量<默認0>)
    gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 6*FSIZE, 0);
    // Enable the assignment to a_Position variable
    gl.enableVertexAttribArray(a_Position);

    // 獲取a_Color變量的存儲地址並賦值
    var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
    gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 6*FSIZE, 2*FSIZE);
    gl.enableVertexAttribArray(a_Color);

    var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
    if(!u_MvpMatrix) { 
      console.log('Failed to get the storage location of u_MvpMatrix');
      return;
    }
    // 設置背景顏色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    // 開啟隱藏面消除
    gl.enable(gl.DEPTH_TEST);

    var modelMatrix = new Matrix4(); // 模型矩陣
    var viewMatrix = new Matrix4();  // 視點矩陣
    var projMatrix = new Matrix4();  // 投影矩陣
    var mvpMatrix = new Matrix4();   // 用於相乘用
    var angle=0;
    // 執行動畫
    (function animate(){
      // 旋轉位移 等於繞原點Y旋轉
      modelMatrix.setRotate((angle++)%360,0,1,0);
      modelMatrix.translate(1, 0, 1);
      // (視點,觀察目標點,上方向)
      viewMatrix.setLookAt(-0.25, -0.25, 5, 0, 0, -100, 0, 1, 0);
      // 投影矩陣(fov可視空間底面和頂面夾角<大於0>,近裁截面寬高比,近裁截面位置<大於0>,遠裁截面位置<大於0> )
      projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
      // 矩陣相乘
      mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
      // 賦值
      gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);

      //清屏|清深度緩沖
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
      // 啟用多邊形偏移,避免深度沖突
      gl.enable(gl.POLYGON_OFFSET_FILL);

      // (基本圖形,第幾個頂點,執行幾次),修改基本圖形項可以生成點,線,三角形,矩形,扇形等
      gl.drawArrays(gl.TRIANGLES, 0, 9);


      //位移后,再將前面3個三角形重新繪制
      modelMatrix.translate(-2, 0, 0);
      mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
      gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);

      //設置偏移量
      gl.polygonOffset(1.0, 1.0);
      gl.drawArrays(gl.TRIANGLES, 0, 9);

      requestAnimationFrame(animate);
    }());
}

main();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM