WebGL2 Fundamentals

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

Buffers are arrays of binary data you upload to the GPU. ... This tells WebGL we want to get data out of a buffer. If we don't turn on the attribute then ... English Deutsch 日本語 한국어 PortuguêsBrasileiro 简体中文 TableofContents WebGL2Fundamentals.org Fix,Fork,Contribute WebGL2Fundamentals Firstthingsfirst,thesearticlesareaboutWebGL2.Ifyou'reinterestedinWebGL1.0 pleasegohere.NotethatWebGL2isnearly100%backward compatiblewithWebGL1.Thatsaid,onceyouenable WebGL2youmightaswelluseitasitwasmeanttobeused.Thesetutorialsfollow thatpath. WebGLisoftenthoughtofasa3DAPI.Peoplethink"I'lluseWebGLandmagicI'llgetcool3d". InrealityWebGLisjustarasterizationengine.Itdrawspoints,lines,andtrianglesbased oncodeyousupply.GettingWebGLtodoanythingelseisuptoyoutoprovidecodetousepoints,lines, andtrianglestoaccomplishyourtask. WebGLrunsontheGPUonyourcomputer.AssuchyouneedtoprovidethecodethatrunsonthatGPU. Youprovidethatcodeintheformofpairsoffunctions.Those2functionsarecalledavertexshader andafragmentshaderandtheyareeachwritteninaverystrictlytypedC/C++likelanguagecalled GLSL.(GLShaderLanguage).Pairedtogethertheyarecalledaprogram. Avertexshader'sjobistocomputevertexpositions.Basedonthepositionsthefunctionoutputs WebGLcanthenrasterizevariouskindsofprimitivesincludingpoints,lines,ortriangles. Whenrasterizingtheseprimitivesitcallsasecondusersuppliedfunctioncalledafragmentshader. Afragmentshader'sjobistocomputeacolorforeachpixeloftheprimitivecurrentlybeingdrawn. NearlyalloftheentireWebGLAPIisaboutsettingupstateforthesepairsoffunctionstorun. Foreachthingyouwanttodrawyousetupabunchofstatethenexecuteapairoffunctionsbycalling gl.drawArraysorgl.drawElementswhichexecutesyourshadersontheGPU. AnydatayouwantthosefunctionstohaveaccesstomustbeprovidedtotheGPU.Thereare4ways ashadercanreceivedata. Attributes,Buffers,andVertexArrays BuffersarearraysofbinarydatayouuploadtotheGPU.Usuallybufferscontain thingslikepositions,normals,texturecoordinates,vertexcolors,etcalthough you'refreetoputanythingyouwantinthem. Attributesareusedtospecifyhowto pulldataoutofyourbuffersandprovidethemtoyourvertexshader. Forexampleyoumightputpositionsinabufferasthree32bitfloats perposition.Youwouldtellaparticularattributewhichbuffertopullthepositionsoutof,whattype ofdataitshouldpullout(3component32bitfloatingpointnumbers),whatoffset inthebufferthepositionsstart,andhowmanybytestogetfromonepositiontothenext. Buffersarenotrandomaccess.Insteadavertexshaderisexecutedaspecifiednumber oftimes.Eachtimeit'sexecutedthenextvaluefromeachspecifiedbufferispulled outandassignedtoanattribute. Thestateofattributes,whichbufferstouseforeachone,andhowtopulloutdata fromthosebuffersiscollectedintoavertexarrayobject(VAO). Uniforms Uniformsareeffectivelyglobalvariablesyousetbeforeyouexecuteyourshaderprogram. Textures Texturesarearraysofdatayoucanrandomlyaccessinyourshaderprogram.Themost commonthingtoputinatextureisimagedatabuttexturesarejustdataandcanjust aseasilycontainsomethingotherthancolors. Varyings Varyingsareawayforavertexshadertopassdatatoafragmentshader.Depending onwhatisbeingrendered,points,lines,ortriangles,thevaluessetonavarying byavertexshaderwillbeinterpolatedwhileexecutingthefragmentshader. WebGLHelloWorld WebGLonlycaresabout2things.Clipspacecoordinatesandcolors. YourjobasaprogrammerusingWebGListoprovideWebGLwiththose2things. Youprovideyour2"shaders"todothis.AVertexshaderwhichprovidesthe clipspacecoordinates,andafragmentshaderthatprovidesthecolor. Clipspacecoordinatesalwaysgofrom-1to+1nomatterwhatsizeyour canvasis.HereisasimpleWebGLexamplethatshowsWebGLinitssimplestform. Let'sstartwithavertexshader #version300es //anattributeisaninput(in)toavertexshader. //Itwillreceivedatafromabuffer invec4a_position; //allshadershaveamainfunction voidmain(){ //gl_Positionisaspecialvariableavertexshader //isresponsibleforsetting gl_Position=a_position; } Whenexecuted,iftheentirethingwaswritteninJavaScriptinsteadofGLSL youcouldimagineitwouldbeusedlikethis //***PSEUDOCODE!!*** varpositionBuffer=[ 0,0,0,0, 0,0.5,0,0, 0.7,0,0,0, ]; varattributes={}; vargl_Position; drawArrays(...,offset,count){ varstride=4; varsize=4; for(vari=0;i TheninJavaScriptwecanlookthatup varcanvas=document.querySelector("#c"); NowwecancreateaWebGL2RenderingContext vargl=canvas.getContext("webgl2"); if(!gl){ //nowebgl2foryou! ... NowweneedtocompilethoseshaderstoputthemontheGPUsofirstweneedtogetthemintostrings. YoucancreateyourGLSLstringsanywayyounormallycreatestringsinJavaScript.Forexamplebyconcatenating, byusingAJAXtodownloadthem,byputtingtheminnon-javascriptscripttags,orinthiscasein multilinetemplatestrings. varvertexShaderSource=`#version300es //anattributeisaninput(in)toavertexshader. //Itwillreceivedatafromabuffer invec4a_position; //allshadershaveamainfunction voidmain(){ //gl_Positionisaspecialvariableavertexshader //isresponsibleforsetting gl_Position=a_position; } `; varfragmentShaderSource=`#version300es //fragmentshadersdon'thaveadefaultprecisionsoweneed //topickone.highpisagooddefault.Itmeans"highprecision" precisionhighpfloat; //weneedtodeclareanoutputforthefragmentshader outvec4outColor; voidmain(){ //Justsettheoutputtoaconstantreddish-purple outColor=vec4(1,0,0.5,1); } `; Infact,most3DenginesgenerateGLSLshadersontheflyusingvarioustypesoftemplates,concatenation,etc. ForthesamplesonthissitethoughnoneofthemarecomplexenoughtoneedtogenerateGLSLatruntime. NOTE:#version300esMUSTBETHEVERYFIRSTLINEOFYOURSHADER.Nocommentsor blanklinesareallowedbeforeit!#version300estellsWebGL2youwanttouseWebGL2's shaderlanguagecalledGLSLES3.00.Ifyoudon'tputthatasthefirstlinetheshader languagedefaultstoWebGL1.0'sGLSLES1.00whichhasmanydifferencesandfarlessfeatures. Nextweneedafunctionthatwillcreateashader,uploadtheGLSLsource,andcompiletheshader. NoteIhaven'twrittenanycommentsbecauseitshouldbeclearfromthenamesofthefunctions whatishappening. functioncreateShader(gl,type,source){ varshader=gl.createShader(type); gl.shaderSource(shader,source); gl.compileShader(shader); varsuccess=gl.getShaderParameter(shader,gl.COMPILE_STATUS); if(success){ returnshader; } console.log(gl.getShaderInfoLog(shader)); gl.deleteShader(shader); } Wecannowcallthatfunctiontocreatethe2shaders varvertexShader=createShader(gl,gl.VERTEX_SHADER,vertexShaderSource); varfragmentShader=createShader(gl,gl.FRAGMENT_SHADER,fragmentShaderSource); Wethenneedtolinkthose2shadersintoaprogram functioncreateProgram(gl,vertexShader,fragmentShader){ varprogram=gl.createProgram(); gl.attachShader(program,vertexShader); gl.attachShader(program,fragmentShader); gl.linkProgram(program); varsuccess=gl.getProgramParameter(program,gl.LINK_STATUS); if(success){ returnprogram; } console.log(gl.getProgramInfoLog(program)); gl.deleteProgram(program); } Andcallit varprogram=createProgram(gl,vertexShader,fragmentShader); Nowthatwe'vecreatedaGLSLprogramontheGPUweneedtosupplydatatoit. ThemajorityoftheWebGLAPIisaboutsettingupstatetosupplydatatoourGLSLprograms. InthiscaseouronlyinputtoourGLSLprogramisa_positionwhichisanattribute. Thefirstthingweshoulddoislookupthelocationoftheattributefortheprogram wejustcreated varpositionAttributeLocation=gl.getAttribLocation(program,"a_position"); Lookingupattributelocations(anduniformlocations)issomethingyoushould doduringinitialization,notinyourrenderloop. Attributesgettheirdatafrombufferssoweneedtocreateabuffer varpositionBuffer=gl.createBuffer(); WebGLletsusmanipulatemanyWebGLresourcesonglobalbindpoints. YoucanthinkofbindpointsasinternalglobalvariablesinsideWebGL. Firstyoubindaresourcetoabindpoint.Then,allotherfunctions refertotheresourcethroughthebindpoint.So,let'sbindthepositionbuffer. gl.bindBuffer(gl.ARRAY_BUFFER,positionBuffer); Nowwecanputdatainthatbufferbyreferencingitthroughthebindpoint //three2dpoints varpositions=[ 0,0, 0,0.5, 0.7,0, ]; gl.bufferData(gl.ARRAY_BUFFER,newFloat32Array(positions),gl.STATIC_DRAW); There'salotgoingonhere.Thefirstthingiswehavepositionswhichisa JavaScriptarray.WebGLonotherhandneedsstronglytypeddatasothepart newFloat32Array(positions)createsanewarrayof32bitfloatingpointnumbers andcopiesthevaluesfrompositions.gl.bufferDatathencopiesthatdatato thepositionBufferontheGPU.It'susingthepositionbufferbecausewebound ittotheARRAY_BUFFERbindpointabove. Thelastargument,gl.STATIC_DRAWisahinttoWebGLabouthowwe'llusethedata. WebGLcantrytousethathinttooptimizecertainthings.gl.STATIC_DRAWtellsWebGL wearenotlikelytochangethisdatamuch. Nowthatwe'veputdatainabufferweneedtotelltheattributehowtogetdata outofit.FirstweneedtocreateacollectionofattributestatecalledaVertexArrayObject. varvao=gl.createVertexArray(); Andweneedtomakethatthecurrentvertexarraysothatallofourattributesettings willapplytothatsetofattributestate gl.bindVertexArray(vao); Nowwefinallysetuptheattributesinthevertexarray.Firstoffweneedtoturntheattributeon. ThistellsWebGLwewanttogetdataoutofabuffer.Ifwedon'tturnontheattribute thentheattributewillhaveaconstantvalue. gl.enableVertexAttribArray(positionAttributeLocation); Thenweneedtospecifyhowtopullthedataout varsize=2;//2componentsperiteration vartype=gl.FLOAT;//thedatais32bitfloats varnormalize=false;//don'tnormalizethedata varstride=0;//0=moveforwardsize*sizeof(type)eachiterationtogetthenextposition varoffset=0;//startatthebeginningofthebuffer gl.vertexAttribPointer( positionAttributeLocation,size,type,normalize,stride,offset) Ahiddenpartofgl.vertexAttribPointeristhatitbindsthecurrentARRAY_BUFFER totheattribute.Inotherwordsnowthisattributeisboundto positionBuffer.Thatmeanswe'refreetobindsomethingelsetotheARRAY_BUFFERbindpoint. TheattributewillcontinuetousepositionBuffer. NotethatfromthepointofviewofourGLSLvertexshaderthea_positionattributeisavec4 invec4a_position; vec4isa4floatvalue.InJavaScriptyoucouldthinkofitsomethinglike a_position={x:0,y:0,z:0,w:0}.Abovewesetsize=2.Attributes defaultto0,0,0,1sothisattributewillgetitsfirst2values(xandy) fromourbuffer.Thez,andwwillbethedefault0and1respectively. Beforewedrawweshouldresizethecanvastomatchitsdisplaysize.CanvasesjustlikeImageshave2sizes. Thenumberofpixelsactuallyinthemandseparatelythesizetheyaredisplayed.CSSdeterminesthesize thecanvasisdisplayed.YoushouldalwayssetthesizeyouwantacanvaswithCSSsinceitisfarfar moreflexiblethananyothermethod. Tomakethenumberofpixelsinthecanvasmatchthesizeit'sdisplayed I'musingahelperfunctionyoucanreadabouthere. Innearlyallofthesesamplesthecanvassizeis400x300pixelsifthesampleisruninitsownwindow butstretchestofilltheavailablespaceifit'sinsideaniframelikeitisonthispage. BylettingCSSdeterminethesizeandthenadjustingtomatchweeasilyhandlebothofthesecases. webglUtils.resizeCanvasToDisplaySize(gl.canvas); WeneedtotellWebGLhowtoconvertfromtheclipspace valueswe'llbesettinggl_Positiontobackintopixels,oftencalledscreenspace. Todothiswecallgl.viewportandpassitthecurrentsizeofthecanvas. gl.viewport(0,0,gl.canvas.width,gl.canvas.height); ThistellsWebGLthe-1+1clipspacemapsto0gl.canvas.widthforxand0gl.canvas.height fory. Weclearthecanvas.0,0,0,0arered,green,blue,alpharespectivelysointhiscasewe'remakingthecanvastransparent. //Clearthecanvas gl.clearColor(0,0,0,0); gl.clear(gl.COLOR_BUFFER_BIT); NextweneedtotellWebGLwhichshaderprogramtoexecute. //Tellittouseourprogram(pairofshaders) gl.useProgram(program); Thenweneedtotellitwhichsetofbuffersuseandhowtopulldataoutofthosebuffersto supplytotheattributes //Bindtheattribute/buffersetwewant. gl.bindVertexArray(vao); AfterallthatwecanfinallyaskWebGLtoexecuteourGLSLprogram. varprimitiveType=gl.TRIANGLES; varoffset=0; varcount=3; gl.drawArrays(primitiveType,offset,count); Becausethecountis3thiswillexecuteourvertexshader3times.Thefirsttimea_position.xanda_position.y inourvertexshaderattributewillbesettothefirst2valuesfromthepositionBuffer. The2ndtimea_position.xywillbesettothe2ndtwovalues.Thelasttimeitwillbe settothelast2values. BecausewesetprimitiveTypetogl.TRIANGLES,eachtimeourvertexshaderisrun3times WebGLwilldrawatrianglebasedonthe3valueswesetgl_Positionto.Nomatterwhatsize ourcanvasisthosevaluesareinclipspacecoordinatesthatgofrom-1to1ineachdirection. BecauseourvertexshaderissimplycopyingourpositionBuffervaluestogl_Positionthe trianglewillbedrawnatclipspacecoordinates 0,0, 0,0.5, 0.7,0, Convertingfromclipspacetoscreenspaceifthecanvassize happenedtobe400x300we'dgetsomethinglikethis clipspacescreenspace 0,0->200,150 0,0.5->200,225 0.7,0->340,150 WebGLwillnowrenderthattriangle.ForeverypixelitisabouttodrawWebGLwillcallourfragmentshader. OurfragmentshaderjustsetsoutColorto1,0,0.5,1.SincetheCanvasisan8bit perchannelcanvasthatmeansWebGLisgoingtowritethevalues[255,0,127,255]intothecanvas. Here'saliveversion clickheretoopeninaseparatewindow Inthecaseaboveyoucanseeourvertexshaderisdoingnothing butpassingonourpositiondatadirectly.Sincethepositiondatais alreadyinclipspacethereisnoworktodo.Ifyouwant3Dit'suptoyou tosupplyshadersthatconvertfrom3DtoclipspacebecauseWebGLisonly arasterizationAPI. Youmightbewonderingwhydoesthetrianglestartinthemiddleandgototowardthetopright. Clipspaceinxgoesfrom-1to+1.Thatmeans0isinthecenterandpositivevalueswill betotherightofthat. Asforwhyit'sonthetop,inclipspace-1isatthebottomand+1isatthetop.Thatmeans 0isinthecenterandsopositivenumberswillbeabovethecenter. For2Dstuffyouwouldprobablyratherworkinpixelsthanclipspaceso let'schangetheshadersowecansupplythepositioninpixelsandhave itconverttoclipspaceforus.Here'sthenewvertexshader -invec4a_position; +invec2a_position; +uniformvec2u_resolution; voidmain(){ +//convertthepositionfrompixelsto0.0to1.0 +vec2zeroToOne=a_position/u_resolution; + +//convertfrom0->1to0->2 +vec2zeroToTwo=zeroToOne*2.0; + +//convertfrom0->2to-1->+1(clipspace) +vec2clipSpace=zeroToTwo-1.0; + *gl_Position=vec4(clipSpace,0,1); } Somethingstonoticeaboutthechanges.Wechangeda_positiontoavec2sincewe're onlyusingxandyanyway.Avec2issimilartoavec4butonlyhasxandy. Nextweaddedauniformcalledu_resolution.Tosetthatweneedtolookupitslocation. varresolutionUniformLocation=gl.getUniformLocation(program,"u_resolution"); Therestshouldbeclearfromthecomments.Bysettingu_resolutiontotheresolution ofourcanvastheshaderwillnowtakethepositionsweputinpositionBuffersupplied inpixelscoordinatesandconvertthemtoclipspace. Nowwecanchangeourpositionvaluesfromclipspacetopixels.Thistimewe'regoingtodrawarectangle madefrom2triangles,3pointseach. varpositions=[ *10,20, *80,20, *10,30, *10,30, *80,20, *80,30, ]; gl.bufferData(gl.ARRAY_BUFFER,newFloat32Array(positions),gl.STATIC_DRAW); Andafterwesetwhichprogramtousewecansetthevaluefortheuniformwecreated. gl.useProgramislikegl.bindBufferaboveinthatitsetsthecurrentprogram.After thatallthegl.uniformXXXfunctionssetuniformsonthecurrentprogram. gl.useProgram(program); //Passinthecanvasresolutionsowecanconvertfrom //pixelstoclipspaceintheshader gl.uniform2f(resolutionUniformLocation,gl.canvas.width,gl.canvas.height); Andofcoursetodraw2trianglesweneedtohaveWebGLcallourvertexshader6times soweneedtochangethecountto6. //draw varprimitiveType=gl.TRIANGLES; varoffset=0; *varcount=6; gl.drawArrays(primitiveType,offset,count); Andhereitis Note:Thisexampleandallfollowingexamplesusewebgl-utils.js whichcontainsfunctionstocompileandlinktheshaders.Noreasontocluttertheexamples withthatboilerplatecode. clickheretoopeninaseparatewindow Againyoumightnoticetherectangleisnearthebottomofthatarea.WebGLconsiderspositiveYas upandnegativeYasdown.Inclipspacethebottomleftcorner-1,-1.Wehaven'tchangedanysigns sowithourcurrentmath0,0becomesthebottomleftcorner. Togetittobethemoretraditionaltopleftcornerusedfor2dgraphicsAPIs wecanjustfliptheclipspaceycoordinate. *gl_Position=vec4(clipSpace*vec2(1,-1),0,1); Andnowourrectangleiswhereweexpectit. clickheretoopeninaseparatewindow Let'smakethecodethatdefinesarectangleintoafunctionso wecancallitfordifferentsizedrectangles.Whilewe'reatit we'llmakethecolorsettable. Firstwemakethefragmentshadertakeacoloruniforminput. #version300es precisionhighpfloat; +uniformvec4u_color; outvec4outColor; voidmain(){ -outColor=vec4(1,0,0.5,1); *outColor=u_color; } Andhere'sthenewcodethatdraws50rectanglesinrandomplacesandrandomcolors. varcolorLocation=gl.getUniformLocation(program,"u_color"); ... //draw50randomrectanglesinrandomcolors for(varii=0;ii<50;++ii){ //Setuparandomrectangle setRectangle( gl,randomInt(300),randomInt(300),randomInt(300),randomInt(300)); //Setarandomcolor. gl.uniform4f(colorLocation,Math.random(),Math.random(),Math.random(),1); //Drawtherectangle. varprimitiveType=gl.TRIANGLES; varoffset=0; varcount=6; gl.drawArrays(primitiveType,offset,count); } } //Returnsarandomintegerfrom0torange-1. functionrandomInt(range){ returnMath.floor(Math.random()*range); } //Fillsthebufferwiththevaluesthatdefinearectangle. functionsetRectangle(gl,x,y,width,height){ varx1=x; varx2=x+width; vary1=y; vary2=y+height; //NOTE:gl.bufferData(gl.ARRAY_BUFFER,...)willaffect //whateverbufferisboundtothe`ARRAY_BUFFER`bindpoint //butsofarweonlyhaveonebuffer.Ifwehadmorethanone //bufferwe'dwanttobindthatbufferto`ARRAY_BUFFER`first. gl.bufferData(gl.ARRAY_BUFFER,newFloat32Array([ x1,y1, x2,y1, x1,y2, x1,y2, x2,y1, x2,y2]),gl.STATIC_DRAW); } Andhere'stherectangles. clickheretoopeninaseparatewindow IhopeyoucanseethatWebGLisactuallyaprettysimpleAPI. Okay,simplemightbethewrongword.Whatitdoesissimple.Itjust executes2usersuppliedfunctions,avertexshaderandfragmentshaderand drawstriangles,lines,orpoints. Whileitcangetmorecomplicatedtodo3Dthatcomplicationis addedbyyou,theprogrammer,intheformofmorecomplexshaders. TheWebGLAPIitselfisjustarasterizerandconceptuallyfairlysimple. Wecoveredasmallexamplethatshowedhowtosupplydatainanattributeand2uniforms. It'scommontohavemultipleattributesandmanyuniforms.Nearthetopofthisarticle wealsomentionedvaryingsandtextures.Thosewillshowupinsubsequentlessons. BeforewemoveonIwanttomentionthatformostapplicationsupdating thedatainabufferlikewedidinsetRectangleisnotcommon.Iusedthat examplebecauseIthoughtitwaseasiesttoexplainsinceitshowspixelcoordinates asinputanddemonstratesdoingasmallamountofmathinGLSL.It'snotwrong,there areplentyofcaseswhereit'stherightthingtodo,butyoushouldkeepreadingtofindout themorecommonwaytoposition,orientandscalethingsinWebGL. Ifyou're100%newtoWebGLandhavenoideawhatGLSLisorshadersorwhattheGPUdoes thencheckoutthebasicsofhowWebGLreallyworks. Youmightalsowanttotakealookatthis interactivestatediagram foranotherwayofunderstandinghowWebGLworks. Youshouldalso,atleastbrieflyreadabouttheboilerplatecodeusedhere thatisusedinmostoftheexamples.Youshouldalsoatleastskim howtodrawmultiplethingstogiveyousomeidea ofhowmoretypicalWebGLappsarestructuredbecauseunfortunatelynearlyalltheexamples onlydrawonethingandsodonotshowthatstructure. Otherwisefromhereyoucangoin2directions.Ifyouareinterestedinimageprocessing I'llshowyouhowtodosome2Dimageprocessing. Ifyouareinterestinginlearningabouttranslation, rotationandscalethenstarthere. English Deutsch 日本語 한국어 PortuguêsBrasileiro 简体中文 Fundamentals HowtouseWebGL2 Fundamentals HowItWorks ShadersandGLSL WebGL2StateDiagram WebGL2vsWebGL1 What'snewinWebGL2 MovingfromWebGL1toWebGL2 DifferencesfromWebGLFundamentals.orgtoWebGL2Fundamentals.org ImageProcessing ImageProcessing ImageProcessingContinued 2Dtranslation,rotation,scale,matrixmath 2DTranslation 2DRotation 2DScale 2DMatrices 3D Orthographic3D 3DPerspective 3D-Cameras 3D-MatrixNaming Lighting DirectionalLighting PointLighting SpotLighting StructureandOrganization LessCode,MoreFun DrawingMultipleThings SceneGraphs Geometry 3DGeometry-Lathe Loading.objfiles Loading.objw.mtlfiles Textures Textures DataTextures Using2orMoreTextures CrossOriginImages PerspectiveCorrectTextureMapping PlanarandPerspectiveProjectionMapping RenderingToATexture RendertoTexture Shadows Shadows Techniques 2D 2D-DrawImage 2D-MatrixStack Sprites 3D Cubemaps Environmentmaps Skyboxes Skinning Fog Picking(clickingonstuff) Text Text-HTML Text-Canvas2D Text-UsingaTexture Text-UsingaGlyphTexture GPGPU GPGPU Tips SmallestPrograms DrawingWithoutData Shadertoy PullingVertices Optimization IndexedVertices(gl.drawElements) InstancedDrawing Misc SetupAndInstallation Boilerplate ResizingtheCanvas Animation Points,Lines,andTriangles MultipleViews,MultipleCanvases VisualizingtheCamera WebGL2andAlpha 2Dvs3Dlibraries Anti-Patterns WebGL2MatricesvsMathMatrices PrecisionIssues Takingascreenshot PreventtheCanvasBeingCleared GetKeyboardInputFromaCanvas UseWebGL2asBackgroundinHTML CrossPlatformIssues QuestionsandAnswers Reference Attributes TextureUnits Framebuffers readPixels References HelperAPIDocs TWGL,AtinyWebGLhelperlibrary github Issue/Bug?Createanissueongithub. Use

codegoeshere
forcodeblocks commentspoweredbyDisqus



請為這篇文章評分?