WebGL tutorial: image processing - posts in a row / Habr

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

WebGL is a web standard for low-level 3D graphics API. It allows you to run your code directly on GPU, giving you all it's power. You write all ... Allstreams Development Admin Design Management Marketing PopSci SearchProfilePulltorefresh ddone 8May2020at14:04WebGLtutorial:imageprocessingJavaScript*WebGL* Sandbox InthistutorialyouwilllearnhowtouseWebGLforimageprocessing.Wewillcoverbasicstufflikeinitialization,textureloading,andsimplefragmentshaders.IwilltrytocoverallaspectsofinteractionwithWebGLbutyoushouldhavedecentunderstandingofvanilajavascript.Ifyouwantmoreindepthexplanations,thereisagoodbookcalled"LearnWebGL",checkitout. Hereareexamplesofwhatwewilldo: Imagetwist Imageblur SOURCECODE Ifyouwanttoskiptheoryandbuildsetupclickhere. Theory Everybodywhotriestoimplementgoodgraphicsquicklyunderstandshenowhassevereperformanceissues.Theamountofcomputationrequiredtoproduceanydecentsceneissimplyhuge.Youneedtoprocesseverypolygon(somemodelshavethousandsofthem)toprojectthemonscreen.Thenyouneedtorasterizethem.Applytextures,shading,reflectionsforeverypixelonthescreen.Andyoualsoneedtodoallofthisstuffatleast30timesinasecond.CPUsjustcan'thandlethisverywell.Especiallywhenusingsomescriptinglanguagewithlotsofoverheadforeveryoperation. LuckilypeoplefoundasolutionandinventedGPUs.Allofthetasksdescribedabovearehighlyparallelintheirnature.Polygonsandpixelsusuallydonotdependoneachotherandcanbeeasily(andmuchmoreefficiently)processedatthesametime.GPUsareespeciallygoodatthis.Whilemodernprocessorsusuallyhave4-8cores,anydecentgraphicscardhasthousandsofthem.TheyaremuchlesscomplexthenCPUcoresandhighlyoptimizedforspecific3D-relatedcalculations. WebGLisawebstandardforlow-level3DgraphicsAPI.ItallowsyoutorunyourcodedirectlyonGPU,givingyouallit'spower.YouwritealloftherenderingcodeinOpenGLShadingLanguageakaGLSL.It'snothardandverysimilartoC.ProgramswritteninGLSLusuallycalledshaders.TheyarecompiledandloadedintoagraphicscardinruntimeusingWebGLAPI. Preparation Technicallyyoudon'tneedtoinstallanything.Butwithoutaproperwebserveryouwon'tbeabletoloadimagesandadditionalscripts.Soit'sagoodideatohaveone.Iwillusewebpack-dev-server,it'seasytosetupanduse. Firstthingthatyouneedtodoiscreateanemptyfolderandrunnpminitinside.YoucanskipallofthequestionsfromNPM. Thenaddthislinestopackage.json { "scripts":{ "build":"webpack", "serve":"webpack-dev-server" }, "devDependencies":{ "copy-webpack-plugin":"^5.1.1", "html-webpack-plugin":"^4.2.0", "raw-loader":"^4.0.1", "webpack":"^4.43.0", "webpack-cli":"^3.3.11", "webpack-dev-server":"^3.10.3" } } Runnpminstallandcreateanewfilenamedwebpack.config.js. Hereisthecontentsofwebpackconfig: constpath=require('path'); constHtmlWebpackPlugin=require('html-webpack-plugin'); constwebpack=require('webpack'); constCopyWebpackPlugin=require('copy-webpack-plugin'); module.exports={ entry:'./index.js', output:{ path:path.resolve(__dirname,'dist'), filename:'index.js', }, plugins:[ newHtmlWebpackPlugin({ template:'index.html' }), newCopyWebpackPlugin([{ from:"styles/*.css", to:"" }]) ], mode:'development' }; Nowyoucanstartdevserverbyrunningnpmrunserveandopenhttp://localhots:8080.Iwillnotexplainthisalldeeply,asthisisnotthemaintopic.Everythingshouldworkoutofthebox. Code Let'sdealwithHTMLrightaway. Allweneedisacanvas,sohereitis. index.html

Webgl JustbasicHTMLtemplatewithacanvasandsliderthatwecanuse. Now,it'stimetoinitializeWebGL. index.js //Usingwebpack'srawloadertogetshadercodeasJSstring. //Muchmoreconvenientthanwritingthemdirectlyasstring //orloadinginruntime importvertfrom'!raw-loader!./vertex.glsl'; importfragfrom'!raw-loader!./fragment.glsl'; functionprepareWebGL(gl){ //Creatingandcompilingvertexshadr letvertSh=gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertSh,vert); gl.compileShader(vertSh); //Thislineisveryimport //Bydefaultifshadercompilationfails, //WebGLwillnotshowyouerror, //sodebuggingisalmostimpossible console.log(gl.getShaderInfoLog(vertSh)); //Creatingandcompilingfragmentshader letfragSh=gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragSh,frag); gl.compileShader(fragSh); //Thislineisveryimport //Bydefaultifshadercompilationfails, //WebGLwillnotshowyouerror, //sodebuggingisalmostimpossible console.log(gl.getShaderInfoLog(fragSh)); //LinkingprogramandpassingittoGPU letprog=gl.createProgram(); gl.attachShader(prog,vertSh); gl.attachShader(prog,fragSh); gl.linkProgram(prog); gl.useProgram(prog); gl.viewport(0,0,c.width,c.height); returnprog; } Errorchecksareomittedforclarity.So,howexactlyWebGLworks?It'ssortofaseparateworld.Youpasssomeprogramsanddatathere.ThenyouGPUexecutesyourprogramwithyourdataandgivesbackanimage. Everyprogramconsistsoftwoparts:vertexandfragmentshaders.Vertexshaderisappliedtoeveryvertexorjustpointinspace,thatyoupassin.Hereyouperformall3Dstuffsuchastransformations,projections,andclipping.ThenGPUrasterizesyourshapes,whichmeansfillingthemwithpixelsakafragments.Actuallyfragmentsarenotexactlypixels.Butforthescopeofthistutorialtheycanbeusedinterchangeably.Afterrasterizationeveryfragmentispassedthroughfragmentshadertodetermineit'scolor.Finallyeverythingisdrawntotheframebufferanddisplayedonthescreen. It'simportanttounderstandthatyourshadersareexecutedinparallel.Oneveryvertexandeveryfragmentindependently.Also,shadersproducevaluesnotbyreturningthembutsettingspecialvariables.Suchasgl_Positionandgl_FragColor,treatthemasreturnstatements. Forthesakeofsimplicitywewillmostlyplaywithfragmentshadersandstayina2Dworld. Hereissimplepass-throughvertexshader: vertex.glsl //Thisisourinputfromjsworld attributevec2coords; //Thisisoutputforthefragmentshader //varyingvariablesarealittlespecial //youwillseewhylater varyinghighpvec2vTextureCoord; voidmain(void){ //Textureandverticieshavedifferentcoordinatespaces //wedothistoinvertYaxis vTextureCoord=-coords; //Settingvertixpositionforshapeassembler //GLSLhasmanyconvenientvectorfunctions //hereweextending2Dcoordsvectorto4Dwith2values //0.0isaZcoordinate //1.1isaW,specialvalueneededfor3Dmath //justleaveit1fornow gl_Position=vec4(coords,0.0,1.0); } Laterwewillfillourcanvaswitharectangle.Thisisneededtohavesomeplaneforapplyingtextures.Tounderstandhowfragmentshaderworksyouneedtocomprehendvaryingvariables.Let'simagineyouhavetwoverticesinaline.Invertexshaderyousetsomevaryingvariabletoredcolorfromoneofthemandtogreenfromanother.Allfragmentsbetweenthesetwopointswillgetdifferentvalueswhenreadingthisvaryingvariable.Itwillsmoothlytransitionfromredtogreen.Soifyousetfragmentcolortothevalueofthisvariableyouwillgetsomethinglikethis.Suchbehavioriscalledinterpolation. Wewillalsouseacoupleofuniformvariables.Butnoneedtoworry,theyarequitesimple.Uniformvariablesarejustconstantparameters.Usefulforpassingglobalsettingandtextureids. fragment.glsl //Settingprecisionforfloatcalculations precisionmediumpfloat; //Thisisinputfromvertexshader varyinghighpvec2vTextureCoord; //Samplersareneedeedtoselecttextures //actuallyitsintegers uniformsampler2DuSampler; //Thiswilltellushowmuchtoscrewtheimage uniformfloatiter; vec2coords; floatx; floaty; floatl; voidmain(void){ //Gettingdistancefromorigin l=length(vTextureCoord); //Justrenamingtoreducetyping x=vTextureCoord[0]; y=vTextureCoord[1]; //Rotatingpointaroundorigin coords[0]=x*cos(iter*l)-y*sin(iter*l); coords[1]=x*sin(iter*l)+y*cos(iter*l); //TransformingcoordinatesfromGLspacetotexturespace //Allmathcanbedonedirectlytovectors coords=coords/2.0-0.5; //Fragmentshadermustsetthisvariable gl_FragColor=texture2D(uSampler,coords); } Thekeyhereistounderstandthatangleofpointrotationisdependentonit'sdistancetothecenter.Thiswillresultinacooleffectoftexturetwistingandsuckinginablackhole.Ifwedeletethe*lpart,thewholethingwilljustrotateevenly. Ourprogramisnowready,compiled,andloaded.Timetodealwiththedata.HereweareloadingvertexcoordinatestoWebGLmemory.Justtwotrianglestocoverthecanvassowehavesomesurfacefortexturing.BeawarethatWebGLcoordinatesworkdifferentfromcanvascoordinates.Unlikecanvas,it'soriginisatthecenterandallcoordinatesarenormalized.Sonomatterwhataspectratioyourcanvashas,XandYarealwaysin-1to1range.AlsoYcoordinateispointingupwards. index.js functionsetArrays(gl,prog){ //GettingWebGLbufferobject letvertex_buffer=gl.createBuffer(); //Thisis2trianglesthatformasquare //Eachtriangleconsistsof3points //Eachpointconsistsoftwonumbers:XandYcoordinates //GLcoordinatespacehasoriginincenter //andspansfrom-1to1onbothaxes //hereiswhyweneedtotransformourcoords //infragmentshader constvertices=[-1.0,-1.0,1.0,-1.0,-1.0,1.0, 1.0,1.0,1.0,-1.0,-1.0,1.0, ] //LoadingourdataasARRAY_BUFFER gl.bindBuffer(gl.ARRAY_BUFFER,vertex_buffer); gl.bufferData(gl.ARRAY_BUFFER,newFloat32Array(vertices),gl.STATIC_DRAW); //Findinglocationofvariable"coords"inGLmemory //andbindingARRAY_BUFFERtoit letcoord=gl.getAttribLocation(prog,"coords"); //VariablebindstolastbufferthatwaswrittentoGLmemory gl.vertexAttribPointer(coord,2,gl.FLOAT,false,0,0); gl.enableVertexAttribArray(coord); //ARRAY_BUFFERisnowfreeandcanbereused return[coord,vertex_buffer]; } Arraydataisloaded.Timefortextures. Bettertofindonethathassizesofpowersoftwo. WebGLabletoautomaticallygeneratemipmapsforsuchtexturesandscalethemproperly.Atfirstwecreatesimple1x1bluepixeltextureandimmediatelyreturnit.Later,whenimageloads,wereplacethispixelwithpropertexturedata. index.js functionloadTexture(gl,prog,url){ //Creating1x1bluetuxture consttexture=gl.createTexture(); constlevel=0; constinternalFormat=gl.RGBA; constwidth=1; constheight=1; constborder=0; constsrcFormat=gl.RGBA; constsrcType=gl.UNSIGNED_BYTE; constpixel=newUint8Array([0,0,255,255]);//opaqueblue(RGBA) //bindTextureworkssimilartobindBuffer gl.bindTexture(gl.TEXTURE_2D,texture); gl.texImage2D(gl.TEXTURE_2D,level,internalFormat, width,height,border,srcFormat,srcType, pixel); //Loadingimage constimage=newImage(); image.onload=function(){ gl.bindTexture(gl.TEXTURE_2D,texture); gl.texImage2D(gl.TEXTURE_2D,level,internalFormat, srcFormat,srcType,image); //WebGL1hasdifferentrequirementsforpowerof2images //vsnonpowerof2imagessocheckiftheimageisa //powerof2inbothdimensions. if(isPowerOf2(image.width)&&isPowerOf2(image.height)){ //Yes,it'sapowerof2.Generatemips. gl.generateMipmap(gl.TEXTURE_2D); }else{ //No,it'snotapowerof2.Turnoffmipsandset //wrappingtoclamptoedge gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR); } //Quikre-rendertodisplaynewtexture //Seeimplementationbelow render(0); }; //Triggeringload image.src=url; returntexture; } functionisPowerOf2(value){ return(value&(value-1))==0; } Preparationsdone.Nowistimetotieeverythingtogether. index.js functionmain(){ //GettingWebGLcontextfromcanvas constc=document.getElementById("c"); c.width=600; c.height=600; //Gettingslider constrange=document.getElementById("range"); constgl=c.getContext("webgl"); constprog=prepareWebGL(gl); constcoord=setArrays(gl,prog); consttexture=loadTexture(gl,prog,"img.jpg"); //Handletocontrolamountoftwist constiter=gl.getUniformLocation(prog,"iter"); constuSampler=gl.getUniformLocation(prog,'uSampler'); //Asissaidsamplersarejustintegers //Telltheshadertousetexture0 gl.uniform1i(uSampler,0) //Thisismainworkhorse render=(it)=>{ //Bindingtexturetoslot0 gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,texture); //Fillingscreenwithblackcolor gl.clearColor(0.0,0.0,0.0,1.0); gl.clear(gl.COLOR_BUFFER_BIT); //Settingitertoslidervalue gl.uniform1f(iter,it); //Triggeringwebglrender gl.drawArrays(gl.TRIANGLES,0,6); } render(0); range.addEventListener("input",(e)=>{ render(e.target.value); }) } main() That'sit.Youcantrytofollowalongandwriteityourself.Ifyoutotallystuckonsomething,theworkingsourcefromdemosaboveishere. Bonus:Blurshader GLSLisquiterestrictive.Forexampleyoucan'twriteloopswithnon-constantbounds.Thismayseemstrangeatfirst,butactuallyhavedecentreasoningbehindit.Mostoftherestrictionsareneededtohelpcompilersapplysomeaggressiveoptimizationtechniques. So,hereistheblurshader. fragment.glsl varyinghighpvec2vTextureCoord; uniformsampler2DuSampler; precisionmediumpfloat; uniformfloatiter; uniformfloatuTextureSize; voidmain(void){ floatpixel=1.0/uTextureSize; vec2coords; vec4color=vec4(0.0,0.0,0.0,0.0); floatdiv=(iter+1.0)*(iter+1.0)*4.0; for(inti=0;i<=100;i++){ if(float(i)>iter){ break; } for(intj=0;j<=100;j++){ if(float(j)>iter){ break; } coords=vTextureCoord.st/2.0-0.5; coords+=vec2(float(i),float(j))*pixel; color+=texture2D(uSampler,coords).rgba/div; coords=vTextureCoord.st/2.0-0.5; coords-=vec2(float(i),float(j))*pixel; color+=texture2D(uSampler,coords).rgba/div; } inti2=-i; for(intj=0;j<=100;j++){ if(float(j)>iter){ break; } coords=vTextureCoord.st/2.0-0.5; coords+=vec2(float(i2),float(j))*pixel; color+=texture2D(uSampler,coords).rgba/div; coords=vTextureCoord.st/2.0-0.5; coords-=vec2(float(i2),float(j))*pixel; color+=texture2D(uSampler,coords).rgba/div; } } gl_FragColor=vec4(color.rgb,1.0); } It'snotthebest,butitworks.AndIhopeyoucanlearnsomethingfromit. Ideas Youcanusethisprojectasatemplateforfutureexperiments. Herearesomecoolideas: SobelEdgedetection Hueshift Variuosphotofilters Goodluckinyourprogrammingjourney.Tags:webgljavascriptimageprocessingblurHubs: JavaScript WebGL Totalvotes3:↑3and↓0+3Views5.3KAddtobookmarks 5 5 Karma 0 Rating @ddone EmbeddedsoftwareengineerComments Comments2 Youraccount Login Signup Sections Posts News Hubs Companies Authors Sandbox Information Howitworks Forauthors Forcompanies Documents Agreement Confidential Services Ads Subscriptionplans Content Seminars Megaprojects FacebookTwitterTelegram Languagesettings About Support Returntooldversion ©2006–2022,Habr


請為這篇文章評分?