Instanced Line Rendering in WebGL - wwwtyro.net
文章推薦指數: 80 %
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
延伸文章資訊
- 1Robust Polyline Rendering with WebGL - Cesium
Rendering with Line Primitives. We ran into a few problems when using line primitives (LINES, LIN...
- 2Instanced Line Rendering in WebGL - wwwtyro.net
In order to render our instanced lines, we're going to need two things - a WebGL context that sup...
- 3Drawing A Line Using gl.LINES - WebGL Programming
- 4WebGL Points, Lines, and Triangles
As mentioned in the first article WebGL draws points, lines, and triangles. It does this when we ...
- 5WebGL - Modes of Drawing - Tutorialspoint
WebGL 2D/3D Programming and Graphics Rendering For The Web. 28 Lectures 4 hours ... To draw a ser...