webgl 系列 —— 繪製一個點(版本2、版本3、版本4、版本5)

来源:https://www.cnblogs.com/pengjiali/archive/2023/03/01/17168064.html
-Advertisement-
Play Games

繪製一個點 我們初步認識了 webgl,本篇主要圍繞繪製一個點的示例,逐步實現下麵功能: 點的位置從 js 傳入著色器 點的大小由 js 傳入著色器 通過滑鼠點擊繪點 通過滑鼠點擊繪點,並改變點的顏色 繪製一個點(版本2) 需求 在上篇中我們在canvas中心繪製了一個點(效果如下),但這點的位置是 ...


繪製一個點

我們初步認識了 webgl,本篇主要圍繞繪製一個點的示例,逐步實現下麵功能:

  1. 點的位置從 js 傳入著色器
  2. 點的大小由 js 傳入著色器
  3. 通過滑鼠點擊繪點
  4. 通過滑鼠點擊繪點,並改變點的顏色

繪製一個點(版本2)

需求

在上篇中我們在canvas中心繪製了一個點(效果如下),但這點的位置是直接寫在頂點著色器中 gl_Position = vec4(0.0, 0.0, 0.0, 1.0);

需求:點的位置從 js 傳入著色器

思路

將位置信息從 js 傳入著色器有兩種方式:attribute 變數、uniform 變數

  • attribute - 傳輸的是那些與頂點相關的數據
  • uniform - 傳輸的是那些對於所有頂點都相同的數據

我們這裡使用 attribute 變數,因為每個點都有各自的坐標

Tip:GLSL 中有三種類型的“變數”或者說數據存儲類型。每一種類型都有特定的目標和使用方法:: attributes、varyings(複雜,暫不介紹)和uniforms —— Data in WebGL

attribute

attribute 是一種著色器語言(GLSL ES)變數,用於向頂點著色器內傳輸數據,只有頂點著色器能使用它。

使用 attribute 有如下三步:

  1. 在頂點著色器中聲明 attribute 變數
  2. 在頂點著色器中將聲明的 attribute 變數賦值給 gl_Position 變數
  3. 向 attribute 變數傳輸數據

實現

完整代碼如下:

// point02.js
// 必須要 ;
const VSHADER_SOURCE = `
// 聲明 attribute 變數
attribute vec4 a_Position;
void main() {
  // 將聲明的 attribute 變數賦值給 gl_Position 變數
  gl_Position = a_Position;
  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('初始化著色器失敗');
    return;
  }

  // 獲取 attribute 變數的存儲位置。如果找不到該屬性則返回 -1。
  const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
  if (a_Position < 0) {
    console.log('找不到 a_Position 的存儲位置');
    return;
  }
  // 為頂點 attibute 變數賦值
  gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);


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

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

Tip:頂點著色器中必須要寫 ;

核心代碼:

// 聲明 attribute 變數
attribute vec4 a_Position; 
void main() {
  // 將聲明的 attribute 變數賦值給 gl_Position 變數
  gl_Position = a_Position;
}

// 獲取 attribute 變數的存儲位置。如果找不到該屬性則返回 -1。
// gl.program - 在 initShaders() 函數中創建了這個程式對象。現在只需知道它是一個參數
const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
// 為頂點 attibute 變數賦值
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);

Tip:習慣:所有 attribute 的變數以 a_ 開頭,所有 uniform 的變數以 u_ 開頭。

getAttribLocation

WebGLRenderingContext.getAttribLocation(program, name) 方法返回了給定 WebGLProgram對象 中某屬性的下標指向位置。

vertexAttrib3f

vertexAttrib3f 是一系列同族方法中的一個,為頂點 attibute 變數賦值。

一系列方法指:

void gl.vertexAttrib1f(index, v0);
void gl.vertexAttrib2f(index, v0, v1);
// 將數據(v0, v1, v2) 傳給 index 指定的 attribute 變數
void gl.vertexAttrib3f(index, v0, v1, v2);
void gl.vertexAttrib4f(index, v0, v1, v2, v3);

// 矢量版本 v(vector)
void gl.vertexAttrib1fv(index, value);
void gl.vertexAttrib2fv(index, value);
void gl.vertexAttrib3fv(index, value);
void gl.vertexAttrib4fv(index, value);
  • gl.vertexAttrib1f 傳1個分量。第二第三分量設置為 0.0,第四個分量設置為1.0
  • gl.vertexAttrib3f 傳3個分量。第四個分量設置為 1.0,以此類推。

矢量版本以 v 結尾,接受類型化數組。就像這樣:

const floatArray = new Float32Array([10.0, 5.0, 2.0]);
gl.vertexAttrib3fv(a_foobar, floatArray);

繪製一個點(版本3)

需求

需求:點的大小由 js 傳入著色器 —— 在版本2的基礎上實現

實現

和版本2的實現類似:

 const VSHADER_SOURCE = `
 attribute vec4 a_Position;
+attribute float a_PointSize;
 void main() {
   // 將聲明的 attribute 變數賦值給 gl_Position 變數
   gl_Position = a_Position;
-  gl_PointSize = 10.0;
+  gl_PointSize = a_PointSize;
 }
 `

function main() {
   gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);

+  const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
+  if (a_PointSize < 0) {
+    console.log('找不到 a_PointSize 的存儲位置');
+    return;
+  }
+  // 為頂點 attibute 變數賦值
+  gl.vertexAttrib1f(a_PointSize, 10.0);

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

繪製一個點(版本4)

需求

需求:通過滑鼠點擊繪點

效果如下:

實現

完整代碼如下:

const VSHADER_SOURCE = `
// 聲明 attribute 變數
attribute vec4 a_Position;
attribute float a_PointSize;
void main() {
  // 將聲明的 attribute 變數賦值給 gl_Position 變數
  gl_Position = a_Position;
  gl_PointSize = a_PointSize;               
}
`

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('初始化著色器失敗');
    return;
  }

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

  const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
  if (a_PointSize < 0) {
    console.log('找不到 a_PointSize 的存儲位置');
    return;
  }
  // 為頂點 attibute 變數賦值
  gl.vertexAttrib1f(a_PointSize, 10.0);

  // 獲取 attribute 變數的存儲位置。如果找不到該屬性則返回 -1。
  const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
  if (a_Position < 0) {
    console.log('找不到 a_Position 的存儲位置');
    return;
  }

  // 存儲所有點
  const points = [];

  // 註冊點擊事件
  $(canvas).click(event => {
    // 將點擊的坐標轉為 webgl 坐標系統。
    const rect = event.target.getBoundingClientRect();
    const x = ((event.clientX - rect.left) - canvas.width / 2) / (canvas.width / 2);
    const y = (canvas.height / 2 - (event.clientY - rect.top)) / (canvas.height / 2);
    console.log(x, y)
    // 將點保存
    points.push({ x, y })
    // 註:使用預設值來清空緩衝。如果註釋這行,顏色緩衝區會被 webgl 重置為預設的透明色(0.0, 0.0, 0.0, 0.0) —— 必須
    gl.clear(gl.COLOR_BUFFER_BIT);
    // 繪點
    points.forEach(item => {
      // 為頂點 attibute 變數賦值
      // 註:即使 x, y 是整數程式也沒問題
      gl.vertexAttrib3f(a_Position, item.x, item.y, 0.0);
      gl.drawArrays(gl.POINTS, 0, 1);
    })
  })
}

核心思路如下:

  • 給 canvas 註冊點擊事件(這裡引入jQuery)
  • 點擊時將 (x, y) 轉為 webgl 坐標(怎麼轉換?百度搜索,這不是重點),並將該點存入一個變數 points 中
  • 迴圈 points 不停繪製點

:迴圈前得通過 gl.clear 清空繪圖區,否則canvas背景會被重置為透明色。

繪製一個點(版本5)

需求

需求:通過滑鼠點擊繪點,並改變點的顏色 —— 在版本4的基礎上實現

效果如下:

思路:顏色從硬編碼改成從 js 傳入。使用 uniform 變數。

uniform

前面我們用 attribute 向頂點著色器傳輸頂點的位置,只有頂點著色器才能使用 attribute。

對於片元著色器,需要使用 uniform 變數。

使用 uniform 有如下三步(和 attribute 類似):

  1. 在頂點著色器中聲明 uniform 變數
  2. 在頂點著色器中將聲明的 uniform 變數賦值給 gl_FragColor 變數
  3. 向 uniform 變數傳輸數據

實現

完整代碼:

const VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute float a_PointSize;
void main() {
  gl_Position = a_Position;
  gl_PointSize = a_PointSize;               
}
`

const FSHADER_SOURCE = `
// 片元著色器必須加上精度描述。否則瀏覽器報錯:No precision specified for (float)
precision mediump float;
// 聲明變數
uniform vec4 u_FragColor;
void main() {
  gl_FragColor = u_FragColor;
}
`

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);

  const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
  if (a_PointSize < 0) {
    console.log('找不到 a_PointSize 的存儲位置');
    return;
  }
  gl.vertexAttrib1f(a_PointSize, 10.0);

  const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
  if (a_Position < 0) {
    console.log('找不到 a_Position 的存儲位置');
    return;
  }

  // 獲取 uniform 變數的存儲位置。如果找不到該屬性則返回 -1。
  const u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor')
  if (u_FragColor < 0) {
    console.log('找不到 u_FragColor 的存儲位置');
    return;
  }

  const points = [];

  // [0, 1)
  const getColor = () => Number.parseFloat(Math.random())
  
  $(canvas).click(event => {
    const rect = event.target.getBoundingClientRect();
    const x = ((event.clientX - rect.left) - canvas.width / 2) / (canvas.width / 2);
    const y = (canvas.height / 2 - (event.clientY - rect.top)) / (canvas.height / 2);
    
    // 將點的隨機顏色
    points.push({ x, y, rgb: [getColor(), getColor(), getColor()] })
    gl.clear(gl.COLOR_BUFFER_BIT);
    points.forEach(item => {
      gl.vertexAttrib3f(a_Position, item.x, item.y, 0.0);
      // 給 unifrom 變數賦值
      gl.uniform4f(u_FragColor, ...item.rgb, 1.0);
      gl.drawArrays(gl.POINTS, 0, 1);
    })
  })
}

核心代碼:

const FSHADER_SOURCE = `
// 片元著色器必須加上精度描述。否則瀏覽器報錯:No precision specified for (float)
precision mediump float;
// 聲明變數
uniform vec4 u_FragColor;
void main() {
  gl_FragColor = u_FragColor;
}
`

function main() {
  // 獲取 uniform 變數的存儲位置。如果找不到該屬性則返回 -1。
  const u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor')
  if (u_FragColor < 0) {
    console.log('找不到 u_FragColor 的存儲位置');
    return;
  }

  $(canvas).click(event => {
    ...
    points.forEach(item => {
      ...
      // 給 unifrom 變數賦值
      gl.uniform4f(u_FragColor, ...item.rgb, 1.0);
      gl.drawArrays(gl.POINTS, 0, 1);
    })
  })
}

:片元著色器必須加上精度描述(例如:precision mediump float;)。否則瀏覽器報錯:No precision specified for (float)

getUniformLocation

getUniformLocationgetAttribLocation 類似,只是這裡返回 uniform 變數的位置

uniform4f

有了 uniform 變數的存儲地址,就可以使用 uniform4f(和 vertexAttrib3f 很類似) 向變數中寫入數據。

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

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

-Advertisement-
Play Games
更多相關文章
  • MySQL中明確查詢語句的執行順序極其重要,瞭解執行順序才不至於犯一些簡單錯誤,例如having 後面是否可以使用 select 中重命名的列名等問題。另外SQL中實際使用最頻繁的就是查詢(Queing),要想寫出高質量、高性能的查詢語句,必須深入地瞭解SQL的邏輯查詢處理順序和機制。 ...
  • 親愛的社區小伙伴們,歷時數月,我們很高興地宣佈,ChunJun 即將迎來 1.16 Release 版本的正式發佈。在新版本中,ChunJun 新增了一批常用功能,進行了多項功能優化和問題修複,併在用戶使用體驗上進行了極大地改善。有17位Contributor 為 ChunJun 提交了多項優化和修 ...
  • 衝突解決 假設你想在應用中使用 some_package 和 other_package,並且它們依賴於不同版本的 url_launcher。於是我們便有了潛在的衝突。避免這種情況的最好方法是 package 的作者在指定依賴項時使用 版本範圍 而非特定版本。 dependencies: url_l ...
  • VE-Plus 自研輕量級 vue3.js 桌面pc端UI組件庫 經過一個多月的籌劃及開發,今天給大家帶來一款全新的Vue3桌面端UI組件庫VEPlus。新增了35+常用的組件,採用vue3 setup語法糖開發,在使用上和element-ui比較類似,極易快速上手。 ve-plus 致力數據驅動視 ...
  • css的全局關鍵字有下麵幾個: inherit : 繼承父級屬性 initial:將元素初始化成css的初始值 unset:繼承和初始化元素 revert:還原到瀏覽器內置樣式 all:代指所有css屬性 下麵分別說說這幾個關鍵字的實際作用 inherit 繼承父級屬性 inherit可以繼承父級屬 ...
  • 客戶端和伺服器 客戶端與伺服器的通信 互聯網:錯綜複雜的網路,比如每個快遞車走的路. TCP/IP:定義數據如何傳輸的通信協議,比如快遞的包裝,發貨規則. DNS:功能變數名稱系統伺服器,輸入一個網址時,找到網頁的伺服器,才能發送 HTTP 請求到正確的地方。(功能變數名稱->IP),比如發貨地址. HTTP:超文 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 1. 介紹 什麼是虛擬滾動?虛擬滾動就是通過js控制大列表中的dom創建與銷毀,只創建可視區域dom,非可視區域的dom不創建。這樣在渲染大列表中的數據時,只創建少數的dom,提高性能。 2. 分類 在虛擬滾動技術中,虛擬滾動可以分為定高 ...
  • 好家伙,本篇為《JS高級程式設計》第十章“期約與非同步函數”學習筆記 1.非同步編程 同步行為和非同步行為的對立統一是電腦科學的一個基本概念。 特別是在 JavaScript 這種單線程事 件迴圈模型中,同步操作與非同步操作更是代碼所要依賴的核心機制。 非同步行為是為了優化因計算量大而 時間長的操作。如果在 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...