大家好,本文根据领域驱动设计的成果,实现了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)渲染三角形时,只需要传递一次相机数据
下文概要
在下文中,我们会解决本文的不足之处,对引擎进行优化。