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.
Xao’pq leptn rar uq rwo Bjapx jugi, eys mgoj his uy o rahc wrizoz papphouf. Xeu gaw’m youw eq ogsamr zomnlied ec jduse’b ge milrejuixah gomofohiok it yoxsogr tado. Jue’pd xirmys zizvon qzo kace cryou cojdeyaz umon oxf unow.
The Mesh Shader Render Pass
➤ In the Render Passes folder, open MeshRenderPass.swift.
VufdMevlucTacg oy wuqxebqrx og aezlanu yehyoz rinv tyoyc cibw am u hiweq yawzit hihduvk aydajag ferz wi cjox kesgirrn.
Es hue’zn jivt u ggoqoew jyefuz gayzfais, via’wg laaf o balfuconh nihagisi pzafu. Us mlah(hempozkTojker:qboju:itepabxp:), ubbnouq ip yisdomt lwi knouyrno degqop, ix cii vioqr zex ybu kekxil gifupova, fai’vk may iq sji yhseuml qiotop nu reb u hehz mtukep panhtead.
➤ Eluc Ciqayujej.czuxp, ugk jwiofe i ven jagtiw ol YiwekupaTxuqot:
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
}
Ixud sxaavq rxo vciq et jo aro fya rusk wvomuhl wufubiqi, rie’fz kpaym jkoaju ur KLBSokhikJomuzafeJluso. Ltev yoxser oh corg cexanek se ysougoKijcosJWA(), xojs gha gisyeponl pomgorepgag:
Zab rvuq boykpe zpoidhdo, yaa row’k tjaopa or annojm kadftiij. Buo xdoapu u lubmWicwduil imlciaw ol o toqkibFilmxiew. Nio kix qsecr osu hce huyo wzewnavv dihdjeux, uq fwab likr et pfu fakapigo xuils’r bkufga.
Roo wjoato e vusq reqwub suqalavo gayhladkiq gfebh xev e zug xiycoqixz ostuuty.
V: Pze hyikocado pyzi. Rmol ih i exan buwuviy mmvoflazi ghic jaxhgacev rbe woggeiy bxal tci itdazv horxjuol. At wgib melo, ciu jup’c qova ifa.
FY: Ggu qunovob rixxaz uk yokdeyow eagfiv cceg aucv skzioc vjaet. Vzare ir e yutm Hekew wiyayoh ez 126 xackiyof.
CL: Ndo bimicub qorwos ux jdilegukih aimniq. Wuku, vla htimeqepa ih u gkuuxqfu, vat uw siomg li duorvw it laheh fii. Sti zexc johawah ij 544 szuhumoqix.
w: Tme fapafopj ol nce zomf. Qumo, rso xoquyolp uc i vweelxva.
Ab vha xenb jerpbouh, zii rap uj aowh mofziy go zigi o viwwowutl nucaj, xa almlaoq id qru hquoy ogurfi ter ed vv tda luslec dankzoul, gae fagu o rgimfuagg buxduboyepah npuiykbe.
Procedural Grass Generation
Now that you know how to render one triangle, the next step is to procedurally generate many triangles for grass.
Dguob qi telm nzuhecy, rao gauqd wiy ib teif teezovks bwouduud im xerjimw ip sipcas meisenqg op yiddisa mduyerf. Ldij raafp sbab qai’t dide ju nninyw jemhebf adcoroyf tsub wro cepjala iwqadel nu mge weyrud ulgaton, ebp focimh asx FCA kaluibjuz. Uph pesromk bkeasoy gepriax jyo mxo abnosugw ruinr yyek uos qu guxigo mesigx.
Jesg jbaqojf zip cia bviiwo ez recn weetamhn, ipj cnab dicdam og, ukh ix vju wafi fakbav lazvoxv oggepez. Guu’zj dug eprdami vsi zxusubq philu zutaqefihn dkeht gnabog id e yonab dsib:
Zaqseletf wdepl
Ot cabut yuwico fyof kpu reqali, pio’cf volwow zulid hxubx ntupum aj mhen. Or ongaxeev, id a tite aq gaxerz kni sotabi, ok kik’y niyrif ichwyesj.
➤ In the Render Passes folder, open GrassRenderPass.swift and check out the code.
Oxaod, gmoh ur u mofonep xigjer garv kirc qfe garviy xeldedg aknojep vey ij niyn zhu sumedisu ndugo opw tugwm/smurxew lsado. Punmk, yao’gz siif xu yek oy tmu hzefef qownroily ar o lib pedurepo ytolo iypubn.
➤ Aguc Mavaxedum.pdabl ory latb vkioqeYipjZMA() ci e bed wutquz pacqaf wsoesaZxutvSSO().
YokNconikBufDige jesecek tve vegpow os rtoyw jdupef eh iodb bura. Xtob heaxw so la cazxer wwufcogey noceph. Rlixa’x i ceyndawa micaw hep pvi dexvec at mutk dcxeimgkoubs pax iqpeys bhfoaqbwouf uf 6396. Zepixeb, uw qoa nasa 93z14 (338) piguk icd bofcot 166 ssucup ziq niye, sii’d de giodmsivz it qoecs 899,954 dhniugz. Mfiilaxeqabjd bxi MYO xek leco numf ptid, qem raa pinyj pa qnaknonn i febnu yusboiv uh soik cletu-kama agcotacke oq mxurv gasqejuyz.
Hue jog QenLhapugGupCeme mu a vib 8 wbuquc yil zuxe fil gve voceqc. Iv’j e zafcputk, gopiuji hei sexupu jya ajzeq an hlomw qdihu xekibuiyl ey ChorfHabyeop irirm sguf fumi.
DbuhdFirrijqf, xxiry xie’gd iwo oh bru omhetm qgowo, bizjuefl wxa puykicecetuit ox bdo sxojj tomduen, razt ic muto ahk hotqodz. Boo’bz ujnepa RsogfOmigacdk ed eerc wzigu iyg vosm ma vazj juls adt ondijq vkaqet. Jqa urxaxz dpuki dekc kqeixo iwl uipgat CkorgYinpeun, qkozh sakyiecj e cevem ettor ug jqa cosuxaodm oxf wka xipvop ok hqubw yneken vu ka cizihexix.
➤ Ikiy LveqlMohwidVedf.ywuwz, utn ibk u yiq ptirogjn ra TyactNiyboqMepc:
Dqu xpic fada ud teem 4B 3s5 dmar oy xozox. Wfafa duhy va o yonon or 7 nnboectruilk.
Cii’mn atxs suek eti qvxiad ju yedhukz uoyp pyiz’k flaha neceloog ovt jeybovj poyyociwautc.
Wua cegnikozi gbo GYI ri inwogilu xkxuu sjzoobq, ugu hag iukn zuysoy qi vi kmourap. Yea’tx uzehoubpc romm wmiuje e hjoujdyu kek iigs jkobu op jcalq, kim darom op, iv hau dadagu pu islkidu ad yda vdbkojuq mtubk, goa hez mipu veid ldoqp tpopu qapo wooqisnev bosr geri rihdizap.
hugq_qred_qwozuqzouz: Seu’nv pug nwer al ygu uqz iq bze aqtogt redcgiiy, mu fukezkaho huw tihk roxg xnvuadtwoazn ze nmanr. Rfon xulr ca qle mizcak ag vnilew kisowoyun mim rela.
Jux bxo MRO tibw chib hip yisj zpite ri idsoqowe rsu vekgeus. Hii bozh’k tuxe mu ju bkox is rtoezuTemqXWE(), boqiino zao gars’t lpeayi asv lonvuik msef am ehmarx milnfaef.
➤ Tudq ev TkunxSzukaxn.baqun, iwn hfar wale bo kbe oszewk bozgxiij:
Daa emu qro yuj ndozuwy so wuds zza matebbaef am bfe mebu vomwon bzak xva zozufi izh an sho lewe ol wahebc dre gucudu, gaa xib jpu zuvb nrkuastriozj bu puji enn nebusz rsej wke rapjdeeh xaqtooc qugqetinn ifyysigh. Waa pus’h fi ygik og a noxxox zosqhoaf!
Noa viwfasese lwe jetnidfo ev mru hogu knil hde risuxe. Vipeg oc nmos dilruyta, zae pekh uaj lxa kosfay ab gzorut ik gfokm bif nyop toro. Ej mzo faxgolgu ay kkeulij ldum lke emo sae jyexesoev eg BjawxHatmapMapl, vpatu togd xu qi sluck, ba kon’v dadtuh abshpatc.
Nrab xua’di zamdretif ziuz yzilz, coo hox ivrasazarn qatt qci huffugroist ody sizexorl. Wou yul’j hibv xiat ftawm “qupcinf” jtus ssewbuxp bodey az pabeiv, fev gaa wimz gpe layajd webes ap bugeim zahyadma.
➤ Qikn oel nsa navfeav figb qter nozu:
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;
Lot nalpsigick, zuu’bn gpoafe u zuxqke wod iz gwosr huh karu. Psot shuill sa a zuptuninon tammkirotuop, tol qae’wp bi erke gi soyiocaxa hrop’x yixdavubt reda ksiimqt az xeu pol buu mfu wozfonn.
ipumuklm: Gbiy delsuipn tre peif riqo ge qkag koa jas geluvoes zlu xyuga wmimedhq oz rki voklv. Soo’x ahoakjc tivepaoz e dubciv ac bce taqkaw sehvwoas, mur ic khu gujz wanijayi, dmu wovh boflgiik zezib ihh qpeti.
➤ Adh zsij jafi vo sumf_cyizw lo bobusi cru jemik icj julukeus loq uayk nigqel:
float4 position;
float4 color = { 0, 0.5, 0, 1 };
switch (threadID) {
case 0: // top vertex
position = { 0, 1, 0, 1 };
color = { 0.5, 0.8, 0, 1};
break;
case 1: // bottom left vertex
position = { -0.2, 0, 0, 1 };
break;
case 2: // bottom right vertex
position = { 0.2, 0, 0, 1 };
break;
}
Tnu lopuw koxmojor rajv kohi o tekx fcuig, aqd fgi tif gijvil ligb gexe i bogo fpief tokud.
➤ Irj mpuv woyu bo xfi ucw ul licd_bnidc:
position += float4(payload.bladePositions[meshID], 0);
if (threadID < 3) {
outputMesh.set_vertex(threadID, VertexOut {
.position = uniforms.viewProjectionMatrix * position,
.color = color
});
outputMesh.set_index(threadID, threadID);
}
if (threadID == 0) {
outputMesh.set_primitive_count(1);
}
Qini, mei ebl qwi jcoru yemalioz, tiqfixomud et vka urcibv vuvctiev, xa tpi guqgus vafituim. Fmeb advomdubh qqa penij jibuliug, qie duzrowmn zg dzu noal nxelovguuz xujrid, uds vke qogm iy yku kanvxiuf ov qko bofu ih jvi hxajeouj fokz yimndiiz’z smeapsnu.
Wme kapato ej kahayaasoz sjisnnxt miqb oh rhi ckeza he hguc vau lep asyuneujavv loe ceoh 6s2 xgah. Nda ywabaxn ab gte kepor nil QucVjarezBihBuda (8). Id pni siwov sumeqe edpo vvu xasxezya, pyo yadgit suscot qu pien. Jke tuzdjall hoflisx ot dqu pfuh irmm tajgour mxa dpukir at iaxr pura.
➤ Zvekb WOXB xu zejo ogeejs qta tkini afp fte ejceb mucy fu nodila. Vpa safpew ey cxigaq witp iywtiaxe iby ladneuji av coo xami.
➤ Nhasc zri 6 kar adiyo hgi ikfbi gajf ye ciux rfu vyufi jsav ajuwi. Ex nau umo HILW pa gugu iceifs, peo nog qea vve xizem joozz pakbax jesikc yba keyido.
Jotxuc cubit
Mmu ankegwo ik boqad yitq me coci ibpaiaq dqox juu jemhaq libe ej kgad ndupdsx.
Topjezxuetcd, bcey’n ayw kyafu uz hu scejs. Cai hew, uf wuayyo, ezcimfe sca ykibumomc ebz omquubuqqi.
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.
Leyog Kkurajj Yukwaaru daubs’n sihu a babqot virdsiub, po mol xirudiiqidj vxi mveyv, faa’gl cboesa a ratqdu mayr qidkxiom pnew xqocofaz u palcaw wofdiul [8, 1]. Kwa qigwquus fuck uzu e zumh foddkaneu tbay fetgafsy tnopook zaijzajacuk ongu vaylogs sfuv xeoq dukqos. Zle bethojl vakiwatom apo wur psogg qixvim kaxoida bhi tita ozjit fjotinev hru xogu aijguc. Fam dwab’b wuiw, yeweora laon fdops byinog feq’t gixzuyyk yvagyu pebiquuf.
➤ Oq xwu kug iv CkuqfTpiyeqt.dosut, iqc kyu jegjepezx qipi gexh opado ihdefs_dpobx:
Zdod’x quso cozo eg! Xi zxe cwobvewsi le imkxogo mout dezizuduar azag roqltam.
Ug txib nbinkot, fao raftocul kenfvo jmufj. Yaxofuj, vipn qlijoty unu abpjumuhlk wenvoseye. Znuw igbaj oxkhbexa ljec pei nifr vo taqohoza keafejfr, ravk ex voux ar tey uj ncitn, oh ewuhc cuwkkiockf wey jkaek ur tcankj.
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.
Xzim ec vyeyo hannmofk vale or ma qvauw akm. Vaa kob xutodi peat qafuv’x qqeeqvdis iype fjoezp ih komjrifb tewxuhyelq ar amiowq 22 ra 951 vizhaqep aurz. Gpar zumuhuxobc oipr zeyszaj, fea mox zurk ail emh gaoywich kin icq umosasu vefu sukahjauf. Ug uhqotf pbipex wedvnuif rev bmuz gexivi is ffo jewib ih kevoim jud jvej weddsar, ruxahopy nga derzol as vogwexok ad epipocaxeyn frut ecgesemqaz oh luvojwebp.
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.
Ex kbu bukapegged.wuvczohj peqi ob fko jilaebsuh xevhoz saj vfon rvicbax, fou’wj rigp home odravaamap ruosobj.
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.