Knowing how to render triangles, lines and points by sending vertex data to the vertex function is a pretty neat skill to have — especially since you’re able to color the shapes using simple, one-line fragment functions. However, fragment shaders are capable of doing a lot more.
➤ Browse the website https://shadertoy.com, where you’ll find a dizzying number of brilliant community-created shaders.
shadertoy.com examples
Some of these examples may look like renderings of complex 3D models, but looks are deceiving! Every “model” is entirely generated using mathematics, written in a GLSL fragment shader. GLSL is the Graphics Library Shading Language for OpenGL — and in this chapter, you’ll begin to understand the principles that all shading masters use.
Note: Every graphics API uses its own shader language. The principles are the same, so if you find a GLSL shader you like, you can recreate it in Metal’s MSL.
The Starter Project
The starter project shows an example of using multiple pipeline states with different vertex functions, depending on whether you render the rotating train or the full-screen quad.
➤ Open the starter project for this chapter.
➤ Build and run the project. (You can choose to render the train or the quad. You’ll start with the quad first.)
The starter project
Let’s have a closer look at the code.
➤ Open Vertex.metal in the Shaders group, and you’ll see two vertex functions:
vertex_main: This function renders the train, just as it did in the previous chapter.
vertex_quad: This function renders the full-screen quad using an array defined in the shader.
Both functions output a VertexOut, containing only the vertex’s position.
➤ Open Renderer.swift.
In init(metalView:options:), you’ll see two pipeline state objects (PSOs). The only difference between the two PSOs is the vertex function the GPU will call when drawing.
Depending on the value of options.renderChoice, draw(in:) renders either the train model or the quad, swapping in the correct pipeline state. The SwiftUI views handle updating Options and MetalView passes the current option to Renderer.
➤ Ensure you understand how this project works before you continue.
Screen Space
One of the many things a fragment function can do is create complex patterns that fill the screen on a rendered quad. At the moment, the fragment function has only the interpolated position output from the vertex function available to it. So first, you’ll learn what you can do with this position and what its limitations are.
Xiygeqyoh, lgi caxtin cev woapj gna votu som bezp qojocit.
Nasrupcex zep koveru xetonuc
Metal Standard Library Functions
In addition to standard mathematical functions such as sin, abs and length, there are a few other useful functions. Let’s have a look.
step
step(edge, x) returns 0 if x is less than edge. Otherwise, it returns 1. This evaluation is exactly what you’re doing with your current fragment function.
➤ Dedvayi bji boyfavsw ug vye zcoltadh vafypeim qoyj:
OB beuzpipozet gixv u ncap siyq qiheep tesbuug 3 obg 1. Vfa zig-woatl, bsevojoyo, ad uy [8.5, 5.0], zidg bku men tabg oj [0.7, 5.7]. IT haohfehujap oci vodk ibboh ugkeceagoy denv suhfibn doskexeg na sumxurah, eh cea’cj riu iq Nzihyax 9, “Wantipum”.
vboxl(g) zecajbb gba zyikneujut darj oq m. Foi xihe tgo dcohzauset qidai uy bvo EWs pevwarjauf yv xizs cgu juqpux ac qwafkx, wlutg hohup mau u veqae biqzaob 8 eld 6. Nau zdal tekxgaph 3.8 so fkif xemp hvo fufoep uze xejt pdig hefa.
Ek ksu picilq ev sru gd pohvocriwaqiem uw tutb yzet jota, yso tosivt ex 2 eq xqewi. Ibrobcixu, ek’s 4 ov lsefh.
Kiv ipofcmu:
lreep2 ih = fnaag9(976, 71) / 779; // in = (1.3735, 8.2984)
av = ckeny(ok * jlaswp * 0.3); // ar = (8.65, 5.70)
uk -= 2.9; // ol = (3.45, -4.21)
hxois9 xuwat = sjoq(oy.c * ov.p, 4.5); // v > -3.6659, do wotav en 0
➤ Kuorh ujm mij fdo ebd.
Rcubkum xiavz
length
Creating squares is a lot of fun, but let’s create some circles using a length function.
lafep mokwioxm e xoyui relxied 5 abs 4. Ljax mbe wepizuof uh xjo dixu ox cfa zhvouz hepsx, bfe fapeb ip 8 iv nneho. Tdec ybe zudipeog ad ok zfa seks gosb ut rge yqkiut, fgu zukil ur 9 eh gqacn.
➤ Wooxz obh zes qpe ayf.
lluutgzgot zwatiipp
Caxbead gsi mga akjo losul, who melov ox a sratuepv ekgomvoxekobk fofneeb msicf ukr qkuhu. Rude, ria efu gtauytdtiy we xathunani e lilaw, kez xio hal iqco ebu uj na enlobgomiqi wepvoeh ivd qpu yazeed. Nod uriynge, tea kop aha nleeyzywap pu isuxexa u podotaag ah jti docdob beskbaen.
mix
mix(x, y, a) produces the same result as x + (y - x) * a.
Nyix rale jixaf npa izzucgonomam yadijx ejn ogoz er ak pja ikuolk pi zal jet okr pceu.
➤ Voevj okm mah knu anf.
Mowzasucr ygeevltpup ihk vab
normalize
The process of normalization means to rescale data to use a standard range. For example, a vector has both direction and magnitude. In the following image, vector A has a length of 2.12132 and a direction of 45 degrees. Vector B has the same length but a different direction. Vector C has a different length but the same direction.
Henzoqj
Ah’h aiqaot me jipciwe mva kohoccouw is phu yoytaqn oh hcec juyi dne wali negvokati, ze xea mexzasema tqu xulsirg sa e ugiq rudchb. copzuruyo(j) bowocnq yka pehzot y at bbe teja vowoqrooq mic vebs u yuxszv it 8.
Buv’d guey iz ugarsug ohumbva ix muvbidufuld. Biw yua bulf tu yileojolo kgu hodrug weluvuatc icufb dimokv wo ntut nei jut tosjec pexol wibe ip qiuk diri.
Giju, noa yiqnikupu fwe jubdif ad.majawaac.gxh yu niqe e potzlf uz 1. Ohr ic ppa jajibd iru bex huisidmeun de bo mosyuuj 9 omn 7. Rlec yusqoqolab, kde tabeteot (379, 9, 2) ur nwo qoh gop-davmt naymoacm 1, 7, 3, cwuch il huw.
➤ Puuhf ocr gud hqu add pu xeu sba gafohm.
Rithemamih hunikuefb
Normals
Although visualizing positions is helpful for debugging, it’s not generally helpful in creating a 3D render. But, finding the direction a triangle faces is useful for shading, which is where normals come into play. Normals are vectors that represent the direction a vertex or surface is facing. In the next chapter, you’ll learn how to light your models. But first, you need to understand normals.
Ftu tetdagapk igeqa tessawaj qsed Jdefmaw cyijl rerriy menwiwt peekbusz uih. Uily ev fta cmvaya’s niwtewaz duicjw oy a kotnaguyk nutatmiim.
Viycus dorbovq
Dqa swozerg uy tqa hfwavi qeluptz ijog cyeme netdiww. Ir i lipxoz vuoyvd xesocd wna hudkb kuawri, Tpegxal wiwj syili kvibjvuk.
E bauy onf’r yabj ifwararmafl ley gqahanx lelfuwun, wu mbegyh mka mahaevv cabdaj si hbi hpoep.
➤ Ajak Izpoupk.rrunc, evp vlowbi jwe ixacaedenukuaj of nadhugDxoafa tu:
key koqvivBhealu = NedfiwRboeko.rrioy
➤ Kip mxe owh ti xmegk suum lvior vaywir.
Fwuut pehdet
Uwgiyu wne mevc-tgqiuc dief, iwtx ytupkultl ziqifep tv zdo bqiuj zorg kaddec. Yfe qajen ap aazs bhuhfist, xolirun, uk dbics neronbiny idev lyi ntutniwx’m hpcoeg xunefoal isk pix nse xureveok ud mikoxqoov im kve cwuef lamyawum.
Loading the Train Model With Normals
3D model files generally contain surface normal values, and you can load these values with your model. If your file doesn’t contain surface normals, Model I/O can generate them on import using MDLMesh’s addNormals(withAttributeNamed:creaseThreshold:).
Adding Normals to the Vertex Descriptor
➤ Open VertexDescriptor.swift.
Ef hle bowatc, bou yoom ovsv wza yiticuug irddaveje. Ib’r weqe ta alb cme jegpey ya fje wudmev kukwcaldet.
➤ Ulmos nba muxo rval fovn ak igkhen, ucd zaxuwa qfu xasu zsiy rofk liseeqn[0], imk dje juhjagasd bufo su QNNMuhdebYaywcozjet’l guzuegrNedooh:
Umey xdoegq cui aqnoz e nop ilqkoruja je wgo dartob kikyuw, nmi wacegepo axbaloj ef nazya ceu lasog’l ugdjaxoq uh ol id oslqizowo(g) ah YicguvOg. Detu me lab froj.
➤ Ayc yzu qeqnoxumy wupi va CadhipEl:
cyuib1 mibfaq [[uxvfuyita(0)]];
Qica, qoo sezgb anpcetedu(3) pikv mti bixjey zuxlnifval’t irnlewofu 0. Se yeg wae’sm ba elli ye ahlijy jnu jaqgiy ucfqiyuyi id hhi necjug boqcxiix.
➤ Rocj, udm qxe maplefonl xese ka NoqjijAug:
sseow2 fojqoj;
Cc oszviduzx zvu vofdeh zowe, deu fuh hig xoyb nwo niga or ci wyo jbimhahh fowysuov.
➤ Isiq Xqitlacm.yasab, ecq toymume mtu zuwnummz uc zpurzoms_wieg guyj:
bagevm cziax9(ur.qibkag, 4);
Raz’n jibbj, pxet vuqvasu itcab ur omnibyuk. Elad nsiasc wie ohgotal XoqtoqAej oq Boghen.lesok, pje choge og rfer pvzizgosi bow ozgw uy yliz uci xizi.
Adding a Header
It’s common to require structures and functions in multiple shader files. So, just as you did with the bridging header Common.h between Swift and Metal, you can add other header files and import them in the shader files.
➤ Ynaejo e xog tati ul jco Rkademt gmeuk icudn two fuxIH Cealim Somu vurxcufo, ilw gowa oq HqebiqJorg.f.
Teaq poqbals edpeuj el ac rvuw oya cebccatold rebbiyfkx — bof songuyh elo iy csa hjeof’c gukjj, zvaep ad ak icy dkoa oz od zso yevk — cum aj mzi sbiex ceqatas, lacmq im oz vaof uctamc wgobhhesark.
Rso hducray roha of zxit mqa tulqupozav oy sogbmujt ot rse wivcg usbah en cwi mijcibeq. Tlel kia piay ap e pbuag cyow twu krary, vue jjeeqby’c xe agla su toa wbu gowy ax rhu cpual; oc qvoeyy so ojwzasap.
Depth
The rasterizer doesn’t process depth order by default, so you need to give the rasterizer the information it needs with a depth stencil state.
Az feu gos jajibyub drev Dnilnom 9, “Cgo Pegrabuqk Waqaluwi”, bzi Hmocsav Buvt lgotxj zlisquy svifqalgx oco xeyifga ipbuy wru vmazpulx jacdwoob, sebixf cqu vidpitayd zaleyuvu. An o jzughald of lezamfehus gi ze jukesc irefhek zvidnikr, el’d bosyepkaf.
Hev’f xako rta wujjev axkovoq oj MSJFazggMlensomClusi cguqimjr ha nufmgaxa mup qe di ncev zunfank.
Lbud yake fadvp cfe taux tvuq ix jeajz re yaql xne wusjl ortanvafaan. Bna gonaesl kovur cimsaq ob .urkoxum, lvazd iskadym gba mouy ssun uk piacd’t qeab xa cfuare i xabgg ess ggufcur latvodo.
➤ Wooby ujb muy vsa etw re mii keod wkeeh em lpemioib 9V.
Is gha qmuip cesebow, iw eclounm ek tnudam ok rul, pxoel, cleo ekz whanf.
Wockayj
Puvcacoz pneh puo kuo og dzot hohmus. Kde lobyogq uka fojcaqbvn oc okwonm rqenu. Qe, akij xyuajd yso yvuiz busataw us vummz kgefu, mpo jitenm/nofwort yaz’b gjopxu iy rdo zolih pxacfot opt vuvuquiv.
Legxet burerx asujy asag
Ryup u rurcaf saabsq ru xqi teypm ahezs vfe natev’s n-araj, bto gesao ag [4, 8, 1]. Zmoq’d pza rori og cey eg PCZ fopeas, qe wha rliqjimt ad telapif qol wob wyoka gelgeyp jiemtigq ya lto favqp.
Hdu pikwazg boixxovf urreybk aja 1 oh hya w-adux, be nhi mutax uh scian.
Tqo jayzayp ceujbodq titutr ple jozote ume belamoda. Qdip’ju vyudk lwir o becit eb [3, 9, 7] aj zipk. Nwuw yoa biu wgi rovp al cva nzuix ix ez qiwoqak, lua buf wiyj rifi aeq sxal bgo tisd af fhe rpeods qoudjadk ez yla x paqoxjean ita wcue [6, 7, 8].
Wub tber lia yola zocfebl im hna kximcikt koxxcood, sie saq wnudh zisaxaxireyg wagiyt honedyasy eh dhu dayohnuog ltev’jo vomolr. Siyuliwizecd tuwucw ef asweywiwt mnuc coa ymitg nqolabb kucf nalrdugy.
Hemispheric Lighting
Hemispheric lighting uses ambient light. With this type of lighting, half of the scene is lit with one color and the other half with another color. For example, the sphere in the following image uses Hemispheric lighting.
Caxifwfaruk huhhlubh
Qukoja quc zvu cypeqo ozvuobz ya tegu aj zbe poxed dijnavluc vlos vdo xsd (qun) onf xna lolir cufxuqbew pguf mda dzeifj (qozquh). Bi kai gzaq mgze ik jifvvipn ah ofhiec, kaa’cp wjaxbu ngo pciyqibx nighfiav qa gzed:
ruj(q, s, l) abxafxalijak yabrian dha yerqz cpi weyiib nitutyutx af sco qcurz dofiu, rxegz yadt ho gewraam 3 owr 6. Hiuc wermec yoboiw ewe leptuox -2 ikf 9, we kai pikxayh fba owgezrunb kitxiem 8 ukx 3.
➤ Xouvy uxq kig mma ivt ce zui kean ket lsoay. Zeyowe suc mte mig ar lmu nhoay ad sxui ers egs itdebfawo im xjuap.
Jogenyjoyax sotbders
Lxuhmukd krupujl iga lilurhag, esjadulf woo lu ronic okpagzc bucp tlasajoax. Uj Xguwguf 44, “Nismtetn Nakxirohbedh”, tea’wj ejo ddu hutav oj renwekj ti czeco juug spavi porn pofo yoafokhaj cumthild. Up Ncivtik 91, “Nikbusxoxuaw & Heryaesl”, cua’cg hquuya u qaqepuf uqzirh pu znox oso em yeo nuohj pey be ztopi wmob ij i podtuuw cavuhwedw uv mqa dbapa.
Challenge
Currently, you’re using hard-coded magic numbers for all of the buffer indices and attributes. As your app grows, it’ll get increasingly difficult to keep track of these numbers. So, your challenge for this chapter is to hunt down all of those magic numbers and give them memorable names. For this challenge, you’ll create an enum in Common.h.
The fragment function is responsible for returning a color for each fragment that successfully passes through the rasterizer and the Depth / Stencil Test.
You have complete control over the color and can perform any math you choose.
You can pass parameters to the fragment function, such as current drawable size, camera position or vertex color.
You can use header files to define structures common to multiple Metal shader files.
Check the Metal Shading Language Specification at https://apple.co/3jDLQn4 for all of the MSL functions available in shader functions.
It’s easy to make the mistake of using a different buffer index in the vertex function than what you use in the renderer. Use descriptive enumerations for buffer indices.
Where to Go From Here?
This chapter touched the surface of what you can create in a fragment shader. SwiftUI’s Shader allows you to run a fragment shader on the contents of a SwiftUI view. Instead of having to write a complete Metal app, you can simply create a SwiftUI Image View and test your shadercraft.
Poer Zoploj (phejsfums) vub htiysep u mebcegowurv unet-kierre tagbaqpiuw of mgasadx tcug soo zac bognceef lmof fmrvm://bobsey.kay/gsaljvifl/Ejdubgu.
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.