可視化學習:如何用WebGL繪制3D物體

在之前的文章中,我們使用WebGL繪制了很多二維的圖形和圖像,在學習2D繪圖的時候,我們提過很多次關于GPU的高效渲染,但是2D圖形的繪制只展示了WebGL部分的能力,WebGL更強大的地方在于,它可以繪制各種3D圖形,而3D圖形能夠極大地增強可視化的表現能力。

相信很多小伙伴都對此有所耳聞,也有不少人學習WebGL,就是沖著它的3D繪圖能力。

接下來,我就用一個簡單的正立方體的例子來演示在WebGL中如何繪制3D物體。

從二維到三維

首先,我們先來繪制一個熟悉的2D圖形,正方形。

// vertex
attribute vec2 a_vertexPosition;
attribute vec4 color;varying vec4 vColor;void main() {gl_PointSize = 1.0;vColor = color;gl_Position = vec4(a_vertexPosition, 1, 1);
}// fragment
#ifdef GL_ES
precision highp float;
#endifvarying vec4 vColor;void main() {gl_FragColor = vColor;
}

// ...
renderer.setMeshData([{positions: [[-0.5, -0.5],[-0.5, 0.5],[0.5, 0.5],[0.5, -0.5]],attributes: {color: [[1, 0, 0, 1],[1, 0, 0, 1],[1, 0, 0, 1],[1, 0, 0, 1],]},cells: [[0, 1, 2], [2, 0, 3]]
}]);
// ...

上述這些代碼比較簡單,我就不過多解釋了。

在畫布上我們看到,繪制了一個紅色的正方形,它是一個平面圖形。

接下來,我們就在這個圖形的基礎上,將它拓展為3D的正立方體。

要想把2維圖形拓展為3維幾何體,第一步就是要把頂點擴展到3維。也就是把vec2擴展為vec3。

// vertex
attribute vec3 a_vertexPosition;
attribute vec4 color;varying vec4 vColor;void main() {gl_PointSize = 1.0;vColor = color;gl_Position = vec4(a_vertexPosition, 1);
}

當然僅僅修改Shader是不夠的,因為數據是從JavaScript傳遞過來的,所以我們需要在JavaScript中計算立方體的頂點數據,然后再傳遞給Shader。

一個立方體有8個頂點,能組成6個面。在WebGL中需要用12個三角形來繪制它。

如果6個面的屬性相同的話,我們可以復用8個頂點來繪制;

但如果屬性不完全相同,比如每個面要繪制成不同的顏色,或者添加不同的紋理圖片,就得把每個面的頂點分開。這樣的話,就需要24個頂點來分別處理6個面。

為了方便使用,我們可以定義一個JavaScript函數,用來生成立方體6個面的24個頂點,以及12個三角形的索引,并且定義每個面的顏色。

/*** 生成立方體6個面的24個頂點,12個三角形的索引,定義每個面的顏色信息* @param size* @param colors* @returns {{cells: *[], color: *[], positions: *[]}}*/
export function cube(size = 1.0, colors = [[1, 0, 0, 1]]) {const h = 0.5 * size;const vertices = [[-h, -h, -h],[-h, h, -h],[h, h, -h],[h, -h, -h],[-h, -h, h],[-h, h, h],[h, h, h],[h, -h, h]];const positions = [];const color = [];const cells = [];let colorIdx = 0;let cellsIdx = 0;const colorLen = colors.length;function quad(a, b, c, d) {[a, b, c, d].forEach(item => {positions.push(vertices[item]);color.push(colors[colorIdx % colorLen]);});cells.push([0, 1, 2].map(i => i + cellsIdx),[0, 2, 3].map(i => i + cellsIdx));colorIdx ++;cellsIdx += 4;}quad(1, 0, 3, 2); // 內quad(4, 5, 6, 7); // 外quad(2, 3, 7, 6); // 右quad(5, 4, 0, 1); // 左quad(3, 0, 4, 7); // 下quad(6, 5, 1, 2); // 上return {positions, color, cells};
}

現在我們就可以通過調用cube這個函數,構建出立方體的頂點信息。

const geometry = cube(1.0, [[1, 0, 0, 1],   // 紅[0, 0.5, 0, 1], // 綠[0, 0, 1, 1]    // 藍
]);

通過這段代碼,我們就能創建出一個棱長為1的立方體,并且六個面的顏色分別是“紅、綠、藍、紅、綠、藍”。

接下來我們就要把這個立方體的頂點信息傳遞給Shader。

在傳遞數據之前,我們需要先了解一個知識點,是關于繪制3D圖形與2D圖形存在的一點不同,那就是繪制3D圖形時,必須要開啟深度檢測和啟用深度緩沖區。

在WebGL中,我們可以通過gl.enable(gl.DEPTH_TEST);這段代碼來開啟深度檢測;在清空畫布的時候,也要用gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);這段代碼來同時清空顏色緩沖區和深度緩沖區。

啟動和清空深度檢測和深度緩沖區這兩個步驟,非常重要。但是一般情況下,我們幾乎不會用原生的方式來編寫代碼,所以了解一下即可。為了方便使用,在本文演示的例子中,我們還是直接使用gl-renderer這個庫,它封裝了深度檢測,我們在使用時,在創建renderer的時候配置一個參數depth: true就可以了。

現在我們就把這個三維立方體用gl-renderer渲染出來。

// ...
renderer = new GlRenderer(glRef.value, {depth: true // 開啟深度檢測
});
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
renderer.setMeshData([{positions: geometry.positions,attributes: {color: geometry.color},cells: geometry.cells
}]);
renderer.render();

現在我們在畫布上看到的是一個紅色正方形,這是因為其他面被遮擋住了。

投影矩陣:變換WebGL坐標系

但是,等等,為什么我們看到的是紅色的一面呢?按照我們所編寫的代碼,預期看到的應該是綠色的一面,也就是說我們預期Z軸是向外的,因為規范的直角坐標系是右手坐標系。所以按照現在的繪制結果,我們發現WebGL的坐標系其實是左手系的?

但一般來說,不管什么圖形庫或者圖形框架,在繪圖的時候,都會默認將坐標系從左手系轉換為右手系,因為這更符合我們的使用習慣。所以這里,我們也去把WebGL的坐標系從左手系轉換為右手系,簡單來說,就是將Z軸坐標方向反轉。關于坐標轉換,可以通過齊次矩陣來完成。對坐標轉換不熟悉的小伙伴,可以參考我之前的一篇關于仿射變換的文章。

將Z軸坐標方向反轉,對應的齊次矩陣是這樣的:

[1, 0, 0, 0,0, 1, 0, 0,0, 0, -1, 0,0, 0, 0, 1
]

這種轉換坐標的齊次矩陣,也被稱為投影矩陣,ProjectionMatrix。

現在我們修改一下頂點著色器,把這個投影矩陣添加進去。

// vertex
attribute vec3 a_vertexPosition; // 1:把頂點從vec2擴展到vec3
attribute vec4 color; // 四維向量varying vec4 vColor;
uniform mat4 projectionMatrix; // 2:投影矩陣-變換坐標系void main() {gl_PointSize = 1.0;vColor = color;gl_Position = projectionMatrix * vec4(a_vertexPosition, 1.0);
}

現在我們就能看到畫布上顯示的是綠色的正方形了。

模型矩陣:讓立方體旋轉起來

現在我們只能看到立方體的一個面,因為Z軸是垂直于屏幕的,這樣子從視覺上看好像和2維圖形沒什么區別,沒法讓人很直觀地聯想、感受到這是一個三維的幾何體,為了將其他的面露出來,我們可以去旋轉立方體。

要想旋轉立方體,我們同樣可以通過矩陣運算來實現。這個矩陣叫做模型矩陣,ModelMatrix,它定義了被繪制的物體變換。

把模型矩陣加入到頂點著色器中,將它與投影矩陣相乘,再乘上齊次坐標,就得到最終的頂點坐標了。

attribute vec3 a_vertexPosition; // 1:把頂點從vec2擴展到vec3
attribute vec4 color; // 四維向量varying vec4 vColor;
uniform mat4 projectionMatrix; // 2:投影矩陣-變換坐標系
uniform mat4 modelMatrix; // 3:模型矩陣-使幾何體旋轉void main() {gl_PointSize = 1.0;vColor = color;gl_Position = projectionMatrix * modelMatrix * vec4(a_vertexPosition, 1.0);
}

現在我們定義一個JavaScript函數,用立方體沿x、y、z軸的旋轉來生成模型矩陣。

以x、y、z三個方向的旋轉得到三個齊次矩陣,然后將它們相乘,就能得到最終的模型矩陣。

import { multiply } from 'ogl/src/math/functions/Mat4Func.js';
// ...
export function fromRotation(rotationX, rotationY, rotationZ) {let c = Math.cos(rotationX);let s = Math.sin(rotationX);const rx = [1,  0, 0, 0, // 繞X軸旋轉0,  c, s, 0,0, -s, c, 0,0,  0, 0, 1];c = Math.cos(rotationY);s = Math.sin(rotationY);const ry = [c,  0, s, 0,0,  1, 0, 0, // 繞Y軸旋轉-s, 0, c, 0,0,  0, 0, 1];c = Math.cos(rotationZ);s = Math.sin(rotationZ);const rz = [c,  s, 0, 0,-s, c, 0, 0,0,  0, 1, 0, // 繞Z軸旋轉0,  0, 0, 1];const ret = [];multiply(ret, rx, ry);multiply(ret, ret, rz);return ret;
}

我們把模型矩陣傳給頂點著色器,不斷更新三個旋轉角度,就能實現立方體旋轉的效果。

// ...
let rotationX = 0;
let rotationY = 0;
let rotationZ = 0;
function update() {rotationX += 0.003;rotationY += 0.005;rotationZ += 0.007;renderer.uniforms.modelMatrix = fromRotation(rotationX, rotationY, rotationZ);requestAnimationFrame(update);
}
update();
// ...

現在我們就能在旋轉中看到立方體的其他幾個面了,能更直觀地感受到這是一個三維物體。

總結

至此,我們就實現了正立方體的繪制。在3D物體的繪制中,正立方體屬于是比較簡單的一類,屏幕前的小伙伴們都可以來動手嘗試下,感興趣的小伙伴,還可以嘗試去實現圓柱體、正四面體等等這些幾何體的繪制。

文章轉載自:beckyye

原文鏈接:https://www.cnblogs.com/beckyyyy/p/18293794

體驗地址:引邁 - JNPF快速開發平臺_低代碼開發平臺_零代碼開發平臺_流程設計器_表單引擎_工作流引擎_軟件架構

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/45036.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/45036.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/45036.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C語言之數據在內存中的存儲(2),浮點數在內存中的存儲

目錄 前言 一、引例 二、浮點型在內存中的存儲 三、浮點數在內存中的存和取過程 1.浮點數的存儲過程 2.浮點數的取過程 四、引例解析 總結 前言 想知道浮點數在內存中是如何存儲的嗎,本文就告訴你答案,雖然一般情況題目還是面試涉及到浮點數在內…

新華三H3CNE網絡工程師認證—ACL使用場景

ACL主要用于實現流量的過濾,業務中網絡的需求不止局限于能夠連同。 一、過略工具 你的公司當中有研發部門,包括有財務部門,財務部門的訪問是要做到控制的,防止被攻擊。 這種的過濾方法為,在設備側可以基于訪問需求來…

解決IntelliJ IDEA連接MySQL時“Public Key Retrieval is not Allowed”問題

前言 在使用IntelliJ IDEA開發環境中連接MySQL數據庫時,可能會遇到“Public Key Retrieval is not allowed”這樣的錯誤提示,即使輸入的用戶名和密碼完全正確。本文將指導你如何解決這一問題,確保順利建立數據庫連接。 錯誤背景 這一問題通…

AI算力發展現狀與趨勢分析

綜合算力發展現狀與趨勢分析 在數字經濟的疾速推動下,綜合算力作為驅動各類應用和服務的新型生產力,其價值日益凸顯。我們深入探討了綜合算力的定義、重要性以及當前發展狀況;并從算力形態、運力性能和存儲技術等角度,預見了其發展…

基于Java技術的校友社交系統

你好呀,我是計算機學姐碼農小野!如果你對校友社交系統感興趣或者有相關需求,可以私信聯系我。 開發語言 Java 數據庫 MySQL 技術 Java技術SpringBoot框架 工具 IDEA/Eclipse、Navicat、Maven 系統展示 首頁 校友會信息界面 校友活動…

Sqli-labs 3

1.按照路徑http://localhost/sqli-labs/sqli-labs-master/Less-3/進入 2.判斷注入類型----字符型 Payload:?id1’) and 11-- 注:根據報錯提示的語法錯誤,在第一行中使用接近’union select 1,2,3--’)的正確語法 3.判斷注入點:…

【Linux】vim詳解

1.什么是vi/vim? 簡單來說,vi是老式的文本編輯器,不過功能已經很齊全了,但是還是有可以進步的地方。vim則可以說是程序開發者的一項很好用的工具,就連 vim的官方網站( http://www.vim.org)自己也說vim是一…

如何計算卷積層輸出圖像的大小以及池化大小輸出

如何計算卷積層輸出圖像的大小&以及池化大小輸出 卷積 在卷積神經網絡(CNN)中,計算卷積層輸出圖像的大小是一個常見的操作。以下是卷積計算的基本公式和步驟: 卷積層輸出尺寸計算公式: Output_size ? Input_s…

區塊鏈項目全球成功指南:全面覆蓋的媒體宣發策略與實踐

隨著區塊鏈技術的迅速普及和發展,全球范圍內對區塊鏈項目的關注度不斷提升。為了在國際市場上取得成功,區塊鏈項目需要通過有效的媒體宣傳策略來提高知名度,吸引投資,并建立強大的社區支持。本文將詳細介紹區塊鏈項目在海外媒體宣…

為企業提升銷售工作效率的工作手機管理系統

在競爭日益激烈的市場環境中,企業的銷售團隊如同前線戰士,其作戰效率直接關乎企業的生存與發展。然而,傳統銷售管理模式下的信息孤島、溝通不暢、數據混亂等問題,正悄然成為制約銷售效率提升的瓶頸。今天,我們為您揭秘…

在 Windows 平臺搭建 MQTT 服務

引言 MQTT 是一種輕量級、基于發布/訂閱模式的消息傳輸協議,旨在用極小的代碼空間和網絡帶寬為物聯網設備提供簡單、可靠的消息傳遞服務。MQTT 經過多年的發展,如今已被廣泛應用于資源開采、工業制造、移動通信、智能汽車等各行各業,使得 MQ…

匯聚榮做拼多多電商怎么樣?

匯聚榮做拼多多電商怎么樣?在當前電商平臺競爭激烈的背景下,拼多多憑借其獨特的商業模式和市場定位迅速崛起。對于想要加入拼多多的商家而言,了解平臺的特點、優勢及挑戰是至關重要的。本文將深入分析加入拼多多電商的多個方面,幫助讀者全面…

ubuntu計劃任務反彈

目錄 實驗環境 實驗步驟 目標主機構造任務計劃 構造語句 語句解釋 kali開啟監聽 監聽成功 問題 原因 實驗環境 攻擊者 操作系統:kali IP:192.168.244.141 目標主機 操作系統:ubuntu IP:192.168.244.151 實驗步驟 目…

論文學習_An Empirical Study of Deep Learning Models for Vulnerability Detection

1. 引言 研究背景:近年來,深度學習漏洞檢測工具取得了可喜的成果。最先進的模型報告了 0.9 的 F1 分數,并且優于靜態分析器。結果令人興奮,因為深度學習可能會給軟件保障帶來革命性的變化。因此,IBM、谷歌和亞馬遜等行業公司非常感興趣,并投入巨資開發此類工具和數據集。…

(8)揭示Python編程精髓:深潛繼承與多態的奇幻之旅

目錄 1. 命名空間與作用域1.1 命名空間概述1.2 作用域1.2.1 局部作用域1.2.2 全局作用域1.2.3 修改全局變量1.2.4 嵌套作用域 2. 繼承3. 多態(Polymorphism) 1. 命名空間與作用域 1.1 命名空間概述 命名空間是一個從名字到對象的映射,它在P…

Qt:19.浮動窗口/子窗口(子窗口介紹、代碼方式創建子窗口、設置子窗口標題、為子窗口添加控件、設置子窗口停靠位置)

目錄 1.子窗口介紹: 2.代碼方式創建子窗口: 3.設置子窗口標題: 4.為子窗口添加控件: 5.設置子窗口停靠位置。 1.子窗口介紹: 在 Qt 中,可以創建和管理子窗口(子窗口體)以實現多窗…

圖片怎么制作成長期可用的活碼?掃碼提供圖片預覽的制作技巧

現在圖片的尺寸和清晰度的質量越來越高,相對應的會占用更多的存儲空間,現在很多人會將圖片存入云端后,通過生成二維碼的方式,掃碼來查看圖片內容。圖片轉換二維碼有利于將圖片分享給其他人查看,還能夠節省更多的空間&a…

SpringBoot自己開發一個starter

提示:本文主要講述如何自行開發一個SpringBoot的starter 文章目錄 目錄 文章目錄 前言 一、Starter是什么 二、創建一個SpringBoot項目 1.創建一個基本的SpringBoot項目 2.選擇要下載的庫 三、設置項目 1.提示 2.配置pom.xml 3.重點代碼 1.創建一個User類…

16.x86游戲實戰-匯編指令push pop pushad popad

免責聲明:內容僅供學習參考,請合法利用知識,禁止進行違法犯罪活動! 本次游戲沒法給 內容參考于:微塵網絡安全 工具下載: 鏈接:https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

【第29章】MyBatis-Plus之分頁插件

文章目錄 前言一、支持的數據庫二、配置方法三、屬性介紹四、自定義 Mapper 方法中使用分頁五、其他注意事項六、Page 類七、實戰1. 配置類2. 分頁類3. 測試 總結 前言 MyBatis-Plus 的分頁插件 PaginationInnerInterceptor 提供了強大的分頁功能,支持多種數據庫&a…