webgl 系列 —— 初識 WebGL

来源:https://www.cnblogs.com/pengjiali/archive/2023/02/27/17156241.html
-Advertisement-
Play Games

初識 WebGL 什麼是 WebGL webgl 在支持 canvas 的瀏覽器中進行 2d 或 3d 渲染。 webgl 程式除了有 Html、javascript,還需要加入著色器語言(GLSL ES)。 WebGL 使得網頁在支持 HTML <canvas> 標簽的瀏覽器中,不需要使用任何插件 ...


初識 WebGL

什麼是 WebGL

webgl 在支持 canvas 的瀏覽器中進行 2d 或 3d 渲染。

webgl 程式除了有 Html、javascript,還需要加入著色器語言(GLSL ES)。

WebGL 使得網頁在支持 HTML <canvas> 標簽的瀏覽器中,不需要使用任何插件,便可以使用基於 OpenGL ES 2.0 的 API 在 canvas 中進行 3D 渲染 —— MDN WebGL 教程

通過 caniuse 得知 webgl(98.15%) 和 webgl 2.0(94.12%) 的支持情況。請看下圖:

Tip:個人電腦上,繪製三維最廣泛使用的技術有 Direct3D 和 OpenGL,前者是微軟的,後者是開源免費的。OpenGL 有個特殊版本 OpenGL ES 專門用於嵌入式電腦、手機,而 WebGL 就是從 OpenGL ES 派生出來的。下圖是 OpenGL、OpenGL ES、WebGL 三者之間的關係。其中 webgl 2.0 基於 OpenGL ES 3.0 未畫出來:

canvas

Canvas_API 提供了一個通過JavaScript 和 HTML的 <canvas>元素來繪製圖形的方式。它可以用於動畫、游戲畫面、數據可視化、圖片編輯以及實時視頻處理等方面。

Canvas API 主要聚焦於 2D 圖形。而同樣使用<canvas>元素的 WebGL API 則用於繪製硬體加速的 2D 和 3D 圖形。

示例:

// canvas.html
<body>
    <canvas id="canvas" width="300" height="300">
        抱歉,您的瀏覽器不支持 canvas 元素
        (這些內容將會在不支持<canvas>元素的瀏覽器或是禁用了 JavaScript 的瀏覽器內渲染並展現)
        </canvas>
        <script>
            var canvas = document.getElementById('canvas');

            // getContext - 方法返回canvas 的上下文,如果上下文沒有定義則返回 null 
            var ctx = canvas.getContext('2d');

            // 設置填充顏色
            ctx.fillStyle = 'green';
            // 繪製矩形
            ctx.fillRect(10, 10, 100, 100);
        </script>
</body>

效果如下:

Tip:不管繪製二維還是三維都是這三步:

  1. 獲取 canvas
  2. 請求繪圖上下文
  3. 調用繪圖上下文中的繪圖函數

第一個webgl示例

需求:清空繪圖區。也就是使用背景色清空 canvas 的繪圖區

實現如下:

// webgl01.html
<body>
    <canvas id="canvas" width="300" height="300">
        抱歉,您的瀏覽器不支持 canvas 元素
        (這些內容將會在不支持<canvas>元素的瀏覽器或是禁用了 JavaScript 的瀏覽器內渲染並展現)
        </canvas>
        <script>
            var canvas = document.getElementById('canvas');
            const gl = canvas.getContext("webgl");
            // 使用完全不透明的藍色清除所有圖像
            gl.clearColor(0.0, 0.0, 1.0, 1.0);
            // 用上面指定的顏色清除緩衝區
            gl.clear(gl.COLOR_BUFFER_BIT);
        </script>
</body>

效果如下:

仍舊是3步:

  1. 獲取 canvas
  2. 請求繪圖上下文
  3. 調用繪圖上下文中的繪圖函數

在 canvas 繪製矩形之前需要指定顏色(ctx.fillStyle = 'green';),在 webgl 中類似,清空繪圖區之前也得指定背景色,一旦指定背景色,背景色就會在 webgl 系統中存留,將來還需要使用同樣的顏色清空繪圖區,,就不需要再次指定背景色。

clearColor 和 clear語法如下:

// WebGLRenderingContext.clearColor() 方法用於設置清空顏色緩衝時的顏色值。指定調用 clear() 方法時使用的顏色值
void gl.clearColor(red, green, blue, alpha) 

// WebGLRenderingContext.clear() 方法使用預設值來清空緩衝。
void gl.clear(mask);
    mask
        gl.COLOR_BUFFER_BIT    // 顏色緩衝區
        gl.DEPTH_BUFFER_BIT    // 深度緩衝區 - 三維世界中使用
        gl.STENCIL_BUFFER_BIT  // 模板緩衝區 - 很少使用

如果沒有指定背景色,預設值如下:

  • 顏色緩衝區 - (0.0, 0.0, 0.0, 0.0)
  • 深度緩衝區 - 1.0

繪製一個點

需求

需求:在 canvas 中心畫一個 10px 紅色的點。

效果如下:

思路

用 canvas 繪製一個矩形很簡單,先指定顏色,在繪製矩形。就像這樣:

// canvas繪製矩形
ctx.fillStyle = 'green';
ctx.fillRect(10, 10, 100, 100);

但 webgl 需要使用著色器,著色器提供了靈活且強大的繪製二維或三維的方法,也更加複雜。

我們先看代碼,有一個具體的感受後,在分析其中細節。

代碼

共3個文件。重點關註 point01.js 即可。

  • 新建入口文件 point01.html:
<!-- point01.html -->
<script src="./cuon-utils.js"></script>
<script src="./point01.js"></script>

<body onload="main()">
    <canvas id="webgl" width="300" height="300"> 抱歉,您的瀏覽器不支持 canvas 元素</canvas>
</body>

Tip:以上這段代碼在 chrome 中運行通過,瀏覽器會自動補全格式,例如把 script 標簽放入 head 中。

  • 新建 point01.js:
// point01.js
// 頂點著色器
const VSHADER_SOURCE = `
void main() {
  gl_Position = vec4(0.0, 0.0, 0.0, 1.0); 
  gl_PointSize = 10.0;               
}
`

// 片元著色器
const FSHADER_SOURCE = `
  void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); 
  }
`

function main() {
  const canvas = document.getElementById('webgl');

  const gl = canvas.getContext("webgl");

  // 初始化著色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }

  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT); 

  gl.drawArrays(gl.POINTS, 0, 1);
}

文檔載入後運行 main() 方法,相對第一個 webgl 示例,這裡增加了初始化著色器

Tip:現在只需要把初始化著色器的方法(initShaders() - 請看本篇 cuon-utils.js 章節)作為一個庫中的輔助方法看待,後續文章將介紹其中原理。

  • 新建 cuon-utils.js(內容見本篇擴展),主要提供初始化著色器的方法

代碼解析

總體流程

文檔載入後執行 main() 方法,有如下5個階段:

  • 獲取canvas
  • 取得 webgl 上下文
  • 初始化著色器
  • 清除繪圖區
  • 調用 drawArrays 繪圖

下麵我們主要講一下第三步和最後一步。

齊次坐標

齊次坐標就是將一個原本是 n 維的向量用一個 n+1 維向量來表示。齊次坐標能提高處理三維數據的有效率,所以在三維繫統中大量使用。齊次坐標(x, y, z, w) 等價於三維坐標 (x/w, y/w, z/w)

頂點著色器

頂點著色器(Vertex Shader) - 用來描述頂點特征的程式。例如這裡的位置和大小。頂點指二維(x, y)或三維(x, y, z)空間中的一個點,例如端點或交點。

內置變數:

  • gl_Position - 用於描述頂點位置,必傳,類型是 vec4(即4個float)
  • gl_PointSize - 用戶描述頂點的尺寸(像素),如果不傳,預設 1.0,類型是 float

關於位置,我們只有 (x, y, z) 三個變數,但 vec4 是 4 個,所以需要使用內置函數 vec4() 幫忙創建 vec4 類型的變數。

代碼中 vec4(0.0, 0.0, 0.0, 1.0),這裡第四個分量是 1.0,使用的是齊次坐標。

Tip:先記著 (0.0, 0.0, 0.0) 就是繪圖區的中心,本篇 坐標系統 中會詳細講解。

片元著色器

片元著色器(Fragment Shader) - 進行逐片元處理過程如光照的程式。片元是 webgl 的一個術語,暫時可以將其理解成像素

內置變數:

  • gl_FragColor - 指定片元顏色(RGBA格式),類型是 vec4
初始化著色器

webgl 需要兩種著色器:頂點著色器(Vertex Shader)、片元著色器(Fragment Shader)。

在三維場景中,僅僅用線條和顏色把圖畫出來不夠,還需要考慮光照上去或者觀察者的視角發生變化,對場景有什麼影響。著色器可以靈活的完成這些工作。

初始化著色器之前,頂點著色器和片元著色器都是空白,把著色器程式作為字元串形式傳給 initShaders(initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE))之後,webgl 系統中的著色器就建立好。

下圖是執行 initShaders() 前後的情形:

Tip: 先執行頂點著色器,然後把 gl_Positiongl_PointSize 傳給片元著色器。實際上片元著色器接收到的是經過柵格化處理後的片元(柵格化在畫三角形時在講解)。

繪圖

建立著色器之後,首先清空繪圖區域,然後使用 gl.drawArrays() 進行繪製。

gl.drawArrays(mode, first, count) 執行頂點著色器,按照 mode 指定的參數繪製圖形。first 指定從哪個點開始繪製,count 指繪製需要幾個點。

Tip:mode 類型有:

  • gl.POINTS: 繪製一系列點。
  • gl.LINE_STRIP: 繪製一個線條。即,繪製一系列線段,上一點連接下一點。
  • gl.LINE_LOOP: 繪製一個線圈。即,繪製一系列線段,上一點連接下一點,並且最後一點與第一個點相連。
  • gl.LINES: 繪製一系列單獨線段。每兩個點作為端點,線段之間不連接。
  • gl.TRIANGLE_STRIP:繪製一個三角帶。
  • gl.TRIANGLE_FAN:繪製一個三角扇。
  • gl.TRIANGLES: 繪製一系列三角形。每三個點作為頂點。

例如我們這裡是:gl.drawArrays(gl.POINTS, 0, 1),繪製圖形(點),需要一個點,從第一個點開始繪製。後續畫多個點時會對 first 和 count 有更清晰的理解。

代碼註釋
// point01.js
// 頂點著色器
const VSHADER_SOURCE = `
// 和 C 語言一樣,必須包含一個 main() 函數,void 表示沒有返回值
// 註:不能給 main() 指定參數
void main() {
  // 頂點著色器內置變數: gl_Position 頂點位置、gl_PointSize 頂點尺寸
  gl_Position = vec4(0.0, 0.0, 0.0, 1.0); 
  gl_PointSize = 10.0;               
}
`

// 片元著色器
const FSHADER_SOURCE = `
  void main() {
    // 片元著色器內置變數: gl_FragColor 指定片元顏色
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); 
  }
`

function main() {
  const canvas = document.getElementById('webgl');

  const gl = canvas.getContext("webgl");

  // 初始化著色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('初始化著色器失敗');
    return;
  }

  // 清空繪圖區
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT); 

  // 繪製圖形(點),需要一個點,從第一個點開始繪製
  gl.drawArrays(gl.POINTS, 0, 1);
}

擴展

坐標系統

webgl 的坐標系(x, y, z)和 canvas 的坐標系(x, y)不同。

canvas 的原點(0, 0)在左上角。webgl 處理的是三維,所以使用三維坐標系統(笛卡爾坐標系),可用(x, y, z) 表示。也可認為是右手坐標系。請看下圖:

webgl 坐標和 canvas 坐標對應關係如下(可對照上面中間那張圖):

cuon-utils.js

// cuon-utils.js (c) 2012 kanda and matsuda
/**
 * Create a program object and make current
 * @param gl GL context
 * @param vshader a vertex shader program (string)
 * @param fshader a fragment shader program (string)
 * @return true, if the program object was created and successfully made current 
 */
function initShaders(gl, vshader, fshader) {
  var program = createProgram(gl, vshader, fshader);
  if (!program) {
    console.log('Failed to create program');
    return false;
  }

  gl.useProgram(program);
  gl.program = program;

  return true;
}

/**
 * Create the linked program object
 * @param gl GL context
 * @param vshader a vertex shader program (string)
 * @param fshader a fragment shader program (string)
 * @return created program object, or null if the creation has failed
 */
function createProgram(gl, vshader, fshader) {
  // Create shader object
  var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
  var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
  if (!vertexShader || !fragmentShader) {
    return null;
  }

  // Create a program object
  var program = gl.createProgram();
  if (!program) {
    return null;
  }

  // Attach the shader objects
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);

  // Link the program object
  gl.linkProgram(program);

  // Check the result of linking
  var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!linked) {
    var error = gl.getProgramInfoLog(program);
    console.log('Failed to link program: ' + error);
    gl.deleteProgram(program);
    gl.deleteShader(fragmentShader);
    gl.deleteShader(vertexShader);
    return null;
  }
  return program;
}

/**
 * Create a shader object
 * @param gl GL context
 * @param type the type of the shader object to be created
 * @param source shader program (string)
 * @return created shader object, or null if the creation has failed.
 */
function loadShader(gl, type, source) {
  // Create shader object
  var shader = gl.createShader(type);
  if (shader == null) {
    console.log('unable to create shader');
    return null;
  }

  // Set the shader program
  gl.shaderSource(shader, source);

  // Compile the shader
  gl.compileShader(shader);

  // Check the result of compilation
  var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (!compiled) {
    var error = gl.getShaderInfoLog(shader);
    console.log('Failed to compile shader: ' + error);
    gl.deleteShader(shader);
    return null;
  }

  return shader;
}
作者:彭加李
出處:https://www.cnblogs.com/pengjiali/p/17156241.html
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 文章對 u-boot 學習路線進行了簡單介紹, 並從 u-boot 構建框架著手解構 u-boot, 以 Kconfig 為索引文件自底向上分析框架。 除此之外還介紹了 Boot Loader 的幾個基本流程, 對其中的 TPL 過程進行了剖析。 ...
  • 首發微信公眾號:SQL資料庫運維 原文鏈接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485212&idx=1&sn=450e9e94fa709b5eeff0de371c62072b&chksm=ea37536cdd40da7 ...
  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 作者: bruce 文章來源:GreatSQL社區原創 什麼是events_statements_current表 在MySQL中,PFS下有一張記憶體表 ...
  • 大數據平臺建設有其天生的複雜性,每一年都在推陳出新,從WareHouse、DataLake到LakeHouse,各種各樣的Batch、Stream、MPP、Machine Learning、Neural Network計算引擎,對應解決的場景和組合的方式非常個性化,建設過程會遇到包括技術層面、組織層... ...
  • 取消原生右鍵事件 在 main.ts 函數中取消瀏覽器預設右鍵菜單: window.oncontextmenu = () => { return false; }; 組件模板 做一個不同區域右鍵點擊之後不同菜單項的組件,創建組件模板: <!-- ContextMenu --> <div ref="m ...
  • 主題說明 這是一款高自定義的博客園主題 Lite,快速預覽 Himmelbleu 的 Lite 主題博客。 支持博主:GitHub 或 Gitee,點個 :star:star 喲~。Lite 基於 Vue3 + Vite 建立在博客園之上的一款博客主題。 版本說明 目前已經更新到 v1.4.7,本次 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 什麼是JWT JWT是全稱是JSON WEB TOKEN,是一個開放標準,用於將各方數據信息作為JSON格式進行對象傳遞,可以對數據進行可選的數字加密,可使用RSA或ECDSA進行公鑰/私鑰簽名。 使用場景 JWT最常見的使用場景就是緩存 ...
  • nestJs中使用typeORM報’QueryFailedError: Table ‘equtype’ already exists’錯誤。里註冊使用了駝峰命名,我後來將其改成小寫就解決了該問題,希望對你有所幫助!如圖,博主在定義實體類的時候,代碼如下。後來我發現我在定義實體的時候。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...