展示:

# 框架设计

在制作之前,首先阅读了WebGL Programming Guide,对WebGL进行了简单的学习,读书笔记:https://1keven1.github.io/2021/11/25/%E3%80%90%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%E3%80%91WebGLProgramingGuide/

流程设计

根据WebGL的学习,借鉴了Unity的表现形式后,进行了如下图的流程设计:

流程

采用更简单的前向渲染,其中标红的为以后再实现的功能。简单来说,就是首先搭建场景,然后加载Obj模型以及Shader文件并解码编译,一切都完成后开始渲染循环。

在渲染循环中,先绘制灯光的shadowmap,然后再逐个灯光,逐个物体绘制。最后进行后处理操作。

为了提升性能,将只用计算一次的矩阵提到循环初期,比如相机和光照的VP矩阵,模型的M矩阵等。

类设计

类设计

如上图所示,简单设计了一些类,学习UE引擎,有一个Actor基类,从其上派生出Camera、Light和Mesh

Mesh包含Material和Model,Model即为OBJ模型,Material包含几个Shader以及一些参数

下面就根据设计图进行初步实现

类的初步实现

先获取canvas以及gl

1
2
3
4
5
6
7
8
9
10
11
12
const canvas = document.querySelector('canvas');
if (!canvas)
{
console.console.error('获取Canvas失败');
}
const gl = getWebGLContext(canvas);
if (!gl)
{
console.error("Get WebGL Render Context Failed");
}
let width = canvas.width;
let height = canvas.height;

Model类

传入文件以及模型缩放,然后实现load函数,加载解码Obj文件,并在加载完成后调用回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Model{
constructor(objFile, scale=1){
this.objFile = objFile;
this.objSource = null;
this.scale = scale;

this.vertices = new Array(0);
this.texCoords = new Array(0);
this.normals = new Array(0);
this.faces = new Array(0);

this.vertexBuffer = null;
this.texcoordBuffer = null;
this.normalBuffer = null;
this.indexBuffer = null;
this.indexNum = -1;

this.bLoaded = false;
}

load(){
this.bLoaded = true;
this.loadOBJFile()
}

loadOver() {}

loadOBJFile(){
let request = new XMLHttpRequest();
request.onreadystatechange = () => {
if (request.readyState === 4 && request.status !== 404){
// 读取Obj文件
this.objSource = request.responseText;
// 分析为信息
if (!this.parseOBJ()){
console.log("分析OBJ文件失败: " + this.objFile);
}
// 解码数据为WebGL可用的Buffer
this.decodeBufferArrays();
this.loadOver()
}
}
request.open('GET', this.objFile, true);
request.send();
}
parseOBJ(){} // 解码OBJ文件
decodeBufferArrays(){} // 储存为WebGL认识的Buffer形式
}

具体的解码以及储存为Buffer函数比较复杂,请看这里:https://github.com/1keven1/WebGLRenderer/blob/main/Model.js

Shader类

传入顶点和片元着色器,实现load函数,读取编译shader,然后调用回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class Shader{
constructor(vShaderFile, fShaderFile)
{
this.vShaderFile = vShaderFile;
this.fShaderFile = fShaderFile;
this.vShaderSource = null;
this.fShaderSource = null;
this.program = null;


this.a_Position = -1;
this.a_TexCoord = -1;
this.a_Normal = -1;
this.u_Matrix_MVP = null;
this.u_Matrix_M_I = null;
this.u_LightPos = null;
this.u_LightColor = null;
this.u_Matrix_Light = null;
}

load(){
this.readShaderFile()
}

loadOver() { }

readShaderFile(){
let vRequest = new XMLHttpRequest();
let fRequest = new XMLHttpRequest();

vRequest.onreadystatechange = () => {
if (vRequest.readyState === 4 && vRequest.status !== 404){
this.vShaderSource = vRequest.responseText;
if (this.vShaderSource && this.fShaderSource) this.compileShader();
}
}
fRequest.onreadystatechange = () => {
if (fRequest.readyState === 4 && fRequest.status !== 404){
this.fShaderSource = fRequest.responseText;
if (this.vShaderSource && this.fShaderSource) this.compileShader();
}
}

vRequest.open('GET', this.vShaderFile, true);
vRequest.send();
fRequest.open('GET', this.fShaderFile, true);
fRequest.send();
}

compileShader(){
this.program = createProgram(gl, this.vShaderSource, this.fShaderSource);
if (!this.program){
console.error(this.vShaderFile + ':编译失败');
}
this.loadOver();
}
}

具体createProgram函数的实现请看:https://github.com/1keven1/WebGLRenderer/blob/main/Lib/cuon-utils.js

Material类

首先新建材质类型的枚举值:

1
2
3
4
5
6
7
let MATERIAL_TYPE = {
OPAQUE: Symbol(0),
MASKED: Symbol(1),
TRANSLUCENT: Symbol(2),
ADDITIVE: Symbol(3)
}
Object.freeze(MATERIAL_TYPE);

在这里只考虑一个光照的情况,所以材质只包含一个base shader,后期会添加Add和Shadow Caster。同样也是实现load函数和回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class Material{
constructor(baseShader, materialType = MATERIAL_TYPE.OPAQUE, queueOffset = 0){
this.baseShader = baseShader;

this.setMaterialType(materialType, queueOffset);

this.bLoaded = false;
}

setMaterialType(materialType, offset){
switch (materialType){
case MATERIAL_TYPE.OPAQUE:
this.bDepthTest = true;
this.bCull = true;
this.bBlend = false;
this.srcFactor = gl.SRC_ALPHA;
this.desFactor = gl.ONE_MINUS_SRC_ALPHA;
this.renderQueue = 1000 + offset;
break;
case MATERIAL_TYPE.MASKED:
this.bDepthTest = true;
this.bCull = true;
this.bBlend = false;
this.srcFactor = gl.SRC_ALPHA;
this.desFactor = gl.ONE_MINUS_SRC_ALPHA;
this.renderQueue = 1500 + offset;
break;
case MATERIAL_TYPE.TRANSLUCENT:
this.bDepthTest = false;
this.bCull = true;
this.bBlend = true;
this.srcFactor = gl.SRC_ALPHA;
this.desFactor = gl.ONE_MINUS_SRC_ALPHA;
this.renderQueue = 2000 + offset;
break;
case MATERIAL_TYPE.ADDITIVE:
this.bDepthTest = false;
this.bCull = true;
this.bBlend = true;
this.srcFactor = gl.ONE;
this.desFactor = gl.ONE;
this.renderQueue = 2000 + offset;
break;
default:
this.bDepthTest = true;
this.bCull = true;
this.bBlend = false;
this.srcFactor = gl.SRC_ALPHA;
this.desFactor = gl.ONE_MINUS_SRC_ALPHA;
this.renderQueue = 1000 + offset;
console.error('材质类型不存在:' + materialType);
break;
}
}

setBlendFactor(srcFactor, desFactor){
this.srcFactor = srcFactor;
this.desFactor = desFactor;
}

load(){
this.bLoaded = true;
this.loadShader();
}

loadOver() { }

loadShader(){
// 加载Base Shader
this.baseShader.loadOver = this.shaderLoadOver.bind(this);
this.baseShader.load();
}

shaderLoadOver(){
// 初始化必要Shader变量
this.baseShader.a_Position = gl.getAttribLocation(this.baseShader.program, 'a_Position');
this.baseShader.a_TexCoord = gl.getAttribLocation(this.baseShader.program, 'a_TexCoord');
this.baseShader.a_Normal = gl.getAttribLocation(this.baseShader.program, 'a_Normal');

this.baseShader.u_Matrix_MVP = gl.getUniformLocation(this.baseShader.program, 'u_Matrix_MVP');
this.baseShader.u_Matrix_M_I = gl.getUniformLocation(this.baseShader.program, 'u_Matrix_M_I');
this.baseShader.u_LightPos = gl.getUniformLocation(this.baseShader.program, 'u_LightPos');
this.baseShader.u_LightColor = gl.getUniformLocation(this.baseShader.program, 'u_LightColor');
this.baseShader.u_Matrix_Light = gl.getUniformLocation(this.baseShader.program, 'u_Matrix_Light');

this.loadOver();
}
}

然后实现了一些工具函数,比如设置材质参数之类的,具体可以看:https://github.com/1keven1/WebGLRenderer/blob/main/Material.js

Actor基类以及子类

Transform类以及函数

1
2
3
4
5
6
7
8
9
10
function Transform(location = new Vector3([0, 0, 0]), rotation = new Vector3([0, 0, 0]), scale = new Vector3([1, 1, 1])){
this.location = location;
this.rotation = rotation;
this.scale = scale;
}

Transform.prototype.copy = function (){
let newTrans = new Transform(this.location.copy(), this.rotation.copy(), this.scale.copy());
return newTrans;
}

Actor类,实现一些对于Transform的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Actor{
constructor(transform = new Transform()){
this.transform = transform;
}

getLocation(){
return this.transform.location.copy();
}

getRotation(){
return this.transform.rotation.copy();
}

getScale(){
return this.transform.scale.copy();
}

setLocation(location){
this.transform.location = location;
}

setRotation(rotation){
this.transform.rotation = rotation;
}

setLocationAndRotation(location, rotation){
this.transform.location = location;
this.transform.rotation = rotation;
}

setScale(scale)
{
this.transform.scale = scale;
}

addLocationOffset(offset){
this.transform.location.add(offset);
}

addRotationOffset(offset){
this.transform.rotation.add(offset);
}
}

Mesh类,包含一个模型和一个材质,实现了构建M矩阵的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Mesh extends Actor{
constructor(transform, model, material, bCastShadow = true){
super(transform);
this.model = model;
this.material = material;
this.bCastShadow = bCastShadow;
this.mMatrix = new Matrix4();
this.mIMatrix = new Matrix4();
}

copy(){
let newMesh = new Mesh(this.transform.copy(), this.model, this.material, this.bCastShadow);

return newMesh;
}

bulidMMatrix(){
this.mMatrix.setTranslate(this.transform.location.x(), this.transform.location.y(), this.transform.location.z()).
rotate(this.transform.rotation.x(), 1, 0, 0).rotate(this.transform.rotation.y(), 0, 1, 0).rotate(this.transform.rotation.z(), 0, 0, 1).
scale(this.transform.scale.x(), this.transform.scale.y(), this.transform.scale.z());
this.mIMatrix.setInverseOf(this.mMatrix);
}
}

Light类,首先还是实现灯光类型的枚举值

1
2
3
4
5
6
let LIGHT_TYPE = {
DIRECTIONAL: Symbol(0),
POINT: Symbol(1),
SPOT: Symbol(2)
}
Object.freeze(LIGHT_TYPE);

灯光类包含灯光颜色,强度,类型,根据类型实现了VP矩阵的构建,方便后期制作shadow map使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Light extends Actor{
constructor(transform, lightColor, intensity, lightType = LIGHT_TYPE.DIRECTIONAL){
super(transform);
this.lightColor = lightColor;
this.intensity = intensity;
this.lightType = lightType;
this.vpMatrix = new Matrix4();
this.shadowMap = null;

if (lightType === LIGHT_TYPE.DIRECTIONAL) this.w = 0;
else this.w = 1;
}

bulidVPMatrix(){
switch (this.lightType){
case LIGHT_TYPE.DIRECTIONAL:
let rotateMatrix = new Matrix4().setRotate(this.transform.rotation.x(), 1, 0, 0).rotate(this.transform.rotation.y(), 0, 1, 0).rotate(this.transform.rotation.z(), 0, 0, 1);
let lookVec = rotateMatrix.multiplyVector3(new Vector3([0, 0, -1]));
let upVec = rotateMatrix.multiplyVector3(new Vector3([0, 1, 0]));

this.vpMatrix.setOrtho(-7, 7, -7, 7, 1, 100).
lookAt(this.transform.location.x(), this.transform.location.y(), this.transform.location.z(),
lookVec.x() + this.transform.location.x(), lookVec.y() + this.transform.location.y(), lookVec.z() + this.transform.location.z(),
upVec.x(), upVec.y(), upVec.z());
break;
case LIGHT_TYPE.POINT:
break;
case LIGHT_TYPE.SPOT:
break;
default:
break;
}
}
}

Camera类,有FOV和远近剪裁平面组成,实现了计算相机VP矩阵的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Camera extends Actor{
constructor(transform, FOV = 60, nearClip = 0.1, farClip = 100){
super(transform);
this.FOV = FOV;
this.nearClip = nearClip;
this.farClip = farClip;

this.vpMatrix = new Matrix4();
}

bulidVPMatrix(){
let rotateMatrix = new Matrix4().setRotate(this.transform.rotation.x(), 1, 0, 0).rotate(this.transform.rotation.y(), 0, 1, 0).rotate(this.transform.rotation.z(), 0, 0, 1);
let lookVec = rotateMatrix.multiplyVector3(new Vector3([0, 0, -1]));
let upVec = rotateMatrix.multiplyVector3(new Vector3([0, 1, 0]));

this.vpMatrix.setPerspective(this.FOV, width / height, this.nearClip, this.farClip).
lookAt(
this.transform.location.x(), this.transform.location.y(), this.transform.location.z(),
lookVec.x() + this.transform.location.x(), lookVec.y() + this.transform.location.y(), lookVec.z() + this.transform.location.z(),
upVec.x(), upVec.y(), upVec.z());
}
}

Texture类

输入文件路径和贴图单元序号即可,同样也需要加载和加载完成的回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Texture{
constructor(texFile, texUnit, texType = gl.TEXTURE_2D)
{
this.texFile = texFile;
this.texUnit = texUnit;
this.texType = texType;

this.texture = null;
}

load(){
// 创建Texture
let texture = gl.createTexture();
if (!texture){
console.error(this.texFile + ':创建Texture失败');
}

// 创建Image
let image = new Image();

image.onload = () => {
// Write the image data to texture object
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // Flip the image Y coordinate

gl.activeTexture(this.texUnit);
gl.bindTexture(this.texType, texture);
gl.texParameteri(this.texType, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texImage2D(this.texType, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

this.loadOver();
};
image.src = this.texFile;

this.texture = texture;
}

loadOver() { }
}

Scene类

Scene包含着整个场景中所有资源和要渲染的Mesh,所以要实现加载所有资源的方法。如果加载后发现MeshList中某个Mesh的资源没有加载,则剔除这个Mesh并报警告信息,以防止渲染错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class Scene{
constructor(modelList, materialList, textureList, meshList, lightList, camera){
this.modelList = modelList;
this.materialList = materialList;
this.textureList = textureList;
this.meshList = meshList;
this.lightList = lightList;
this.camera = camera;

this.modelLoadedNum = 0;
this.materialLoadedNum = 0;
this.textureLodedNum = 0;
}

load(){
console.log('loading scene');
// 加载Model
this.modelList.forEach((model, index, arr) =>{
model.loadOver = this.modelLoadOver.bind(this);
model.load()
})
// 加载Material
this.materialList.forEach((material, index, arr) =>{
material.loadOver = this.materialLoadOver.bind(this);
material.load();
})
// 加载贴图
this.textureList.forEach((texture, index, arr) =>{
texture.loadOver = this.textureLoadOver.bind(this);
texture.load();
})
}

loadOver() { }

modelLoadOver(){
console.log('mesh load over');
this.modelLoadedNum++;
this.checkIfLoadOver();
}

materialLoadOver(){
console.log('mesh load over');
this.materialLoadedNum++;
this.checkIfLoadOver();
}

textureLoadOver(){
console.log('texture load over');
this.textureLodedNum++;
this.checkIfLoadOver();
}

checkIfLoadOver(){
if (this.modelLoadedNum === this.modelList.length &&
this.materialLoadedNum === this.materialList.length &&
this.textureLodedNum === this.textureList.length){
this.meshList.forEach((mesh, index, arr) => {
if (!mesh.model.bLoaded || !mesh.material.bLoaded){
if (!mesh.model.bLoaded) console.warn(mesh.model.objFile + ':没有加载完整');
if(!mesh.material.bLoaded) console.warn(mesh.material.baseShader.vShaderFile + ':没有加载');
this.meshList.splice(index, 1);
}
})
this.loadOver();
}
}
}

然后实现计算矩阵的方法

1
2
3
4
5
6
7
8
9
10
11
12
calculateMatrices(){
// 计算Mesh的M矩阵
this.meshList.forEach((mesh, index, arr) => {
mesh.bulidMMatrix();
})
// 计算灯光VP矩阵
this.lightList.forEach((light, index, arr) => {
light.bulidVPMatrix();
})
// 计算相机VP矩阵
this.camera.bulidVPMatrix();
}

最后也是最重要的,就是渲染方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
render(){
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

this.lightList.forEach((light, lightIndex, arr) => {
this.meshList.forEach((mesh, meshIndex, arr) => {
this.drawMesh(mesh, light, lightIndex);
})
})
}

drawMesh(mesh, light, lightIndex) {
// Base Shader进行渲染
if (lightIndex === 0) {
gl.useProgram(mesh.material.getBaseProgram());

if (mesh.material.bDepthTest) gl.enable(gl.DEPTH_TEST);
else gl.disable(gl.DIPTH_TEST);

// 绑定Vertex Buffer
if (mesh.material.baseShader.a_Position >= 0) this.bindAttributeToBuffer(mesh.material.baseShader.a_Position, mesh.model.vertexBuffer);
if (mesh.material.baseShader.a_TexCoord >= 0) this.bindAttributeToBuffer(mesh.material.baseShader.a_TexCoord, mesh.model.texCoordBuffer);
if (mesh.material.baseShader.a_Normal >= 0) this.bindAttributeToBuffer(mesh.material.baseShader.a_Normal, mesh.model.normalBuffer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, mesh.model.indexBuffer);

// 传入默认变量
if (mesh.material.baseShader.u_LightPos) gl.uniform4f(mesh.material.baseShader.u_LightPos, light.getLocation().x(), light.getLocation().y(), light.getLocation().z(), light.w);
if (mesh.material.baseShader.u_LightColor) gl.uniform4f(mesh.material.baseShader.u_LightColor, light.lightColor.x(), light.lightColor.y(), light.lightColor.z(), 1);

let mvpMatrix = new Matrix4().set(camera.vpMatrix).multiply(mesh.mMatrix);
gl.uniformMatrix4fv(mesh.material.baseShader.u_Matrix_MVP, false, mvpMatrix.elements);
gl.uniformMatrix4fv(mesh.material.baseShader.u_Matrix_M_I, false, mesh.mIMatrix.elements);

let mvpMatrixLight = new Matrix4().set(light.vpMatrix).multiply(mesh.mMatrix);
if (mesh.material.baseShader.u_Matrix_Light) gl.uniformMatrix4fv(mesh.material.baseShader.u_Matrix_Light, false, mvpMatrixLight.elements);

// 绘制
gl.drawElements(gl.TRIANGLES, mesh.model.indexNum, mesh.model.indexBuffer.dataType, 0);
}
else { }
}

bindAttributeToBuffer(a_attribute, buffer){
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(a_attribute, buffer.dataNum, buffer.dataType, false, 0, 0);
gl.enableVertexAttribArray(a_attribute);
}

首先是对所有灯光,然后对所有mesh,进行渲染。

drawMesh相关较为复杂,可以看这里:https://1keven1.github.io/2021/11/25/%E3%80%90%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%E3%80%91WebGLProgramingGuide/#Hello-Cube

渲染器类

通过Scene构造,在外部调用Start后开始加载资源,加载后开始渲染。其中的customBeginPlay和customTick可以再外部重写以实现一些简单的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class WebGLRenderer{
constructor(scene, editableMat = null)
{
this.scene = scene;

gl.clearColor(0, 0, 0, 1);
}

start(){
// 加载场景
this.scene.loadOver = this.startRenderLoop.bind(this);
this.scene.load();
}

customBeginPlay(){
console.warn('未重写Custom Begin Play');
}

startRenderLoop(){
this.customBeginPlay();

console.log('开始渲染循环');
let lastTime = 0;
let deltaSecond = 0;
let renderLoop = (timeStamp) => {
deltaSecond = (timeStamp - lastTime) * 0.01;
lastTime = timeStamp;

this.customTick(deltaSecond);
this.scene.calculateMatrices();
this.scene.render();
requestAnimationFrame(renderLoop);
}
renderLoop(0);
}

customTick(deltaSecond){
console.warn('未重写Custom Tick');
}
}

到这里,框架基本完成了

进行渲染测试

创建网页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebGL渲染器</title>
</head>
<body>
<canvas width="700" height="700">请使用支持Canvas的浏览器</canvas>
<textarea rows="50" cols="100"></textarea>

<script src="./Lib/webgl-utils.js"></script>
<script src="./Lib/webgl-debug.js"></script>
<script src="./Lib/cuon-utils.js"></script>
<script src="./Lib/cuon-matrix.js"></script>

<script src="./Actor.js"></script>
<script src="./Texture.js"></script>
<script src="./Shader.js"></script>
<script src="./Material.js"></script>
<script src="./Model.js"></script>
<script src="./Scene.js"></script>
<script src="./WebGLRenderer.js"></script>
<script src="./FunctionLibrary.js"></script>

<script src="./Main.js"></script>
</body>
</html>

构建真正运行的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
let camera = new Camera(new Transform(new Vector3([0.0, 2.0, 6.0]), new Vector3([-20, 0, 0])), 60, 0.1, 100);
let light = new Light(new Transform(new Vector3([10.0, 10.0, 10.0]), new Vector3([0, 0, 0])), new Vector3([1.0, 1.0, 1.0]), 10);

let diffuseShader = new Shader('./Resource/DefaultShader/Diffuse.vert', './Resource/DefaultShader/Diffuse.frag');
let diffuseMaterial = new Material(diffuseShader, MATERIAL_TYPE.OPAQUE, 0);
let texShader = new Shader('./Resource/DefaultShader/DiffuseTex.vert', './Resource/DefaultShader/DiffuseTex.frag');
let texMaterial = new Material(texShader);

let cube = new Model('./Resource/Cube.obj');
let plane = new Model('./Resource/Plane.obj');
let sphere = new Model('./Resource/Sphere.obj')

let meshCube = new Mesh(new Transform(), cube, texMaterial);
let floor = new Mesh(new Transform(new Vector3([0, -1, 0])), plane, diffuseMaterial);

let texture = new Texture('./Resource/test.jpg', gl.TEXTURE0, gl.TEXTURE_2D);

let scene = new Scene(
[cube, sphere, plane],
[diffuseMaterial, texMaterial],
[texture],
[meshCube, floor],
[light],
camera
);

let renderer = new WebGLRenderer(scene);


renderer.customBeginPlay = () =>
{
diffuseMaterial.setUniformVector3f('u_AmbientColor', 0.2, 0.2, 0.2);
texMaterial.setUniformVector3f('u_AmbientColor', 0.2, 0.2, 0.2);
texMaterial.setTexture('u_Tex', 0);

document.onmousedown = () =>
{
meshCube.material = diffuseMaterial;
meshCube.model = sphere;
floor.material = texMaterial;
}
document.onmouseup = () =>
{
meshCube.material = texMaterial;
meshCube.model = cube;
floor.material = diffuseMaterial;
}
}

renderer.customTick = (deltaSecond) =>
{
meshCube.addRotationOffset(new Vector3([0, 1.5, 0]).multiplyf(deltaSecond));
}


renderer.start();

上面都在创建资源,然后输入到Scene中,最后传入到WebGLRenderer类里;再重写类的两个函数,在BeginPlay中设置了两个材质的参数和贴图,写了鼠标函数;在Tick中让Cube缓慢旋转,最后Start。

效果

资源

网址:https://1keven1.github.io/BlogSrc/WebGLRenderer/SimpleDemo.html