1. 首页
  2. IT资讯

「WebGL基础」:第二部分

“u003Cdivu003Eu003Cpu003E本文基于这个系列第一部分中介绍的框架——u003Ca class=”pgc-link” data-content=”mp” href=”https:u002Fu002Fwww.toutiao.comu002Fi6712251340900270605u002F?group_id=6712251340900270605″ target=”_blank”u003E「WebGL基础」:第一部分u003Cu002Fau003E,另外还增加了一个模型导入器,和针对3D对象定制的类。 你会从中了解到动画和控制,内容很多,我们赶紧开始吧。u003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003E因为严重依赖于上一篇文章,所以,如果你还没读过,建议先读一下。u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003EWebGL在3D世界中操纵物体的方式是使用称为变换的数学公式。所以,在我们开始构建3D类之前,我将向你展示不同类型的一些变换,以前它们是如何实现的。变换u003Cu002Fpu003Eu003Cpu003E有三种基本变换可作用于3D对象。u003Cu002Fpu003Eu003Culu003Eu003Cliu003E移动u003Cu002Fliu003Eu003Cliu003E缩放u003Cu002Fliu003Eu003Cliu003E旋转u003Cu002Fliu003Eu003Cu002Fulu003Eu003Cpu003E这些函数中的每一个都可作用于X轴、Y轴或Z轴,因而组合得到9种基本的变换。它们通过不同的方式来影响3D对象的4×4变换矩阵。 为了在同一个对象中执行多个变换,而不产生重叠的问题,我们要将将每个变换乘到对象的矩阵中去,而不是逐一地直接应用到对象的矩阵上。 移动变换是最简单的,我们先从移动开始。u003Cu002Fpu003Eu003Ch1u003Eu003Cstrongu003E移动又称为平移 (Translation)。u003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Cpu003E移动一个3D对象是最简单的一种变换,因为在4×4矩阵中为它保留了特殊的位置。 我们可以不用涉及任何数学;只需要把X,Y和Z坐标放到矩阵中指定位置上,就可以了。如果你观察这个4×4矩阵,你会发现它们被放在最后一行上。 此外,你需要知道的是,正Z轴指向摄像机后面。因而,Z值为-100时,会导致对象深入屏幕100个单元。在我们的代码中会对此进行补偿。u003Cu002Fpu003Eu003Cpu003E为了执行多个变换,你不能简单地修改对象的真实矩阵;你必须将变换应用于一个新的空白矩阵,称为u003Cu002Fpu003Eu003Cpu003E单位矩阵u003Cu002Fpu003Eu003Cpu003E,然后将其与主矩阵相乘。u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002Faa1e21a6b28946edbc8ae8ec8c2f9099″ img_width=”600″ img_height=”220″ alt=”「WebGL基础」:第二部分” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E矩阵乘法理解起来会有些困难,但基本思想是第一个矩阵的竖直的列乘以第二个矩阵的水平行。 比如,新矩阵第一个数为第一个矩阵第一行乘以另一矩阵的第一列。新矩阵第二个数是第一个矩阵的第一行乘以第二个矩阵的第二列,依此类推。u003Cu002Fpu003Eu003Cpu003E下面的代码片断是JavaScript中实现的矩阵乘法。将其加到你的.js文件中,参见本系列教程第一部分。u003Cu002Fpu003Eu003Cpreu003Efunction MH(A, B) {u003Cbru003E var Sum = 0;u003Cbru003E for (var i = 0; i < A.length; i++) {u003Cbru003E Sum += A[i] * B[i];u003Cbru003E }u003Cbru003E return Sum;u003Cbru003E}u003Cbru003Efunction MultiplyMatrix(A, B) {u003Cbru003E var A1 = [A[0], A[1], A[2], A[3]];u003Cbru003E var A2 = [A[4], A[5], A[6], A[7]];u003Cbru003E var A3 = [A[8], A[9], A[10], A[11]];u003Cbru003E var A4 = [A[12], A[13], A[14], A[15]];u003Cbru003E var B1 = [B[0], B[4], B[8], B[12]];u003Cbru003E var B2 = [B[1], B[5], B[9], B[13]];u003Cbru003E var B3 = [B[2], B[6], B[10], B[14]];u003Cbru003E var B4 = [B[3], B[7], B[11], B[15]];u003Cbru003E return [u003Cbru003E MH(A1, B1), MH(A1, B2), MH(A1, B3), MH(A1, B4),u003Cbru003E MH(A2, B1), MH(A2, B2), MH(A2, B3), MH(A2, B4),u003Cbru003E MH(A3, B1), MH(A3, B2), MH(A3, B3), MH(A3, B4),u003Cbru003E MH(A4, B1), MH(A4, B2), MH(A4, B3), MH(A4, B4)];u003Cbru003E}u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E我认为我们无需纠缠于如何理解这个过程,因为它们只不过是数学上矩阵乘法的必要步骤。我们接着介绍缩放吧。u003Cu002Fpu003Eu003Ch1u003Eu003Cstrongu003E缩放u003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Cpu003E缩放一个模型同样简单-因为它也是乘法。你需要将第三个对角元素乘以缩放系数。 再一次,记得顺序是X,Y和Z。所以,如果你想让你的对象在所有三个坐标轴上都变成两倍大,则你需要让第一个,第六个和第十一个元素都乘以2。u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002F487f1fb6a95847aaa1964aaf114b7066″ img_width=”250″ img_height=”220″ alt=”「WebGL基础」:第二部分” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Ch1u003Eu003Cstrongu003E旋转u003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Cpu003E旋转是最难懂的变换,因为旋转轴在三个坐标轴上时,旋转矩阵都不一样。下图给出了每个坐标轴上的旋转方程。u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002F29a5406b03254ca69a28f93922c53393″ img_width=”600″ img_height=”220″ alt=”「WebGL基础」:第二部分” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E如果你完全看不懂也没关系;我们马上会在JavaScript的具体实现中复习一下的。u003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003E重要的一点是,执行变换的顺序是很关键的;不同的顺序会产生不同的结果。u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003E重要的一点是,执行变换的顺序是很关键的;不同的顺序会产生不同的结果。 如果你先移动对象然后再旋转,WebGL会像挥舞球拍一样舞动你的对象,而不只是让对象在原地旋转。 如果你先旋转再移动,则你会将对象移动到指定的位置上,只不过它会朝向你指定的方向上。 这是因为在3D世界中,变换是绕原点-0,0,0-来执行的。不存在对的或错的顺序。最终都是取决于你想要实现的效果。u003Cu002Fpu003Eu003Cpu003E要实现一些高级的动画,需要的每一种变换可能都会多个。比如,如果你想让一扇门绕绞链转动,你会先移动门,让它的绞链位于Y轴上,即在X轴和Z轴上都为零。 然后,绕Y轴旋转,这样门就可以绕绞链转动了。最后,你还需要将其再次移动,使得它可以放到场景中的指定位置上。u003Cu002Fpu003Eu003Cpu003E这些类型的动画在不同的场合下需要进行不同的定制,所以就没有必要专门写一个函数了。 不过,我会写一个函数执行最基本的顺序的变换:缩放,旋转,移动。这确保了所有物体都在指定位置,并有正确的朝向。u003Cu002Fpu003Eu003Cpu003E现在你已经对所有幕后的数学有了基本的理解,并了解了动画的工作原理,让我们创建一个JavaScript数据类型,来存储我们的3D对象。u003Cu002Fpu003Eu003Ch1u003Eu003Cstrongu003EGL对象u003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Cpu003E回忆本系列教程的第一部分,你需要三个数组来绘制一个基本的3D对象:顶点数组,三角数组和纹理数组。它们将是我们的数据类型的基础。 我们还需要用一些变量来表示在每一个轴上的三种变换。最后,我们需要用一个变量来表示纹理图像,并用来指示模型是否已经加载完毕。u003Cu002Fpu003Eu003Cpu003E下面是一个3D对象在JavaScript中的实现。u003Cu002Fpu003Eu003Cpreu003Efunction GLObject(VertexArr, TriangleArr, TextureArr, ImageSrc) {u003Cbru003E this.Pos = {u003Cbru003E X: 0,u003Cbru003E Y: 0,u003Cbru003E Z: 0u003Cbru003E };u003Cbru003E this.Scale = {u003Cbru003E X: 1.0,u003Cbru003E Y: 1.0,u003Cbru003E Z: 1.0u003Cbru003E };u003Cbru003E this.Rotation = {u003Cbru003E X: 0,u003Cbru003E Y: 0,u003Cbru003E Z: 0u003Cbru003E };u003Cbru003E this.Vertices = VertexArr;u003Cbru003E this.Triangles = TriangleArr;u003Cbru003E this.TriangleCount = TriangleArr.length;u003Cbru003E this.TextureMap = TextureArr;u003Cbru003E this.Image = new Image();u003Cbru003E this.Image.onload = function () {u003Cbru003E this.ReadyState = true;u003Cbru003E };u003Cbru003E this.Image.src = ImageSrc;u003Cbru003E this.Ready = false;u003Cbru003E u002Fu002FAdd Transformation function Hereu003Cbru003E}u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E我增加了两个独立的“ready”变量:一个用来表示图像是否准备好了,一个用于模型。当图像准备完毕,我们将通过将图像变换为WebGL纹理,以及将三个数组缓存于WebGL的缓存中,从而准备我们的模型。 这会加速我们的程序,因为不需要在每个绘制循环中都缓存一次数据。因为我们将数组存到缓存中去了,我们需要将三角形的数目存于一个独立的变量中。u003Cu002Fpu003Eu003Cpu003E现在,让我们加一个函数,来计算对象的变换矩阵。这个函数将取出所有的局部变量,并让它们以之前提到的顺序 (缩放,旋转,然后平移) 相乘。 你可以在这个变换顺序下得到一些不同的效果。将注释u002Fu002FAdd Transformation function Here换成如下代码:u003Cu002Fpu003Eu003Cpreu003Ethis.GetTransforms = function () {u003Cbru003Eu002Fu002FCreate a Blank Identity Matrixu003Cbru003Evar TMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];u003Cbru003Eu002Fu002FScalingu003Cbru003Evar Temp = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];u003Cbru003ETemp[0] *= this.Scale.X;u003Cbru003ETemp[5] *= this.Scale.Y;u003Cbru003ETemp[10] *= this.Scale.Z;u003Cbru003ETMatrix = MultiplyMatrix(TMatrix, Temp);u003Cbru003Eu002Fu002FRotating Xu003Cbru003ETemp = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];u003Cbru003Evar X = this.Rotation.X * (Math.PI u002F 180.0);u003Cbru003ETemp[5] = Math.cos(X);u003Cbru003ETemp[6] = Math.sin(X);u003Cbru003ETemp[9] = -1 * Math.sin(X);u003Cbru003ETemp[10] = Math.cos(X);u003Cbru003ETMatrix = MultiplyMatrix(TMatrix, Temp);u003Cbru003Eu002Fu002FRotating Yu003Cbru003ETemp = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];u003Cbru003Evar Y = this.Rotation.Y * (Math.PI u002F 180.0);u003Cbru003ETemp[0] = Math.cos(Y);u003Cbru003ETemp[2] = -1 * Math.sin(Y);u003Cbru003ETemp[8] = Math.sin(Y);u003Cbru003ETemp[10] = Math.cos(Y);u003Cbru003ETMatrix = MultiplyMatrix(TMatrix, Temp);u003Cbru003Eu002Fu002FRotating Zu003Cbru003ETemp = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];u003Cbru003Evar Z = this.Rotation.Z * (Math.PI u002F 180.0);u003Cbru003ETemp[0] = Math.cos(Z);u003Cbru003ETemp[1] = Math.sin(Z);u003Cbru003ETemp[4] = -1 * Math.sin(Z);u003Cbru003ETemp[5] = Math.cos(Z);u003Cbru003ETMatrix = MultiplyMatrix(TMatrix, Temp);u003Cbru003Eu002Fu002FMovingu003Cbru003ETemp = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];u003Cbru003ETemp[12] = this.Pos.X;u003Cbru003ETemp[13] = this.Pos.Y;u003Cbru003ETemp[14] = this.Pos.Z * -1;u003Cbru003Ereturn MultiplyMatrix(TMatrix, Temp);u003Cbru003E}u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E因为旋转公式相互重叠,它们必须一次执行一个。这个函数替换了上一个教程中的MakeTransform函数,所以你可以将它从脚本中删除。u003Cu002Fpu003Eu003Ch1u003Eu003Cstrongu003EOBJ导入器u003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Cpu003E现在,我们有了一个3D类,我们还需要一种方式来导入数据。我们将编写一个简单的模型导入器,它会将.obj文件变换为必要的数据,然后得到一个我们新创建的GLObject的对象。 使用.obj模型格式的原因在于,它用一种原始的形式来存储所有的数据,并且它有很好的文档介绍它的信息存储方式。 如果你的3D建模程序不支持导出.obj文件,则你总是可以编写一个基它数据格式的导入器。 .obj是一种标准的3D文件类型;所以,应该不会有什么问题。或者,你也可以安装Blender,这是一个跨平台的3D建模程序,它是支持导出.obj的。u003Cu002Fpu003Eu003Cpu003E在.obj文件中,每一行的头两个字母告诉我们该行中包含了什么类型的数据。 “v”表示一个”顶点坐标”行,”vt”表示一个”纹理坐标”行,而”f”是一个映射行。基于这些信息,我编写了下面的函数:u003Cu002Fpu003Eu003Cpreu003Efunction LoadModel(ModelName, CB) {u003Cbru003E var Ajax = new XMLHttpRequest();u003Cbru003E Ajax.onreadystatechange = function () {u003Cbru003E if (Ajax.readyState == 4 && Ajax.status == 200) {u003Cbru003E u002Fu002FParse Model Datau003Cbru003E var Script = Ajax.responseText.split(“\n”);u003Cbru003E var Vertices = [];u003Cbru003E var VerticeMap = [];u003Cbru003E var Triangles = [];u003Cbru003E var Textures = [];u003Cbru003E var TextureMap = [];u003Cbru003E var Normals = [];u003Cbru003E var NormalMap = [];u003Cbru003E var Counter = 0;u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E此函数接受两个参数:模型名称和回调函数。回调函数接受四个数组作为参数:顶点,三角形,纹理和法向量数组。 我之前还没介绍过法向量,所以你可以现在暂时忽略。我会在接下来的文章中讨论光照时进行介绍。u003Cu002Fpu003Eu003Cpu003E这个导入器首先创建一个XMLHttpRequest对象,并定义它的onreadystatechange事件处理器。在此处理器内部,我们将文件分割成行,然后定义了一些变量。 .obj文件首先定义了所有的唯一坐标,并定义它们的顺序。这也是为什么为顶点、纹理和法向量定义了两个变量的原因。 计数器counter变量用于填充三角形数组,因为.obj文件是按照顺序定义这些三角形的。u003Cu002Fpu003Eu003Cpu003E接下来,我们必须遍历文件的每一行,并检查它们各自是哪一种类型:u003Cu002Fpu003Eu003Cpreu003Efor (var I in Script) {u003Cbru003E var Line = Script[I];u003Cbru003E u002Fu002FIf Vertice Lineu003Cbru003E if (Line.substring(0, 2) == “v “) {u003Cbru003E var Row = Line.substring(2).split(” “);u003Cbru003E Vertices.push({u003Cbru003E X: parseFloat(Row[0]),u003Cbru003E Y: parseFloat(Row[1]),u003Cbru003E Z: parseFloat(Row[2])u003Cbru003E });u003Cbru003E }u003Cbru003E u002Fu002FTexture Lineu003Cbru003E else if (Line.substring(0, 2) == “vt”) {u003Cbru003E var Row = Line.substring(3).split(” “);u003Cbru003E Textures.push({u003Cbru003E X: parseFloat(Row[0]),u003Cbru003E Y: parseFloat(Row[1])u003Cbru003E });u003Cbru003E }u003Cbru003E u002Fu002FNormals Lineu003Cbru003E else if (Line.substring(0, 2) == “vn”) {u003Cbru003E var Row = Line.substring(3).split(” “);u003Cbru003E Normals.push({u003Cbru003E X: parseFloat(Row[0]),u003Cbru003E Y: parseFloat(Row[1]),u003Cbru003E Z: parseFloat(Row[2])u003Cbru003E });u003Cbru003E u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E前三行非常简单;它们包含了唯一性坐标的一个列表,用于顶点、纹理和法向量。 我们需要做的是将这些坐标存入相应的数组中。最后一种行的类型稍微复杂一些,因为它包含了多个东西。 它可以包含顶点,或顶点和纹理,或顶点、纹理和法向量。这样,我们不得不检查是这三种情况中的哪一种。下面的代码实现了这个功能:u003Cu002Fpu003Eu003Cpreu003Eu002Fu002FMapping Lineu003Cbru003E else if (Line.substring(0, 2) == “f “) {u003Cbru003E var Row = Line.substring(2).split(” “);u003Cbru003E for (var T in Row) {u003Cbru003E u002Fu002FRemove Blank Entriesu003Cbru003E if (Row[T] != “”) {u003Cbru003E u002Fu002FIf this is a multi-value entryu003Cbru003E if (Row[T].indexOf(“u002F”) != -1) {u003Cbru003E u002Fu002FSplit the different valuesu003Cbru003E var TC = Row[T].split(“u002F”);u003Cbru003E u002Fu002FIncrement The Triangles Arrayu003Cbru003E Triangles.push(Counter);u003Cbru003E Counter++;u003Cbru003E u002Fu002FInsert the Vertices u003Cbru003E var index = parseInt(TC[0]) – 1;u003Cbru003E VerticeMap.push(Vertices[index].X);u003Cbru003E VerticeMap.push(Vertices[index].Y);u003Cbru003E VerticeMap.push(Vertices[index].Z);u003Cbru003E u002Fu002FInsert the Texturesu003Cbru003E index = parseInt(TC[1]) – 1;u003Cbru003E TextureMap.push(Textures[index].X);u003Cbru003E TextureMap.push(Textures[index].Y);u003Cbru003E u002Fu002FIf This Entry Has Normals Datau003Cbru003E if (TC.length > 2) {u003Cbru003E u002Fu002FInsert Normalsu003Cbru003E index = parseInt(TC[2]) – 1;u003Cbru003E NormalMap.push(Normals[index].X);u003Cbru003E NormalMap.push(Normals[index].Y);u003Cbru003E NormalMap.push(Normals[index].Z);u003Cbru003E }u003Cbru003E }u003Cbru003E u002Fu002FFor rows with just verticesu003Cbru003E else {u003Cbru003E Triangles.push(Counter); u002Fu002FIncrement The Triangles Arrayu003Cbru003E Counter++;u003Cbru003E var index = parseInt(Row[T]) – 1;u003Cbru003E VerticeMap.push(Vertices[index].X);u003Cbru003E VerticeMap.push(Vertices[index].Y);u003Cbru003E VerticeMap.push(Vertices[index].Z);u003Cbru003E }u003Cbru003E }u003Cbru003E }u003Cbru003E }u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E这个代码虽然长,但并不算复杂。虽然我讨论了.obj文件中只包含有顶点数据的情况,但我们的框架还需要顶点坐标和纹理坐标。 如果一个.obj文件只包含顶点数据,你将必须手动地添加纹理坐标数据。u003Cu002Fpu003Eu003Cpu003E现在,让我们将这些数据传递给回调函数,并完成我们的LoadModel函数。u003Cu002Fpu003Eu003Cpreu003E }u003Cbru003E u002Fu002FReturn The Arraysu003Cbru003E CB(VerticeMap, Triangles, TextureMap, NormalMap);u003Cbru003E }u003Cbru003E }u003Cbru003E Ajax.open(“GET”, ModelName + “.obj”, true);u003Cbru003E Ajax.send();u003Cbru003E}u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E你需要小心的是,我们的WebGL框架是非常基本的,只能画用三角形构造出来的模型。所以,你需要相应地编辑你的3D模型。 幸运的是,大部分3D应用都支持或有插件支持模型的三角化。我通过基本的建模技术构造了一个简单的房子的模型,包含在源码中,供你使用。u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002F1b65009fabb44d7a9bb86c4a5e3480b3″ img_width=”574″ img_height=”276″ alt=”「WebGL基础」:第二部分” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E现在,让我们修改上篇文章中的Draw函数,使之能够处理我们新的3D模型的数据类型。u003Cu002Fpu003Eu003Cpreu003Ethis.Draw = function (Model) {u003Cbru003E if (Model.Image.ReadyState == true && Model.Ready == false) {u003Cbru003E this.PrepareModel(Model);u003Cbru003E }u003Cbru003E if (Model.Ready) {u003Cbru003E this.GL.bindBuffer(this.GL.ARRAY_BUFFER, Model.Vertices);u003Cbru003E this.GL.vertexAttribPointer(this.VertexPosition, 3, this.GL.FLOAT, false, 0, 0);u003Cbru003E this.GL.bindBuffer(this.GL.ARRAY_BUFFER, Model.TextureMap);u003Cbru003E this.GL.vertexAttribPointer(this.VertexTexture, 2, this.GL.FLOAT, false, 0, 0);u003Cbru003E this.GL.bindBuffer(this.GL.ELEMENT_ARRAY_BUFFER, Model.Triangles);u003Cbru003E u002Fu002FGenerate The Perspective Matrixu003Cbru003E var PerspectiveMatrix = MakePerspective(45, this.AspectRatio, 1, 1000.0);u003Cbru003E var TransformMatrix = Model.GetTransforms();u003Cbru003E u002Fu002FSet slot 0 as the active Textureu003Cbru003E this.GL.activeTexture(this.GL.TEXTURE0);u003Cbru003E u002Fu002FLoad in the Texture To Memoryu003Cbru003E this.GL.bindTexture(this.GL.TEXTURE_2D, Model.Image);u003Cbru003E u002Fu002FUpdate The Texture Sampler in the fragment shader to use slot 0u003Cbru003E this.GL.uniform1i(this.GL.getUniformLocation(this.ShaderProgram, “uSampler”), 0);u003Cbru003E u002Fu002FSet The Perspective and Transformation Matricesu003Cbru003E var pmatrix = this.GL.getUniformLocation(this.ShaderProgram, “PerspectiveMatrix”);u003Cbru003E this.GL.uniformMatrix4fv(pmatrix, false, new Float32Array(PerspectiveMatrix));u003Cbru003E var tmatrix = this.GL.getUniformLocation(this.ShaderProgram, “TransformationMatrix”);u003Cbru003E this.GL.uniformMatrix4fv(tmatrix, false, new Float32Array(TransformMatrix));u003Cbru003E u002Fu002FDraw The Trianglesu003Cbru003E this.GL.drawElements(this.GL.TRIANGLES, Model.TriangleCount, this.GL.UNSIGNED_SHORT, 0);u003Cbru003E }u003Cbru003E};u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E新的绘制函数首先检查模型是否已经为WebGL准备好。如果纹理已经加载,它会开始准备绘制模型。我们呆会儿会介绍这个PrepareModel函数。 如果模型准备好了,它会连接到着色器中的缓存,并和之前一样,加载透视矩阵和变换矩阵。唯一实在的差别在于,它的所有数据都来自于模型对象。u003Cu002Fpu003Eu003Cpu003EPrepareModel函数只不过是将纹理和数据数组转变为与WebGL兼容的变量。下面就是这个函数;将它加到绘制函数之前。u003Cu002Fpu003Eu003Cpreu003Ethis.PrepareModel = function (Model) {u003Cbru003E Model.Image = this.LoadTexture(Model.Image);u003Cbru003E u002Fu002FConvert Arrays to buffersu003Cbru003E var Buffer = this.GL.createBuffer();u003Cbru003E this.GL.bindBuffer(this.GL.ARRAY_BUFFER, Buffer);u003Cbru003E this.GL.bufferData(this.GL.ARRAY_BUFFER, new Float32Array(Model.Vertices), this.GL.STATIC_DRAW);u003Cbru003E Model.Vertices = Buffer;u003Cbru003E Buffer = this.GL.createBuffer();u003Cbru003E this.GL.bindBuffer(this.GL.ELEMENT_ARRAY_BUFFER, Buffer);u003Cbru003E this.GL.bufferData(this.GL.ELEMENT_ARRAY_BUFFER, new Uint16Array(Model.Triangles), this.GL.STATIC_DRAW);u003Cbru003E Model.Triangles = Buffer;u003Cbru003E Buffer = this.GL.createBuffer();u003Cbru003E this.GL.bindBuffer(this.GL.ARRAY_BUFFER, Buffer);u003Cbru003E this.GL.bufferData(this.GL.ARRAY_BUFFER, new Float32Array(Model.TextureMap), this.GL.STATIC_DRAW);u003Cbru003E Model.TextureMap = Buffer;u003Cbru003E Model.Ready = true;u003Cbru003E};u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E现在,我们的框架已经完成,我们可以开始修改HTML页面。u003Cu002Fpu003Eu003Ch1u003Eu003Cstrongu003EHTML页面u003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Cpu003E你可以清除script标签中的所有代码,由于新的GLObject的功劳,我们可以把代码写得更紧凑一些。u003Cu002Fpu003Eu003Cpu003E下面是完整的JavaScript代码:u003Cu002Fpu003Eu003Cpreu003Evar GL;u003Cbru003Evar Building;u003Cbru003E u003Cbru003Efunction Ready() {u003Cbru003E GL = new WebGL(“GLCanvas”, “FragmentShader”, “VertexShader”);u003Cbru003E LoadModel(“House”, function (VerticeMap, Triangles, TextureMap) {u003Cbru003E Building = new GLObject(VerticeMap, Triangles, TextureMap, “House.png”);u003Cbru003E u003Cbru003E Building.Pos.Z = 650;u003Cbru003E u003Cbru003E u002Fu002FMy Model Was a bit too bigu003Cbru003E Building.Scale.X = 0.5;u003Cbru003E Building.Scale.Y = 0.5;u003Cbru003E Building.Scale.Z = 0.5;u003Cbru003E u003Cbru003E u002Fu002FAnd Backwardsu003Cbru003E Building.Rotation.Y = 180;u003Cbru003E u003Cbru003E setInterval(Update, 33);u003Cbru003E });u003Cbru003E}u003Cbru003E u003Cbru003Efunction Update() {u003Cbru003E Building.Rotation.Y += 0.2u003Cbru003E GL.Draw(Building);u003Cbru003E}u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E我们加载一个模型,告诉页面每秒钟更新30次。Update函数让模型绕Y轴旋转,这是通过更新这个对象的Y轴Rotation实现的。 我的模型对于WebGL来说还是大了一些,这不太好,所以我需要在代码中稍作调整。u003Cu002Fpu003Eu003Cpu003E除非你想要那种影院般的WebGL展示,你很可能希望添加一些控制功能。让我们看看如何在应用中添加鼠标控制功能。u003Cu002Fpu003Eu003Ch1u003Eu003Cstrongu003E键盘控制u003Cu002Fstrongu003Eu003Cu002Fh1u003Eu003Cpu003E这只不过是原生的JavaScript功能,并非WebGL的技术,但它对于控制和放置3D模型是很有帮助的。你需要做的全部事情只是为键盘的keydown或keyup事件添加一个事件监听器,并检查到底是哪个键被按下了。 每个键都一个特殊的代码,找出这种对应关系的一种较好的办法是在事件触发时在终端中记录下按键的代码。所以,在加载模型的代码处,在setInterval行之后添加如下的代码:u003Cu002Fpu003Eu003Cpreu003Edocument.onkeydown = handleKeyDown;u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E这会设置函数handleKeyDown,来处理keydown事件。下面是handleKeyDown函数的代码:u003Cu002Fpu003Eu003Cpreu003Efunction handleKeyDown(event) {u003Cbru003E u002Fu002FYou can uncomment the next line to find out each key’s codeu003Cbru003E u002Fu002Falert(event.keyCode);u003Cbru003E if (event.keyCode == 37) {u003Cbru003E u002Fu002FLeft Arrow Keyu003Cbru003E Building.Pos.X -= 4;u003Cbru003E } else if (event.keyCode == 38) {u003Cbru003E u002Fu002FUp Arrow Keyu003Cbru003E Building.Pos.Y += 4;u003Cbru003E } else if (event.keyCode == 39) {u003Cbru003E u002Fu002FRight Arrow Keyu003Cbru003E Building.Pos.X += 4;u003Cbru003E } else if (event.keyCode == 40) {u003Cbru003E u002Fu002FDown Arrow Keyu003Cbru003E Building.Pos.Y -= 4;u003Cbru003E }u003Cbru003E}u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E这个函数的功能是更新对象的属性;而我们WebGL框架会处理剩下的所有事情。u003Cu002Fpu003Eu003Cpu003E更多精彩内容,请微信u003Cstrongu003E关注“前端达人”公众号u003Cu002Fstrongu003E!u003Cu002Fpu003Eu003Cu002Fdivu003E”

原文始发于:「WebGL基础」:第二部分

主题测试文章,只做测试使用。发布者:杀手梦三刀,转转请注明出处:http://www.cxybcw.com/10768.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code