幼儿饰品瑜伽美体用品微软
投稿投诉
微软创意
爱情通信
用品婚姻
爱好看病
美体软件
影音星座
瑜伽周边
星座办公
饰品塑形
搞笑减肥
幼儿两性
智家潮品

JavaScript实战3D建模软件开发

  Subsurfer是使用HTML5Canvas控件和WebGL用JavaScript编写的3D建模应用程序。它实现了CatmullClark细分曲面算法。该程序的一个独特功能是编辑窗口使用自定义JavaScript代码在2D画布上下文中实现3D投影。视图窗口使用WebGL的3D画布上下文。Subsurfer是用Notepad编写并在Chrome中调试的,源代码可以从这里下载。1、介绍
  Subsurfer中的建模基于立方体,每个模型都以立方体开始。顶部的按钮选择当前工具。使用实体工具,你可以右键单击实体并更改其某些属性,例如颜色。使用滑块工具完成模型的平移、缩放和旋转。上下文菜单和颜色选择器在Canvas控件中实现。此3D投影和所有模型编辑均在2D环境中完成。
  模型是通过将连续的细分曲面应用于实体,结合挤出和切面的分裂来开发的。该界面是按键命令和使用实体、小平面、边和顶点工具的右键单击菜单的组合。在这里,我们看到了立方体表面细分的连续应用。
  复选框控制查看选项。在这里,我们看到选中了清除和轮廓选项的相同模型。
  在这里,我们看到一个被挤压的刻面。挤压是一个右键菜单项和一个击键命令。使用Facet工具选择Facet。你可以单击一个构面,单击并滚动以选择多个构面,或拖动一个框以净选构面。
  挤出刻面时的一件重要事情是避免有共同的内壁。当挤压法线指向同一方向的多个相邻面时,可能会发生这种情况。共享内墙会混淆CatmullClark算法,结果看起来不正确。为避免这种情况,在拉伸相邻面时,除非它们的法线朝向不同的方向,否则最好使用挤出组命令。
  边循环影响曲面细分将如何塑造模型。可以使用Bevel命令(Facet工具)或使用Split命令(Edge工具)添加循环边。可以使用边缘工具的右键单击菜单选项来选择边缘循环。
  Subsurfer中的每个面都是四边形。CatmullClark算法可以很好地处理四边形,并且它们可以更容易地实现可以遍历模型以查找边缘循环和小平面循环的算法。
  顶点工具可以用来拖动顶点,就像面工具可以拖动面,边工具可以拖动边一样。拖动模型元素时,显示网格(网格复选框选项)很重要,这样您就会知道您正在拖动哪个2维。否则,结果可能是意外和不受欢迎的。
  Subsurfer有一个编辑窗口(2D画布上下文)和一个查看窗口(3D画布上下文)。它们由编辑和查看复选框控制。在这里,我们在编辑窗口中看到了一个模型,在视图窗口中看到了它的WebGL等效模型。
  细分曲面建模生成具有平滑圆角曲线的形状。通过仔细的规划和耐心的编辑,可以通过小平面的挤压、分割、缩放和倾斜、边缘和顶点的平移以及平滑算法的连续应用来生成复杂的模型。
  这是编辑窗口中spacepig模型的网格视图。像所有Subsurfer模型一样,它最初是一个立方体。
  Subsurfer支持一些内置纹理,例如木纹(如下所示)。名为textures。png的图像文件包含所有纹理。
  如果要从文件系统运行程序,浏览器安全设置将不允许网页加载纹理图像。HTML页面和PNG图像都必须托管在同一台服务器上。如果你有合适的软件来设置它,您可以从localhost运行该程序。或者,你可以使用特殊的命令行选项运行Chrome。exe,以允许从文件系统加载纹理。需要执行的命令是chrome。exeallowfileaccessfromfiles。在执行此操作之前,你必须关闭所有Chrome实例。
  包括各种纹理,包括下面看到的mod佩斯利。有一个挤压系列命令可以自动连续挤压刻面,这有助于创造幻觉的洛夫克拉夫特式噩梦。
  源命令(左侧按钮)打开一个新选项卡,显示当前模型网格的文本表示。
  Save、Open和Delete按钮是使用AJAX调用实现和测试的,以将模型存储在服务器上并按名称检索它们。但是,出于本文的目的,我不想在我的服务器上进行任何点击,因此我更改了路径和名称,因此按钮不会做任何事情。你仍然可以使用提供的AJAX代码,但你必须实现自己的SOAPWeb服务并更改客户端代码以匹配。
  但是,你仍然可以通过复制Source命令中的文本将模型保存在本地文件中,如上所示。如果想将本地保存的模型输入到Subsurfer中,请使用输入按钮。这是左侧的命令之一,但未在这些图片中显示。输入命令会弹出一个表单,你只需将网格文本粘贴到字段中,如下所示。即使对于大型模型,这似乎也很有效。你可能会遇到浏览器安全设置的问题,但对我来说效果很好。
  包括各种WebGL着色器,可以从右上角的下拉菜单中选择。WebGL中的着色器是使用GLSL实现的。具有可选镜面反射的平面着色和Phong(平滑)着色是最有用的。边缘锐利的物体应使用平面阴影。带有Phong阴影的立方体看起来很有趣。我还实现了一些不真实的自定义着色器,包括下图的节日彩虹着色器(这不是纹理,它是自定义着色器)。这个着色器对物体在空间中的位置很敏感,所以当物体旋转时颜色会以一种非常诡异的方式变化。
  程序中内置了一个帮助文件和一个击键列表(左侧的最后两个按钮),但是开始使用Subsurfer的最快方法是使用击键命令通过挤出刻面和平滑实体来进行实验,看看什么您可以制作各种奇怪而有趣的模型。挤出小平面的击键命令是e,平滑实体的击键命令是s。您将希望选择Facet工具,以便您可以选择facet。您可以使用Facet工具(和大多数其他工具)通过在窗口中单击鼠标右键并拖动来旋转模型。加号和减号键将放大或缩小。单击一个构面以将其选中。您还可以净选择构面并单击拖动以选择区域。可以同时挤出多个面。但是,如果进行多次拉伸,请确保小平面不朝向完全相同的方向,否则您最终会得到共享的内壁,这会影响细分算法。如果挤压面向相同方向的相邻面,最好使用挤压组(击键g)。2、使用代码
  你可以从本地文件系统运行HTML文件。如上所述,如果在本地运行,则会遇到安全问题,并且纹理不会在WebGL中显示。
  要解决此问题,请关闭所有Chrome实例并使用以下命令启动Chrome:chrome。exeallowfileaccessfromfiles。
  此外,Save、Open和Delete按钮被有效禁用。要保存模型,请使用Source命令(左侧按钮)复制网格规范。要将保存的模型输入到Subsurfer,请使用输入命令并将网格文本粘贴到提供的表格中。
  挤出刻面时的一件重要事情是避免有共同的内壁。当挤压法线指向同一方向的多个相邻面时,可能会发生这种情况。内墙会弄乱CatmullClark算法的结果。为避免这种情况,在拉伸相邻面时,除非它们的法线朝向不同的方向,否则最好使用挤出组命令。3、构建编辑视图
  应用程序中有大约14000行代码。WebGL部分使用JamesCoglan的Sylvester矩阵数学库,根据许可协议使用。在本文中,我将介绍使该程序正常工作的一些基本元素。我可能会在以后的文章中更深入地介绍一些主题。
  本节介绍如何在2D绘图环境中生成编辑视图的3D投影。
  该程序使用HTML5Canvas控件,该控件具有两个上下文。这是初始化程序UI的函数。它添加了两个Canvas控件并为一个获取2D上下文,为另一个获取webgl(3D)上下文。如果webgl不可用,它会回退到实验性webgl。WebGL功能似乎在所有主要浏览器上都得到了很好的支持。其余代码为用户输入设置侦听器并处理其他事务,例如将可用的着色器选项添加到listbox。functionstartModel(){alertUser();filename;setInterval(timerEvent,10);makeCube();canvasdocument。createElement(canvas);canvas2document。createElement(canvas);document。body。appendChild(canvas);document。body。appendChild(canvas2);canvas。style。positionfixed;canvas2。style。positionfixed;ctxcanvas。getContext(2d);glcanvas2。getContext(webgl)canvas2。getContext(experimentalwebgl);posnewPoint(0,0);lastknownpositionlastClickPosnewPoint(0,0);lastclickpositionwindow。addEventListener(resize,resize);window。addEventListener(keydown,keyDown);window。addEventListener(keyup,keyRelease);canvas。addEventListener(mousemove,mouseMove);canvas。addEventListener(mousedown,mouseDown);canvas。addEventListener(mouseup,mouseUp);canvas。addEventListener(mouseenter,setPosition);canvas。addEventListener(click,click);canvas2。addEventListener(mousemove,mouseMoveGL);canvas2。addEventListener(mousedown,mouseDownGL);canvas2。addEventListener(mouseup,mouseUpGL);canvas。style。backgroundColorcolorString(canvasBackgroundColor,false);canvas。style。positionabsolute;canvas。style。border1pxsolidblack;canvas2。style。positionabsolute;canvas2。style。border1pxsolidblack;resize();document。getElementById(checkboxoutlines)。checkedfalse;document。getElementById(checkboxsolid)。checkedtrue;document。getElementById(checkboxgrid)。checkedfalse;document。getElementById(toolslider)。checkedtrue;document。getElementById(checkboxtwosided)。checkedtrue;document。getElementById(checkboxwebgl)。checkedfalse;document。getElementById(checkbox2DWindow)。checkedtrue;document。getElementById(checkboxtransparent)。checkedfalse;if(gl!null){gl。clearColor(canvasBackgroundColor。R255。0,canvasBackgroundColor。G255。0,canvasBackgroundColor。B255。0,1。0);gl。enable(gl。DEPTHTEST);gl。depthFunc(gl。LEQUAL);gl。clear(gl。COLORBUFFERBITgl。DEPTHBUFFERBIT);}addShaderToList(Phong);addShaderToList(Rainbow1);addShaderToList(Rainbow2);addShaderToList(Stripes);addShaderToList(Chrome);addShaderToList(Smear);addShaderToList(Flat);addShaderToList(TMap);addShaderToList(Comic);addShaderToList(Comic2);addShaderToList(Topo);addShaderToList(PaintByNumbers);varrectcanvas。getBoundingClientRect();originnewPoint((rect。width2),(rect。height2));setEditViewOptions();hideInputForm();}
  由于各种原因,程序中的所有编辑都是在2D上下文中完成的,因为我似乎更容易解决与2D上下文中的命中检测和用户交互相关的问题。在2D上下文中绘图也比在WebGL中绘图简单得多。
  为了在2D中创建3D投影,只需要发生一些事情。这是将3D点映射到二维的投影代码。为了实现这一点,只需要想象一个位于模型和观察者眼睛之间的沿Z轴的XY平面。然后计算从眼睛到每个3D模型顶点的光线与该平面相交的位置。functionTo2D(p3d)givesa3D2Dperspectiveprojection{varpoint3dnewPoint3D(p3d。x,p3d。y,p3d。z);RotateXYZ(point3d,myCenter,radiansX,radiansY,radiansZ);varxRisepoint3d。xmyCenter。x;varyRisepoint3d。ymyCenter。y;varzRunEyezEyePlanepoint3d。z;varzRunViewzViewingPlanepoint3d。z;varfactor(zRunEyezRunView)zRunEye;varx(myCenter。x(factorxRise));vary(myCenter。y(factoryRise));xctx。canvas。width;xdocSize;yctx。canvas。width;ydocSize;varpnewPoint(Math。floor(x),Math。floor(y));havetoflipsignofYcoordinate,thismakesitmatchtheGLsidep。xorigin。x;p。yorigin。y;returnp;}
  请注意,上述函数所做的第一件事是将点从其实际位置旋转到当前查看位置。这是为用户提供一种旋转作品并从各个角度查看的方式。这也是一件小事,如下所示。每当用户输入鼠标输入来旋转视图时,变量radiansX、radiansY和radiansZ都会更新并重新绘制投影。functionRotateXYZ(p,rotationpoint,radiansX,radiansY,radiansZ){if(radiansZ!0。0)rotateaboutZaxis{radiansZnormalizeradians(radiansZ);if(radiansZ!0){varydiff(p。y)(rotationpoint。y);varxdiff(p。x)(rotationpoint。x);varxd(xdiffMath。cos(radiansZ))(ydiffMath。sin(radiansZ));xdMath。round(xd,0);varyd(xdiffMath。sin(radiansZ))(ydiffMath。cos(radiansZ));ydMath。round(yd,0);p。xrotationpoint。x(xd);p。yrotationpoint。y(yd);}}if(radiansY!0。0)rotateabouttheYaxis{radiansYnormalizeradians(radiansY);if(radiansY!0){varzdiff(p。z)(rotationpoint。z);varxdiff(p。x)(rotationpoint。x);varxd(xdiffMath。cos(radiansY))(zdiffMath。sin(radiansY));xdMath。round(xd,0);varzd(xdiffMath。sin(radiansY))(zdiffMath。cos(radiansY));zdMath。round(zd,0);p。xrotationpoint。x(xd);p。zrotationpoint。z(zd);}}if(radiansX!0。0)rotateabouttheXaxis{radiansXnormalizeradians(radiansX);if(radiansX!0){varydiff(p。y)(rotationpoint。y);varzdiff(p。z)(rotationpoint。z);varzd(zdiffMath。cos(radiansX))(ydiffMath。sin(radiansX));zdMath。round(zd,0);varyd(zdiffMath。sin(radiansX))(ydiffMath。cos(radiansX));ydMath。round(yd,0);p。zrotationpoint。z(zd);p。yrotationpoint。y(yd);}}}
  模型由方面组成。面由边组成,边由点组成。以下是保存模型的基本数据结构。请注意,就本程序而言,立方体仍然是立方体,无论它有多少面。每个模型都以具有6个面的立方体开始,但随着挤压、分割和平滑算法的应用,将向立方体添加更多面。functioncube(left,right,top,bottom,front,back){if(leftundefined){left0;}if(rightundefined){right0;}if(topundefined){top0;}if(bottomundefined){bottom0;}if(frontundefined){front0;}if(backundefined){back0;}this。colornewColor(190,180,190);defaultsolidcolorthis。outlineColornewColor(0,0,0);defaultsolidoutlinecolorthis。textureName;this。nSubpide0;this。leftleft;this。rightright;this。toptop;this。bottombottom;this。frontfront;this。backback;this。previousFacetLists〔〕;this。facets〔〕;varlefttopbacknewPoint3D(left,top,back);varlefttopfrontnewPoint3D(left,top,front);varrighttopfrontnewPoint3D(right,top,front);varrighttopbacknewPoint3D(right,top,back);varleftbottombacknewPoint3D(left,bottom,back);varleftbottomfrontnewPoint3D(left,bottom,front);varrightbottomfrontnewPoint3D(right,bottom,front);varrightbottombacknewPoint3D(right,bottom,back);vartopPoints〔〕;topPoints。push(clonePoint3D(lefttopback));topPoints。push(clonePoint3D(righttopback));topPoints。push(clonePoint3D(righttopfront));topPoints。push(clonePoint3D(lefttopfront));topPoints。reverse();varbottomPoints〔〕;bottomPoints。push(clonePoint3D(leftbottomfront));bottomPoints。push(clonePoint3D(rightbottomfront));bottomPoints。push(clonePoint3D(rightbottomback));bottomPoints。push(clonePoint3D(leftbottomback));bottomPoints。reverse();varfrontPoints〔〕;frontPoints。push(clonePoint3D(lefttopfront));frontPoints。push(clonePoint3D(righttopfront));frontPoints。push(clonePoint3D(rightbottomfront));frontPoints。push(clonePoint3D(leftbottomfront));frontPoints。reverse();varbackPoints〔〕;backPoints。push(clonePoint3D(righttopback));backPoints。push(clonePoint3D(lefttopback));backPoints。push(clonePoint3D(leftbottomback));backPoints。push(clonePoint3D(rightbottomback));backPoints。reverse();varleftPoints〔〕;leftPoints。push(clonePoint3D(lefttopback));leftPoints。push(clonePoint3D(lefttopfront));leftPoints。push(clonePoint3D(leftbottomfront));leftPoints。push(clonePoint3D(leftbottomback));leftPoints。reverse();varrightPoints〔〕;rightPoints。push(clonePoint3D(righttopfront));rightPoints。push(clonePoint3D(righttopback));rightPoints。push(clonePoint3D(rightbottomback));rightPoints。push(clonePoint3D(rightbottomfront));rightPoints。reverse();varid1;vars1newFacet();s1。IDid;s1。pointstopPoints;this。facets。push(s1);vars2newFacet();s2。IDid;s2。pointsbottomPoints;this。facets。push(s2);vars3newFacet();s3。IDid;s3。pointsbackPoints;this。facets。push(s3);vars4newFacet();s4。IDid;s4。pointsfrontPoints;this。facets。push(s4);vars5newFacet();s5。IDid;s5。pointsleftPoints;this。facets。push(s5);vars6newFacet();s6。IDid;s6。pointsrightPoints;this。facets。push(s6);for(varn0;nthis。facets。length;n){this。facets〔n〕。cubethis;}}functionFacet(){this。cube1;this。ID1;this。points〔〕;this。point1newPoint(0,0);this。point2newPoint(0,0);this。closedfalse;this。fillfalse;this。averagePoint3DnewPoint3D(0,0,0);this。normal1;this。edges〔〕;this。neighbors〔〕;this。greatestRotatedZ0;this。greatestLeastRotatedZ0;this。averageRotatedZ0;this。boundsMinnewPoint3D(0,0,0);this。boundsMaxnewPoint3D(0,0,0);}functionPoint3D(x,y,z){this。xx;this。yy;this。zz;}
  要在2D中绘制模型,只需将每个facet描述的多边形从3D映射到2D,然后填充生成的2D多边形。只有两个并发症。第一个是每个面必须根据其相对于表示光源的矢量的角度进行着色。第二个是在给定当前视图旋转的情况下,必须根据它们沿Z轴的位置从后到前对刻面进行排序。这样,首先绘制背面的刻面,而前面的刻面将它们遮住,这就是你想要的。
  需要注意的是,这种通过沿Z轴对多边形进行排序来描绘实体对象的方法是一种近似。它不考虑构面之间的交叉点。此外,当对象包含凹面时,Z排序会给出看起来不正确的结果。然而,当对象没有凹面并且表面之间没有相交时,该方法会产生足够好的结果。当小平面相对于模型的大小较小时,异常的发生会大大减少,就像应用了平滑时一样。如果存在不规则性,你始终可以在编辑期间通过旋转模型和或使用清除和轮廓查看选项并将模型视为具有透明表面的线框来解决它们。任何此类异常都不会出现在查看窗口中,
  要对多边形着色,必须获取其法线。这是一个垂直于刻面表面的向量(使用叉积计算)。计算此法线和光源矢量之间的角度(使用点积),这用于使刻面颜色变亮或变暗。如果角度更接近0,则刻面颜色变亮。如果角度接近180,则刻面颜色变暗。这是计算刻面法线并对刻面进行着色的代码。functionCalculateNormal(facet){varnormal1;if(facet。points。length2){varp0facet。points〔0〕;varp1facet。points〔1〕;varp2facet。points〔2〕;varatimesPoint(minusPoints(p1,p0),8);varbtimesPoint(minusPoints(p2,p0),8);normalnewline(clonePoint3D(p0),newPoint3D((a。yb。z)(a。zb。y),crossproduct((a。xb。z)(a。zb。x)),(a。xb。y)(a。yb。x)));normal。endLengthPoint(normal,cubeSize2);varavgaverageFacetPoint(facet。points);normal。end。xavg。xnormal。start。x;normal。end。yavg。ynormal。start。y;normal。end。zavg。znormal。start。z;normal。startavg;}returnnormal;}functiongetLightSourceAngle(normal){varangle0;if(normal!1){anglenormalizeradians(vectorAngle(lightSource,minusPoints(ToRotated(normal。end),ToRotated(normal。start))));}returnangle;}functionvectorAngle(vector1,vector2){varangle0。0;varlength1Math。sqrt((vector1。xvector1。x)(vector1。yvector1。y)(vector1。zvector1。z));varlength2Math。sqrt((vector2。xvector2。x)(vector2。yvector2。y)(vector2。zvector2。z));vardotproduct(vector1。xvector2。xvector1。yvector2。yvector1。zvector2。z);varcosineofangledotproduct(length1length2);angleMath。acos(cosineofangle);returnangle;}functionShadeFacet(color,angle){vardarkenrange0。75;varlightenrange0。75;varresultnewColor(color。R,color。G,color。B);if(angle180){angle360angle;}if(angle90)darken{vardarkenamount(angle90)90;darkenamountdarkenrange;varrcolor。R(color。Rdarkenamount);vargcolor。G(color。Gdarkenamount);varbcolor。B(color。Bdarkenamount);rMath。min(255,Math。max(0,r));gMath。min(255,Math。max(0,g));bMath。min(255,Math。max(0,b));resultnewColor(r,g,b);}elselighten{varlightenamount(90angle)90;lightenamountlightenrange;varrcolor。R((255color。R)lightenamount);vargcolor。G((255color。G)lightenamount);varbcolor。B((255color。B)lightenamount);rMath。max(0,Math。min(255,r));gMath。max(0,Math。min(255,g));bMath。max(0,Math。min(255,b));resultnewColor(r,g,b);}returnresult;}
  一旦刻面被着色,就必须将它们从后到前排序,这样当你按顺序绘制它们时,最近的刻面将覆盖它们后面的刻面。functionsortFacets(){allFacets〔〕;for(varw0;wcubes。length;w){varcubecubes〔w〕;for(vari0;icube。facets。length;i){allFacets。push(cube。facets〔i〕);}}sortFacetsOnZ(allFacets);}functionsortFacetsOnZ(facets){for(vari0;ifacets。length;i){setAverageAndGreatestRotatedZ(facets〔i〕);}facets。sort(function(a,b){if(a。greatestRotatedZb。greatestRotatedZ){if(a。leastRotatedZb。leastRotatedZ){returna。averageRotatedZb。averageRotatedZ;}else{returna。leastRotatedZb。leastRotatedZ;}}else{returna。greatestRotatedZb。greatestRotatedZ}});}
  下面是一些在2D上下文中使用3D投影绘制编辑显示的代码。这里发生的基本事情是sortFacets()和drawCubes()。这就是产生立体形状错觉的3D投影的原因。此处的其他代码与更新WebGL视图和编辑UI的绘图元素有关。编辑UI元素包括矩形方向网格和上下文菜单,以及模型元素(面、边、顶点),这些元素会受到翻转行为和高亮行为的影响,必须根据当前工具和鼠标位置重新绘制不同的颜色。functionupdateModel(){for(varc0;ccubes。length;c){updateCube(cubes〔c〕);}sortFacets();reloadSceneGL();draw();}functiondraw(){if(isGLgl!null){drawSceneGL();}if(is2dWindow!isGL){ctx。clearRect(0,0,canvas。width,canvas。height);findGridOrientation();if(gridChosen()){drawGridXY();}lineColorlineColorShape;drawCubes();if(mouseIsDowndraggingShape){draw3DRectangleFrom2DPoints(mouseDownPos,pos,false,white);}if(hitLine!1){varpts〔〕;pts。push(To2D(hitLine。start));pts。push(To2D(hitLine。end));drawPolygonHighlighted(pts);}if(hitFacet!1toolChosen()facet){drawPolygon3d(hitFacet。points,true,true,yellow,true);}for(varg0;gselectedLines。length;g){varpts〔〕;pts。push(To2D(selectedLines〔g〕。start));pts。push(To2D(selectedLines〔g〕。end));drawPolygonSelected(pts);}if(hitVertex!1){drawVertex(hitVertex,false);}for(varqq0;qqselectedVertexes。length;qq){drawVertex(selectedVertexes〔qq〕,true);}if(lineDiv!1lineDiv2!1){drawLine2D(lineDiv,blue);drawLine2D(lineDiv2,blue);}if(draggingRect){draw2DRectangleFrom2DPoints(mouseDownPos,pos,black);}if(colorPickMode。length0){drawColors(0,0,colorPickHeight);}drawMenu();}}functiondrawCubes(){vardrawlinesisOutline!isShade;vardrawNormalsisNormals;varshadeSolidsisShade;vardualisDualSided;for(vari0;iallFacets。length;i){varfacetallFacets〔i〕;if(facet。normal1){facet。normalCalculateNormal(facet);}varcfacet。cube。color;if(colorPickMode。length0){if(facet。cubehitSolid){cnewColor(23,100,123);}if(listHas(selectedSolids,facet。cube)){cnewColor(200,30,144);}if(listHas(selectedFacets,facet)){cnewColor(0,255,255);}}cShadeFacet(c,degreesfromradians(getLightSourceAngle(facet。normal)));varshowtrue;if(!dual){showShowFacet(degreesfromradians(getFrontSourceAngle(facet。normal)));}varcolorFillStylecolorString(c,isTransparent);varcolorOutlineStylecolorString(facet。cube。outlineColor,isTransparent);if(listHas(selectedSolids,facet。cube)){drawlinestrue;colorOutlineStylered;}if(show){drawPolygon3d(facet。points,true,shadeSolidslistHas(selectedFacets,facet),colorFillStyle,drawlines,colorOutlineStyle);if(drawNormals){drawLine3D(facet。normal,magenta);}}}}functiondrawPolygon3d(points,isClosed,isFill,fillColor,isOutline,outlineColor){varresult〔〕;if(points。length0){for(vari0;ipoints。length;i){result。push(To2D(points〔i〕));}drawPolygon(result,isClosed,isFill,fillColor,isOutline,outlineColor);}}functiondrawPolygon(points,isClosed,isFill,fillColor,isOutline,outlineColor,lineThickness){if(points。length0){isClosedisClosed?isClosed:false;isFillisFill?isFill:false;if(isOutlineundefined){isOutlinetrue;}if(lineThicknessundefined){lineThickness1;}if(outlineColorundefined){outlineColorlineColor;}ctx。beginPath();ctx。lineWidthlineThickness;ctx。lineCapround;ctx。strokeStyleoutlineColor;if(isFill){ctx。fillStylefillColor;}ctx。moveTo(points〔0〕。x,points〔0〕。y);for(vari1;ipoints。length;i){ctx。lineTo(points〔i〕。x,points〔i〕。y);}if(isClosed){ctx。lineTo(points〔0〕。x,points〔0〕。y);}if(isFill){ctx。fill();}if(isOutline){ctx。stroke();}}}4、构建WebGL模型
  因此2D编辑视图的制作相当简单。WebGL视图的制作有点困难,将在以后的文章中更深入地讨论。我只会展示一些将我们的JavaScript数据结构绑定到我们模型的WebGL表示的代码。有五个基本元素必须被缓冲并绑定到WebGL。这是完成这项工作的主要功能。functionbindModelGL(){bindVerticesGL();bindColorsGL();bindVertexIndicesGL();bindTextureCoordinatesGL();bindNormalsGL();}
  将颜色绑定到我们的模型。每个立方体只能是一种颜色。每个方面都有一个指向其父多维数据集的指针。请注意,就我们的目的而言,多维数据集只是一个分面列表,它可能是也可能不是实际的多维数据集。所有面的列表将为我们提供每个顶点的正确颜色。每个顶点需要4个元素:R、G、B和A(表示透明度的alpha通道)。我们为A使用1。0,因此我们的WebGL模型将始终是不透明的。functionbindColorsGL(){if(isGLgl!null){vargeneratedColors〔〕;for(vari0;iallFacets。length;i){varfallFacets〔i〕;varccolor2FromColor(f。cube。color);varb〔〕;b。push(c。R);b。push(c。G);b。push(c。B);b。push(1。0);repeateachcolor4timesforthe4verticesofeachfacetfor(vars0;s4;s){generatedColors。push(b〔0〕);generatedColors。push(b〔1〕);generatedColors。push(b〔2〕);generatedColors。push(b〔3〕);}}cubeVerticesColorBuffergl。createBuffer();gl。bindBuffer(gl。ARRAYBUFFER,cubeVerticesColorBuffer);gl。bufferData(gl。ARRAYBUFFER,newFloat32Array(generatedColors),gl。STATICDRAW);}}
  我们必须绑定刻面法线,以便WebGL可以对模型进行着色。请注意,对于每个面法线,我们只需要3个数字。这是因为WebGL只关心法线的方向,而不关心它在空间中的位置。
  这里的一个具体问题是Subsurfer支持Phong着色,这需要顶点法线。如果你认为每个小平面法线垂直于小平面表面,那么顶点法线是包含该顶点的所有小平面的法线的平均值。因此,当Phong着色生效时,必须计算顶点法线。我们不在2D投影中使用这些,因为我们只做平面着色,所以我们只需要小平面法线。但是WebGL中的Phong着色需要顶点法线。如果我们在WebGL中进行平面着色,则不必计算顶点法线。在平面着色的情况下,我们只是使用面法线作为每个顶点的法线。functionbindNormalsGL(){if(isGLgl!null){cubeVerticesNormalBuffergl。createBuffer();gl。bindBuffer(gl。ARRAYBUFFER,cubeVerticesNormalBuffer);varvertexNormals〔〕;for(q0;qallFacets。length;q){varfallFacets〔q〕;if(f。normal1){f。normalCalculateNormal(f);}}if(fastVertexNormalMethod){if(isSmoothShading()){allSortedPointsgetFacetPointsAndSetUpBackPointers(allFacets);sortPointsByXYZ(allSortedPoints);stageVertexNeighborFacets(allSortedPoints);}}if(isSmoothShading()){for(q0;qallFacets。length;q){varfallFacets〔q〕;for(varj0;jf。points。length;j){varpf。points〔j〕;varvnp。vertexNormal;if(vnundefined){vncalculateVertexNormal(p,allFacets);p。vertexNormalvn;}vertexNormals。push((vn。end。xreductionFactor)(vn。start。xreductionFactor));vertexNormals。push((vn。end。yreductionFactor)(vn。start。yreductionFactor));vertexNormals。push((vn。end。zreductionFactor)(vn。start。zreductionFactor));}}}else{for(q0;qallFacets。length;q){varfallFacets〔q〕;for(vari0;i4;i){vertexNormals。push((f。normal。end。xreductionFactor)(f。normal。start。xreductionFactor));vertexNormals。push((f。normal。end。yreductionFactor)(f。normal。start。yreductionFactor));vertexNormals。push((f。normal。end。zreductionFactor)(f。normal。start。zreductionFactor));}}}gl。bufferData(gl。ARRAYBUFFER,newFloat32Array(vertexNormals),gl。STATICDRAW);}}
  我们必须绑定模型中的每个顶点。尽管WebGL需要三角形而不是四边形才能正常工作,但不必复制顶点,因为我们将在顶点缓冲区中提供索引列表。一些索引将被重复,这给了我们三角形。functionbindVerticesGL(){cubeVerticesBuffergl。createBuffer();gl。bindBuffer(gl。ARRAYBUFFER,cubeVerticesBuffer);varvertices〔〕;for(vari0;iallFacets。length;i){varfallFacets〔i〕;for(varj0;jf。points。length;j){varpoint3df。points〔j〕;vertices。push(point3d。xreductionFactor);vertices。push(point3d。yreductionFactor);vertices。push((point3d。zreductionFactor));}}gl。bufferData(gl。ARRAYBUFFER,newFloat32Array(vertices),gl。STATICDRAW);}
  在这里,我们构建顶点索引缓冲区并将其绑定到WebGL。索引模式0、1、2后跟0、2、3将我们的四个面顶点划分为两个三角形。functionbindVertexIndicesGL(){cubeVerticesIndexBuffergl。createBuffer();gl。bindBuffer(gl。ELEMENTARRAYBUFFER,cubeVerticesIndexBuffer);varcubeVertexIndices〔〕;vart0;for(vari0;iallFacets。length;i){cubeVertexIndices。push(t0);cubeVertexIndices。push(t1);cubeVertexIndices。push(t2);cubeVertexIndices。push(t0);cubeVertexIndices。push(t2);cubeVertexIndices。push(t3);t4;}gl。bufferData(gl。ELEMENTARRAYBUFFER,newUint16Array(cubeVertexIndices),gl。STATICDRAW);}
  我们模型中的每个顶点都有X、Y、Z表示空间位置,加上另外两个坐标U和V,它们是纹理图像的偏移量。U和V值介于0和1之间。对于复杂的形状,我们会自动分配U和V坐标,就好像纹理被包裹在图像周围一样。这是由assignPolarUV2()函数完成的。functionbindTextureCoordinatesGL(){for(vari0;icubes。length;i){assignPolarUV2(cubes〔i〕,i);}cubeVerticesTextureCoordBuffergl。createBuffer();gl。bindBuffer(gl。ARRAYBUFFER,cubeVerticesTextureCoordBuffer);vartextureCoordinates〔〕;for(vari0;iallFacets。length;i){if(isPolarUV){varfallFacets〔i〕;textureCoordinates。push(f。points〔0〕。u);textureCoordinates。push(f。points〔0〕。v);textureCoordinates。push(f。points〔1〕。u);textureCoordinates。push(f。points〔1〕。v);textureCoordinates。push(f。points〔2〕。u);textureCoordinates。push(f。points〔2〕。v);textureCoordinates。push(f。points〔3〕。u);textureCoordinates。push(f。points〔3〕。v);}else{textureCoordinates。push(0。0);textureCoordinates。push(0。0);textureCoordinates。push(1。0);textureCoordinates。push(0。0);textureCoordinates。push(1。0);textureCoordinates。push(1。0);textureCoordinates。push(0。0);textureCoordinates。push(1。0);}}gl。bufferData(gl。ARRAYBUFFER,newFloat32Array(textureCoordinates),gl。STATICDRAW);}5、平面着色器
  当直接处理WebGL时,有必要编写自己的着色器。这些是用称为GLSL的语言编写的。每个着色器都必须有一个main()过程。着色器是一个在计算机图形芯片上编译和加载的小程序。
  着色器包含在scriptHTML文件的标签中,可以按名称寻址。如果您为模型使用纹理而不是纯色,则需要不同的着色器。Subsurfer具有用于颜色和纹理的平面着色器,以及用于颜色和纹理的Phong(平滑)着色器。还包括几个古怪的自定义着色器。在这里,我将只提到纯色情况下的平面着色器和Phong着色器。
  这是纯色的平面着色器。您必须提供顶点着色器和片段着色器。这适用于您实现的每种类型的着色。顶点着色器为您提供每个顶点的颜色。片段着色器可以在顶点之间进行插值以创建更平滑的外观。它基本上为每个单独的像素着色。
  下面的平面着色器的外观与我们在JavaScript中构建的3D投影非常相似,它显示在编辑窗口中。如果你看一下这个顶点着色器在做什么,它实际上和我们之前在JavaScript函数中看到的计算是一样的shadeFacet()。它采用顶点法线(在这种情况下,与小平面法线相同)和光源方向矢量之间的角度(点积),并使用它来使小平面颜色变亮或变暗。但是着色器可以更快地完成它,因为它在大规模并行设备上运行。此外,它还考虑了光的颜色,以及定向光和环境光。请注意,在此着色器中,灯光颜色和方向是硬编码的。
  这里的片段着色器并没有做太多,它只是一个传递。这是因为平面着色器没有插值或平滑处理,因此平面上的所有像素都可以被着色为相同的颜色。6、Phong着色器
  Phong着色提供更平滑的外观,因为它在顶点之间进行插值以单独着色每个像素。颜色Phong着色器如下所示。
  请注意,顶点着色器并没有发生太多事情。大多数动作都发生在片段着色器中,因为我们要计算每个单独的像素。关于顶点着色器最有趣的一点是,转换后的顶点法线被声明为可变的。这将导致它为片段着色器中的每个像素进行平滑插值。
  所以这个片段着色器实际上对每个像素使用不同的法线。您看不到任何明确的代码来执行此操作,因为它内置于GLSL语言和可变类型中。与平面着色器一样,环境光和定向光的颜色是硬编码的,光的方向也是如此。此外,使用光方向向量与顶点法线之间的角度计算颜色与平面着色器非常相似。这里的不同之处在于,计算发生在片段着色器中,对每个像素使用不同的插值法线值。这就是赋予光滑外观的原因。Phong着色器比平面着色器更慢,因为它必须进行更多的计算。
  关于Phong着色器的最后一件事是我已经实现了镜面反射。如果选中UI上的镜面反射复选框,则统一值specularUniform将设置为1。如果发生这种情况,只要光源和顶点法线之间的角度足够小,该像素的颜色就会自动设置为白色。这会产生使模型看起来有光泽的镜面高光。6、细分曲面算法
  我本来打算多说一些关于CatmullClark细分曲面算法以及我在JavaScript中的实现,但是这篇文章已经太长了,所以我将把它留到以后的文章中。但是,如果查看代码,你可以看到发生了什么。我只想说大部分动作发生在函数subpisionSurfaceProcessFacet()内,它通过计算称为重心的加权平均值来细分单个面。使用计时器在三个函数中实现该算法的原因是,我可以在屏幕底部绘制一个进度指示计。我不得不这样做,因为JavaScript中没有真正的线程。该算法采用一个分面列表并将其替换为一个列表,其中每个分面都已被四个新分面替换。请注意,当模型中有孔时必须小心。位于此类孔边界上的刻面被视为特殊情况。functionstartSubpision(solid){informUser(Subpiding,pleasewait。。。);subpSurfaceLoopCounter0;varfacetssolid。facets;solidToSubpidesolid;isSubpidingtrue;if(solid。nSubpide0){solid。previousFacetLists。push(solid。facets);}for(vari0;ifacets。length;i){facets〔i〕。edgesgetFacetLines(facets〔i〕);facets〔i〕。averagePoint3DaverageFacetPoint(facets〔i〕。points);}findFacetNeighborsAndAdjacents(facets);for(vari0;ifacets。length;i){varfacetfacets〔i〕;for(varj0;jfacet。edges。length;j){varedgefacet。edges〔j〕;varlist〔〕;list。push(edge。start);list。push(edge。end);if(edge。parentFacet!1edge。adjacentFacet!1){list。push(edge。parentFacet。averagePoint3D);list。push(edge。adjacentFacet。averagePoint3D);}edge。edgePointaverageFacetPoint(list);}}subpTimerIdsetTimeout(subpisionSurfaceProcessFacet,0);newSubpFacets〔〕;}functionsubpisionSurfaceProcessFacet(){varfacetsolidToSubpide。facets〔subpSurfaceLoopCounter〕;varnEdge0;varneighborsAndCornersfacetNeighborsPlusFacet(facet);for(varj0;jfacet。points。length;j){varpfacet。points〔j〕;varfacepoints〔〕;varedgepoints〔〕;varfacetsTouchingPointfindFacetsTouchingPoint(p,neighborsAndCorners);for(varn0;nfacetsTouchingPoint。length;n){varffacetsTouchingPoint〔n〕;facepoints。push(averageFacetPoint(f。points));}varedgesTouchingPointfindEdgesTouchingPoint(p,facetsTouchingPoint);for(varm0;medgesTouchingPoint。length;m){varledgesTouchingPoint〔m〕;edgepoints。push(midPoint3D(l。start,l。end));}varonBorderfalse;if(facepoints。length!edgepoints。length){onBordertrue;vertexisonaborder}varFaverageFacetPoint(facepoints);varRaverageFacetPoint(edgepoints);varnfacepoints。length;varbarycenterroundPoint(pPoint(plusPoints(plusPoints(F,timesPoint(R,2)),timesPoint(p,n3)),n));varn1nEdge;if(n1facet。edges。length1){n10;}varn2n11;if(n20){n2facet。edges。length1;}if(onBorder){varborderAverage〔〕;varetpedgesTouchingPoint;for(varq0;qetp。length;q){varletp〔q〕;if(lineIsOnBorder(l)){borderAverage。push(midPoint3D(l。start,l。end));}}borderAverage。push(clonePoint3D(p));barycenteraverageFacetPoint(borderAverage);}varnewFacetnewFacet();newFacet。points。push(clonePoint3D(facet。edges〔n2〕。edgePoint));newFacet。points。push(clonePoint3D(barycenter));newFacet。points。push(clonePoint3D(facet。edges〔n1〕。edgePoint));newFacet。points。push(clonePoint3D(facet。averagePoint3D));newSubpFacets。push(newFacet);newFacet。cubesolidToSubpide;nEdge;}drawThermometer(solidToSubpide。facets。length,subpSurfaceLoopCounter);subpSurfaceLoopCounter;if(subpSurfaceLoopCountersolidToSubpide。facets。length){clearInterval(subpTimerId);finishSubpision(solidToSubpide);}else{subpTimerIdsetTimeout(subpisionSurfaceProcessFacet,0);}}functionfinishSubpision(parentShape){parentShape。nSubpide;parentShape。facetsnewSubpFacets;fuseFaster(parentShape);selectedFacets〔〕;selectedLines〔〕;selectedVertexes〔〕;sortFacets();setFacetCount(parentShape);isSubpidingfalse;alertUser();reloadSceneGL();draw();}
  原文链接:http:www。bimant。comblogjs3dmodelerdevtutorial

美文欣赏静守当下,随缘刚好指尖岁月,刹那经年。恍若,一切活在世间里的事物,不论最初是心生高兴,还是暂系悲情,是充满希望,还是触及失望最终,都将化作字符,一一写进生活的素笺,任笔迹九曲回肠、千回百转。落目……大s与韩国明星具俊晔的结婚照一出,论女生对自己够狠有多重要大s与韩国明星具俊晔的结婚照一出,论女生对自己够狠有多重要!论女生漂亮会打扮有多重要!台湾明星大s和韩国明星具俊晔,在6月22日晒出了他们的结婚照,看到他们那么甜蜜的样子……18岁的孙颖莎轻取丁宁,却没被胜利冲昏头脑,每一句话都得到应孙颖莎能在18岁的时候一跃进入国家队主力球员队列,我想与在澳大利亚公开赛中以40的比分战胜丁宁有着很大关系,那场比赛中,莎莎的表现十分神勇,轻取丁宁的同时,让我们看到了国乒未来……三星电视连续16年卖成世界第一一哥不是盖的在国内电视市场,小米连续13个季度居出货量第一,但放眼全球的话,则是另外一副光景。调研机构Omdia本周公布了今年一季度结束后,全球电视品牌的销售份额统计。总的来看……菱智PLUS旅行版出行打开格局新世界工作以后每天两点一线厌倦烦闷的日常通勤工作生活哪种车可以满足上班族的需求?一辆菱智PLUS旅行版让你打开出行格局新世界尽享舒适体验风光……建议女人35岁后,多吃5种含花青素的食物,抗衰老,常吃显年轻导语:建议女人:35岁后,多吃5种含花青素的食物,抗衰老,常吃显年轻对于女人来说,无论是哪个年龄段,都希望自己年轻漂亮,不希望老得快,但女人过了35岁后,身体就不如从前了……宝宝太小不能吃盐,那么多大之后就可以吃呢?父母们别弄错了小多多,今年已经三岁了,他可是特别喜欢重口味的食物,毕竟从他初吃辅食的时候就开始添加儿童酱油了。不过说来也奇怪,这个孩子明明嘴挺壮,可就是不怎么长个儿,看起来整个人也不结实。其……土豆最好吃的5种做法,简单营养,鲜香味美,学会了做给家人吃吧大家好,我是大磊,土豆是我们生活中比较常见的食材,一年四季皆可食用,而且价格也不贵,今天就给大家分享土豆最好吃的5种做法,简单营养,鲜香味美,喜欢的就一起来试试吧。一。【……3个版本,16年坚持,45万次下载,红警2最大玩家成果宣告收红色警戒2曾是许多人的童年,无论是1V7冷酷的大神,还是欺负简单电脑的新手,都是自己难以忘怀的美好回忆。现在想起来,红警2从2000年发售开始,如今已经过去了21个年头,……告诉十月(随笔)今天是十月三十一日,让我不得不向我爱的十月说告别,虽然心痛,但不得不向走进尾秋的盛宴挥手,对于他人来讲也可能不是永恒的告别,而我有可能,因为明天和意外谁也不知那个先来,何况进入……我们的祖先是否与恐龙共存过?那人类是否称霸一时呢?在侏罗纪和白垩纪时期,地球上随处可见凶狠残暴的两栖类爬行动物,这些庞然大物身披厚厚的假证,名片闪闪发光,爪牙尖利无比,甚至眼眶上都能长出犄角。他们开端了严酷的地球霸主争夺战,身……4天拿下11亿票房!独行月球中这9个配角功不可没沈腾、马丽主演的电影《独行月球》火了,仅仅4天就拿下了11亿票房,打破了5项影史纪录,目前各大平台预测电影最终票房会超40亿,豆瓣上也有接近17万人给出了评分,可以说这部电影注……
头发稀疏,该怎么办呢,朋友给出的一些建议,和大家分享一下熬夜真的很伤头发,会导致脱发的。如果熬夜不可避免,那就从其他方面下手注意防护。保持头发干净,熬夜会出油,如果不保持干净,油脂堵塞毛孔会造成脱发的。经常按摩头皮,熬夜……骁龙8旗舰提前清仓12G256G直降2300元,120W快充从国产手机品牌今两年的举动看,追求高端机的量产已经成为主流,不管是小米OV还是荣耀华为,他们已经把主要精力放在了高利润高端机身上。因为本品牌都去冲击高端了,所以他们把性价比的市……河南酒席,一锅20斤肉再配上50斤大白菜,网友评论不想吃现在有很多的酒席,都是需要大家参加的。大家也都知道,吃席的时候,都吃的比较的豪华,而且菜类比较丰富。但如果是大家,其实只有一餐的时候,是比较丰盛的。而且在那一天,会有很多人来帮……孤独,能让你晚景凄凉也能助你完成梦想孤独,是一种最昂贵的自由。孤独的人其灵魂也是孤独的,无人与之交流、无人倾听亦无对象可以倾诉,你灵魂孤独时思想却是毫无羁绊的,你的想象可以恣肆狂放,再也不用担心别人的窥视。孤独终……土耳其百万欧洲人准备来土耳其过冬!为什么土耳其吸引欧洲人?土耳其总统埃尔多安的顾问表示,许多冬天没有天然气可用的欧洲人将在土耳其过冬!预计将有百万欧洲居民前来!欧洲的天然气大部分储备不足,现在的储备量大概80左右,有的国家储备量更少,……热血传奇那些无敌外挂,你还记得吗?今天我要与各位谈一谈传奇中的外挂。一些外挂的操作是非常变态的。让很多玩家没有游戏体验。所以许多玩家选择离开游戏。在外挂首次发布时,它虽然很低调。之后外挂经过了长期的发展,……明日方舟BI1通关方法明日方舟BI1怎么打?风雪过境活动BI1关卡基本没有难道,只需要里面干员即可轻松通过,不过本次副本机制很有趣。下面带来明日方舟BI1速刷攻略,希望对小伙伴们有所帮助。明日……赛程近半数据榜篮网两人,保罗领衔助攻榜,勇士占据一席NBA常规赛继续进行,由于不少球队受疫情影响严重,球员触发健康与安全协议无法出席比赛,使得有些球队的比赛被迫延迟,细看之下发现比赛进行最多的球队已经打了42场,这也意味着新赛季……撕开唯美外衣,她暖到我了敢相信?有生之年,我居然会为两棵二次元的树流泪!那是伫立在悬崖边,同根相连的两棵文冠树。某日天降惊雷,将它们生生劈开。不管弟弟抓得多紧,哥哥还是掉下了悬崖。……真相来了!郭艾伦落选国家队事出有因?与赵睿当年如出一辙中国篮协正式公布了本次出征世预赛第五窗口期比赛的14人大名单,集训的17人中,孙铭徽、阿不都沙拉木和陆文博离队。内外线核心由周琦、赵睿领衔。其实,早前关于17人的集训名单……主板一年换新华硕重炮手WIFI主板R75700X套装1768华硕重炮手WIFI主板B550R75700X处理器套装日常售价为2399元。京东双十一大促期间,活动价为2048元,下单打9。6折,领取150元优惠券,到手价为1768元……英超悲喜夜三大豪强均取胜,诞生2大惨案,哈兰德连续10场破门昨晚今晨,英超打完了5场比赛,曼城、切尔西、热刺三大豪强纷纷出战。期间曼城主场40狂胜南安普顿,哈兰德连续10场破门;切尔西主场30大胜狼队,斩获三连胜;热刺客场10击败布莱顿……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网