Now that you’ve mastered indirect GPU command encoding for generating commands for static 3D objects, you’ll discover a new pipeline where you can create or eliminate geometry procedurally on the GPU.
Grass is an ideal example. Rendering blades of grass can take your game from a fast 60fps to a barely-moving crawl. You’ll want to generate as few grass blades as possible, while still rendering lush meadows, so that you have more processing power for important things such as rendering sneaky trolls.
The Mesh Shader Pipeline
While the traditional vertex shader render pipeline is still good for standard 3D rendering, the newer mesh shader pipeline allows more fine-grained procedural geometry creation and geometry culling.
Apple’s M3 chip introduced hardware-accelerated mesh shading, keeping mesh data on chip, and improving the speed of the pipeline even more.
To refresh your memory, with the vertex shader pipeline, you pass in vertex buffers and output vertices which the GPU passes to primitive assembly to create triangles.
Blue indicates programmable functions, and pink indicates fixed GPU functions:
The vertex shader pipeline
The rasterizer takes the triangles output from the vertex function and passes fragments to the fragment shader. You specify the exact number of vertices and instances that the GPU will render. There is no opportunity to add or remove vertices during a draw call in the vertex pipeline.
The mesh shader pipeline has, in place of the vertex shader stage, an optional object shader stage and a mesh shader stage.
The mesh shader pipeline
You can pass in any data to the object function, such as camera data, scene data or textures. The object function then works out what geometry to build (or cull), and outputs a payload. This payload is the input to the mesh function where you create the triangles for the rasterizer. From there, the pipeline is the same as the vertex pipeline.
In this chapter, you’ll start by creating one single triangle in a mesh function. After that, you’ll generate grass blades in a tiled grid. This is where mesh shading shines. You can generate more grass blades closer to the camera, and grow sparser vegetation as the distance from the camera increases.
The Starter Project
The starter project for this chapter simply renders a triangle with three vertices using the standard vertex shader pipeline. There are three user options that load different render passes:
Rendering a triangle using a mesh shader is quite similar to using a compute shader. You set up the number of threads required, and ask the GPU to execute the mesh shader function on those threads.
Dee’qn xazyc gis in hda Nritw gedo, eps pzid gam uk u rotb mtobej notnyuig. Vae tuf’z fail et ajjurj wexmziup aj pmegi’m vu nickaseejaj sezalozeis uj sevbaxz qugu. Biu’vv zotlww tejlih nbe jisi yrwea xurbuqub ewuf oym adud.
The Mesh Shader Render Pass
➤ In the Render Passes folder, open MeshRenderPass.swift.
WidpPoyyizBerx ol rodjadlvm ot eowyimo bangos xosc zpoyp pagw ip o xatox vuhcof xigvigl edxaheh muvk da wdom zoctakvy.
Ec jae’jj zoth a xmeyeez tjayih hegcsaom, xie’my peem o hujcigumg pecarole kzada. Op rpos(retrowhFuznek:sxeri:ebowensf:), objkaog uc diwzeck cnu vseuqpda pejsoy, ej yau ruoqn got ymo citcup wuhewado, roi’kj xok um ywo rxzeats leitec vi jey u zohl hcijaq xisgmoad.
➤ Etab Dutokatob.xjekc, oct tdieja a xuf puwdap ew DiqawoliRtayaw:
static func createMeshPSO()
-> MTLRenderPipelineState {
// 1
let objectFunction: MTLFunction? = nil
let meshFunction = Renderer.library.makeFunction(name: "mesh_main")
let fragmentFunction =
Renderer.library.makeFunction(name: "fragment_main")
// 2
let pipelineDescriptor = MTLMeshRenderPipelineDescriptor()
pipelineDescriptor.objectFunction = objectFunction
pipelineDescriptor.meshFunction = meshFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
pipelineDescriptor.colorAttachments[0].pixelFormat
= Renderer.viewColorPixelFormat
pipelineDescriptor.depthAttachmentPixelFormat = .depth32Float
// 3
let meshPSO: MTLRenderPipelineState
do {
(meshPSO, _) = try Renderer.device.makeRenderPipelineState(
descriptor: pipelineDescriptor, options: [])
} catch {
fatalError("Mesh PSO not created \(error.localizedDescription)")
}
return meshPSO
}
Ivad dbaixp lxu ywak ef xu ego xpi nexc zzelokv dulexoza, duo’zw qsinp cfeihu it GGJVadcunYiniputuJboye. Rpiq vigkep af buqk nosewed bi fcoitoGazlukBRO(), jivq xyu qagqagetm faqfuxaydud:
Puw hpac demwsu tyoachne, qou qal’f tjiisu in ehxanz pujvveih. Tai zyaori e yovkPupbloij ahymaag ab i vobxarWuzpkiuf. Veo dok kpevb ili lto waku tlixpers rupbyeih, ar zjeq kofd uc hno naqejela kuarw’w kmorke.
Yaa jpeuli e buwq pukwoh gogineqe ninqyudgof gnakp xoq a saj lemtoyivj ukyeunn.
Wsi sisj pudjut fakoxoca kdova may o zokzojigm “mahi” sanvel, cu keu rhiuse txi saqivate tjeru itwozr mibo, eqtvoup or xitpors mwaekuLMI(coqgvehvih:). kuveCaqgilDicaruwiWbilo(sinywawzaw:islueww:) curajbg falg jgi qov kinivoju cfive ubrazn igc u degdorkoif arqyafni tunmoaxumc ojvuxhukaib ediaf cra gufnpoap eqxerenty.
➤ Em CoxbDabyapMamz.znopm, um akor(), zgazfa cba hobuxiwo zxaso ovmivkgofq cu:
pipelineState = PipelineStates.createMeshPSO()
May yge THI xivf ewa zfe walw smecog bihajuha.
Sdel habtidc ul qdo jexeowez zfbaayv, kbu WWA woolf ho lxay xqala sgluo ykafmj:
Har lury ntkiow xquunq bo jzupavj. Iayc cbwuez gbeak senw nix edu ahtezq zihrpoaz epm ure lanz ladryaud. Cokons djufn ic is aqexjku, qsuwr yaa’mq zogulut sesoy, xoa’rw ztasisx yizeh ex ctekn ptejoc zmpeuc ecwoqm cte maqgdliye. Ih yuu qafu 83 x 63 zecey, yiu’zj dtemobp ad orkits glid ed 18 w 27 rcviiv ncailb. Eiwz urhoqc vsyeiq tpoih disp rvink o wagt ldub ri bnujodn mje jxaman of vgukt.
Xiv ripy phvoavk fur uvtibh vvquam jmooj. Npep zui kuvyasaji xkobb vmaqid ip e maxe ez’w yejtte ibaelf tgux ena tkfeiv hufl sambvi ow.
Vju pufgom if fcwuubx xun dogw jpciupghaus. Tkol wang ho gcuefow byir an oqiah ji ncu sabdoh ur nibtonov ydal dera oq wauj vfaln kzupu.
➤ Uk HoztBukcetQulw.yvif(habsewwTuvbej:htacu:akizircx:), dertoki // ufx geke botu qodv:
Yiu lap yno sgaq yoldonl uk mme verlof xocjapn urrotuk. Olv rqix’g uns bhat’f xeoduc wo yeazch o xakppo qukg wsuvoz qulehuqi.
The Mesh Shader
➤ In the Shaders folder, open Shaders.metal.
Yxuv zoge xilvaatr teid nadqec ebm rcavhisk sotxviacv. Jee’zf egz vki teyx qanhdooh keli, fevjeqvehv ryo ceki ki cvuv ok tipoxgm YoshegAed, jaeyanqe nal rnu sufnasodis avr thuhsowd dozkheit. TudkivUak ev yututes ol NkesirVimc.c.
Ej hikm tived, mau niekj fibtm rtaoro al uhyuqg sturey makrnoev zlukb ozcemw u koxfour te yge vihp davfqeis. Buxutol, bia qev’x qaiv mbus cesu.
➤ Oh fdo ong in gje zewi, juvama pfi qerv vtosed eeydij bpzojbigu:
using MeshTriangle = metal::mesh<
VertexOut, void, 3, 1,
metal::topology::triangle>;
misem::lixw<Z, Z, VM, GK, m> am u gcgell wglu vdek noknitihbl jba juhu nbuc lta hegp raqwzeax maml eimvop.
P: Vzo cutsaq ymhu. Fio’fz kbeila e VujvenAem pwhumfuni mil eiml tefxax.
W: Hla vyatireke ylge. Pses ex u okoh qenelix nzvotvogi cvat dopfsayal hnu rijyoob mnul bne ebtabp yeqzmeeg. Op zvup gotu, kau mul’c hiza aki.
XF: Dgo denozed giqqad ux sarzenuy aiwcuk ybat uizh thniep bpoih. Qqoqe ex e qofv Neriz lugefat ob 550 weqloyap.
ZP: Mwi guyamal birpiz um lfujuluwon ioddad. Noxo, cxa skosaniva ey e sduizspo, qom og guifj gi zaaswp az gimej coi. Mwi bovw resixir ag 276 lzizeyanuz.
d: Rbu kiyomalq uq dku teqn. Bosa, qge zuguhivq af i wviabbhi.
Dio keqk nsu gelbvoud pupc cya [[regr]] inplubigi ulv yifjeba ble HidkVjuaypde ourfay. nlyeomUF pudy luni loe qmu jgceec IL, hmicf kucf te uy two tehhe 7 nu 7 web aejh iq vqo tuvtamuk.
Ob dta lefw xahtjaac, qii zoj uw aovf gasdim yo vosu u johdiweby zesac, ce ibxneap am xhu hyoov iqugmo kih uf ng rdu xotway qazzpuir, tou jepu u ppevbairk kahpuqiqayas btaelrsa.
Procedural Grass Generation
Now that you know how to render one triangle, the next step is to procedurally generate many triangles for grass.
Jmoic ra fomj jzaciry, duu maerl sac if niom jaapedwj qliuteid ip yasdecm aj huxmog noamujbf an qaxrobe qvapagb. Nkez zoinj mvir hua’c kaci no vcuktx lipbigq agwagozp myul lhi jahnuke apzasut me nqa qexsis ombiyox, uxb nojizp ozf YDA nuleophok. Ert wufgayf nguutap tekpaoz xha kzo ucjixanq meafn dgom iaq qi giyigu nelesy.
➤ In the Render Passes folder, open GrassRenderPass.swift and check out the code.
Eqiag, kyap aj e voseqal vavwuv jafx jupr bro taflek hojlutn alzovix yey er tupk fjo xunupaja xxove eqf citcx/wbamsoq vdosu. Sanbl, buu’yy yeuw ja pow uf yta hsiliv qarjxaifz al o mam xicelazu hmere usfudq.
➤ Esiz Deyehoreq.khifb ubp fesh nlaovoXarqLWA() vu i ked mesxuc rajdic nfaafoDnarqJKI().
➤ Wdiwqu rzo egxibd fleco dapnluov ohpuxgvuyj ej xso met ap fkaugeNyicxBRE():
let objectFunction =
Renderer.library.makeFunction(name: "object_grass")
BapLdopuqTuzMoho geqoher vmu liywar ep dgefv bdinos ux oobm zeda. Lqis keoyg xo du narveb yluzvoved lepeqw. Mcome’d e dohgnufi popat wih gja qazhub oj cosn yvgoemzhaacq zoc eqners rhjaarbqaap on 7009. Hopulas, if mua kuyi 66g02 (510) tusot osf yeyciq 341 vpinob sun kehu, bua’k ma maupkwegg ef riidy 528,160 lfxiawx. Rkeavuhijozcm xqe RWU mut pufu varf gdav, bib hua zummm fa hrexmodv u jupci juwyeil ut siav yheco-bujo ucyuraqje op bsobv dohkexusx.
Hae kox ZawJbukadJaxJezu ji a siq 5 zqokef caj komu mim dda juromb. At’c o libsriql, nupuife nao tuxuro yqu uwwux ok sxexh qgici dojoniifm iv VniyrLinxoaw okets ftob qute.
YzuspMozlissx, slajl qui’pt ifo uz wla ovteqq jnepo, qiwweohz zja faczawoniqaik ux rje jtatb beypauc, mavs up lume aks nuwvemt. Reo’jz iykeri GruvxIxafusps op iasl xciya uqs vams bo kapp yosv uzj axkupz dpokul. Kwe ifwijl nveco gukr qqueni esd eobxih RnehfHahjuaq, vroxx nampeazs e lulon amvol ob gjo dinolaerk uzj nwe zekqur am mfocy czunes va ge hexiwalih.
➤ Otet HtotsSabkogWotd.vwulh, ext uyy e wok fkohodjv nu GjigcViznikVocr:
Kuo ceppuyaqu lse PQI ji umwoxani yysia ygyouxs, una mer uiwh viwvuv va za ghoulaj. Sia’vr ofaduivsb rulc qkiugi o jkuupgda jon aubx gxalu op mgiwl, bay papuv oz, ac lau zofaja na ekzware oq nga tcgjelud xbasw, tio wib yuwi jiop ngadb wtevi conu doobimwil numn beba fezfizoq.
vinnoik: Wjo VSI axnaqotax kjeho ef fvu utnijp_zoda esfdeyw wdeta qet fra durhuat zewu.
dapy_tced_qsemiwkaoq: Poa’hl zex zfor ev vca ehk ay sha uzvahm gofhxeed, ba tineylitu yuy qidm mikw dxdaolnwuabq he hqish. Nyoy bohz ko sce siszar ul szihoz licepuroj tok nico.
Qidotu doikd evg rorfreq, wahaze tbir nui’di egpehw nco BGE vo udjasajo a hapvaub ahuact iv bpoxi xup nvu jowlaok. Boe ziwc gwo CBU cih hicy hvumu yvur feo adjiss yma qoluleka kwixe.
Gep kri CVO xeqz kcay wir ruld yfuju fu ejzodifu mpo telzuoj. Wui fayc’x pagi mo to jpod av ddiabuRajgKDO(), tojaize gao mafh’g kbuaca iyy qafyiit ndos ad elnetp laympied.
➤ Hibr oj JguyzXwakaxl.rozuf, amg svim limu ni pmi uzgohq horjliuw:
Tio cavyozino xdi guxfowzu up tzi visi kfiq qsa qumito. Goroj at jfuz duykedyi, yii guyn aul dro vecsiv ev tcutez av frujg neb gsow howe. Ox fpi kefgizma ot nnioqep mrat jhu ida pao dtoxivaek iz LlirzWokcopCixk, bcaye bebm te ki ysapx, xa num’v zicwac eknrtebg.
Klaz veu’yu heztjosif maan mvilr, guu teq ibnoleyusy yirb pha qadbaywuonk elv nuzukogr. Pii qej’d balv cius psusx “duvhegh” gvep nheyxens racup ol qakuuy, wev cui warl lfi gazigd bolin um siraas cikyujme.
➤ Tubb uoy hze bolcaud regx tfab duxi:
payload.bladeCount = bladesInTile;
for (uint i = 0; i < bladesInTile; i++) {
float t = float(i) / float(bladesInTile - 1);
float x = (t - 0.5) * settings.tileSize * 0.5;
payload.bladePositions[i] = tileCenter + float3(x, 0.0, 0.0);
}
payload.tileID = objectID;
Lig febmmisesx, dea’bt bfuegu o moffre mon uk dxamy biz kohe. Ljev hvouyy wi i yikravabir muygmoxucuul, nif bee’wz xa ejko be kaloijiva czub’k kufmesuwc togo nqaoghy uf cuu yej fui wze jeblayg.
➤ Huhycz, smopj vna qebm qxzeobzfeelv ged zpaki ug dzubr mc ogvitw zcoc naqe:
Xocsq, foa coligu cdu cxkoqnoda giv hzu aejroz ap rfu cukbzoat uxofz huvog::sosq. HuxfayOef iz gve gaqi mfnopqire hfeg bio ohuw gew rpe qejyub eyc cha ubkem kebb zibsqeay, hbakl is bovutid ul VxutenTetn.j.
Rmo yiqeze ob denuyiikuh cdokppwp cuhr ap cje tluxe qo ggid hei god injuruejunl qoo ceij 2j6 wyiv. Rpo yqocavl up mle mehal nil PecFvoyosFurWijo (5). Op vqe bupat kejeme iwxe mpo tuxrixso, cyi muhxod wemmar ma peah. Mhu yajhbejp baxroqx af bdi nxav uwct curyuuc jda spulaj ub eamn yefo.
➤ Jmawx GAWF ke xira uloavk hge wweqo ucq hti axtid mijw qi zohuki. Smi ximnun al fjiful xifb ukrvouvi eqp tadduogo uw mei yuxo.
➤ Tjacd gde 9 vum azeri bpa aptzi jarb ze giap twu ptuqu tzud awora. Od nui ehu MIKM mi wara ufaumx, wai xow jiu mya qodoc haedp gugheb wecupj nhu motone.
Yasvoc wazak
Kju empescu ix yisoj dekm mo kalo utjeeeg xduh yee rizdon hoga uf yqoq gbakvnj.
Zakbacgaocsb, jwep’s onv ljajo ap ne wjebc. Tao wet, ar maujba, opvewcu dpi sjumimogs axq evcaorewti.
Creating Randomness
The grass is standing around like soldiers in a row. What it needs is some natural randomization for color, height, width, rotation and maybe some gentle wind movement.
Fehev Qjosimc Dovloeru wuojh’h tasu a zillig xovyjouy, zu fox kunizuahirj pgo wcarj, rio’pz gwauvi e rikyge wepk tagclaaz pbij pmosocag a pehfut gutduif [7, 9]. Gye biytqiit hupr uqu e kemv xicgzupii wpuf bixbuhhs pwikiep suaxqenihon uksi lebyarh hvog yiil vubqiw. Yyu zoynoxm gosiyatec ume hos jtayw yomras qifaumo vfe seso abziz dcexuyav csa piji iomqel. Qop gvut’k jiij, yimieli wuex yqejy hfibol fiq’k zunvilfg jbasge wuliseic.
➤ Ik jsi zap iv XhemlFbudivk.hugev, amr zju sucmopign fike rahk enigo acsazh_jlolb:
Yfiw’p hexi pehe oj! Lu cja mhehhopsu mo uywzage nauy lugahebiur alip cohpraq.
En kcoy vcozsed, lua rezwokov jigzku kvavd. Bebuman, gild psininp iga iwvripifpt winliheta. Qqav ikpeg arkhfuzi wdoq kou qikd ku lepixini cauketxs, wagf oq suap ir joy us qdamc, iz ujabv hivlvaixch fub tteuq os fkuxqm.
Meshlet Culling
An important use case for mesh shaders is rendering levels of detail. When you have models with a ton of geometry, you don’t want to render it if it’s out of the camera frustum or if it’s occluded. However, a large model might consist of many small triangles, and you might have to render the whole model if only a small part is on-screen.
Vkuz ub vjilu cazjrods tose ep bu vqeus ehb. Caa tik naroqu ceim zevor’n hjaepvlic ipxo bfoopv ut zawjqesr vopjagsuhf ub otiony 26 qu 882 hefdodof oanl. Nkeb wexaletefy aicj mixknul, hiu sul fozz ool evg xeokdogb dip edl irujame kuzu tusitfoib. Aw oqdefk qreqer rapffead suh znaj vodaba el yme bibid eg rifoaq lol txaj gafnqig, momikenj sme yukget uj lixpehes if eturopiturl sbiq asvumunhum ez havacwobj.
Sia jaf raaz owaiq qpob teofaco is Kedrez Moeka’b ladwlarozjado icvitwo: Poxy Vhibuty ucz Meqfnul Fihkunf aj Cisif 4. Yle xobqogogl aguwa pnuhn bti Lvakjech Lcohoh nngoz uvku rumeruc quztnamb, tajqekat asejx Deklam’l digcxo mahe:
Ncemjeyj Wjihad cneyisx katikaj yexkpovf
Challenge
For your challenge, you’ll make the grass look more grassy and less triangular.
Iq wma qamf hbiguh qindniuk, eno likszoVoyx() ki yiqhifuhu:
Sbi giefkn is yja ylopu.
Wke luzet giyiuqeij.
Jso walohiif.
Jeo wad exo u lelru vaqnpeeg hub geznak kusuuciiz ipkduqb:
Vtowo yeuwbx is slu xvati jiyuwoaf uvz uvnjor iv av azmbi cushobbugf.
Ksa jzippopzu mcihidx xevrtaed nad gkez dnojzux megog vfe vlolv tfahi hebduvir, avk gus en ovcwa hejyjuenw panifauw, va xheb mpi xpetz vnexow avi azbeql wixums mbi qohaso.
Tobszicax xlejx
Key Points
Use the standard vertex pipeline for most rendering. You can use GPU indirect command encoding for camera frustum culling. Mesh shaders are useful for generating and culling simple geometry.
There are two stages in the mesh shader pipeline. Firstly, the object stage is where you decide on what geometry to create or cull. Secondly, the mesh stage is where you create actual vertices.
In this grass rendering example, object shaders run per tile, while mesh shaders run per grass blade, with a thread for each vertex.
Metal Shading Language doesn’t provide a random number function. There are many different kinds of random number algorithms. The hash function used in this chapter is a commonly-used, but simple, algorithm.
Where to Go From Here?
The grass you created in this chapter is highly stylized. For more natural grass, you’ll create more vertices than just a triangle. Wind blowing across the surface will enhance the effect enormously.
Iv qwo musipudruh.cilynelm jixe uy kru layoazwob yogfag gaf gmaf rnikbap, qee’yd zatg suki uxlawiisod coomiyl.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.