• 从0开发3D引擎(十三):使用领域驱动设计,从最小3D程序中提炼引擎(第四部分)


    大家好,本文根据领域驱动设计的成果,实现了start API,完成了实现,更新了领域视图。

    上一篇博文

    从0开发3D引擎(十二):使用领域驱动设计,从最小3D程序中提炼引擎(第三部分)

    继续实现

    实现“DirectorJsAPI.start”

    实现“主循环”

    1、修改DirectorJsAPI.re,实现API
    DirectorJsAPI.re代码为:

    let start = DirectorApService.start;
    

    2、实现主循环
    1)修改DomExtend.re,定义requestAnimationFrame的FFI
    DomExtend.re相关代码为:

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

    2)修改DirectorApService.re,实现主循环
    DirectorApService.re相关代码为:

    let _loopBody = () => {
      //暂时没有逻辑
      ResultContainerVO.succeed();
    };
    
    let rec _loop = () =>
      DomExtend.requestAnimationFrame((time: float) => {
        //用抛出异常的方式处理Result错误
        _loopBody() |> ResultContainerVO.handleFail(Error.throwError) |> ignore;
    
        _loop() |> ignore;
      });
    
    let start = () => {
      _loop();
    };
    

    实现“设置清空颜色缓冲时的颜色值”限界上下文

    1、修改DirectorApService,重写_loopBody函数
    DirectorApService.re相关代码为:

    let _loopBody = () => {
      ClearColorClearColorDoService.clearColor()
    };
    

    2、在src/domain_layer/domain/loop/clear_color/service/中加入ClearColorClearColorDoService.re,创建领域服务ClearColor
    ClearColorClearColorDoService.re代码为:

    let clearColor = (): ResultContainerVO.t(unit, Js.Exn.t) => {
      ContextContextEntity.getGl()
      |> ResultContainerVO.mapSuccess(gl => {ContextContextEntity.clearColor(gl)});
    };
    

    3、修改ContextContextEntity.re,实现clearColor函数
    ContextContextEntity.re相关代码为:

    let clearColor = gl => {
      let (r, g, b, a) = ContextRepo.getClearColor();
    
      WebGL1.clearColor(r, g, b, a, gl);
    };
    

    实现“清空画布”限界上下文

    1、修改DirectorApService,重写_loopBody函数
    DirectorApService.re相关代码为:

    let _loopBody = () => {
      ClearColorClearColorDoService.clearColor()
      |> ResultContainerVO.bind(() => {ClearClearCanvasDoService.clearCanvas()});
    };
    

    2、在src/domain_layer/domain/loop/clear_canvas/service/中加入ClearCanvasClearCanvasDoService.re,创建领域服务ClearCanvas
    ClearCanvasClearCanvasDoService.re代码为:

    let clearCanvas = (): ResultContainerVO.t(unit, Js.Exn.t) => {
      ContextContextEntity.getGl()
      |> ResultContainerVO.mapSuccess(gl => {
           ContextContextEntity.clearCanvas(gl)
         });
    };
    

    3、修改ContextContextEntity.re,实现clearCanvas函数
    ContextContextEntity.re相关代码为:

    let clearCanvas = gl => {
      WebGL1.clear(
        WebGL1.getColorBufferBit(gl) lor WebGL1.getDepthBufferBit(gl),
        gl,
      );
    };
    

    实现“渲染”限界上下文

    1、修改DirectorApService,重写_loopBody函数
    DirectorApService.re相关代码为:

    let _loopBody = () => {
      ClearColorClearColorDoService.clearColor()
      |> ResultContainerVO.bind(() => {
           ClearCanvasClearCanvasDoService.clearCanvas()
           |> ResultContainerVO.bind(() => {RenderRenderDoService.render()})
         });
    };
    

    下面,我们依次实现“渲染”的引擎逻辑:

    实现“设置WebGL状态”

    1、在src/domain_layer/domain/loop/render/service/中加入RenderRenderDoService.re,创建领域服务Render
    RenderRenderDoService.re代码为:

    let _initGlState = gl => {
      ContextContextEntity.enableDepthTest(gl);
      ContextContextEntity.enableBackCullFace(gl);
    };
    
    let render = () => {
      ContextContextEntity.getGl()
      |> ResultContainerVO.bind(gl => {
           _initGlState(gl) |> ResultContainerVO.succeed
         });
    };
    

    2、修改ContextContextEntity.re,实现相关函数
    ContextContextEntity.re相关代码为:

    let enableDepthTest = gl => gl |> WebGL1.enable(WebGL1.getDepthTest(gl));
    
    let enableBackCullFace = gl => {
      WebGL1.enable(WebGL1.getCullFace(gl), gl);
      WebGL1.cullFace(WebGL1.getBack(gl), gl);
    };
    

    实现“创建和初始化三个三角形的VBO”

    1、设计值对象Geometry的DO中与VBO相关的字段

    根据领域模型:
    此处输入图片的描述

    在领域模型中,Geometry组合了一个VBO,这应该如何体现到Geometry的DO中?
    这个与前面我们设计值对象Material的DO(Material组合了一个Shader)类似,解决方案为:
    1)实体VBO的DO只包含VBO的id,其中id为一个从0开始不断加1的int类型
    2)Geometry DO包含一个VBO的id

    这样就使Geometry通过VBO的id,与VBO关联起来了!

    因此我们可以修改Geometry的DO,加入vbo字段:

    //值对象Geometry的DO
    type t = {
      ...
      //因为在创建值对象Geometry时,它的VBO还没有创建,在渲染时才会创建它的VBO,所以vbo字段为option类型
      vbo: option(VBO DO),
    };
    

    2、设计“VBO管理”限界上下文的DO

    根据领域模型:
    此处输入图片的描述

    我们来设计相关的DO:
    1)设计实体VBO、值对象VertexBuffer、值对象IndexBuffer的DO
    VBO DO只包含VBO的id:

    type id = int;
    
    type t =
      | VBO(id);
    

    VertexBuffer DO

    type t =
      | VertexBuffer(WebGL1.buffer);
    

    IndexBuffer DO

    type t =
      | IndexBuffer(WebGL1.buffer);
    

    2)设计聚合根VBOManager的DO

    VBO的id逻辑为:创建实体VBO时,将最大的VBO id加1,作为它的id。
    为了实现该逻辑,需要在VBOManager DO中保存最大的VBO id:

    type t = {
      maximumId: VBO DO,
    };
    

    我们需要根据VBO id,获得它的值对象VertexBuffer和值对象IndexBuffer的DO,所以类似于聚合根ShaderManager DO的programMap,VBOManager DO需要两个map,它们的key是VBO id,value分别为值对象VertexBuffer的DO和值对象IndexBuffer的DO。
    这里值得注意的是:这两个map并不是hash map,因为它的key为int而不是string类型!所以我们需要加入sparse map。sparse map和hash map的区别就是map的key的类型不同。

    应该在领域视图的“容器”限界上下文中,加入值对象ImmutableSparseMap、值对象MutableSparseMap,其中ImmutableSparseMap用于实现不可变的sparse map,MutableSparseMap用于实现可变的sparse map。

    我们先设计它们的DO,它们的DO是一样的:
    ImmutableSparseMap DO

    type t('index, 'value) = array('value);
    

    MutableSparseMap DO

    type t('index, 'value) = array('value);
    

    现在我们可以设计出聚合根VBOManager完整的DO:

    type t = {
      maximumId: VBO DO,
      vertexBufferMap:ImmutableSparseMap.t(VBO DO, VertexBuffer DO)
      indexBufferMap:ImmutableSparseMap.t(VBO DO, IndexBuffer DO),
    };
    

    3、实现sparse map
    类似于实现hash map:
    1)在src/domain_layer/domain/structure/container/value_object/中创建文件夹sparse_map/
    2)在sparse_map/文件夹中加入ImmutableSparseMapContainerVO.re、MutableSparseMapContainerVO.re、SparseMapContainer.re、SparseMapContainerType.re
    ImmutableSparseMapContainerVO.re负责实现Immutable Sparse Map;
    MutableSparseMapContainerVO.re负责实现Mutable Sparse Map;
    SparseMapContainer.re从两者中提出的公共代码;
    SparseMapContainerType.re定义SparseMap的类型和相关的类型转换。

    相关代码如下:
    SparseMapContainerType.re

    type t('index, 'value) = array('value);
    type t2('value) = t(int, 'value);
    
    external notNullableToNullable: 'a => Js.Nullable.t('a) = "%identity";
    

    SparseMapContainer.re

    let createEmpty = (): SparseMapContainerType.t2('a) => [||];
    
    let copy = Js.Array.copy;
    
    let _unsafeGet = (index: int, map: SparseMapContainerType.t2('a)): 'a => {
      Array.unsafe_get(map, index);
    };
    
    let _isEmpty = (value: 'a): bool =>
      value |> SparseMapContainerType.notNullableToNullable |> Js.Nullable.test;
    
    let get = (index: int, map) => {
      let value = _unsafeGet(index, map);
      _isEmpty(value) ? None : Some(value);
    };
    
    let has = (index: int, map) => !_isEmpty(_unsafeGet(index, map));
    

    ImmutableSparseMapContainerVO.re

    type t('index, 'value) = SparseMapContainerType.t('index, 'value);
    
    let createEmpty = SparseMapContainer.createEmpty;
    
    let copy = SparseMapContainer.copy;
    
    let get = SparseMapContainer.get;
    
    let has = SparseMapContainer.has;
    
    let set =
        (key: int, value: 'a, map: SparseMapContainerType.t2('a))
        : SparseMapContainerType.t2('a) => {
      let newMap = map |> copy;
    
      Array.unsafe_set(newMap, key, value);
    
      newMap;
    };
    

    MutableSparseMapContainerVO.re

    type t('index, 'value) = SparseMapContainerType.t('index, 'value);
    
    let createEmpty = SparseMapContainer.createEmpty;
    
    let copy = SparseMapContainer.copy;
    
    let get = SparseMapContainer.get;
    
    let has = SparseMapContainer.has;
    
    let set =
        (key: int, value: 'a, map: SparseMapContainerType.t2('a))
        : SparseMapContainerType.t2('a) => {
      Array.unsafe_set(map, key, value);
    
      map;
    };
    

    4、修改RenderRenderDoService.re,用伪代码实现“创建和初始化三个三角形的VBO”
    RenderRenderDoService.re相关伪代码为:

    let _initVBOs = (...) => {
      从Scene PO中获得所有三角形的Geometry DO
      |> 遍历((每个三角形的Geometry DO) => {
              let vbo:option(VBO DO) = 获得每个三角形的Geometry DO的vbo字段;
    
              if(已经创建和初始化了vbo,即vbo为Some){
                continue;
              }
              else{
                创建VBO和它的值对象
                初始化创建的VBO
                加入创建的VBO DO和它的值对象的DO到PO中
                将创建的VBO DO设置到Scene PO->该三角形->Geometry数据->vbo字段中
              }
            })
    };
    
    let render = () => {
      ContextContextEntity.getGl()
      |> ResultContainerVO.bind(gl => {
           ...
           
           _initVBOs(...);
         });
    };
    

    5、修改RenderRenderDoService.re,具体实现“创建和初始化三个三角形的VBO”
    RenderRenderDoService.re相关代码为:

    let _initVBOs = gl => {
      SceneSceneGraphEntity.getAllTriangles()
      |> List.iteri((triangleIndex, {geometry}: TriangleSceneGraphVO.t) => {
           let {vbo, vertices, indices}: GeometrySceneGraphVO.t = geometry;
    
           GeometrySceneGraphVO.hasVBO(vbo)
             ? {
               ();
             }
             : {
               let vbo = VBOManagerVBOManagerEntity.createVBO();
    
               (
                 VBOVBOManagerEntity.createVertexBuffer(gl),
                 VBOVBOManagerEntity.createIndexBuffer(gl),
               )
               |> VBOManagerVBOManagerEntity.initVBO(gl, (vertices, indices))
               |> VBOManagerVBOManagerEntity.addVBO(vbo);
    
               /* setVBO函数负责实现“将VBO DO设置到Scene PO->该三角形->Geometry数据->vbo字段中”
               
               这里需要思考的是:在哪里实现setVBO函数呢?
    
               因为该函数需要使用仓库来设置到Scene PO中,而我们应该只在实体中使用仓库,所以该函数应该放在聚合根Scene而不是值对象Triangle或者值对象Geometry中。
               另外,为了在设置时定位到Scene PO对应的三角形,需要该三角形在Scene DO的triangles字段(为一个list集合)中的序号triangleIndex。
               */
               SceneSceneGraphEntity.setVBO(vbo, triangleIndex);
             };
         });
    };
    
    let render = () => {
      ContextContextEntity.getGl()
      |> ResultContainerVO.bind(gl => {
           ...
    
           _initVBOs(gl) |> ResultContainerVO.succeed;
      });
    });
    

    5、修改GeometrySceneGraphVO.re,Geometry DO加入vbo字段,实现hasVBO函数,修改create函数

    type t = {
      vertices: VerticesSceneGraphVO.t,
      indices: IndicesSceneGraphVO.t,
      vbo: option(VBOVBOManagerEntity.t),
    };
    
    let create = (vertices, indices): ScenePOType.geometry => {
      ...
      vbo: None,
    };
    
    let hasVBO = vbo => {
      vbo |> OptionContainerDoService.isSome;
    };
    

    6、修改OptionContainerDoService.re,实现isSome函数
    OptionContainerDoService.re相关代码为:

    let isSome = Js.Option.isSome;
    

    7、修改SceneSceneGraphEntity.re和相关的仓库,实现getAllTriangles函数
    SceneSceneGraphEntity.re相关代码为:

    let getAllTriangles = () => {
      SceneRepo.getAllTriangles();
    };
    

    SceneRepo.re相关代码为:

    let getAllTriangles = () => {
      _getTriangles(Repo.getScene()) |> List.map(TriangleSceneRepo.build);
    };
    

    TriangleSceneRepo.re相关代码为:

    let build =
        ({transform, geometry, material}: ScenePOType.triangle)
        : TriangleSceneGraphVO.t => {
      transform: TransformSceneRepo.build(transform),
      geometry: GeometrySceneRepo.build(geometry),
      material: MaterialSceneRepo.build(material),
    };
    

    TransformSceneRepo.re相关代码为:

    let build = ({position}: ScenePOType.transform): TransformSceneGraphVO.t => {
      position: position |> VectorMathVO.create |> PositionSceneGraphVO.create,
    };
    

    GeometrySceneRepo.re相关代码为:

    let build =
        ({vertices, indices, vbo}: ScenePOType.geometry): GeometrySceneGraphVO.t => {
      vertices: vertices |> VerticesSceneGraphVO.create,
      indices: indices |> IndicesSceneGraphVO.create,
      vbo: vbo |> OptionContainerDoService.map(VBOVBOManagerEntity.create),
    };
    

    MaterialSceneRepo.re相关代码为:

    let build = ({shader, colors}: ScenePOType.material): MaterialSceneGraphVO.t => {
      shader: shader |> ShaderShaderEntity.create,
      colors: colors |> List.map(color => {color |> Color3ContainerVO.create}),
    };
    

    8、实现“VBO管理”限界上下文

    我们按照下面的步骤来实现:
    1)实现相关的领域模型
    2)实现相关的PO数据
    3)实现相关的仓库

    现在来具体实现:
    1)在src/domain_layer/domain/webgl_object_manager/vbo_manager/entity/中加入VBOManagerVBOManagerEntity.re,创建聚合根VBOManager
    VBOManagerVBOManagerEntity.re代码为:

    type t = {
      maximumId: VBOVBOManagerEntity.t,
      vertexBufferMap:
        ImmutableSparseMapContainerVO.t(
          VBOVBOManagerEntity.t,
          VertexBufferVBOManagerVO.t,
        ),
      indexBufferMap:
        ImmutableSparseMapContainerVO.t(
          VBOVBOManagerEntity.t,
          IndexBufferVBOManagerVO.t,
        ),
    };
    
    let createVBO = () => {
      let (newId, maximumId) =
        VBOManagerRepo.getMaximumId() |> VBOVBOManagerEntity.generateId;
    
      VBOManagerRepo.setMaximumId(maximumId);
    
      newId;
    };
    
    let initVBO = (gl, (vertices, indices), (vertexBuffer, indexBuffer)) => {
      (
        VBOVBOManagerEntity.createVertexBuffer(gl)
        |> VBOVBOManagerEntity.initVertexBuffer(gl, vertices),
        VBOVBOManagerEntity.createIndexBuffer(gl)
        |> VBOVBOManagerEntity.initIndexBuffer(gl, indices),
      );
    };
    
    let addVBO = (vbo, (vertexBuffer, indexBuffer)) => {
      VBOManagerRepo.addVBO(vbo, vertexBuffer, indexBuffer);
    };
    

    2)在src/domain_layer/domain/webgl_object_manager/vbo_manager/entity/中加入VBOVBOManagerEntity.re,创建实体VBO
    VBOVBOManagerEntity.re代码为:

    type id = int;
    
    type t =
      | VBO(id);
    
    let create = id => VBO(id);
    
    let getId = vbo =>
      switch (vbo) {
      | VBO(id) => id
      };
    
    let mapId = (f, vbo) => vbo |> getId |> f |> create;
    
    let generateId = maximumId => {
      (maximumId, maximumId |> mapId(maximumId => {maximumId |> succ}));
    };
    
    let createVertexBuffer = gl => {
      WebGL1.createBuffer(gl) |> VertexBufferVBOManagerVO.create;
    };
    
    let createIndexBuffer = gl => {
      WebGL1.createBuffer(gl) |> IndexBufferVBOManagerVO.create;
    };
    
    let initVertexBuffer = (gl, vertices, vertexBuffer) => {
      let vertexBuffer = VertexBufferVBOManagerVO.value(vertexBuffer);
    
      WebGL1.bindBuffer(WebGL1.getArrayBuffer(gl), vertexBuffer, gl);
    
      WebGL1.bufferFloat32Data(
        WebGL1.getArrayBuffer(gl),
        vertices |> VerticesSceneGraphVO.value,
        WebGL1.getStaticDraw(gl),
        gl,
      );
    
      vertexBuffer |> VertexBufferVBOManagerVO.create;
    };
    
    let initIndexBuffer = (gl, indices, indexBuffer) => {
      let indexBuffer = IndexBufferVBOManagerVO.value(indexBuffer);
    
      WebGL1.bindBuffer(WebGL1.getElementArrayBuffer(gl), indexBuffer, gl);
    
      WebGL1.bufferUint16Data(
        WebGL1.getElementArrayBuffer(gl),
        indices |> IndicesSceneGraphVO.value,
        WebGL1.getStaticDraw(gl),
        gl,
      );
    
      indexBuffer |> IndexBufferVBOManagerVO.create;
    };
    

    3)在src/domain_layer/domain/webgl_object_manager/vbo_manager/value_object/中加入VertexBufferVBOManagerVO.re,创建值对象VertexBuffer
    VertexBufferVBOManagerVO.re代码为:

    type t =
      | VertexBuffer(WebGL1.buffer);
    
    let create = buffer => VertexBuffer(buffer);
    
    let value = buffer =>
      switch (buffer) {
      | VertexBuffer(value) => value
      };
    

    4)在src/domain_layer/domain/webgl_object_manager/vbo_manager/value_object/中加入IndexBufferVBOManagerVO.re,创建值对象IndexBuffer
    IndexBufferVBOManagerVO.re代码为:

    type t =
      | IndexBuffer(WebGL1.buffer);
    
    let create = buffer => IndexBuffer(buffer);
    
    let value = buffer =>
      switch (buffer) {
      | IndexBuffer(value) => value
      };
    

    5)修改POType.re
    POType.re相关代码为:

    type po = {
      ...
      vboManager: VBOManagerPOType.vboManager,
    };
    

    5)在src/infrastructure_layer/data/po/中加入VBOManagerPOType.re,定义VBOManager PO的类型
    VBOManagerPOType.re代码为:

    type vboId = int;
    
    type vboManager = {
      maximumId: vboId,
      vertexBufferMap: ImmutableSparseMap.t(vboId, WebGL1.buffer),
      indexBufferMap: ImmutableSparseMap.t(vboId, WebGL1.buffer),
    };
    

    这里的vertexBufferMap、indexBufferMap与聚合根VBOManager DO中的vertexBufferMap、indexBufferMap一样,也是sparse map,但不能直接使用领域层的值对象ImmutableSparseContainerVO来定义它的类型!因为PO属于基础设施层,它不能依赖领域层!
    因此,与之前我们加入ImmutableHash.re模块类似,我们应该在基础设施层的“数据”中创建一个ImmutableSparseMap.re模块,尽管它的类型和函数都与ImmutableSparseMapContainerVO一样。

    在src/infrastructure_layer/data/structure/中加入ImmutableSparseMap.re。
    为了方便,目前暂时直接用ImmutableSparseMapContainerVO来实现ImmutableSparseMap,ImmutableSparseMap.re代码为:

    type t('index, 'a) = ImmutableSparseMapContainerVO.t('index, 'a);
    
    let createEmpty = ImmutableSparseMapContainerVO.createEmpty;
    
    let get = ImmutableSparseMapContainerVO.get;
    
    let set = ImmutableSparseMapContainerVO.set;
    

    6)在src/domain_layer/repo/中加入VBOManagerRepo.re,实现仓库对VBOManager PO的操作
    VBOManagerRepo.re代码为:

    open VBOManagerPOType;
    
    let _getMaximumId = ({maximumId}) => maximumId;
    
    let getMaximumId = () => {
      _getMaximumId(Repo.getVBOManager()) |> VBOVBOManagerEntity.create;
    };
    
    let setMaximumId = maximumId => {
      Repo.setVBOManager({
        ...Repo.getVBOManager(),
        maximumId: VBOVBOManagerEntity.getId(maximumId),
      });
    };
    
    let _getVertexBufferMap = ({vertexBufferMap}) => vertexBufferMap;
    
    let _getIndexBufferMap = ({indexBufferMap}) => indexBufferMap;
    
    let addVBO = (vbo, vertexBuffer, indexBuffer) => {
      Repo.setVBOManager({
        ...Repo.getVBOManager(),
        vertexBufferMap:
          _getVertexBufferMap(Repo.getVBOManager())
          |> ImmutableSparseMap.set(
               VBOVBOManagerEntity.getId(vbo),
               VertexBufferVBOManagerVO.value(vertexBuffer),
             ),
    
        indexBufferMap:
          _getIndexBufferMap(Repo.getVBOManager())
          |> ImmutableSparseMap.set(
               VBOVBOManagerEntity.getId(vbo),
               IndexBufferVBOManagerVO.value(indexBuffer),
             ),
      });
    };
    

    7)修改OptionContainerDoService.re,实现map函数
    OptionContainerDoService.re相关代码为:

    let map = (func, optionData) =>
      optionData |> Js.Option.map((. data) => func(data));
    

    8)修改Repo.re,实现仓库对VBOManager PO的操作
    Repo.re相关代码为:

    let getVBOManager = () => {
      let po = ContainerManager.getPO();
    
      po.vboManager;
    };
    
    let setVBOManager = vboManager => {
      let po = ContainerManager.getPO();
    
      {...po, vboManager} |> ContainerManager.setPO;
    };
    

    9)修改CreateRepo.re,实现创建VBOManager PO
    CreateRepo.re相关代码为:

    let create = () => {
      ...
      vboManager: {
        maximumId: 0,
        vertexBufferMap: ImmutableSparseMap.createEmpty(),
        indexBufferMap: ImmutableSparseMap.createEmpty(),
      },
    };
    

    9、实现setVBO函数
    我们按照下面的步骤来实现:
    1)实现相关的领域模型
    2)实现相关的PO数据
    3)实现相关的仓库

    现在来具体实现:
    1)修改SceneSceneGraphEntity.re,实现setVBO函数
    SceneSceneGraphEntity.re相关代码为:

    let setVBO = (vbo, triangleIndex) => {
      SceneRepo.setVBO(vbo, triangleIndex);
    };
    

    2)修改ScenePOType.re,Scene PO的geometry加入vbo字段
    ScenePOType.re相关代码为:

    type vboId = int;
    
    type geometry = {
      ...
      vbo: option(vboId),
    };
    

    3)修改SceneRepo.re,实现setVBO函数
    SceneRepo.re相关代码为:

    let setVBO = (vbo, targetTriangleIndex) => {
      Repo.setScene({
        ...Repo.getScene(),
        triangles:
          _getTriangles(Repo.getScene())
          |> List.mapi((triangleIndex, triangle) => {
               triangleIndex === targetTriangleIndex
                 ? {
                   TriangleSceneRepo.setVBO(vbo, triangle);
                 }
                 : triangle
             }),
      });
    };
    

    4)修改TriangleSceneRepo.re,实现setVBO函数
    TriangleSceneRepo.re相关代码为:

    let setVBO = (vbo, ({geometry}: ScenePOType.triangle) as triangle) => {
      {...triangle, geometry: GeometrySceneRepo.setVBO(vbo, geometry)};
    };
    

    5)修改GeometrySceneRepo.re,实现setVBO函数
    GeometrySceneRepo.re相关代码为:

    let setVBO = (vbo, geometry): ScenePOType.geometry => {
      {...geometry, vbo: Some(vbo |> VBOVBOManagerEntity.getId)};
    };
    

    实现“渲染三个三角形”

    1、加入值对象Render
    从0开发3D引擎(十):使用领域驱动设计,从最小3D程序中提炼引擎(第一部分)的“给出值对象Render的类型定义”中,我们已经定义了值对象Render的类型,所以我们直接将设计转换为实现:
    在src/domain_layer/domain/loop/render/value_object/中加入RenderRenderVO.re,创建值对象Render
    RenderRenderVO.re代码为:

    type triangle = {
      mMatrix: Js.Typed_array.Float32Array.t,
      vertexBuffer: WebGL1.buffer,
      indexBuffer: WebGL1.buffer,
      indexCount: int,
      colors: list((float, float, float)),
      program: WebGL1.program,
    };
    
    type triangles = list(triangle);
    
    type camera = {
      vMatrix: Js.Typed_array.Float32Array.t,
      pMatrix: Js.Typed_array.Float32Array.t,
    };
    
    type gl = WebGL1.webgl1Context;
    
    type t = (gl, camera, triangles);
    

    2、创建领域服务BuildRenderData,构造值对象Render
    这里需要思考的问题时:
    根据从0开发3D引擎(十):使用领域驱动设计,从最小3D程序中提炼引擎(第一部分)的“渲染”限界上下文的防腐设计,我们知道领域服务BuildRenderData在构造值对象Render时可以获得场景图DO数据(即Scene DO),它包含了_initVBOs函数需要的数据:“所有三角形的Geometry DO”。那么能否在构造值对象Render时执行_initVBOs函数呢?

    答案是不能。这是因为在构造值对象Render时,只应该进行读的操作(如读取PO,获得场景图DO数据),而_initVBOs函数需要进行写的操作,所以应该将读写分离,在“构造值对象Render”之前执行_initVBOs函数。

    现在我们开始创建领域服务BuildRenderData:
    1)创建值对象Matrix
    因为构造值对象Render的相机数据(view matrix、projection matrix)时需要创建值对象Matrix,所以需要先实现相关代码。
    在src/domain_layer/domain/structure/container/value_object/中加入MatrixContainerVO.re,创建值对象Matrix:

    open Js.Typed_array;
    
    type t =
      | Matrix(Float32Array.t);
    
    let createWithoutCheck = resultFloat32Arr => Matrix(resultFloat32Arr);
    
    let createIdentityMatrix = () =>
      Float32Array.make([|
        1.,
        0.,
        0.,
        0.,
        0.,
        1.,
        0.,
        0.,
        0.,
        0.,
        1.,
        0.,
        0.,
        0.,
        0.,
        1.,
      |])
      |> createWithoutCheck;
    
    let value = mat =>
      switch (mat) {
      | Matrix(value) => value
      };
    
    let _getEpsilon = () => 0.000001;
    
    let setLookAt = (eye, center, up, mat) => {
      let eye = eye |> EyeSceneGraphVO.value;
      let (eyeX, eyeY, eyeZ) = eye |> VectorMathVO.value;
      let center = center |> CenterSceneGraphVO.value;
      let (centerX, centerY, centerZ) = center |> VectorMathVO.value;
      let up = up |> UpSceneGraphVO.value;
      let (upX, upY, upZ) = up |> VectorMathVO.value;
    
      Js.Math.abs_float(eyeX -. centerX) < _getEpsilon()
      && Js.Math.abs_float(eyeY -. centerY) < _getEpsilon()
      && Js.Math.abs_float(eyeZ -. centerZ) < _getEpsilon()
        ? mat
        : {
          let z = VectorMathVO.sub(eye, center) |> VectorMathVO.normalize;
    
          let x = VectorMathVO.cross(up, z) |> VectorMathVO.normalize;
    
          let y = VectorMathVO.cross(z, x) |> VectorMathVO.normalize;
    
          let (x1, x2, x3) = VectorMathVO.value(x);
          let (y1, y2, y3) = VectorMathVO.value(y);
          let (z1, z2, z3) = VectorMathVO.value(z);
    
          let resultFloat32Arr = value(mat);
    
          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,
            -. VectorMathVO.dot(x, eye),
          );
          Float32Array.unsafe_set(
            resultFloat32Arr,
            13,
            -. VectorMathVO.dot(y, eye),
          );
          Float32Array.unsafe_set(
            resultFloat32Arr,
            14,
            -. VectorMathVO.dot(z, eye),
          );
          Float32Array.unsafe_set(resultFloat32Arr, 15, 1.);
    
          resultFloat32Arr |> createWithoutCheck;
        };
    };
    
    let buildPerspective = ((fovy, aspect, near, far), mat) => {
      let fovy = FovySceneGraphVO.value(fovy);
    
      Js.Math.sin(Js.Math._PI *. fovy /. 180. /. 2.) === 0.
        //使用Result处理错误
        ? ResultContainerVO.failWith("frustum should not be null")
        : {
          let aspect = AspectSceneGraphVO.value(aspect);
          let near = NearSceneGraphVO.value(near);
          let far = FarSceneGraphVO.value(far);
    
          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;
    
          let resultFloat32Arr = value(mat);
    
          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 |> createWithoutCheck |> ResultContainerVO.succeed;
        };
    };
    
    let setTranslation = (v, mat) => {
      let resultFloat32Arr = value(mat);
      let (x, y, z) = VectorMathVO.value(v);
    
      Float32Array.unsafe_set(resultFloat32Arr, 12, x);
      Float32Array.unsafe_set(resultFloat32Arr, 13, y);
      Float32Array.unsafe_set(resultFloat32Arr, 14, z);
    
      resultFloat32Arr |> createWithoutCheck;
    };
    

    2)修改VectorContainerVO.re,实现值对象Matrix需要的函数
    VectorContainerVO.re代码为:

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

    3)在src/domain_layer/domain/loop/render/service/中加入BuildRenderDataRenderDoService.re,创建领域服务BuildRenderData
    BuildRenderDataRenderDoService.re代码为:

    let build = gl => {
      SceneSceneGraphEntity.getCamera()
      |> OptionContainerDoService.get
      |> ResultContainerVO.bind(
           ({eye, center, up, near, far, fovy, aspect}: CameraSceneGraphVO.t) => {
           let vMatrix =
             MatrixMathVO.createIdentityMatrix()
             |> MatrixMathVO.setLookAt(eye, center, up);
    
           MatrixMathVO.createIdentityMatrix()
           |> MatrixMathVO.buildPerspective((fovy, aspect, near, far))
           |> ResultContainerVO.bind(pMatrix => {
                (vMatrix |> MatrixMathVO.value, pMatrix |> MatrixMathVO.value)
                //因为需要调用OptionContainerDoService.unsafeGet函数,从option数据中取出值(当option数据为None时,会抛出异常),所以需要使用Result.tryCatch将异常转换为Result
                |> ResultContainerVO.tryCatch(((vMatrix, pMatrix)) => {
                     (
                       gl,
                       {vMatrix, pMatrix}: RenderRenderVO.camera,
                       SceneSceneGraphEntity.getAllTriangles()
                       |> List.map(
                            (
                              {transform, geometry, material}: TriangleSceneGraphVO.t,
                            ) => {
                            let {position}: TransformSceneGraphVO.t = transform;
                            let {vbo, indices}: GeometrySceneGraphVO.t = geometry;
                            let {shader, colors}: MaterialSceneGraphVO.t = material;
    
                            let (vertexBuffer, indexBuffer) =
                              VBOManagerVBOManagerEntity.getVBOBuffers(
                                vbo |> OptionContainerDoService.unsafeGet,
                              );
    
                            (
                              {
                                mMatrix:
                                  MatrixMathVO.createIdentityMatrix()
                                  |> MatrixMathVO.setTranslation(
                                       PositionSceneGraphVO.value(position),
                                     )
                                  |> MatrixMathVO.value,
                                vertexBuffer:
                                  vertexBuffer
                                  |> OptionContainerDoService.unsafeGet
                                  |> VertexBufferVBOManagerVO.value,
                                indexBuffer:
                                  indexBuffer
                                  |> OptionContainerDoService.unsafeGet
                                  |> IndexBufferVBOManagerVO.value,
                                indexCount: IndicesSceneGraphVO.length(indices),
                                colors:
                                  colors |> List.map(Color3ContainerVO.value),
                                program:
                                  ShaderManagerShaderEntity.getProgram(shader)
                                  |> OptionContainerDoService.unsafeGet,
                              }: RenderRenderVO.triangle
                            );
                          }),
                     )
                   })
              });
         });
    };
    

    4)修改SceneSceneGraphEntity.re和相关的仓库,实现getCamera函数
    SceneSceneGraphEntity.re相关代码为:

    let getCamera = () => {
      SceneRepo.getCamera();
    };
    

    SceneRepo.re相关代码为:

    let _getCamera = ({camera}) => camera;
    
    let getCamera = () => {
      _getCamera(Repo.getScene())
      |> OptionContainerDoService.map(CameraSceneRepo.build);
    };
    

    CamereSceneRepo.re相关代码为:

    let build =
        ({eye, center, up, near, far, fovy, aspect}: ScenePOType.camera)
        : CameraSceneGraphVO.t => {
      {
        eye: eye |> VectorMathVO.create |> EyeSceneGraphVO.create,
        center: center |> VectorMathVO.create |> CenterSceneGraphVO.create,
        up: up |> VectorMathVO.create |> UpSceneGraphVO.create,
        near: NearSceneGraphVO.create(near),
        far: FarSceneGraphVO.create(far),
        fovy: FovySceneGraphVO.create(fovy),
        aspect: AspectSceneGraphVO.create(aspect),
      };
    };
    

    5)修改VBOManagerVBOManagerEntity.re和相关的仓库,实现getVBOBuffers函数
    VBOManagerVBOManagerEntity.re相关代码为:

    let getVBOBuffers = vbo => {
      VBOManagerRepo.getVBOBuffers(vbo);
    };
    

    VBOManagerRepo.re相关代码为:

    let getVBOBuffers = vbo => {
      let vboId = VBOVBOManagerEntity.getId(vbo);
    
      (
        _getVertexBufferMap(Repo.getVBOManager())
        |> ImmutableSparseMap.get(vboId)
        |> OptionContainerDoService.map(VertexBufferVBOManagerVO.create),
        _getIndexBufferMap(Repo.getVBOManager())
        |> ImmutableSparseMap.get(vboId)
        |> OptionContainerDoService.map(IndexBufferVBOManagerVO.create),
      );
    };
    

    6)修改IndicesSceneGraphVO.re,实现length函数
    IndicesSceneGraphVO.re相关代码:

    let map = (f, indices) => indices |> value |> f;
    
    let length = indices => indices |> map(Uint16Array.length);
    

    7)修改ShaderManagerShaderEntity.re和相关的仓库,实现getProgram函数
    ShaderManagerShaderEntity.re相关代码:

    let getProgram = shader => {
      ShaderManagerRepo.getProgram(shader);
    };
    

    ShaderManagerRepo.re相关代码:

    let getProgram = shader => {
      _getProgramMap(Repo.getShaderManager())
      |> ImmutableHashMap.get(ShaderShaderEntity.getId(shader));
    };
    

    3、修改RenderRenderDoService.re,用伪代码实现“渲染三个三角形”
    RenderRenderDoService.re相关伪代码为:

    let render = () => {
      ContextContextEntity.getGl()
      |> ResultContainerVO.bind(gl => {
           ...
    
           BuildRenderDataRenderDoService.build(gl)
           |> ResultContainerVO.bind(renderData => {
                renderData
                |> ResultContainerVO.tryCatch(
                     (
                       (gl, camera, triangles),
                     ) => {
                     triangles
                     |> List.iter(
                          (
                            {
                              mMatrix,
                              vertexBuffer,
                              indexBuffer,
                              indexCount,
                              colors,
                              program,
                            }: RenderRenderVO.triangle,
                          ) => {
                            渲染每个三角形
                        })
                   })
              });
         });
    };
    

    4、实现渲染每个三角形

    1)修改RenderRenderDoService.re,实现伪代码:“渲染每个三角形”

    “渲染每个三角形”伪代码的实现跟最小3D程序的_render函数中“渲染一个三角形”的代码差不多。最小3D程序的_render函数->渲染第一个三角形的相关代码为:

      //省略了Utils.re的相关实现代码
    
      WebGL1.useProgram(program1, gl);
    
      Utils.sendAttributeData(vertexBuffer1, program1, gl);
    
      Utils.sendCameraUniformData((vMatrix, pMatrix), program1, gl);
    
      Utils.sendModelUniformData1((mMatrix1, color1), program1, gl);
    
      WebGL1.bindBuffer(WebGL1.getElementArrayBuffer(gl), indexBuffer1, gl);
    
      WebGL1.drawElements(
        WebGL1.getTriangles(gl),
        indices1 |> Js.Typed_array.Uint16Array.length,
        WebGL1.getUnsignedShort(gl),
        0,
        gl,
      );
    

    我们参考最小3D程序,实现RenderRenderDoService.re:

    let _sendAttributeData = (vertexBuffer, program, gl) => {
      let positionLocation =
        ContextContextEntity.getAttribLocation(program, "a_position", gl);
    
      positionLocation === (-1)
        ? Error.error({j|Failed to get the storage location of a_position|j}) : ();
    
      ContextContextEntity.bindBuffer(
        ContextContextEntity.getArrayBuffer(gl),
        vertexBuffer,
        gl,
      );
    
      ContextContextEntity.vertexAttribPointer(
        ~gl,
        ~location=positionLocation,
        ~size=3,
        (),
      );
      ContextContextEntity.enableVertexAttribArray(positionLocation, gl);
    };
    
    let _sendCameraUniformData = ((vMatrix, pMatrix), program, gl) => {
      let vMatrixLocation =
        ContextContextEntity.unsafeGetUniformLocation(program, "u_vMatrix", gl);
      let pMatrixLocation =
        ContextContextEntity.unsafeGetUniformLocation(program, "u_pMatrix", gl);
    
      ContextContextEntity.uniformMatrix4fv(
        ~location=vMatrixLocation,
        ~value=vMatrix,
        ~gl,
        (),
      );
      ContextContextEntity.uniformMatrix4fv(
        ~location=pMatrixLocation,
        ~value=pMatrix,
        ~gl,
        (),
      );
    };
    
    let _sendModelUniformData = ((mMatrix, colors), program, gl) => {
      let mMatrixLocation =
        ContextContextEntity.unsafeGetUniformLocation(program, "u_mMatrix", gl);
    
      colors
      |> List.iteri((index, (r, g, b)) => {
           let colorLocation =
             ContextContextEntity.unsafeGetUniformLocation(
               program,
               {j|u_color$index|j},
               gl,
             );
    
           ContextContextEntity.uniform3f(colorLocation, r, g, b, gl);
         });
    
      ContextContextEntity.uniformMatrix4fv(
        ~location=mMatrixLocation,
        ~value=mMatrix,
        ~gl,
        (),
      );
      (mMatrixLocation, false, mMatrix, gl);
    };
    
    let render = () => {
      ContextContextEntity.getGl()
      |> ResultContainerVO.bind(gl => {
           ...
    
           BuildRenderDataRenderDoService.build(gl)
           |> ResultContainerVO.bind(renderData => {
                renderData
                |> ResultContainerVO.tryCatch(
                     (
                       (gl, {vMatrix, pMatrix}: RenderRenderVO.camera, triangles),
                     ) => {
                     triangles
                     |> List.iter(
                          (
                            {
                              mMatrix,
                              vertexBuffer,
                              indexBuffer,
                              indexCount,
                              colors,
                              program,
                            }: RenderRenderVO.triangle,
                          ) => {
                          ContextContextEntity.useProgram(program, gl);
    
                          _sendAttributeData(vertexBuffer, program, gl);
    
                          _sendCameraUniformData(
                            (vMatrix, pMatrix),
                            program,
                            gl,
                          );
    
                          _sendModelUniformData((mMatrix, colors), program, gl);
    
                          ContextContextEntity.bindBuffer(
                            WebGL1.getElementArrayBuffer(gl),
                            indexBuffer,
                            gl,
                          );
    
                          ContextContextEntity.drawElements(
                            ~gl,
                            ~count=indexCount,
                            (),
                          );
                        })
                   })
              });
         });
    };
    

    2)修改ContextContextEntity.re,实现相关函数
    ContextContextEntity.re相关代码为:

    let useProgram = (program, gl) => WebGL1.useProgram(program, gl);
    
    ...
    
    let getAttribLocation = (program, name, gl) =>
      WebGL1.getAttribLocation(program, name, gl);
    
    let getUniformLocation = (program, name, gl) =>
      WebGL1.getUniformLocation(program, name, gl);
    
    //如果location不存在(为null),则抛出异常;否则获得location
    let unsafeGetUniformLocation = (program, name, gl) =>
      getUniformLocation(program, name, gl) |> Js.Null.getExn;
    
    let bindBuffer = (bufferTarget, buffer, gl) =>
      WebGL1.bindBuffer(bufferTarget, buffer, gl);
    
    let vertexAttribPointer =
        (~gl, ~size, ~location, ~type_=WebGL1.getFloat(gl), ()) =>
      WebGL1.vertexAttribPointer(location, size, type_, false, 0, 0, gl);
    
    let enableVertexAttribArray = (location, gl) =>
      WebGL1.enableVertexAttribArray(location, gl);
    
    let getArrayBuffer = gl => gl |> WebGL1.getArrayBuffer;
    
    let getElementArrayBuffer = gl => gl |> WebGL1.getElementArrayBuffer;
    
    let uniform3f = (location, x, y, z, gl) =>
      WebGL1.uniform3f(location, x, y, z, gl);
    
    let uniformMatrix4fv = (~gl, ~location, ~value, ~transpose=false, ()) =>
      WebGL1.uniformMatrix4fv(location, transpose, value, gl);
    
    let drawElements =
        (
          ~gl,
          ~count,
          ~mode=WebGL1.getTriangles(gl),
          ~type_=WebGL1.getUnsignedShort(gl),
          ~offset=0,
          (),
        ) =>
      WebGL1.drawElements(mode, count, type_, offset, gl);
    

    实现用户代码并运行测试

    1、在项目根目录上执行webpack命令,更新wd.js文件

    yarn webpack
    

    2、实现index.html相关代码

    index.html代码为:

      <script>
        ...
        wd.Director.start();
      </script>
    

    3、运行测试

    运行index.html页面,显示三个三角形,如下图所示:
    此处输入图片的描述

    更新后的领域视图

    通过本文的实现后, 加入了下面的领域模型:

    • “容器”限界上下文
      • 加入的领域服务
        Array、Option
      • 加入的值对象
        Result、ImmutableHashMap、MutableHashMap、ImmutableSparseMap、MutableSparseMap

    总结

    恭喜你,经过长途跋涉,终于实现了从最小3D程序中提炼引擎!

    本文成果

    我们通过本文的实现,获得了下面的成果:
    1、实现了用户代码index.html
    2、提炼了引擎
    3、更新了领域视图

    本文不足之处

    1、需要进行优化:
    1)初始化Shader时,使用getProgramParameter、getProgramParameter来验证的时间开销大
    2)渲染三角形时,只需要传递一次相机数据

    下文概要

    在下文中,我们会解决本文的不足之处,对引擎进行优化。

    本文完整代码地址

    Book-Extract-Engine Github Repo

  • 相关阅读:
    SDOI2015 寻宝游戏
    SDOI2015 排序
    CF 500G
    CF 506E
    CEOI2014 wall Spoiler
    java 反射
    安卓资源网站收集
    JNI学习2:android 调用C语言方法与C语言调用android方法
    自定义视图收藏
    Android开源项目第一篇——个性化控件(View)篇
  • 原文地址:https://www.cnblogs.com/chaogex/p/12418292.html
Copyright © 2020-2023  润新知