序
这一系列博客主要是为了记录自己对GAMES202 课程的作业完成情况.
本博客主要是因为自己个人对于JavaScript
以及HTML
了解不多,
需要专门解读作业0的框架.
目前还没完全写完, 但是我觉得足够了,
有些没看的后面等我看了再及时更新.
解读参考:
WebGL API
Three.js
gl-matrix
WebGLFundamentals
注: : 1. 强烈建议阅读此代码框架是对照着官网实例 来看,
或者自己先通看一遍. 2. 在运行过程中老是会存在模型加载不出来的方式, 这里有一个解决方案
index.html
参考该链接 所知,
在html
中我们需要给出canvas
元素
因此我们可以看到在这个项目的index.html
中给出了如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <html > <head > <style > ... #glcanvas { top : 0 ; width : 100% ; height : 100% ; } </style > </head > <body > <canvas id ="glcanvas" > </canvas > </body > </html >
中间的各种script
主要看engine.js
,
其中包含有main
函数:
engine.js
在这个js
文件中包含了main()
函数.
在main
中首先获取了canvas
这里为了方便, 先忽略掉整个有关gui
的代码
注: 这里很多都有相应的文档说明, 因此我这里尽量列出文档.
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 const canvas = document .querySelector ('#glcanvas' );canvas.width = window .screen .width ; canvas.height = window .screen .height ; const gl = canvas.getContext ('webgl' );if (!gl) { alert ('Unable to initialize WebGL. Your browser or machine may not support it.' ); return ; } const camera = new THREE .PerspectiveCamera (75 , gl.canvas .clientWidth / gl.canvas .clientHeight , 0.1 , 1000 );const cameraControls = new THREE .OrbitControls (camera, canvas);cameraControls.enableZoom = true ; function setSize (width, height ) { camera.aspect = width / height; camera.updateProjectionMatrix (); } setSize (canvas.clientWidth , canvas.clientHeight );window .addEventListener ('resize' , () => setSize (canvas.clientWidth , canvas.clientHeight ));camera.position .set (cameraPosition[0 ], cameraPosition[1 ], cameraPosition[2 ]); cameraControls.target .set (0 , 1 , 0 );
接下来, 有两个句代码分别创建了pointLight
和
WebGLRender
,
这里最重要的就是进入了WebGLRender
的render()
函数.
值得一提的是: 这里的addLight()
方法:
addLight(light) { this.lights.push({ entity: light, meshRender: new MeshRender(this.gl, light.mesh, light.mat) }); }
其实是为Light
创建了一个meshRender
,
meshRender
后面会说
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const pointLight = new PointLight (250 , [1 , 1 , 1 ]);const renderer = new WebGLRenderer (gl, camera);renderer.addLight (pointLight); loadOBJ (renderer, 'assets/mary/' , 'Marry' ); function mainLoop (now ) { cameraControls.update (); renderer.render (guiParams); requestAnimationFrame (mainLoop); } requestAnimationFrame (mainLoop);
WebGLRenderer.js(渲染器)
这个文件按照参考链接中的教程2的后半部分即可
这些操作很多都是固定操作,
两层循环中: 1.
外层循环为对light
也即光源进行Render
; 2.
然后进入内层,对每一个mesh进行Render
.
draw()函数
这里对每个mesh调用了draw()
函数, 在本框架中,
所有mesh
包括light
其所使用的Render
均为MeshRender.js
中定义的.
对于光源:
默认在addLight
时就是用的MeshRender
对于Mesh
,
则在loadOBJ.js
的loadOBJ()
添加(在engine.js
)中被调用(关于loadOBJ
的解读见附录).
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 (guiParams ) { const gl = this .gl ; gl.clearColor (0.0 , 0.0 , 0.0 , 1.0 ); gl.clearDepth (1.0 ); gl.enable (gl.DEPTH_TEST ); gl.depthFunc (gl.LEQUAL ); gl.clear (gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT ); const timer = Date .now () * 0.00025 ; let lightPos = [ Math .sin (timer * 6 ) * 100 , Math .cos (timer * 4 ) * 150 , Math .cos (timer * 2 ) * 100 ]; if (this .lights .length != 0 ) { for (let l = 0 ; l < this .lights .length ; l++) { let trans = new TRSTransform (lightPos); this .lights [l].meshRender .draw (this .camera , trans); for (let i = 0 ; i < this .meshes .length ; i++) { const mesh = this .meshes [i]; const modelTranslation = [guiParams.modelTransX , guiParams.modelTransY , guiParams.modelTransZ ]; const modelScale = [guiParams.modelScaleX , guiParams.modelScaleY , guiParams.modelScaleZ ]; let meshTrans = new TRSTransform (modelTranslation, modelScale); this .gl .useProgram (mesh.shader .program .glShaderProgram ); this .gl .uniform3fv (mesh.shader .program .uniforms .uLightPos , lightPos); mesh.draw (this .camera , meshTrans); } } } else { for (let i = 0 ; i < this .meshes .length ; i++) { const mesh = this .meshes [i]; let trans = new TRSTransform (); mesh.draw (this .camera , trans); } } }
MeshRender.js
同理, 请参考教程2
注: :
在这个类的构造函数中调用了this.material.compile(gl)
,
实现了两个shader
的编译. 因此,
在draw()
函数结束就算是完成了整个框架.
这个文件中包含许多的if
语句, 其是一一对应的,
主要是为了进行Buffer
的绑定.
这里面对于整个框架理解比较关键的是uniform
和attribute
的绑定.
这个步骤就是为了向vertex shader
和Fragment shader
传递JS
的变量.
其中除了Material.js
中定义的四个uniform
是通用的(也即我们知道类型),
其他的都需要自己设置类型.
所以才会有后面的循环(这里可以看出常用的6个类型)
这里摘抄三个变量的绑定方式:
Vertex shaders need data. They can get that data in 3 ways.
Attributes (data pulled from buffers)
Uniforms (values that stay the same for all vertices of a single
draw call)
Textures (data from pixels/texels)
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 89 90 91 draw (camera, transform ) { const gl = this .gl ; let modelViewMatrix = mat4.create (); let projectionMatrix = mat4.create (); camera.updateMatrixWorld (); mat4.invert (modelViewMatrix, camera.matrixWorld .elements ); mat4.translate (modelViewMatrix, modelViewMatrix, transform.translate ); mat4.scale (modelViewMatrix, modelViewMatrix, transform.scale ); mat4.copy (projectionMatrix, camera.projectionMatrix .elements ); if (this .mesh .hasVertices ) { const numComponents = 3 ; const type = gl.FLOAT ; const normalize = false ; const stride = 0 ; const offset = 0 ; gl.bindBuffer (gl.ARRAY_BUFFER , this .#vertexBuffer); gl.vertexAttribPointer ( this .shader .program .attribs [this .mesh .verticesName ], numComponents, type, normalize, stride, offset); gl.enableVertexAttribArray ( this .shader .program .attribs [this .mesh .verticesName ]); } gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER , this .#indicesBuffer); gl.useProgram (this .shader .program .glShaderProgram ); gl.uniformMatrix4fv ( this .shader .program .uniforms .uProjectionMatrix , false , projectionMatrix); gl.uniformMatrix4fv ( this .shader .program .uniforms .uModelViewMatrix , false , modelViewMatrix); gl.uniform3fv ( this .shader .program .uniforms .uCameraPos , [camera.position .x , camera.position .y , camera.position .z ]); for (let k in this .material .uniforms ) { if (this .material .uniforms [k].type == 'matrix4fv' ) { gl.uniformMatrix4fv ( this .shader .program .uniforms [k], false , this .material .uniforms [k].value ); } else if (this .material .uniforms [k].type == '3fv' ) { gl.uniform3fv ( this .shader .program .uniforms [k], this .material .uniforms [k].value ); } else if (this .material .uniforms [k].type == '1f' ) { gl.uniform1f ( this .shader .program .uniforms [k], this .material .uniforms [k].value ); } else if (this .material .uniforms [k].type == '1i' ) { gl.uniform1i ( this .shader .program .uniforms [k], this .material .uniforms [k].value ); } else if (this .material .uniforms [k].type == 'texture' ) { gl.activeTexture (gl.TEXTURE0 ); gl.bindTexture (gl.TEXTURE_2D , this .material .uniforms [k].value .texture ); gl.uniform1i (this .shader .program .uniforms [k], 0 ); } } { const vertexCount = this .mesh .count ; const type = gl.UNSIGNED_SHORT ; const offset = 0 ; gl.drawElements (gl.TRIANGLES , vertexCount, type, offset); } }
Material
这个类是个材料基类, 集中包含了我们要传给WebGl
的参数:
uniforms
其中, 这四个uniforms
是必须基本上都要有的.
1 this .#flatten_uniforms = ['uModelViewMatrix' , 'uProjectionMatrix' , 'uCameraPos' , 'uLightPos' ];
至于其他的参数, 则需要我们自己定义派生类,
来向这里添加.(添加到uniforms
中),
一定要注意自己添加的请标明类型
注: 其实可以参考作业0让我们写的派生类
至于编译这一步, 是固定步骤, 我上面给的参考教程中也有示例.
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 class Material { #flatten_uniforms; #flatten_attribs; #vsSrc; #fsSrc; constructor (uniforms, attribs, vsSrc, fsSrc ) { this .uniforms = uniforms; this .attribs = attribs; this .#vsSrc = vsSrc; this .#fsSrc = fsSrc; this .#flatten_uniforms = ['uModelViewMatrix' , 'uProjectionMatrix' , 'uCameraPos' , 'uLightPos' ]; for (let k in uniforms) { this .#flatten_uniforms.push (k); } this .#flatten_attribs = attribs; } setMeshAttribs (extraAttribs ) { for (let i = 0 ; i < extraAttribs.length ; i++) { this .#flatten_attribs.push (extraAttribs[i]); } } compile (gl ) { return new Shader (gl, this .#vsSrc, this .#fsSrc, { uniforms : this .#flatten_uniforms, attribs : this .#flatten_attribs }); } }
作业0---Shader
phongShader
作业0的任务是写出phongShader
, 有了之前的基础,
明确好uniform``attribute
以及varying
的区别即可明白所有变量的作用.
至于主函数:
vertexShader
负责完成modelView
以及投影变换;
fragmentShader
则主要是套公式.
glsl
所提供的函数可以在glsl
docbook 上来找.
PhongMaterial
继承自Material
,
主要是完成编译Shader
并拿到你所创建的uniform
的位置.
注:
uniform
的绑定和attribute
的buffer的创建是在MeshRender.js
中完成的
attribute
是后添加的,
再创建材料时不用添加(是先和buffer
绑定后再添加的).
loadOBJ
创建所创建的PhongMaterial
,
并创建MeshRender
附录(其他文件):
Shader.js
这个文件完全按照实例教程中的Add
2D content to a WebGl Context 来即可
Light
1 2 3 4 5 6 7 8 9 10 11 12 13 class EmissiveMaterial extends Material { constructor (lightIntensity, lightColor ) { super ({ 'uLigIntensity' : { type : '1f' , value : lightIntensity }, 'uLightColor' : { type : '3fv' , value : lightColor } }, [], LightCubeVertexShader , LightCubeFragmentShader ); this .intensity = lightIntensity; this .color = lightColor; } }
LoadOBJ
这里用到的很多类都是Three.js
中的库,
详情可以参考代码中给出的参考链接.
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 function loadOBJ (renderer, path, name ) { const manager = new THREE .LoadingManager (); manager.onProgress = function (item, loaded, total ) { console .log (item, loaded, total); }; function onProgress (xhr ) { if (xhr.lengthComputable ) { const percentComplete = xhr.loaded / xhr.total * 100 ; console .log ('model ' + Math .round (percentComplete, 2 ) + '% downloaded' ); } } function onError ( ) { } new THREE .MTLLoader (manager) .setPath (path) .load (name + '.mtl' , function (materials ) { materials.preload (); new THREE .OBJLoader (manager) .setMaterials (materials) .setPath (path) .load (name + '.obj' , function (object ) { object.traverse (function (child ) { if (child.isMesh ) { let geo = child.geometry ; let mat; if (Array .isArray (child.material )) mat = child.material [0 ]; else mat = child.material ; var indices = Array .from ({ length : geo.attributes .position .count }, (v, k ) => k); let mesh = new Mesh ({ name : 'aVertexPosition' , array : geo.attributes .position .array }, { name : 'aNormalPosition' , array : geo.attributes .normal .array }, { name : 'aTextureCoord' , array : geo.attributes .uv .array }, indices); let colorMap = null ; if (mat.map != null ) colorMap = new Texture (renderer.gl , mat.map .image ); let myMaterial = new PhongMaterial (mat.color .toArray (), colorMap, mat.specular .toArray (), renderer.lights [0 ].entity .mat .intensity ); let meshRender = new MeshRender (renderer.gl , mesh, myMaterial); renderer.addMesh (meshRender); } }); }, onProgress, onError); }); }
PointLight
PointLight
构造函数接收两个参数,
一个是光照强度(lightIntensity
),
另一个是光的颜色(lightColor
):
默认 mesh
是cube()
:
就是大小位于[-1,1]的小立方体
Light的材质是 EmissiveMaterial: 接收两个参数
1 2 3 4 5 6 class PointLight { constructor (lightIntensity, lightColor ) { this .mesh = Mesh .cube (); this .mat = new EmissiveMaterial (lightIntensity, lightColor); } }