GWT 如何在您的瀏覽器中解鎖增強現實

已發表: 2022-03-11

在我們之前關於 GWT Web Toolkit 的文章中,我們討論了 GWT 的優勢和特點,回想一下大體的想法,它讓我們可以將 Java 源代碼轉換為 JavaScript 並無縫混合 Java 和 JavaScript 庫。 我們注意到 GWT 生成的 JavaScript 得到了極大的優化。

在今天的博文中,我們希望更深入地了解 GWT 工具包的實際應用。 我們將演示如何利用 GWT 構建一個特殊的應用程序:一個實時運行的增強現實 (AR) Web 應用程序,完全使用 JavaScript,在瀏覽器中。

瀏覽器中的增強現實?這比你想像的要容易。

在本文中,我們將重點介紹 GWT 如何使我們能夠輕鬆地與許多 JavaScript API(例如 WebRTC 和 WebGL)交互,並允許我們利用從未打算在瀏覽器中使用的大型 Java 庫 NyARToolkit。 我們將展示 GWT 如何讓我和我在 Jooink 的團隊將所有這些部分放在一起創建我們的寵物項目Picshare ,這是一個基於標記的 AR 應用程序,您現在可以在瀏覽器中試用。

這篇文章不會全面介紹如何構建應用程序,而是展示如何使用 GWT 輕鬆克服看似壓倒性的挑戰。

項目概述:從現實到增強現實

使用 GWT、WebRTC、WebGL 和 ARToolKit 在瀏覽器中實現基於標記的增強現實的管道。

Picshare使用基於標記的增強現實。 這種類型的 AR 應用程序會在場景中搜索標記:一種特定的、易於識別的幾何圖案,就像這樣。 標記提供有關標記對象的位置和方向的信息,允許軟件以逼真的方式將額外的 3D 風景投影到圖像中。 這個過程的基本步驟是:

  • 訪問攝像頭:在處理本地桌面應用程序時,操作系統提供對設備大部分硬件的 I/O 訪問。 當我們處理 Web 應用程序時,情況就不一樣了。 瀏覽器被構建為從網絡下載的 JavaScript 代碼的“沙箱”,最初並不打算讓網站與大多數設備硬件進行交互。 WebRTC 使用 HTML5s 媒體捕獲功能突破了這一障礙,使瀏覽器能夠訪問設備攝像頭及其流等。
  • 分析視頻流:我們有視頻流……現在怎麼辦? 我們必須分析每一幀以檢測標記,併計算標記在重建的 3D 世界中的位置。 這項複雜的任務是 NyARToolkit 的業務。
  • 增強視頻:最後,我們要顯示添加了合成 3D 對象的原始視頻。 我們使用 WebGL 將最終的增強場景繪製到網頁上。

利用 GWT 充分利用 HTML5 的 API

使用 WebGL 和 WebRTC 等 JavaScript API 可以在瀏覽器和用戶之間實現意外和不尋常的交互。

例如,WebGL 允許硬件加速圖形,並在類型化數組規範的幫助下,使 JavaScript 引擎能夠以幾乎原生的性能執行數字運算。 同樣,使用 WebRTC,瀏覽器能夠直接從計算機硬件訪問視頻(和其他數據)流。

WebGL 和 WebRTC 都是必須內置到 Web 瀏覽器中的 JavaScript 庫。 大多數現代 HTML5 瀏覽器都至少部分支持這兩種 API(如您在此處和此處所見)。 但是我們如何在用 Java 編寫的 GWT 中利用這些工具呢? 正如上一篇文章中所討論的,GWT 的互操作層 JsInterop(在 GWT 2.8 中正式發布)使這變得輕而易舉。

在 GWT 2.8 中使用 JsInterop 就像將-generateJsInteropExports作為參數添加到編譯器一樣簡單。 可用的註釋在包jsinterop.annotations中定義,捆綁在gwt-user.jar中。

網絡RTC

例如,通過最少的編碼工作,在帶有 GWT 的 Chrome 上使用 WebRTC 的getUserMedia變得像編寫一樣簡單:

 Navigator.webkitGetUserMedia( configs, stream -> video.setSrc( URL.createObjectURL(stream) ), e -> Window.alert("Error: " + e) );

其中類Navigator可以定義如下:

 @JsType(namespace = JsPackage.GLOBAL, isNative = true, name="navigator") final static class Navigator { public static native void webkitGetUserMedia( Configs configs, SuccessCallback success, ErrorCallback error); }

有趣的是SuccessCallbackErrorCallback接口的定義,它們都由上面的 lambda 表達式實現,並通過@JsFunction註釋在 Java 中定義:

 @JsFunction public interface SuccessCallback { public void onMediaSuccess(MediaStream stream); } @JsFunction public interface ErrorCallback { public void onError(DomException error); }

最後,類URL的定義幾乎與Navigator的定義相同,類似地, Configs類可以定義為:

 @JsType(namespace = JsPackage.GLOBAL, isNative = true, name="Object") public static class Configs { @JsProperty public native void setVideo(boolean getVideo); }

所有這些功能的實際實現都發生在瀏覽器的 JavaScript 引擎中。

您可以在 GitHub 上找到上述代碼。

在此示例中,為簡單起見,使用了已棄用的navigator.getUserMedia() API,因為它是唯一一個在當前穩定版本的 Chrome 上無需 polyfill 即可工作的 API。 在生產應用程序中,我們可以使用 adapter.js 通過更新的navigator.mediaDevices.getUserMedia() API 訪問流,在所有瀏覽器中統一,但這超出了當前討論的範圍。

WebGL

從 GWT 中使用 WebGL 與使用 WebRTC 並沒有太大區別,但由於 OpenGL 標準的內在復雜性,它有點乏味。

我們這裡的方法反映了上一節中遵循的方法。 包裝的結果可以在Picshare中使用的 GWT WebGL 實現中看到,可以在這裡找到,GWT 產生的結果示例可以在這裡找到。

單獨啟用 WebGL 並沒有真正為我們提供 3D 圖形功能。 正如格雷格·塔瓦雷斯所寫:

很多人不知道的是,WebGL 實際上是一個 2D API,而不是 3D API。

3D 運算必須由其他代碼執行,並轉換為 WebGL 的 2D 圖像。 有一些用於 3D WebGL 圖形的優秀 GWT 庫。 我最喜歡的是 Parallax,但對於Picshare的第一個版本,我們遵循了更“自己動手”的路徑,編寫了一個用於渲染簡單 3D 網格的小型庫。 該庫允許我們定義透視相機並管理對象場景。 隨意檢查一下,在這裡。

使用 GWT 編譯第三方 Java 庫

NyARToolkit 是 ARToolKit 的純 Java 端口,ARToolKit 是一個用於構建增強現實應用程序的軟件庫。 該端口是由 Nyatla 的日本開發人員編寫的。 雖然最初的 ARToolKit 和 Nyatla 版本與最初的移植版本相比有所不同,但 NyARToolkit 仍然在積極維護和改進。

基於標記的 AR 是一個專業領域,需要計算機視覺、數字圖像處理和數學方面的能力,如下所示:

使用 ARToolKit 進行基於標記的增強現實圖像分析。

轉載自 ARToolKit 文檔。

帶有 ARToolKit 的基於標記的增強現實管道。

轉載自 ARToolKit 文檔。

該工具包使用的所有算法都有文檔記錄且易於理解,但從頭開始重寫它們是一個漫長且容易出錯的過程,因此最好使用現有的、經過驗證的工具包,例如 ARToolKit。 不幸的是,當定位到網絡時,沒有這樣的東西可用。 大多數功能強大的高級工具包都沒有在 JavaScript 中實現,這種語言主要用於處理 HTML 文檔和數據。 這就是 GWT 證明其無價優勢的地方,使我們能夠簡單地將 NyARToolkit 轉換為 JavaScript,並在 Web 應用程序中輕鬆使用它。

使用 GWT 編譯

由於 GWT 項目本質上是一個 Java 項目,因此使用 NyARToolkit 只需在源路徑中導入源文件即可。 但是,請注意,由於將 GWT 代碼轉換為 JavaScript 是在源代碼級別完成的,因此您需要 NyARToolkit 的源代碼,而不僅僅是包含已編譯類的 JAR。

Picshare使用的庫可以在這裡找到。 它僅依賴於此處存檔的 NyARToolkit 構建中的lib/srclib/src.markersystem中的包。 我們必須將這些包複製並導入到我們的 GWT 項目中。

我們應該將這些第三方包與我們自己的實現分開,但要繼續 NyARToolkit 的“GWT 化”,我們必須提供一個 XML 配置文件,通知 GWT 編譯器在哪裡尋找源代碼。 在包jp.nyatla.nyartoolkit中,我們添加文件NyARToolkit.gwt.xml

 <module> <source path="core" /> <source path="detector" /> <source path="nyidmarker" /> <source path="processor" /> <source path="psarplaycard" /> <source path="markersystem" /> </module>

現在,在我們的主包com.jooink.gwt.nyartoolkit中,我們創建主配置文件GWT_NyARToolKit.gwt.xml ,並指示編譯器通過從其 XML 文件繼承將 Nyatla 的源代碼包含在類路徑中:

 <inherits name='jp.nyatla.nyartoolkit.NyARToolkit'/>

其實很簡單。 在大多數情況下,這就是所有需要的,但不幸的是我們還沒有完成。 如果我們在這個階段嘗試通過 Super Dev Mode 編譯或執行,我們會遇到一個錯誤說明,非常令人驚訝:

 No source code is available for type java.io.InputStream; did you forget to inherit a required module?

原因是 NyARToolkit(即用於 Java 項目的 Java 庫)使用 GWT 的 Emulated JRE 不支持的 JRE 類。 我們在上一篇文章中簡要討論了這一點。

在這種情況下,問題在於InputStream和相關的 IO 類。 碰巧的是,我們甚至不需要使用大多數這些類,但我們需要為編譯器提供一些實現。 好吧,我們可以投入大量時間從 NyARToolkit 源中手動刪除這些引用,但這太瘋狂了。 GWT 為我們提供了更好的解決方案:通過<super-source> XML 標記提供我們自己的不受支持類的實現。

<super-source>

如官方文檔中所述:

<super-source>標籤指示編譯器重新根源路徑。 這對於您希望為 GWT 項目重用現有 Java API 但原始源不可用或不可翻譯的情況很有用。 這樣做的一個常見原因是模擬 GWT 未實現的部分 JRE。

所以<super-source>正是我們所需要的。

我們可以在 GWT 項目中創建一個jre目錄,我們可以在其中放置導致我們出現問題的類的實現:

 java.io.FileInputStream java.io.InputStream java.io.InputStreamReader java.io.StreamTokenizer java.lang.reflect.Array java.nio.ByteBuffer java.nio.ByteOrder

除了java.lang.reflect.Array之外,所有這些都沒有真正使用,所以我們只需要愚蠢的實現。 例如,我們的FileInputStream如下所示:

 package java.io; import java.io.InputStream; import com.google.gwt.user.client.Window; public class FileInputStream extends InputStream { public FileInputStream(String filename) { Window.alert("WARNING, FileInputStream created with filename: " + filename ); } @Override public int read() { return 0; } }

構造函數中的Window.alert語句在開發過程中很有用。 儘管我們必須能夠編譯該類,但我們希望確保我們永遠不會真正使用它,因此這會提醒我們以防無意中使用了該類。

java.lang.reflect.Array實際上是由我們需要的代碼使用的,因此需要一個不完全愚蠢的實現。 這是我們的代碼:

 package java.lang.reflect; import jp.nyatla.nyartoolkit.core.labeling.rlelabeling.NyARRleLabelFragmentInfo; import jp.nyatla.nyartoolkit.markersystem.utils.SquareStack; import com.google.gwt.user.client.Window; public class Array { public static <T> Object newInstance(Class<T> c, int n) { if( NyARRleLabelFragmentInfo.class.equals(c)) return new NyARRleLabelFragmentInfo[n]; else if(SquareStack.Item.class.equals(c)) return new SquareStack.Item[n]; else Window.alert("Creating array of size " + n + " of " + c.toString()); return null; } }

現在,如果我們將<super-source path="jre"/>放在GWT_NyARToolkit.gwt.xml模塊文件中,我們就可以在我們的項目中安全地編譯和使用 NyARToolkit!

將所有內容與 GWT 粘合在一起

現在我們處於以下位置:

  • WebRTC,一種能夠從網絡攝像頭獲取流並將其顯示在<video>標籤中的技術。
  • WebGL,一種能夠在 HTML <canvas>中操作硬件加速圖形的技術。
  • NyARToolkit,一個 Java 庫,能夠獲取圖像(作為像素數組),搜索標記,如果找到,則為我們提供一個完全定義標記在 3D 空間中的位置的變換矩陣。

現在的挑戰是將所有這些技術集成在一起。

將 3D 空間投影到相機上。

我們不會深入探討如何實現這一點,但基本思想是使用視頻圖像作為我們場景的背景(上圖中應用到“遠”平面的紋理)並構建 3D 數據結構允許我們使用 NyARToolkit 的結果將該圖像投影到太空中。

這種結構為我們提供了與 NyARToolkit 的標記識別庫交互的正確結構,並在相機場景的頂部繪製 3D 模型。

使相機流可用有點棘手。 視頻數據只能繪製到<video>元素。 HTML5 <video>元素是不透明的,不允許我們直接提取圖像數據,所以我們不得不將視頻複製到中間的<canvas>中,提取圖像數據,將其轉換為像素數組,最後將其推送到 NyARToolkit 的Sensor.update()方法。 然後 NyARToolkit 可以完成識別圖像中標記的工作,並返回一個與它在我們的 3D 空間中的位置相對應的變換矩陣。

使用這些元素,我們可以在實時視頻流中以 3D 形式將合成對象精確地配置在標記上! 由於 GWT 的高性能,我們擁有大量的計算資源,因此我們甚至可以在畫布上應用一些視頻效果,例如棕褐色或模糊,然後將其用作 WebGL 場景的背景。

以下精簡代碼描述了該過程的核心:

 // given a <canvas> drawing context with appropriate width and height // and a <video> where the mediastream is drawn ... // for each video frame // draw the video frame on the canvas ctx.drawImage(video, 0, 0, w, h); // extract image data from the canvas ImageData capt = ctx.getImageData(0, 0, w, h); // convert the image data in a format acceptable by NyARToolkit ImageDataRaster input = new ImageDataRaster(capt); // push the image in to a NyARSensor sensor.update(input); // update the NyARMarkerSystem with the sensor nyar.update(sensor); // the NyARMarkerSystem contains information about the marker patterns and is able to detect them. // After the call to update, all the markers are detected and we can get information for each // marker that was found. if( nyar.isExistMarker( marker_id ) ) { NyARDoubleMatrix44 m = nyar.getMarkerMatrix(marker_id); // m is now the matrix representing the pose (position and orientation) of // the marker in the scene, so we can use it to superimpose an object of // our choice ... } ...

使用這種技術,我們可以生成如下結果:

Picshare 瀏覽器內增強現實應用程序的結果。

Picshare 瀏覽器內具有多個標記的增強現實應用程序的結果。

這是我們用來創建Picshare的過程,邀請您打印標記或將其顯示在您的手機上,並在您的瀏覽器中使用基於標記的 AR。 享受!

最後的話

Picshare是我們在 Jooink 的長期寵物項目。 第一次實施可以追溯到幾年前,即便如此,它的速度也足以令人印象深刻。 在此鏈接中,您可以看到我們早期的實驗之一,該實驗於 2012 年編譯,從未接觸過。 請注意,在示例中只有一個<video> 。 其他兩個窗口是顯示處理結果的<canvas>元素。

即使在 2012 年,GWT 也足夠強大。隨著 GWT 2.8 的發布,我們獲得了與 JsInterop 的互操作性層的大幅改進,進一步提升了性能。 此外,為了慶祝許多人,我們還獲得了一個更好的開發和調試環境,超級開發模式。 哦,是的,還有 Java 8 支持。

我們期待 GWT 3.0!