<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      從0開發3D引擎(九):實現最小的3D程序-“繪制三角形”

      大家好,本文開始編程,實現最小的3D程序。

      我們首先進行需求分析,確定功能點;
      然后進行事件風暴,建立領域驅動設計的通用語言;
      然后進行初步實現,劃分頂層模塊,進行頂層實現,給出類型簽名和實現的偽代碼;
      然后進行具體實現,從頂層到底層依次實現;
      最后更新通用語言。

      上一篇博文

      從0開發3D引擎(八):準備“搭建引擎雛形”

      下一篇博文

      從0開發3D引擎(十):使用領域驅動設計,從最小3D程序中提煉引擎(第一部分)

      最終效果截圖

      此處輸入圖片的描述

      術語解釋

      • 文件與模塊
        在Reason中,一個Reason文件(如Main.re)就是一個模塊(Module),所以我們既稱Main.re為Main文件,也稱它為Main模塊。

      需求分析

      首先,我們分析最小3D程序的目標和特性;
      接著,根據特性,我們進行頭腦風暴,識別出功能關鍵點和擴展點;
      最后,根據功能關鍵點和擴展點,我們確定最小3D程序的功能點。

      目標

      可從最小3D程序中提煉出通用的、最簡化的引擎雛形

      特性

      為了達成目標,最小3D程序應該具備以下的特性:

      • 簡單
        最小3D程序應該很簡單,便于我們分析和提煉。
      • 具有3D程序的通用特性
        為了使從中提煉出的引擎雛形可擴展,最小3D程序需要包含3D程序主要的流程和通用的模式

      頭腦風暴

      現在,我們根據特性,進行頭腦風暴,識別出最小3D程序的功能關鍵點和擴展點。

      下面從兩個方面來分析:
      1、從功能上分析
      最簡單的功能就是沒有任何交互,只是渲染模型;
      而最簡單的模型就是三角形;

      識別功能關鍵點:
      a)渲染三角形
      b)只渲染,沒有任何交互

      2、從流程上分析
      3D程序應該包含兩個步驟:
      1)初始化
      進一步分解,識別出最明顯的子步驟:

      //“|>”是函數式編程中的管道操作。例如:“A |> B”表示先執行A,然后將其返回值傳給B,再執行B
      初始化 = 初始化Shader |> 初始化場景
      

      識別功能擴展點:
      a)多組GLSL
      因為在3D場景中,通常有各種渲染效果,如光照、霧、陰影等,每種渲染效果對應一個或多個Shader,而每個Shader對應一組GLSL,每組GLSL包含頂點GLSL和片段GLSL,所以最小3D程序需要支持多組GLSL。

      2)主循環
      進一步分解,識別出最明顯的子步驟:

      主循環 =  使用requestAnimationFrame循環執行每一幀
      
      每一幀 = 清空畫布 |> 渲染
      
      渲染 = 設置WebGL狀態 |> 設置相機 |> 渲染場景中所有的模型
      

      識別功能擴展點:
      b)多個渲染模式
      3D場景往往需要用不同的模式來渲染不同的模型,如用不同的模式來渲染所有透明的模型和渲染所有非透明的模型。

      c)多個WebGL狀態
      每個渲染模式需要設置對應的多個WebGL狀態。

      d)多個相機
      3D場景中通常有多個相機。在渲染時,設置其中一個相機作為當前相機。

      e)多個模型
      3D場景往往包含多個模型。

      f)每個模型有不同的位置、旋轉和縮放

      確定需求

      現在,我們根據功能關鍵點和擴展點,確定最小3D程序的需求。

      下面分析非功能性需求和功能性需求:

      非功能性需求
      最小3D程序不考慮非功能性需求

      功能性需求
      我們已經識別了以下的功能關鍵點:
      a)渲染三角形
      b)只渲染,沒有任何交互

      結合功能關鍵點,我們對功能擴展點進行一一分析和決定,得到最小3D程序要實現的功能點:
      a)多組GLSL
      為了簡單,實現兩組GLSL,它們只有細微的差別,從而可以用相似的代碼來渲染使用不同GLSL的三角形,減少代碼復雜度
      b)多個渲染模式
      為了簡單,只有一個渲染模式:渲染所有非透明的模型
      c)多個WebGL狀態
      我們設置常用的兩個狀態:開啟深度測試、開啟背面剔除。
      d)多個相機
      為了簡單,只有一個相機
      e)多個模型
      渲染三個三角形
      f)每個模型有不同的位置、旋轉和縮放
      為了簡單,每個三角形有不同的位置(它們的z值,即深度不一樣,從而測試“開啟深度測試”的效果),但不考慮旋轉和縮放

      根據上面的分析,我們給出最小3D程序要實現的功能點:

      • 只渲染,沒有交互
      • 有兩組GLSL
        它們的區別為:
        第一組GLSL接收一個顏色的uniform;
        第二組GLSL接收兩個顏色的uniform。
      • 場景有三個三角形,對應不同的GLSL
        第一個三角形有一個顏色數據,用第一組的GLSL;
        第二個三角形有兩個顏色數據,用第二組的GLSL;
        第三個三角形有一個顏色數據,用第一組的GLSL;
      • 所有三角形都是非透明的
      • 開啟深度測試和背面剔除
      • 只有一個固定的透視投影相機
      • 三角形的位置不同,不考慮旋轉和縮放

      事件風暴

      我們在分析的需求的基礎上,進行事件風暴,建立通用語言。

      識別領域事件

      實現最小的3D程序-“繪制三角形”:事件風暴-識別領域事件 (1).png-40.1kB

      識別命令,尋找聚合,劃分限界上下文

      此處輸入圖片的描述

      通用語言

      此處輸入圖片的描述

      初步實現

      現在,我們對最小3D程序進行初步實現:

      1、劃分最小3D程序的頂層模塊:
      實現最小的3D程序-“繪制三角形”:頁面調用.png-8.3kB

      程序的邏輯放在Main模塊的main函數中;
      index.html頁面執行main函數;
      在瀏覽器中運行index.html頁面,渲染三角形場景。

      2、用類型簽名和偽代碼,對main函數進行頂層實現:

      //unit表示無返回類型,類似于C語言的void
      type main = unit => unit;
      let main = () => {
          _init() 
          //開啟主循環
          |> _loop
          //使用“ignore”來忽略_loop的返回值,從而使main函數的返回類型為unit
          |> ignore;
      };
      
      //data是用于主循環的數據
      type _init = unit => data;
      let _init = () => {
          獲得WebGL上下文 
          //因為有兩組GLSL,所以有兩個Shader
          |> 初始化所有Shader 
          |> 初始化場景
      };
      
      type _loop = data => int;
      //用“rec”關鍵字將_loop設為遞歸調用
      let rec _loop = (data) => 
          requestAnimationFrame((time:int) => {
              //執行主循環的邏輯
              _loopBody(data);
              //遞歸調用_loop
              _loop(data) |> ignore;    
          });
          
      type _loopBody = data => unit;
      let _loopBody = (data) => {
          data
          |> _clearColor
          |> _clearCanvas
          |> _render
      };
      
      type _render = data => unit;
      let _render = (data) => {
          設置WebGL狀態 
          |> 渲染三個三角形
      };
      

      具體實現

      現在,我們從頂層到底層依次實現最小3D程序,使其能夠在瀏覽器中運行。

      新建TinyWonder項目

      首先通過從0開發3D引擎(三):搭建開發環境,搭建Reason的開發環境;
      然后新建空白的TinyWonder文件夾,將Reason-Example項目的內容拷貝到該項目中,刪除src/First.re文件;
      在項目根目錄下,依次執行“yarn install”,“yarn watch”,“yarn start”。

      TinyWonder項目結構為:
      截屏2020-01-26上午10.41.55.png-58kB

      src/文件夾放置Reason代碼;
      lib/es6_global/文件夾放置編譯后的js代碼(使用es6 module模塊規范)。

      實現頂層模塊

      在src/中加入Main.re文件,定義一個空的main函數:

      let main = () => {
          console.log("main");
      };
      

      重寫index.html頁面為:

      <!DOCTYPE html>
      <html lang="en">
      
      <head>
        <meta charset="utf-8" />
        <title>Demo</title>
      </head>
      
      <body>
        <canvas id="webgl" width="400" height="400">
          Please use a browser that supports "canvas"
        </canvas>
      
        <script type="module">
          import { main } from "./lib/es6_global/src/Main.js";
      
          window.onload = () => {
            main();
          };
        </script>
      
      </body>
      
      </html>
      

      index.html創建了一個canvas,并通過ES6 module引入了編譯后的Main.js文件,執行main函數。

      運行index.html頁面
      瀏覽器地址中輸入 http://127.0.0.1:8080, 運行index.html頁面。
      打開瀏覽器控制臺->Console,可以看到輸出“main”。

      實現_init

      現在我們來實現main函數,它包括_init和_loop函數。

      我們首先實現_init函數,它的偽代碼為:

      type _init = unit => data;
      let _init = () => {
          獲得WebGL上下文 
          |> 初始化所有Shader 
          |> 初始化場景
      };
      

      實現“獲得WebGL上下文”

      通過以下步驟來實現:
      1、獲得canvas dom
      需要調用window.querySelector方法來獲得它 ,因此需要寫FFI。
      在src/中加入DomExtend.re,該文件放置與Dom交互的FFI。
      在其中定義FFI:

      type htmlElement = {
        .
        "width": int,
        "height": int,
      };
      
      type body;
      
      type document = {. "body": body};
      
      [@bs.send] external querySelector: (document, string) => htmlElement = "";
      

      在Main.re的_init函數中,通過canvas dom id來獲得canvas:

      let canvas = DomExtend.querySelector(DomExtend.document, "#webgl");
      

      2、從canvas中獲得webgl1的上下文
      需要調用canvas的getContext方法,因此需要寫FFI。
      在src/中增加Gl.re,該文件放置與webgl1 API相關的FFI。

      在其中定義相關FFI:

      type webgl1Context;
      
      type contextConfigJsObj = {
        .
        "alpha": bool,
        "depth": bool,
        "stencil": bool,
        "antialias": bool,
        "premultipliedAlpha": bool,
        "preserveDrawingBuffer": bool,
      };
      
      [@bs.send]
      external getWebGL1Context:
        ('canvas, [@bs.as "webgl"] _, contextConfigJsObj) => webgl1Context =
        "getContext";
      

      在Main.re的_init函數中,獲得上下文,指定它的配置項:

      let gl =
      WebGL1.getWebGL1Context(
        canvas,
        {
          "alpha": true,
          "depth": true,
          "stencil": false,
          "antialias": true,
          "premultipliedAlpha": true,
          "preserveDrawingBuffer": false,
        }: WebGL1.contextConfigJsObj,
      );
      

      我們通過網上的資料,解釋下配置項:

      WebGL上下文屬性:
      alpha :布爾值,指示畫布是否包含alpha緩沖區.
      depth :布爾值,指示繪圖緩沖區的深度緩沖區至少為16位.
      stencil :布爾值,指示繪圖緩沖區具有至少8位的模板緩沖區.
      antialias :布爾值,指示是否執行抗鋸齒.
      premultipliedAlpha :布爾值,指示頁面合成器將假定繪圖緩沖區包含具有預乘alpha的顏色.
      preserveDrawingBuffer :如果該值為true,則不會清除緩沖區,并且將保留其值,直到作者清除或覆蓋.
      failIfMajorPerformanceCaveat :布爾值,指示如果系統性能低下是否將創建上下文.

      premultipliedAlpha需要設置為true,否則紋理無法進行 Texture Filtering(除非使用最近鄰插值)。具體可以參考Premultiplied Alpha 到底是干嘛用的
      這里忽略了“failIfMajorPerformanceCaveat“。

      實現“初始化所有Shader”

      一共有兩個Shader,分別對應一組GLSL。

      • 在src/中加入GLSL.re,定義兩組GLSL

      GLSL.re:

      let vs1 = {|
        precision mediump float;
        attribute vec3 a_position;
        uniform mat4 u_pMatrix;
        uniform mat4 u_vMatrix;
        uniform mat4 u_mMatrix;
      
        void main() {
          gl_Position = u_pMatrix * u_vMatrix * u_mMatrix * vec4(a_position, 1.0);
        }
          |};
      
      let fs1 = {|
          precision mediump float;
      
          uniform vec3 u_color0;
      
          void main(){
              gl_FragColor = vec4(u_color0,1.0);
          }
          |};
      
      let vs2 = {|
        precision mediump float;
        attribute vec3 a_position;
        uniform mat4 u_pMatrix;
        uniform mat4 u_vMatrix;
        uniform mat4 u_mMatrix;
      
        void main() {
          gl_Position = u_pMatrix * u_vMatrix * u_mMatrix * vec4(a_position, 1.0);
        }
          |};
      
      let fs2 = {|
          precision mediump float;
      
          uniform vec3 u_color0;
          uniform vec3 u_color1;
      
          void main(){
              gl_FragColor = vec4(u_color0 * u_color1,1.0);
          }
          |};
      

      這兩組GLSL類似,它們的頂點GLSL一樣,都傳入了model、view、projection矩陣和三角形的頂點坐標a_position;
      它們的片段GLSL有細微的差別:第一個的片段GLSL只傳入了一個顏色u_color0,第二個的片段GLSL傳入了兩個顏色u_color0、u_color1。

      • 在Gl.re中定義FFI

      WebGL1.re:

      type program;
      
      type shader;
      
      [@bs.send.pipe: webgl1Context] external createProgram: program = "";
      
      [@bs.send.pipe: webgl1Context] external linkProgram: program => unit = "";
      
      [@bs.send.pipe: webgl1Context]
      external shaderSource: (shader, string) => unit = "";
      
      [@bs.send.pipe: webgl1Context] external compileShader: shader => unit = "";
      
      [@bs.send.pipe: webgl1Context] external createShader: int => shader = "";
      
      [@bs.get] external getVertexShader: webgl1Context => int = "VERTEX_SHADER";
      
      [@bs.get] external getFragmentShader: webgl1Context => int = "FRAGMENT_SHADER";
      
      [@bs.get] external getCompileStatus: webgl1Context => int = "COMPILE_STATUS";
      
      [@bs.get] external getLinkStatus: webgl1Context => int = "LINK_STATUS";
      
      [@bs.send.pipe: webgl1Context]
      external getProgramParameter: (program, int) => bool = "";
      
      [@bs.send.pipe: webgl1Context]
      external getShaderInfoLog: shader => string = "";
      
      [@bs.send.pipe: webgl1Context]
      external getProgramInfoLog: program => string = "";
      
      [@bs.send.pipe: webgl1Context]
      external attachShader: (program, shader) => unit = "";
      
      [@bs.send.pipe: webgl1Context]
      external bindAttribLocation: (program, int, string) => unit = "";
      
      [@bs.send.pipe: webgl1Context] external deleteShader: shader => unit = "";
      
      • 傳入對應的GLSL,初始化兩個shader,創建并獲得兩個program

      因為"初始化Shader"是通用邏輯,因此在Main.re的_init函數中提出該函數。

      Main.re的_init函數的相關代碼如下:

      //通過拋出異常來處理錯誤
      let error = msg => Js.Exn.raiseError(msg);
      
      let _compileShader = (gl, glslSource: string, shader) => {
        WebGL1.shaderSource(shader, glslSource, gl);
        WebGL1.compileShader(shader, gl);
      
        WebGL1.getShaderParameter(shader, WebGL1.getCompileStatus(gl), gl) === false
          ? {
            let message = WebGL1.getShaderInfoLog(shader, gl);
      
            error(
              {j|shader info log: $message
              glsl source: $glslSource
              |j},
            );
          }
          : ();
      
        shader;
      };
      
      let _linkProgram = (program, gl) => {
        WebGL1.linkProgram(program, gl);
      
        WebGL1.getProgramParameter(program, WebGL1.getLinkStatus(gl), gl) === false
          ? {
            let message = WebGL1.getProgramInfoLog(program, gl);
      
            error({j|link program error: $message|j});
          }
          : ();
      };
      
      let initShader = (vsSource: string, fsSource: string, gl, program) => {
        let vs =
          _compileShader(
            gl,
            vsSource,
            WebGL1.createShader(WebGL1.getVertexShader(gl), gl),
          );
        let fs =
          _compileShader(
            gl,
            fsSource,
            WebGL1.createShader(WebGL1.getFragmentShader(gl), gl),
          );
      
        WebGL1.attachShader(program, vs, gl);
        WebGL1.attachShader(program, fs, gl);
      
        //需要確保attribute 0 enabled,具體原因可參考:    http://stackoverflow.com/questions/20305231/webgl-warning-attribute-0-is-disabled-this-has-significant-performance-penalt?answertab=votes#tab-top
        WebGL1.bindAttribLocation(program, 0, "a_position", gl);
      
        _linkProgram(program, gl);
      
        WebGL1.deleteShader(vs, gl);
        WebGL1.deleteShader(fs, gl);
      
        program;
      };
      
      let program1 =
      gl |> WebGL1.createProgram |> initShader(GLSL.vs1, GLSL.fs1, gl);
      
      let program2 =
      gl |> WebGL1.createProgram |> initShader(GLSL.vs2, GLSL.fs2, gl);
      

      因為error和initShader函數屬于輔助邏輯,所以我們進行重構,在src/中加入Utils.re,將其移到其中。

      實現“初始化場景”

      我們需要創建場景數據,“渲染”時使用。

      我們在后面實現“渲染”時,要使用drawElements來繪制三角形,因此在這里不僅需要創建三角形的vertices數據,還需要創建三角形的indices數據。

      我們把vertices、indices數據統稱為“頂點數據”。

      另外,我們決定使用VBO來保存三角形的頂點數據,因此在這里要創建和初始化每個三角形的VBO。

      我們來細化“初始化場景”:

      初始化場景 = 創建三個三角形的頂點數據 |>  創建和初始化對應的VBO |> 創建三個三角形的位置數據 |> 創建三個三角形的顏色數據 |> 創建相機view matrix需要的數據和projection matrix需要的數據
      

      下面分別實現子邏輯:

      • 創建三個三角形的頂點數據

      因為每個三角形的頂點數據都一樣,所以在Utils.re中增加通用的createTriangleVertexData函數:

      let createTriangleVertexData = () => {
        open Js.Typed_array;
      
        let vertices =
          Float32Array.make([|
            0.,
            0.5,
            0.0,
            (-0.5),
            (-0.5),
            0.0,
            0.5,
            (-0.5),
            0.0,
          |]);
      
        let indices = Uint16Array.make([|0, 1, 2|]);
      
        (vertices, indices);
      };
      

      這里使用Reason提供的Js.Typed_array.Float32Array庫來操作Float32Array。

      在Main.re的_init函數中,創建三個三角形的頂點數據:

      let (vertices1, indices1) = Utils.createTriangleVertexData();
      let (vertices2, indices2) = Utils.createTriangleVertexData();
      let (vertices3, indices3) = Utils.createTriangleVertexData();
      
      • 創建和初始化對應的VBO

      在Gl.re中定義FFI:

      type bufferTarget =
        | ArrayBuffer
        | ElementArrayBuffer;
        
       type usage =
        | Static;
        
      [@bs.send.pipe: webgl1Context] external createBuffer: buffer = "";
      
      [@bs.get]
      external getArrayBuffer: webgl1Context => bufferTarget = "ARRAY_BUFFER";
      
      [@bs.get]
      external getElementArrayBuffer: webgl1Context => bufferTarget =
        "ELEMENT_ARRAY_BUFFER";
      
      [@bs.send.pipe: webgl1Context]
      external bindBuffer: (bufferTarget, buffer) => unit = "";
      
      [@bs.send.pipe: webgl1Context]
      external bufferFloat32Data: (bufferTarget, Float32Array.t, usage) => unit =
        "bufferData";
      
      [@bs.send.pipe: webgl1Context]
      external bufferUint16Data: (bufferTarget, Uint16Array.t, usage) => unit =
        "bufferData";
        
      [@bs.get] external getStaticDraw: webgl1Context => usage = "STATIC_DRAW";
      

      因為每個三角形“創建和初始化VBO”的邏輯都一樣,所以在Utils.re中增加通用的initVertexBuffers函數:

      let initVertexBuffers = ((vertices, indices), gl) => {
        let vertexBuffer = WebGL1.createBuffer(gl);
      
        WebGL1.bindBuffer(WebGL1.getArrayBuffer(gl), vertexBuffer, gl);
      
        WebGL1.bufferFloat32Data(
          WebGL1.getArrayBuffer(gl),
          vertices,
          WebGL1.getStaticDraw(gl),
          gl,
        );
      
        let indexBuffer = WebGL1.createBuffer(gl);
      
        WebGL1.bindBuffer(WebGL1.getElementArrayBuffer(gl), indexBuffer, gl);
      
        WebGL1.bufferUint16Data(
          WebGL1.getElementArrayBuffer(gl),
          indices,
          WebGL1.getStaticDraw(gl),
          gl,
        );
      
        (vertexBuffer, indexBuffer);
      };
      

      在Main.re的_init函數中,創建和初始化對應的VBO:

      let (vertexBuffer1, indexBuffer1) =
      Utils.initVertexBuffers((vertices1, indices1), gl);
      
      let (vertexBuffer2, indexBuffer2) =
      Utils.initVertexBuffers((vertices2, indices2), gl);
      
      let (vertexBuffer3, indexBuffer3) =
      Utils.initVertexBuffers((vertices3, indices3), gl);
      
      • 創建三個三角形的位置數據

      三角形的位置數據為本地坐標系中的x、y、z坐標。

      在Main.re的_init函數中,創建三個三角形的位置數據:

      let (position1, position2, position3) = (
          (0.75, 0., 0.),
          ((-0.), 0., 0.5),
          ((-0.5), 0., (-2.)),
      );
      
      • 創建三個三角形的顏色數據

      在Main.re的_init函數中,創建三個三角形的顏色數據:

         //第一個和第三個三角形只有一個顏色數據,第二個三角形有兩個顏色數據
        let (color1, (color2_1, color2_2), color3) = (
          (1., 0., 0.),
          ((0., 0.8, 0.), (0., 0.5, 0.)),
          (0., 0., 1.),
        );
      
      • 創建相機view matrix需要的數據和projection matrix需要的數據

      view matrix需要eye、center、up這三個向量。在Main.re的_init函數中,創建它們:

        let ((eyeX, eyeY, eyeZ), (centerX, centerY, centerZ), (upX, upY, upZ)) = (
          (0., 0.0, 5.),
          (0., 0., (-100.)),
          (0., 1., 0.),
        );
      

      projection matrix需要near, far, fovy, aspect這四個值。在Main.re的_init函數中,創建它們:

        let (near, far, fovy, aspect) = (
          1.,
          100.,
          30.,
          (canvas##width |> Js.Int.toFloat) /. (canvas##height |> Js.Int.toFloat),
        );
      

      返回用于主循環的數據

      在Main.re的_init函數中,將WebGL上下文和場景數據返回,供主循環使用(只讀):

      (
          gl,
          (
            (program1, program2),
            (indices1, indices2, indices3),
            (vertexBuffer1, indexBuffer1),
            (vertexBuffer2, indexBuffer2),
            (vertexBuffer3, indexBuffer3),
            (position1, position2, position3),
            (color1, (color2_1, color2_2), color3),
            (
              ((eyeX, eyeY, eyeZ), (centerX, centerY, centerZ), (upX, upY, upZ)),
              (near, far, fovy, aspect),
            ),
          ),
        )
      

      實現_loop

      _init函數實現完畢,接下來實現_loop函數,它的偽代碼為:

      type _loop = data => int;
      let rec _loop = (data) => 
          requestAnimationFrame((time:int) => {
              _loopBody(data);
              _loop(data) |> ignore;    
          });
      

      實現“主循環”

      需要調用window.requestAnimationFrame來開啟主循環。

      在DomExtend.re中定義FFI:

      [@bs.val] external requestAnimationFrame: (float => unit) => int = "";
      

      然后定義空函數_loopBody,實現_loop的主循環,并通過編譯檢查:

      let _loopBody = (data) => ();
      
      let rec _loop = data =>
        DomExtend.requestAnimationFrame((time: float) => {
          _loopBody(data);
          _loop(data) |> ignore;
        });
      

      實現“_clearColor”

      接下來我們要實現_loopBody,它的偽代碼為:

      type _loopBody = data => unit;
      let _loopBody = (data) => {
          data
          |> _clearColor
          |> _clearCanvas
          |> _render
      };
      

      我們首先實現_clearCanvas函數,為此需要在Gl.re中定義FFI:

      [@bs.send.pipe: webgl1Context]
      external clearColor: (float, float, float, float) => unit = "";
      

      然后在Main.re中實現_clearColor函數,設置清空顏色緩沖時的顏色值為黑色:

      let _clearColor = ((gl, sceneData) as data) => {
        WebGL1.clearColor(0., 0., 0., 1., gl);
      
        data;
      };
      

      實現“_clearCanvas”

      在Gl.re中定義FFI:

      [@bs.send.pipe: webgl1Context] external clear: int => unit = "";
      
      [@bs.get]
      external getColorBufferBit: webgl1Context => int = "COLOR_BUFFER_BIT";
      
      [@bs.get]
      external getDepthBufferBit: webgl1Context => int = "DEPTH_BUFFER_BIT";
      

      然后在Main.re中實現_clearCanvas函數,清空畫布:

      let _clearCanvas = ((gl, sceneData) as data) => {
        WebGL1.clear(WebGL1.getColorBufferBit(gl) lor WebGL1.getDepthBufferBit(gl), gl);
      
        data;
      };
      

      實現“_render”

      _render的偽代碼為:

      type _render = data => unit;
      let _render = (data) => {
          設置WebGL狀態 
          |> 計算view matrix和projection matrix
          |> 計算三個三角形的model matrix
          |> 渲染三個三角形
      };
      

      下面分別實現:

      設置WebGL狀態

      在Gl.re中定義FFI:

      [@bs.get] external getDepthTest: webgl1Context => int = "DEPTH_TEST";
      
      [@bs.send.pipe: webgl1Context] external enable: int => unit = "";
      
      [@bs.get] external getCullFace: webgl1Context => int = "CULL_FACE";
      
      [@bs.send.pipe: webgl1Context] external cullFace: int => unit = "";
      
      [@bs.get] external getBack: webgl1Context => int = "BACK";
      

      在Main.re的_render函數中設置WebGL狀態,開啟深度測試和背面剔除:

      WebGL1.enable(WebGL1.getDepthTest(gl), gl);
      
      WebGL1.enable(WebGL1.getCullFace(gl), gl);
      WebGL1.cullFace(WebGL1.getBack(gl), gl);
      

      計算view matrix和projection matrix

      計算這兩個矩陣需要操作矩陣,而操作矩陣又需要操作向量,所以我們在src/中加入Matrix.re和Vector.re,增加對應的函數:

      Matrix.re:

      open Js.Typed_array;
      
      let createIdentityMatrix = () =>
        Js.Typed_array.Float32Array.make([|
          1.,
          0.,
          0.,
          0.,
          0.,
          1.,
          0.,
          0.,
          0.,
          0.,
          1.,
          0.,
          0.,
          0.,
          0.,
          1.,
        |]);
      
      let _getEpsilon = () => 0.000001;
      
      let setLookAt =
          (
            (eyeX, eyeY, eyeZ) as eye,
            (centerX, centerY, centerZ) as center,
            (upX, upY, upZ) as up,
            resultFloat32Arr,
          ) =>
        Js.Math.abs_float(eyeX -. centerX) < _getEpsilon()
        && Js.Math.abs_float(eyeY -. centerY) < _getEpsilon()
        && Js.Math.abs_float(eyeZ -. centerZ) < _getEpsilon()
          ? resultFloat32Arr
          : {
            let (z1, z2, z3) as z = Vector.sub(eye, center) |> Vector.normalize;
      
            let (x1, x2, x3) as x = Vector.cross(up, z) |> Vector.normalize;
      
            let (y1, y2, y3) as y = Vector.cross(z, x) |> Vector.normalize;
      
            Float32Array.unsafe_set(resultFloat32Arr, 0, x1);
            Float32Array.unsafe_set(resultFloat32Arr, 1, y1);
            Float32Array.unsafe_set(resultFloat32Arr, 2, z1);
            Float32Array.unsafe_set(resultFloat32Arr, 3, 0.);
            Float32Array.unsafe_set(resultFloat32Arr, 4, x2);
            Float32Array.unsafe_set(resultFloat32Arr, 5, y2);
            Float32Array.unsafe_set(resultFloat32Arr, 6, z2);
            Float32Array.unsafe_set(resultFloat32Arr, 7, 0.);
            Float32Array.unsafe_set(resultFloat32Arr, 8, x3);
            Float32Array.unsafe_set(resultFloat32Arr, 9, y3);
            Float32Array.unsafe_set(resultFloat32Arr, 10, z3);
            Float32Array.unsafe_set(resultFloat32Arr, 11, 0.);
            Float32Array.unsafe_set(resultFloat32Arr, 12, -. Vector.dot(x, eye));
            Float32Array.unsafe_set(resultFloat32Arr, 13, -. Vector.dot(y, eye));
            Float32Array.unsafe_set(resultFloat32Arr, 14, -. Vector.dot(z, eye));
            Float32Array.unsafe_set(resultFloat32Arr, 15, 1.);
      
            resultFloat32Arr;
          };
      
      let buildPerspective =
          ((fovy: float, aspect: float, near: float, far: float), resultFloat32Arr) => {
        Js.Math.sin(Js.Math._PI *. fovy /. 180. /. 2.) === 0.
          ? Utils.error("frustum should not be null") : ();
      
        let fovy = Js.Math._PI *. fovy /. 180. /. 2.;
        let s = Js.Math.sin(fovy);
        let rd = 1. /. (far -. near);
        let ct = Js.Math.cos(fovy) /. s;
        Float32Array.unsafe_set(resultFloat32Arr, 0, ct /. aspect);
        Float32Array.unsafe_set(resultFloat32Arr, 1, 0.);
        Float32Array.unsafe_set(resultFloat32Arr, 2, 0.);
        Float32Array.unsafe_set(resultFloat32Arr, 3, 0.);
        Float32Array.unsafe_set(resultFloat32Arr, 4, 0.);
        Float32Array.unsafe_set(resultFloat32Arr, 5, ct);
        Float32Array.unsafe_set(resultFloat32Arr, 6, 0.);
        Float32Array.unsafe_set(resultFloat32Arr, 7, 0.);
        Float32Array.unsafe_set(resultFloat32Arr, 8, 0.);
        Float32Array.unsafe_set(resultFloat32Arr, 9, 0.);
        Float32Array.unsafe_set(resultFloat32Arr, 10, -. (far +. near) *. rd);
        Float32Array.unsafe_set(resultFloat32Arr, 11, -1.);
        Float32Array.unsafe_set(resultFloat32Arr, 12, 0.);
        Float32Array.unsafe_set(resultFloat32Arr, 13, 0.);
        Float32Array.unsafe_set(resultFloat32Arr, 14, (-2.) *. far *. near *. rd);
        Float32Array.unsafe_set(resultFloat32Arr, 15, 0.);
      
        resultFloat32Arr;
      };
      

      Vector.re:

      let dot = ((x, y, z), (vx, vy, vz)) => x *. vx +. y *. vy +. z *. vz;
      
      let sub = ((x1, y1, z1), (x2, y2, z2)) => (x1 -. x2, y1 -. y2, z1 -. z2);
      
      let scale = (scalar, (x, y, z)) => (x *. scalar, y *. scalar, z *. scalar);
      
      let cross = ((x1, y1, z1), (x2, y2, z2)) => (
        y1 *. z2 -. y2 *. z1,
        z1 *. x2 -. z2 *. x1,
        x1 *. y2 -. x2 *. y1,
      );
      
      let normalize = ((x, y, z)) => {
        let d = Js.Math.sqrt(x *. x +. y *. y +. z *. z);
        d === 0. ? (0., 0., 0.) : (x /. d, y /. d, z /. d);
      };
      

      在Main.re的_render函數中,傳入數據,計算這兩個矩陣:

        let vMatrix =
          Matrix.createIdentityMatrix()
          |> Matrix.setLookAt(
               (eyeX, eyeY, eyeZ),
               (centerX, centerY, centerZ),
               (upX, upY, upZ),
             );
        let pMatrix =
          Matrix.createIdentityMatrix()
          |> Matrix.buildPerspective((fovy, aspect, near, far));
      

      計算三個三角形的model matrix

      在Matrix.re中增加setTranslation函數:

      let setTranslation = ((x, y, z), resultFloat32Arr) => {
        Float32Array.unsafe_set(resultFloat32Arr, 12, x);
        Float32Array.unsafe_set(resultFloat32Arr, 13, y);
        Float32Array.unsafe_set(resultFloat32Arr, 14, z);
      
        resultFloat32Arr;
      };
      

      在Main.re的_render函數中,從三角形的位置數據中構造model matrix:

        let mMatrix1 =
          Matrix.createIdentityMatrix() |> Matrix.setTranslation(position1);
        let mMatrix2 =
          Matrix.createIdentityMatrix() |> Matrix.setTranslation(position2);
        let mMatrix3 =
          Matrix.createIdentityMatrix() |> Matrix.setTranslation(position3);
      

      渲染第一個三角形

      在_render函數中需要渲染三個三角形。
      我們來細化“渲染每個三角形”:

      渲染每個三角形 = 使用對應的Program |> 傳遞三角形的頂點數據 |> 傳遞view matrix和projection matrix |> 傳遞三角形的model matrix |> 傳遞三角形的顏色數據 |> 繪制三角形
      

      下面先繪制第一個三角形,分別實現它的子邏輯:

      • 使用對應的Program

      在Gl.re中定義FFI:

      [@bs.send.pipe: webgl1Context] external useProgram: program => unit = "";
      

      在Main.re的_render函數中使用program1:

      WebGL1.useProgram(program1, gl);
      
      • 傳遞三角形的頂點數據

      在Gl.re中定義FFI:

      type attributeLocation = int;
      
      [@bs.send.pipe: webgl1Context]
      external getAttribLocation: (program, string) => attributeLocation = "";
      
      [@bs.send.pipe: webgl1Context]
      external vertexAttribPointer:
        (attributeLocation, int, int, bool, int, int) => unit =
        "";
      
      [@bs.send.pipe: webgl1Context]
      external enableVertexAttribArray: attributeLocation => unit = "";
      
      [@bs.get] external getFloat: webgl1Context => int = "FLOAT";
      

      因為“傳遞頂點數據”是通用邏輯,所以在Utils.re中增加sendAttributeData函數:
      首先判斷program對應的GLSL中是否有vertices對應的attribute:a_position;
      如果有,則開啟vertices對應的VBO;否則,拋出錯誤信息。

      相關代碼如下:

      let sendAttributeData = (vertexBuffer, program, gl) => {
        let positionLocation = WebGL1.getAttribLocation(program, "a_position", gl);
      
        positionLocation === (-1)
          ? error({j|Failed to get the storage location of a_position|j}) : ();
      
        WebGL1.bindBuffer(WebGL1.getArrayBuffer(gl), vertexBuffer, gl);
      
        WebGL1.vertexAttribPointer(
          positionLocation,
          3,
          WebGL1.getFloat(gl),
          false,
          0,
          0,
          gl,
        );
        WebGL1.enableVertexAttribArray(positionLocation, gl);
      };
      

      在Main.re的_render函數中調用sendAttributeData:

      Utils.sendAttributeData(vertexBuffer1, program1, gl);
      
      • 傳遞view matrix和projection matrix

      在Gl.re中定義FFI:

      type uniformLocation;
      
      [@bs.send.pipe: webgl1Context]
      external uniformMatrix4fv: (uniformLocation, bool, Float32Array.t) => unit =
        "";
        
      [@bs.send.pipe: webgl1Context]
      external getUniformLocation: (program, string) => Js.Null.t(uniformLocation) =
        "";
      

      因為“傳遞view matrix和projection matrix”是通用邏輯,所以在Utils.re中增加sendCameraUniformData函數:
      首先判斷program對應的GLSL中是否有view matrix對應的uniform:u_vMatrix和projection matrix對應的uniform:u_pMatrix;
      如果有,則傳遞對應的矩陣數據;否則,拋出錯誤信息。

      相關代碼如下:

      let _unsafeGetUniformLocation = (program, name, gl) =>
        switch (WebGL1.getUniformLocation(program, name, gl)) {
        | pos when !Js.Null.test(pos) => Js.Null.getUnsafe(pos)
        | _ => error({j|$name uniform not exist|j})
        };
      
      let sendCameraUniformData = ((vMatrix, pMatrix), program, gl) => {
        let vMatrixLocation = _unsafeGetUniformLocation(program, "u_vMatrix", gl);
        let pMatrixLocation = _unsafeGetUniformLocation(program, "u_pMatrix", gl);
      
        WebGL1.uniformMatrix4fv(vMatrixLocation, false, vMatrix, gl);
        WebGL1.uniformMatrix4fv(pMatrixLocation, false, pMatrix, gl);
      };
      

      在Main.re的_render函數中調用sendCameraUniformData:

      Utils.sendCameraUniformData((vMatrix, pMatrix), program1, gl);
      
      • “傳遞三角形的model matrix”以及“傳遞三角形的顏色數據”

      在Gl.re中定義FFI:

      [@bs.send.pipe: webgl1Context]
      external uniform3f: (uniformLocation, float, float, float) => unit = "";
      

      因為這兩個邏輯都是傳遞GLSL的uniform數據,所以放在一個函數中;又因為使用不同GLSL的三角形,傳遞的顏色數據不一樣,所以需要在Utils.re中,增加sendModelUniformData1、sendModelUniformData2函數,分別對應第一組和第二組GLSL。第一個和第三個三角形使用sendModelUniformData1,第二個三角形使用sendModelUniformData2。
      這兩個函數都需要判斷GLSL中是否有model matrix對應的uniform:u_mMatrix和顏色對應的uniform;
      如果有,則傳遞對應的數據;否則,拋出錯誤信息。

      相關代碼如下:

      let _sendColorData = ((r, g, b), gl, colorLocation) =>
        WebGL1.uniform3f(colorLocation, r, g, b, gl);
      
      let sendModelUniformData1 = ((mMatrix, color), program, gl) => {
        let mMatrixLocation = _unsafeGetUniformLocation(program, "u_mMatrix", gl);
        let colorLocation = _unsafeGetUniformLocation(program, "u_color0", gl);
      
        WebGL1.uniformMatrix4fv(mMatrixLocation, false, mMatrix, gl);
        _sendColorData(color, gl, colorLocation);
      };
      
      let sendModelUniformData2 = ((mMatrix, color1, color2), program, gl) => {
        let mMatrixLocation = _unsafeGetUniformLocation(program, "u_mMatrix", gl);
        let color1Location = _unsafeGetUniformLocation(program, "u_color0", gl);
        let color2Location = _unsafeGetUniformLocation(program, "u_color1", gl);
      
        WebGL1.uniformMatrix4fv(mMatrixLocation, false, mMatrix, gl);
        _sendColorData(color1, gl, color1Location);
        _sendColorData(color2, gl, color2Location);
      };
      

      在Main.re的_render函數中調用sendModelUniformData1:

        Utils.sendModelUniformData1((mMatrix1, color1), program1, gl);
      
      • 繪制三角形

      在Gl.re中定義FFI:

      [@bs.get] external getTriangles: webgl1Context => int = "TRIANGLES";
      
      [@bs.get] external getUnsignedShort: webgl1Context => int = "UNSIGNED_SHORT";
      
      [@bs.send.pipe: webgl1Context]
      external drawElements: (int, int, int, int) => unit = "";
      

      在Main.re的_render函數中,綁定indices1對應的VBO,使用drawElements繪制第一個三角形:

      WebGL1.bindBuffer(WebGL1.getElementArrayBuffer(gl), indexBuffer1, gl);
      
      WebGL1.drawElements(
          WebGL1.getTriangles(gl),
          indices1 |> Js.Typed_array.Uint16Array.length,
          WebGL1.getUnsignedShort(gl),
          0,
          gl,
      );
      

      渲染第二個和第三個三角形

      與渲染第一個三角形類似,在Main.re的_render函數中,使用對應的program,傳遞相同的相機數據,調用對應的Utils.sendModelUniformData1或sendModelUniformData2函數、綁定對應的VBO,來渲染第二個和第三個三角形。

      Main.re的_render函數的相關代碼如下:

        //渲染第二個三角形
      
        WebGL1.useProgram(program2, gl);
      
        Utils.sendAttributeData(vertexBuffer2, program2, gl);
      
        Utils.sendCameraUniformData((vMatrix, pMatrix), program2, gl);
      
        Utils.sendModelUniformData2((mMatrix2, color2_1, color2_2), program2, gl);
      
        WebGL1.bindBuffer(WebGL1.getElementArrayBuffer(gl), indexBuffer2, gl);
      
        WebGL1.drawElements(
          WebGL1.getTriangles(gl),
          indices2 |> Js.Typed_array.Uint16Array.length,
          WebGL1.getUnsignedShort(gl),
          0,
          gl,
        );
        
        //渲染第三個三角形
      
        WebGL1.useProgram(program1, gl);
      
        Utils.sendAttributeData(vertexBuffer3, program1, gl);
      
        Utils.sendCameraUniformData((vMatrix, pMatrix), program1, gl);
      
        Utils.sendModelUniformData1((mMatrix3, color3), program1, gl);
      
        WebGL1.bindBuffer(WebGL1.getElementArrayBuffer(gl), indexBuffer3, gl);
      
        WebGL1.drawElements(
          WebGL1.getTriangles(gl),
          indices3 |> Js.Typed_array.Uint16Array.length,
          WebGL1.getUnsignedShort(gl),
          0,
          gl,
        );
      

      最終的項目結構圖

      如下圖所示:
      實現最小的3D程序-“繪制三角形”:最終領域模型 (1).png-75kB

      運行測試

      在瀏覽器中運行index.html頁面,渲染結果如下圖所示,其中測試場景包括三個三角形:
      此處輸入圖片的描述

      更新后的通用語言

      此處輸入圖片的描述

      總結

      本文通過需求分析、事件風暴、初步實現和具體實現,實現了最小的3D程序,渲染了三角形。

      本文成果

      我們通過本文的工作,獲得了下面的成果:
      1、最小3D程序
      2、領域驅動設計的通用語言

      本文不足之處

      但是,還有很多不足之處:
      1、場景邏輯和WebGL API的調用邏輯混雜在一起
      2、存在重復代碼:
      1)在_init函數的“初始化所有Shader”中有重復的模式
      2)在_render中,渲染三個三角形的代碼非常相似
      3)Utils的sendModelUniformData1和sendModelUniformData2有重復的模式
      3、_init傳遞給主循環的數據過于復雜

      下文概要

      這些不足之處都是因為本文沒有進行設計造成的。本文只建立了領域驅動設計的通用語言,重在實現,快速跑通了一個最小Demo。
      我們會在下文中按照領域驅動設計的思想進行設計,解決這些不足之處。

      本文完整代碼地址

      Book-Demo-Triangle Github Repo

      posted @ 2020-01-26 19:32  楊元超  閱讀(956)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 海兴县| 午夜成人无码福利免费视频| 亚洲av色精品一区二区| 日韩人妻无码一区二区三区久久 | 亚洲一区二区| 亚洲熟妇自偷自拍另亚洲| 午夜射精日本三级| 亚洲男人第一无码av网站| 中文字幕日韩一区二区不卡| 一区二区三区四区亚洲自拍| 久久精品国产亚洲AⅤ无码| 亚洲人成人伊人成综合网无码| 国产一区二区三区尤物视频| 国产精品天干天干综合网| 亚洲精品无码久久一线| 崇信县| 老熟妇乱子交视频一区| 国产精品午夜福利资源| 性色欲情网站iwww九文堂| 国产成人无码AV片在线观看不卡| 少妇内射高潮福利炮| 玩弄丰满少妇人妻视频| 日韩精品一区二区三区日韩| 日韩av裸体在线播放| 亚洲老熟女一区二区三区| 久久精品国产清自在天天线| 锡林浩特市| 国产精品福利自产拍在线观看| 一本本月无码-| 国产在线播放专区av| 欧美偷窥清纯综合图区| 精品久久久久久无码国产| 爆乳日韩尤物无码一区| 日韩在线视频观看免费网站| 日韩人妻无码一区二区三区99| 性色a码一区二区三区天美传媒 | 亚洲精品综合网二三区| 欧美z0zo人禽交另类视频| 色综合久久网| 亚洲国产亚洲国产路线久久| 精品人妻伦九区久久aaa片|