日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

X3D:現(xiàn)代Web的聲明式3D技術(shù)

 西北望msm66g9f 2019-10-07
作者|Adrian Sureshkumar
譯者|夏夜
編輯|王文婧
現(xiàn)代 Web 技術(shù)使開發(fā)人員能夠創(chuàng)建干凈而視覺豐富的用戶體驗,這些體驗被所有主流瀏覽器作為標(biāo)準(zhǔn)進(jìn)行廣泛支持。那么,如何為 Web 編寫基于標(biāo)準(zhǔn)的可視化程序呢?對 3D 圖形的支持到底又有哪些呢?讓我們首先回顧 HTML 標(biāo)準(zhǔn)中支持的兩種主要方法:SVG 和 Canvas。
SVG:可伸縮的矢量圖形 

SVG 本身是基于 XML 的一種獨立的數(shù)據(jù)格式,用于聲明式的 2D 矢量圖形。但是,它也可以嵌入到 HTML 文檔中,這是所有主流瀏覽器都支持的。

讓我們考慮一個例子,如何使用 SVG 繪制一個可調(diào)整大小的圓:
<html style='height: 100%; width: 100%'>
  <body style='height: 100%; width: 100%; margin: 0px'>
    <svg style='height: 100%; width: 100%; display: block' viewBox='0 0 100 100'>
      <circle cx='50' cy='50' r='25' fill='red' stroke='black'
              vector-effect='non-scaling-stroke' />
    </svg>
  </body>
</html>

想要理解這段代碼很容易!我們只是向瀏覽器描述了要繪制什么(與傳統(tǒng) HTML 文檔非常相似)。它保留了這個描述,并負(fù)責(zé)如何在屏幕上繪制它。

當(dāng)瀏覽器窗口調(diào)整大小或縮放時,它將重新縮放圖像,而不會丟失圖像的任何質(zhì)量(因為圖像是根據(jù)形狀定義的,而不是根據(jù)像素定義的)。當(dāng) SVG 元素被 JavaScript 代碼修改時,它還會自動重新繪制圖像,這使得 SVG 特別適合與 JavaScript 庫(如 D3)一起使用,D3 將數(shù)據(jù)綁定到 DOM 中的元素,從而能夠創(chuàng)建從簡單圖表到更奇特的交互式數(shù)據(jù)可視化的任何內(nèi)容。

這種聲明性方法也稱為保留模式圖形繪制(retained-mode graphics rendering)。

 畫  布 
canvas 元素只是在網(wǎng)頁上提供了一個可以繪圖的區(qū)域。使用 JavaScript 代碼,首先從畫布獲取上下文,然后使用提供的 API,定義繪制圖像的函數(shù)。
const canvas = document.getElementById(id);
const context = canvas.getContext(contextType);

// call some methods on context to draw onto the canvas

當(dāng)腳本執(zhí)行時,圖像立即繪制成了底層位圖的像素,瀏覽器不保留繪制方式的任何信息。為了更新繪圖,需要再次執(zhí)行腳本。重新縮放圖像時,也會觸發(fā)更新繪圖,否則,瀏覽器只會拉伸原始位圖,導(dǎo)致圖像明顯模糊或像素化。

這種函數(shù)式方法也稱為即時模式圖形繪制(immediate-mode graphics rendering)。

上下文:2D

首先讓我們考慮 2D 繪制的上下文,它提供了一個用于在畫布上繪制 2D 圖形的高級 API。

讓我們來看一個例子,看看如何使用它來繪制我們可調(diào)整大小的圓:
<html style='height: 100%; width: 100%'>
  <body style='height: 100%; width: 100%; margin: 0px'>
    <canvas id='my-canvas' style='height: 100%; width: 100%; display: block'></canvas>
    <script>
      const canvas = document.getElementById('my-canvas');
      const context = canvas.getContext('2d');

      function render() {
        // Size the drawing surface to match the actual element (no stretch).
        canvas.height = canvas.clientHeight;
        canvas.width = canvas.clientWidth;

        context.beginPath();

        // Calculate relative size and position of circle in pixels.
        const x = 0.5 * canvas.width;
        const y = 0.5 * canvas.height;
        const radius = 0.25 * Math.min(canvas.height, canvas.width);

        context.arc(x, y, radius, 0, 2 * Math.PI);

        context.fillStyle = 'red';
        context.fill();

        context.strokeStyle = 'black';
        context.stroke();
      }

      render();
      addEventListener('resize', render);
    </script>
  </body>
</html>

同樣,這非常簡單,但肯定比前面的示例更冗長!我們必須自己根據(jù)畫布的當(dāng)前大小,以像素為單位計算圓的半徑和中心位置。這也意味著我們必須監(jiān)聽縮放的事件并相應(yīng)地重新繪制。

那么,既然更加復(fù)雜,為什么還要使用這種方法而不是 SVG 呢?在大多數(shù)情況下,你可能不會使用該方法。然而,這給了你對渲染的內(nèi)容更多的控制。對于要繪制更多對象的、更復(fù)雜的動態(tài)可視化,它可能比更新 DOM 中的大量元素,并讓瀏覽器來決定何時呈現(xiàn)和呈現(xiàn)什么,帶來更好的性能。

上下文:WebGL 

大多數(shù)現(xiàn)代瀏覽器也支持 webgl 上下文。這為您提供了使用 WebGL 標(biāo)準(zhǔn)繪制硬件加速圖形的底層 API,盡管這需要 GPU 支持。它可以用來渲染 2D,更重要的是,也可以用來渲染本篇博客所說的 3D 圖形。

現(xiàn)在讓我們來看一個例子,看看如何使用 WebGL 渲染我們的圓圈:
<html style='height: 100%; width: 100%'>
  <body style='height: 100%; width: 100%; margin: 0px'>
    <canvas id='my-canvas' style='height: 100%; width: 100%; display: block'></canvas>
    <script>
      const canvas = document.getElementById('my-canvas');
      const context = canvas.getContext('webgl');

      const redColor = new Float32Array([1.0, 0.0, 0.0, 1.0]);
      const blackColor = new Float32Array([0.0, 0.0, 0.0, 1.0]);

      // Use an orthogonal projection matrix as we're rendering in 2D.
      const projectionMatrix = new Float32Array([
        1.0, 0.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 1.0,
      ]);

      // Define positions of the vertices of the circle (in clip space).
      const radius = 0.5;
      const segmentCount = 360;
      const positions = [0.0, 0.0];
      for (let i = 0; i < segmentCount + 1; i++) {
          positions.push(radius * Math.sin(2 * Math.PI * i / segmentCount));
        positions.push(radius * Math.cos(2 * Math.PI * i / segmentCount));
      }

      const positionBuffer = context.createBuffer();
      context.bindBuffer(context.ARRAY_BUFFER, positionBuffer);
      context.bufferData(context.ARRAY_BUFFER, new Float32Array(positions), context.STATIC_DRAW);

      // Create shaders and program.
      const vertexShader = context.createShader(context.VERTEX_SHADER);
      context.shaderSource(vertexShader, `
        attribute vec4 position;
        uniform mat4 projection;

        void main() {
          gl_Position = projection * position;
        }
      `);
      context.compileShader(vertexShader);

      const fragmentShader = context.createShader(context.FRAGMENT_SHADER);
      context.shaderSource(fragmentShader, `
        uniform lowp vec4 color;

        void main() {
          gl_FragColor = color;
        }
      `);
      context.compileShader(fragmentShader);

      const program = context.createProgram();
      context.attachShader(program, vertexShader);
      context.attachShader(program, fragmentShader);
      context.linkProgram(program);

      const positionAttribute = context.getAttribLocation(program, 'position');

      const colorUniform = context.getUniformLocation(program, 'color');
      const projectionUniform = context.getUniformLocation(program, 'projection');

      function render() {
        // Size the drawing surface to match the actual element (no stretch).
        canvas.height = canvas.clientHeight;
        canvas.width = canvas.clientWidth;

        context.viewport(0, 0, canvas.width, canvas.height);

        context.useProgram(program);

        // Scale projection to maintain 1:1 ratio between height and width on canvas.
        projectionMatrix[0] = canvas.width > canvas.height ? canvas.height / canvas.width : 1.0;
        projectionMatrix[5] = canvas.height > canvas.width ? canvas.width / canvas.height : 1.0;
        context.uniformMatrix4fv(projectionUniform, false, projectionMatrix);

        const vertexSize = 2;
        const vertexCount = positions.length / vertexSize;

        context.bindBuffer(context.ARRAY_BUFFER, positionBuffer);
        context.vertexAttribPointer(positionAttribute, vertexSize, context.FLOAT, false, 0, 0);
        context.enableVertexAttribArray(positionAttribute);

        context.uniform4fv(colorUniform, redColor);
        context.drawArrays(context.TRIANGLE_FAN, 0, vertexCount);

        context.uniform4fv(colorUniform, blackColor);
        context.drawArrays(context.LINE_STRIP, 1, vertexCount - 1);
      }

      render();
      addEventListener('resize', render);
    </script>
  </body>
</html>

復(fù)雜度升級得相當(dāng)快!在我們渲染任何東西之前,要做很多設(shè)置。我們必須使用頂點列表,將圓定義為由小三角形組成的一個序列。我們還必須定義一個投影矩陣,將我們的 3D 模型(一個平面圓)投影到 2D 畫布上。然后,我們必須編寫“著色器”(用一種稱為 GLSL 的語言),在 GPU 上編譯并運行,以確定頂點的位置和顏色。

但是,額外的復(fù)雜性和較底層的 API,確實能夠讓我們更好地控制 2D 圖形繪制的性能(如果我們真的需要的話)。它還為我們提供了渲染 3D 可視化的能力,即使我們還沒有考慮過這樣的例子。

面向聲明式的 3D 圖形 

現(xiàn)在我們已經(jīng)了解了 WebGL,并了解了如何使用它來繪制一個圓。隨著我們進(jìn)入 3D 圖形的世界,下一個步驟就是使用它來繪制一個球體。然而,這增加了另一層次的復(fù)雜性,因為我們將要思考,如何使用一組頂點來表示球面。我們還需要添加一些燈光效果,這樣我們就可以看到一個球體的輪廓,而不是從任何角度都只能看到一個平坦的紅色圓圈。

我們還看到,對于絕對性能并不重要的場景,SVG 等簡單而簡潔的聲明式方法可以發(fā)揮多大的作用。它們還可以讓我們使用 D3 這樣的庫,輕松地生成與數(shù)據(jù)連接起來的可視化。所以,如果我們能以類似的方式表示基于 Web 的 3D 圖形,那不是更好嗎?

遺憾的是,目前 HTML 中的標(biāo)準(zhǔn)還不支持這個操作。但也許還有另一種方法……

正如 Mike Bostock(D3 的創(chuàng)建者)在 POC(Proof of Concept)中所演示的,在 DOM 中定義 2D“素描”的定制化 XML 表示,并將其與一些 JavaScript 代碼結(jié)合,使用 2D 上下文將其繪制到畫布上,這相對來說會更加簡單。

這意味著在所有主流瀏覽器上運行的聲明式 3D 真正需要的是:
  • 基于 XML 格式的 3D 模型聲明;
  • 使用 webgl 上下文將它們繪制到畫布上的 JavaScript 代碼。

 X3D ——拼圖中缺失的這塊? 

X3D 是表示 3D 模型的 ISO 標(biāo)準(zhǔn),是虛擬現(xiàn)實建模語言(VRML)的后續(xù)標(biāo)準(zhǔn)。它可以表示為各種編碼,包括 JSON 和 XML。后者特別適合嵌入到 HTML 文檔中。它由 Web3D 聯(lián)盟維護(hù),他們希望它能像 SVG 一樣在 HTML5 中得到原生支持。

目前有兩種被 Web3D 聯(lián)盟認(rèn)可的 JavaScript 開源 X3D 實現(xiàn):X3DOM 和 X_ite。

X3DOM 是由弗勞恩霍夫計算機圖形研究所 IGD(The Fraunhofer Institute for Computer Graphics Research IGD)開發(fā)的,IGD 本身也是 Web3D 聯(lián)盟的成員。為了使用它,您只需要在 HTML 頁面中包含 X3DOM JavaScript 代碼和樣式表。

讓我們來看看用 X3D 和 X3DOM 繪制圓圈的例子:
<html style='height: 100%; width: 100%'>
  <head>
    <script type='text/javascript' src='http://www./release/x3dom-full.js'></script>
    <link rel='stylesheet' type='text/css' href='http://www./release/x3dom.css'>
    <style>x3d > canvas { display: block; }</style>
  </head>
  <body style='height: 100%; width: 100%; margin: 0px'>
    <x3d style='height: 100%; width: 100%'>
      <scene>
        <orthoviewpoint></orthoviewpoint>
        <shape>
          <appearance>
            <material diffuseColor='1 0 0'></material>
          </appearance>
          <disk2d outerRadius='0.5'></disk2d>
        </shape>
        <shape>
          <appearance>
            <material emissiveColor='0 0 0'></material>
          </appearance>
          <circle2d radius='0.5'></circle2d>
        </shape>
      </scene>
    </x3d>
  </body>
</html>

這比 WebGL 示例更容易接受一些!但是,如果您將 X3DOM 圓與我們的 WebGL 版本進(jìn)行比較,您會注意到圓周看起來不那么光滑。這是因為 X3DOM 庫對形狀的近似只使用了 32 條線段。而我們的 WebGL 繪制中選擇了 360 條線段。我們對要渲染什么有一個更簡單的描述,但同時也會放棄對如何渲染的一些控制。

現(xiàn)在是時候走出我們的“平面”世界,渲染一些 3D 的東西了!如前所述,讓我們來看看一個球體的繪制:
<html style='height: 100%; width: 100%'>
  <head>
    <script type='text/javascript' src='http://www./release/x3dom-full.js'></script>
    <link rel='stylesheet' type='text/css' href='http://www./release/x3dom.css'>
    <style>x3d > canvas { display: block; }</style>
  </head>
  <body style='height: 100%; width: 100%; margin: 0px'>
    <x3d style='height: 100%; width: 100%'>
      <scene>
        <orthoviewpoint></orthoviewpoint>
        <navigationinfo headlight='false'></navigationinfo>
        <directionallight direction='1 -1 -1' on='true' intensity='1.0'></directionallight>
        <shape>
          <appearance>
            <material diffuseColor='1 0 0'></material>
          </appearance>
          <sphere radius='0.5'></sphere>
        </shape>
      </scene>
    </x3d>
  </body>
</html>

這又是很直接的。我們使用一個 XML 元素定義了一個球體,該元素具有單一屬性:半徑。為了看到球體的輪廓,我們還調(diào)整了光線,移除了與觀察者頭部對齊的默認(rèn)光源,并用與我們視角成一定角度的定向光替換它。這不需要為球體的表面定義一個復(fù)雜的網(wǎng)格或者編寫一個著色器來控制光照效果。

X3DOM 還提供了開箱即用的導(dǎo)航功能,允許您旋轉(zhuǎn)、平移和縮放模型。根據(jù)您正在編寫的應(yīng)用程序的類型,還可以使用各種不同的控制方案和導(dǎo)航模式。

 結(jié)  論 

就是這樣!我們已經(jīng)看到可以使用 X3D 和 X3DOM 庫來編寫聲明式的 3D 圖形應(yīng)用,這些圖形將在大多數(shù)現(xiàn)代 Web 瀏覽器中運行。這是一種比直接深鉆 WebGL 更簡單的 Web 3D 圖形入門方法,代價是增加對底層繪制的控制。如果您有興趣了解這個庫的更多信息,官方 X3DOM 文檔中有一些教程。

在我的下一篇博客文章中,我將演示如何將 X3DOM 與 D3 結(jié)合起來生成動態(tài) 3D 圖表。

英文原文:

https://blog./2019/08/27/declarative-3d-for-the-modern-web.html

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多