Instanced Line Rendering in WebGL - wwwtyro.net

文章推薦指數: 80 %
投票人數:10人

In order to render our instanced lines, we're going to need two things - a WebGL context that supports instancing, and an instance geometry for ... home/twitter/ github/ rss InstancedLineRenderingPartI RyeTerrell November18,2019 NativelinerenderinginWebGLleavesalottobedesired.There’sinconsistencyinthe implementation,soyoucan’ttrustthatthelinewidthyouwantwillbeavailabletoyou,for example.It’salsomissingsomeprettyimportantfeatures,likelinejoinsandcaps.Forthese reasons,quiteabitofworkhasbeendonetoimplementcustomlinerendering-summarizedexpertly byMattDeslauriersinhisexcellentpiece “DrawingLinesisHard”. Still,there’sonepieceoftheWebGLnativelinerenderingthatIratherlike-thedatastructure. AllyouneedtoshiptotheGPUaretheverticesinyourlinesegmentsorlinestrip-sooneortwo verticesperlinesegment.It’seasytoreasonaboutandefficienttocreate,shiptotheGPU,and update.WiththatinmindI’dliketopresentanotherlinerenderingtechniqueforyour consideration:instancedlinerendering.It’snotquiteassimpleasnativelinerendering,but it’sveryclose,it’sveryfast,andit’salotmoreflexibleandfeatureful. Inthispost,I’mgoingtoreviewnativelinerendering,thenpresentafewimplementationsof instancedlinerendering,includingoneimplementationthat allowsyoutorenderaline,completewithcapsandjoins,inasingledrawcall. TL;DR NativeWebGLlinerenderingkindasucks-itsimplementationisn’tuniformandyoudon’tgetline joinsorcaps. It’sniceinthatthedataformatissimple-justgiveitverticesandgo! Wecangetthebestofbothworldswithinstancedlinerendering. NativeWebGLLines We’llstartbytakingalookattwoprimitivesthatcanbeusedtodrawlinesnativelyinWebGL: GL_LINESandGL_LINE_STRIP.Givendataoftheform vertexData=[x0,y0,x1,y1,x2,y2,...,xn,yn]; GL_LINESwillrenderasetofindependentlinesegmentswhereeachsegmentiseachsequentialpair ofpointsinyourdata: TheGL_LINESprimitive. GL_LINE_STRIP,ontheotherhand,willrenderalinesegmentbetweeneveryvertexandits neighbors: TheGL_LINE_STRIPprimitive. I’llbeusingmyfavoriteWebGLlibrary,regl,forallof theexamplesinthispost.Inregl,wedefinecommandsthatwrapupallourshadercodeandrender stateintoasingleconfigurablefunction,calledadrawcommand.Here’swhatwritingasimple GL_LINESdrawcommandmightlooklike. First,we’llcreateourreglcontext: constREGL=require("regl"); constregl=REGL({canvas:canvas}); Thenthecommand-we’llstartwithsimplevertexandfragmentshaders: constglLines=regl({ vert:` precisionhighpfloat; attributevec2position; uniformmat4projection; voidmain(){ gl_Position=projection*vec4(position,0,1); }`, frag:` precisionhighpfloat; uniformvec4color; voidmain(){ gl_FragColor=color; }`, Thenwe’llbringinthepositionattribute: attributes:{ position:regl.prop("position") }, Andthecolorandprojectionuniforms: uniforms:{ color:regl.prop("color"), projection:regl.prop("projection") }, We’llsettheprimitivetoGL_LINESandthelinewidthtoaconfigurableproperty: primitive:"lines", lineWidth:regl.prop("width"), Andfinallydefinethenumberofverticeswe’llberenderingandtheviewport: count:regl.prop("count"), viewport:regl.prop("viewport") }); Next,weneedtogeneratethedatatosendtoourcommand(inthepositionprop).Theexamplecode hasaconveniencefunctionforgeneratingthisdatathatIwon’twasteyourtimewithhere.Suffice ittosaythatthedataisgeneratedintheformdescribedpreviously: constvertexData=[x0,y0,x1,y1,x2,y2,...,xn,yn]; Andthenwrappedinaregl.bufferbeforebeingshippedofftothereglcommand: constbuffer=regl.buffer(vertexData); Nowwehaveeverythingweneedtoinvokeourreglcommandandrenderourlines: glLines({ position:buffer, count:vertexData.length, width:regl.limits.lineWidthDims[1], color:[0,0,0,1], projection, viewport }); Notethatregl.limits.lineWidthDims[1]isthemaximumlinewidthsupportedbytheclient’s browser. Here’stheresult(mouseovertoanimate): GL_LINESinaction. Unfortunately,Idon’treallyknowwhatyou’reseeingrightnow!Asmentionedabove,Ican’tknow (asIwritethis)whatyourimplementationsupports,forexample,intermsofmaximumlinewidth.As ofthiswriting,Iseelinesthatare10pixelsinwidth,whichmayormaynotbewhatyousee. Withasimplechange,wecanusethesamecodetorenderourdataasalinestrip: primitive:"linestrip", Whichresultsinthefollowing(mouseovertoanimate): GL_LINE_STRIPinaction. Basicinstancedlinerendering Let’sstartbyreimplementingwhatWebGLalreadygivesus-somesimplelinesegmentsandstrips withnocapsorjoins.We’lltakeitalittlebitfartherandaddlinewidthsupport. Inanutshell,instancingisatechniqueyoucanusetorenderthesamegeometrylotsoftimes,with slightvariationstothatgeometryforeachinstanceofit.So,ifyouwantedtorenderamillion bunnies,eachwiththeirowncolorandrotationeveryframe,you’dwanttouseinstancing.Ifyou didn’t,you’dneedtoeithera)createasinglemillion-bunnygeometryandshipittotheGPUevery frameorb)executeamilliondrawcallseveryframe.Neitherofthoseoptionsaregoingtoperform well.Instead,you’dcreateasinglebunnygeometryandalistofthecolorsandrotationsforeach bunny,eachframe.Much,muchfaster. Inordertorenderourinstancedlines,we’regoingtoneedtwothings-aWebGLcontextthat supportsinstancing,andaninstancegeometryforeachsegment.Here’showwecancreatearegl contextwiththeinstancingextension: constregl=REGL({canvas:canvas,extensions:["ANGLE_instanced_arrays"]}); Nowtheinstancegeometry-here’swhatitlookslike: Theinstancegeometrywe’lluseforalinesegment. So,twotriangles,sixvertices,centeredontheoriginvertically,butshiftedtotherightone unithorizontally.The\(x\)-componentwillrepresentdistancealongthelengthofourlinesegment, andthe\(y\)-componentwillrepresentadistancealongitswidth.Let’sgoaheadanddefineit: constsegmentInstanceGeometry=[ [0,-0.5], [1,-0.5], [1,0.5], [0,-0.5], [1,0.5], [0,0.5] ]; Let’sworkonreplicatingGL_LINESbehavior.We’llstartwiththecommand,vertexshaderfirst: constinterleavedSegments=regl({ vert:` precisionhighpfloat; uniformfloatwidth; We’llpassinthreeattributes: positionisthepositionoftheinstancegeometryvertex pointAisthepositionofthefirstvertexofourlinesegment pointBisthepositionofthesecondvertexofourlinesegment attributevec2position,pointA,pointB; Andwe’llpassinourprojectionmatrixasauniform: uniformmat4projection; Nowthebrasstacks.Let’sstartbycalculatingthevectorfrompointAtopointB: voidmain(){ vec2xBasis=pointB-pointA; Andthenwe’llcalculatethe(normalized)perpendiculardirection: vec2yBasis=normalize(vec2(-xBasis.y,xBasis.x)); Recallthatthe\(x\)-componentofpositionisalongthelengthofourlinesegment.We’lltake ourpointAendpointandaddthevectorfrompointAtopointBmultipliedbyposition.x.Since position.xwillbeeitherzeroorone,we’llendupateitherpointAorpointB.Thenwe’lldo thesamethingforposition.y(whichisalongthewidthofourlinesegment),butusingthewidth ofthelineandtheperpendiculardirection.Addingthosetwooffsetsleavesusattheappropriate pointinspaceforthisvertex. Transformingtheinstancegeometryintothelinesegment. vec2point=pointA+xBasis*position.x+yBasis*width*position.y; Andfinallywe’llapplytheprojection: gl_Position=projection*vec4(point,0,1); }`, Thevertexshaderwasoneofthetwointerestingbitsofthiscommand.Definingourattributesis theother.Firstwe’lladdanattributeforourinstancedgeometry.Notethatwesetthedivisor tozero,whichindicatesthatthisattributeisidenticalforeveryinstance: attributes:{ position:{ buffer:regl.buffer(segmentInstanceGeometry), divisor:0 }, Nextwe’lldefinetheattributesforourlinevertices.There’safewthingstonotehere.First,we setthedivisortoone,whichindicatesthatthisattributehasonevalueperinstance.Second,we settheoffsetandstrideofeachpointattributesuchthateachinstancereceivestheappropriate endpoints.Third,we’reusingasinglebufferforbothattributes,withnounnecessarilyduplicated data: Usingdifferentoffsetsandstridesallowustoreusethesamesetofdatafordifferentattributes. pointA:{ buffer:regl.prop("points"), divisor:1, offset:Float32Array.BYTES_PER_ELEMENT*0, stride:Float32Array.BYTES_PER_ELEMENT*4 }, pointB:{ buffer:regl.prop("points"), divisor:1, offset:Float32Array.BYTES_PER_ELEMENT*2, stride:Float32Array.BYTES_PER_ELEMENT*4 } }, We’llpullinourwidth(whichwecannowpreciselyandconfidentlycontrol),color,and projectionuniforms: uniforms:{ width:regl.prop("width"), color:regl.prop("color"), projection:regl.prop("projection") }, Anddefineourvertexcountwhichisthenumberofverticesinourinstancegeometry: count:segmentInstanceGeometry.length, Thenwe’lldefinehowmanyinstanceswehave,whichisthenumberofsegments: instances:regl.prop("segments"), Andfinishupwiththeviewport: viewport:regl.prop("viewport") }); Nowwecaninvokeourcommand: interleavedSegments({ points:buffer, width:canvas.width/18, color:[0,0,0,1], projection, viewport, segments:vertexData.length/2 }); (Herewe’venamedthecommandinterleavedSegmentsbecausethe\(x-\)and\(y-\)componentsofour dataareinterleavedintoasinglebuffer.Therearecasesforwhichyouwouldlikelynotwantto organizeyourdatathisway-moreonthatinabit.) Here’stheresult(mouseovertoanimate): GL_LINESreplicatedwithinstancedlinerendering.Notethatwenowhavefullcontroloverthewidth. Withonlyafewminorchanges,wecanrenderlinestripsaswell.We’llmakeanewcommandwiththe moreappropriatenameinterleavedStrip: constinterleavedStrip=regl({ ... andwe’llchangetheoffsetandstrideofthepointattributessothateachinstanceisgettingthe appropriateendpoints: Theattributelayoutforlinestrips. attributes:{ ... pointA:{ buffer:regl.prop("points"), divisor:1, offset:Float32Array.BYTES_PER_ELEMENT*0 }, pointB:{ buffer:regl.prop("points"), divisor:1, offset:Float32Array.BYTES_PER_ELEMENT*2 } }, Everythingelseaboutthecommandwillremainthesame,includingthevertexandfragmentshaders. Notethatforthesamedataset,we’llbebumpingthenumberofsegments,sincealinestripwill giveus\(N-1\)linesegmentsinsteadofthe\(N/2\)wegotwithourinterleavedSegments command,where\(N\)isthenumberofvertices: interleavedStrip({ points:buffer, width:canvas.width/18, color:[0,0,0,1], projection, viewport, segments:vertexData.length-1 }); GL_LINE_STRIPreplicatedwithinstancedlinerendering. LineCapsandJoins Linejoinsaretheshapesusedtojointolinesegmentswheretheymeet,whilecapsaretheshapes usedattheendsoflines. There’sthreecommonlinejoins-miter,round,andbevel,andthreecommonlinecaps-butt,round, andsquare: Thevariouscommoncapsandjoins. Notethatwegetbuttcapsforfree,sincethat’swhattheendsofourlinesegmentsalreadylook like! Thestrategywe’lluseforrenderinglineswithjoinsandcapsistorenderthelines,joins,and capswithindependentinstanceddrawcalls,allusingthesameattributebuffer-noduplicatedata. Forexample,ifwe’rerenderingalinestripwithfoursegments,we’llrender Thefoursegmentsinasingleinstanceddrawcall Thethreejoinsinasingleinstanceddrawcall Thetwocapsintwodrawcalls I’memployingthisstrategyforsimplicityandconsistency,butitmaybepossibletoreducethe numberofdrawcallswithvertexshadersmorespecifictoyourpurpose.Forexample,youmightbe abletowriteavertexshaderthatwouldrenderthesegmentsandmiterjoinsinasingledrawcall (thoughI’mdubiousaboutalsofittingthecapsintoitexceptinthecaseofroundcapsandround joins,whichwe’llcoverlater). RoundJoins Forroundjoins,we’llcreateaninstancegeometryintheshapeofacircle,usingthe GL_TRIANGLE_FANprimitive.Here’sasimplefunctionthatgeneratesthegeometrybuffergivena reglcontextandaresolution: functioncircleGeometry(regl,resolution){ constposition=[[0,0]]; for(wedge=0;wedge<=resolution;wedge++){ consttheta=(2*Math.PI*wedge)/resolution; position.push([0.5*Math.cos(theta),0.5*Math.sin(theta)]); } return{ buffer:regl.buffer(position), count:position.length }; } We’llcallthecircleGeometryfunction: constroundBuffer=circleGeometry(regl,16); Nextwe’llcreatetherendercommand.Thevertexshaderistrivial-itscalesthegeometrybythe widthandpositionsit.Theonlysubtlepiecehereistheoffsetforthepointsattribute.We’ll skipthefirstpoint(sinceit’sanendpoint),andwhenwecallthecommand,we’llcallitwith \(N-2\)instances,where\(N\)isthenumberofverticesintheline. constroundJoin=regl({ vert:` precisionhighpfloat; attributevec2position; attributevec2point; uniformfloatwidth; uniformmat4projection; voidmain(){ gl_Position=projection*vec4(width*position+point,0,1); }`, ... attributes:{ position:{ buffer:roundBuffer.buffer, divisor:0 }, point:{ buffer:regl.prop("points"), divisor:1, offset:Float32Array.BYTES_PER_ELEMENT*2 } }, primitive:"trianglefan", count:roundBuffer.count, instances:regl.prop("instances"), }); Nextwe’llinvokethecommandtorendertheroundjoins: roundJoin({ points:buffer, width:canvas.width/18, color:[1,0,0,1], projection, viewport, instances:vertexData.length-2 }); Andthenwe’lldrawourlinestriptheusualway: interleavedStrip({ points:buffer, width:canvas.width/18, color:[0,0,0,1], projection, viewport, segments:vertexData.length-1 }); Here’stheexampleinaction(mouseovertoanimate,clicktotogglejoinhighlights): Instancedlinestripandroundjoins. MiterJoins Miterjoingeometry. We’llrendertwotrianglesforeachmiterjoin,asindicatedbythehighlightedareainthefigure above.Firstwe’lldefineourmiterjoininstancegeometry,whichisn’tsomuchageometryasaset ofcoefficientswe’llusetoindexourmiterbasisvectors(moreonthatinamoment): instanceMiterJoin=[ [0,0,0], [1,0,0], [0,1,0], [0,0,0], [0,1,0], [0,0,1] ]; Nextwe’llcreateourvertexshader.We’llpassinthethreeverticesofthetwoneighboringline segmentswe’reconsidering,ourinstancegeometryposition,thelinewidth,andtheprojection matrix: constmiterJoin=regl({ vert:` precisionhighpfloat; attributevec2pointA,pointB,pointC; attributevec3position; uniformfloatwidth; uniformmat4projection; We’llcalculatethemitervector: voidmain(){ vec2tangent=normalize(normalize(pointC-pointB)+normalize(pointB-pointA)); vec2miter=vec2(-tangent.y,tangent.x); Thenwe’llfindthetwoperpendicularvectorsforeachline: vec2ab=pointB-pointA; vec2cb=pointB-pointC; vec2abNorm=normalize(vec2(-ab.y,ab.x)); vec2cbNorm=-normalize(vec2(-cb.y,cb.x)); Thenwe’lldeterminethedirectionofthebend: floatsigma=sign(dot(ab+cb,miter)); Anduseittocalculatethebasisvectorsforthemitergeometry: vec2p0=0.5*width*sigma*(sigma<0.0?abNorm:cbNorm); vec2p1=0.5*miter*sigma*width/dot(miter,abNorm); vec2p2=0.5*width*sigma*(sigma<0.0?cbNorm:abNorm); Finallyweusethebasisvectorsandthepositionattributecoefficientstocalculatethefinal vertexposition: vec2point=pointB+position.x*p0+position.y*p1+position.z*p2; gl_Position=projection*vec4(point,0,1); }`, Thistimewe’llpassinthreedifferentviews,againtothesamebuffer: Reusingthelinevertexbufferforrenderingmiterjoins. attributes:{ position:{ buffer:regl.buffer(instanceMiterJoin), divisor:0 }, pointA:{ buffer:regl.prop("points"), divisor:1, offset:Float32Array.BYTES_PER_ELEMENT*0 }, pointB:{ buffer:regl.prop("points"), divisor:1, offset:Float32Array.BYTES_PER_ELEMENT*2 }, pointC:{ buffer:regl.prop("points"), divisor:1, offset:Float32Array.BYTES_PER_ELEMENT*4 } }, Andwe’llfinishitoffwiththeinstancegeometrycount: count:instanceMiterJoin.length, }); Therestofthecommandisidenticaltowhatwe’veusedpreviously. Finallywe’llinvoketheinterleavedStripandmiterJoincommandstorenderourline: interleavedStrip({ points:buffer, width:canvas.width/18, color:[0,0,0,1], projection, viewport, segments:vertexData.length-1 }); miterJoin({ points:buffer, width:canvas.width/18, color:[1,0,0,1], projection, viewport, instances:vertexData.length-2 }); Here’stheexampleinaction(mouseovertoanimate,clicktotogglejoinhighlights): Instancedlinestripandmiterjoins. BevelJoins Beveljoinsareverysimilartomiterjoins-we’llbelocatingpoints\(p_0\)and\(p_1\)inthe figurebelowandcreatingatrianglewiththealready-knownpoint\(B\).Firstwe’llcreatethe instancegeometry,whichagainislessageometryandmoreasetofcoefficientswe’llusewithour bevelbasisvectors: Beveljoingeometry. instanceBevelJoin=[[0,0],[1,0],[0,1]]; Nextwe’llstartourbeveljoincommand,whichagainwilltaketheendpointsofthetwosegmentsas vertexattributesinthevertexshader: constbevelJoin=regl({ vert:` precisionhighpfloat; attributevec2pointA,pointB,pointC; attributevec2position; uniformfloatwidth; uniformmat4projection; Asbefore,we’llcalculateatangentvectorandnormaltoit: voidmain(){ vec2tangent=normalize(normalize(pointC-pointB)+normalize(pointB-pointA)); vec2normal=vec2(-tangent.y,tangent.x); Thenwe’llcalculatetwoperpendicularvectorsforeachsegment: vec2ab=pointB-pointA; vec2cb=pointB-pointC; vec2abn=normalize(vec2(-ab.y,ab.x)); vec2cbn=-normalize(vec2(-cb.y,cb.x)); Andthedirectionofthebend: floatsigma=sign(dot(ab+cb,normal)); Andfinallycalculatethebasisvectorsforthebevelgeometryandthefinalvertexposition: vec2p0=0.5*sigma*width*(sigma<0.0?abn:cbn); vec2p1=0.5*sigma*width*(sigma<0.0?cbn:abn); vec2point=pointB+position.x*p0+position.y*p1; gl_Position=projection*vec4(point,0,1); }`, Everythingelseisthesameasthemiterjoincommand,we’lljustpassintheinstanceBevelJoin geometryinstead. Here’stheexampleinaction(mouseovertoanimate,clicktotogglejoinhighlights): RoundCaps We’llrendersolidcirclesforourroundcaps,andreusethecirclegeometryfromtheroundjoins. Thecommandissimple: constroundCap=regl({ vert:` precisionhighpfloat; attributevec2position; uniformvec2point; uniformfloatwidth; uniformmat4projection; voidmain(){ gl_Position=projection*vec4(point+width*position,0,1); }`, frag:` precisionhighpfloat; uniformvec4color; voidmain(){ gl_FragColor=color; }`, attributes:{ position:{ buffer:roundBuffer.buffer } }, uniforms:{ point:regl.prop("point"), width:regl.prop("width"), color:regl.prop("color"), projection:regl.prop("projection") }, primitive:"trianglefan", count:roundBuffer.count, viewport:regl.prop("viewport") }); We’renotevenusinginstancedrenderinghere,justthrowingcirclesonthescreenwithasingle drawcalleach.Thepositionofeachcapisnolongeranattribute,butratherstoredinthepoint uniform.Let’srenderourcapsfirst.Notethatwepassthefirstandlastvertextotheinvocations ofroundCapinthepointuniform: roundCap({ point:vertexData[0], width:canvas.width/18, color:[1,0,0,1], projection, viewport }); roundCap({ point:vertexData[vertexData.length-1], width:canvas.width/18, color:[1,0,0,1], projection, viewport }); Andfinallywe’llrenderthelinestripandbeveljoin: interleavedStrip({ points:buffer, width:canvas.width/18, color:[0,0,0,1], projection, viewport, segments:vertexData.length-1 }); bevelJoin({ points:buffer, width:canvas.width/18, color:[0,0,0,1], projection, viewport, instances:vertexData.length-2 }); Sincewe’rerenderingafullcircleforthecap,there’sgoingtobesomeoverdraw.Ifyou’dliketo avoidthat,youcanuseasemicircleinstead,justmakesureyoupassininformationaboutthe orientationofthesemicircleaswell. Here’swhattheroundcapswithbeveljoinslookslike(mouseovertoanimate,clicktotogglecap highlights): SquareCaps Nextupissquarecaps.Forthese,wecan’tgetawaywithnotorientingthem,sowe’llneedtopass inalittlemoredata.Foreachcap,we’llpassinthelineendpoint(beginningorend),andthe pointimmediatelyafterorbeforeitontheline,dependingonwhichcapwe’rerendering: Squarecapsareorientedalongthefirstandlastlinesegments. We’llreusesegmentInstanceGeometrysinceitisalreadypreparedinthewayweneed.Let’stakea lookatthevertexshader.We’llpassinthegeometrypositionandpointsAandB: constsquareCap=regl({ vert:` precisionhighpfloat; attributevec2position; uniformvec2pointA,pointB; uniformfloatwidth; uniformmat4projection; Thenwe’llcalculateourbasisvectorsandmultiplythembythewidthofthelineandthegeometry vertexpositiontorecoverthefinalvertexposition: voidmain(){ vec2xBasis=normalize(pointB-pointA); vec2yBasis=vec2(-xBasis.y,xBasis.x); vec2point=pointB+xBasis*0.5*width*position.x+yBasis*width*position.y; gl_Position=projection*vec4(point,0,1); }`, Therestofthecommandisasyou’dexpect: frag:` precisionhighpfloat; uniformvec4color; voidmain(){ gl_FragColor=color; }`, attributes:{ position:{ buffer:regl.buffer(segmentInstanceGeometry) } }, uniforms:{ pointA:regl.prop("pointA"), pointB:regl.prop("pointB"), width:regl.prop("width"), color:regl.prop("color"), projection:regl.prop("projection") }, count:segmentInstanceGeometry.length, viewport:regl.prop("viewport") }); Nowwecaninvokeourcommand,onceforeachcap: squareCap({ pointA:vertexData[1], pointB:vertexData[0], width:canvas.width/18, color:[1,0,0,1], projection, viewport }); squareCap({ pointA:vertexData[vertexData.length-2], pointB:vertexData[vertexData.length-1], width:canvas.width/18, color:[1,0,0,1], projection, viewport }); Here’swhatsquarecapswithbeveljoinslookslike(mouseovertoanimate,clicktotogglecap highlights): SpecialCase:RoundCapsandJoins Ifwewanttorenderroundcapsandjoins,wecanorganizeourgeometrysuchthatwecanrenderthe linestrip,joins,andcapsallinasingledrawcall.Ifindthisparticularlysatisfyingforthree reasons: It’sreallysimpletosetupandinvokeasingledrawcall. Ifyou’redrawcallbound,thiswillminimizethenumberofdrawcallsyouneedtomakeperline strip. It’shardtobeatlineswithroundcapsandjoinsintermsofapplicationflexibility.Youdon’t needtoworryabouttoo-sharpanglesasyoudowithmiterjoins.Allpointswithinthewidthof thelinearerendered,andnonewithout,unlikethebeveljoinandthebuttandsquarecaps.And sincethejoinsarethesameatanyangle,youcanjoinmultiplesegmentsatthesamejointwith noartifacts. Thefirstthingwe’llneedisaninstancegeometryforeachlinesegment.Theapproachwe’lltakeis toincludetherectangularpieceofthesegmentandasemicircleonbothends.Wherelinesegments join,thetwosemicircleswillformthejoinwithnogaps,andwillserveastheroundcapsatthe endsofourlinestrip.Insteadofstoringjustanx-andy-component,we’llalsouseaz-component thatwillindicatewhichendofthelinesegmenteachvertexbelongsto.Ifwemovethevertices leftandrightaccordingtothevalueoftheirz-component,thegeometrywilllooklikethis: Aninstancegeometryforrenderingroundcapsandjoinsinasingledrawcall. Alright,let’stakealookatthecodeforcreatingthegeometry.We’llwriteafunctionthattakes ourreglcontextandasemicircleresolution,andwe’llreturnanattributebufferandavertex count.Firstwe’llcreatethesimplerectangularsectionofourgeometry: functionroundCapJoinGeometry(regl,resolution){ constinstanceRoundRound=[ [0,-0.5,0], [0,-0.5,1], [0,0.5,1], [0,-0.5,0], [0,0.5,1], [0,0.5,0] ]; Notethateachvertexisgiventhreecomponentsinsteadoftwo.Thethirdcomponentdescribeswhich sideofthelinesegmentthatvertexbelongsto-zerofor“left”,onefor“right”.I’musing“left” and“right”here,butthetermreallymeans“proximitytothebeginningofthedataset”. Thenwe’lladdtheverticesforthe“left”semicircle(Notethattheverticesdefine\(z=0\)): for(letstep=0;step



請為這篇文章評分?