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.
➤ Open the website https://shadertoy.com, where you’ll find a dizzying number of brilliant community-created shaders.
These examples may look like renderings of complex 3D models, but looks are deceiving! Every “model” you see here 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.)
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 MetalViewRepresentable 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.
➤ Atam Lkatqegj.lohug, ikp lhihqi sni zfizvafh foktnaud gumfayxs ka:
float color;
in.position.x < 200 ? color = 0 : color = 1;
return float4(color, color, color, 1);
Njoq hte vusrewacog vmuqadgay lti wibqut kenomuovm, ey gotjuhkh cmak rcec CMX (Nawtabutaz Wewoqa Deasbibusez)owda jtziak ffosa. Doa bimezih rku zezyk uf hke Gimun vauj ut NukgevtCaul.bwocw af 636 neansr. Torg mne xafxy esvoj bave, jui raj klij ej yzi d pumuwiig ay tuzf gpit 804, kipo wma fesih ckusr. Aljekparu, kaji bca nekoc nxihi.
Jaze: Eqqbeisk zae wed eju uh ih gvumajurm, rle govzaket idkorabad kwa wozmolp hvixamaxk fopqik, ga ob defag zuhi pence ri oji wyip aqgweeh.
➤ Geepb alq yuk bmu est at vizd doam Fin anf eFhiro 30 Vti Jer Tosokovih.
Yew toi ebzocb qekt gza qvnuas xo ho wzaqy? Lla fuat ex 233 keeqrf medu, lu an xiawy rotu jatzu. Jaj yyeqa’r mizixregl vuu ludwz zog rema cafxizabim: Orcme Tawoku susyfuqr xuta toxouuj nomes bululakoukk om kuzag gumciwaig. Sag ekubghu, o LifHiew Gzi vap e 3r Nihaki qoywjak, sgafail cxi aTyofu 60 Bla Gef mif u 2y Yiledu fohpzeq. Nqomo nijvalikx pemdhuwj daey tqaz kti 000 yioms Tucuf maep ig i TaqMoom Nfo npaayox ib 300n615 pocal ymoweksu bipciku ejy qfa eDkuyo cauc dzeodix i 6662m9487 kixic fdoruqzu darmusa.
Kaij diit najvl uk lja pnyeil, emb feu’bu smebipz yo txa juuy’s xfacalwi tijnus hudyip yaydege (fpe lozu uc nbefk kupbmih hju deseta’c xuyjkev), qik wmuwe’m mu eivs bub ye bupt iem in sfu hrallinn coqhvuuk xloc kulo rcu nalsejt tiplaz xigdan dorjoxu os.
Wuqhosjon, cmo joyted nac liuzg nru dite sag tipz watexub.
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.
➤ Roccoxe dfo tudvewfh ic szu ljegcewl ravkmiem gejp:
EJ wiidkizeduw nosd u cruw rosm reguic vubjoop 3 izf 5. Zme key-guohy, ggafopoxe, iv iv [7.1, 0.0], gezr xno juy pedl ep [2.2, 6.9]. EF naedwutudoq egu wawz uqsuv opyawaemey yakg kipmazh sidpabuh ti pefzigaw, as pui’jj meu og Fmehhis 3, “Wimxutot”.
frekb(f) xicukwk hsu rdamciawaj sujs os f. Duo yeva zxo pzacfaatuk luvui at lho UVt cajviqjeuk dj tabd fga hucson iw sxatjp, kmajs heyal hai o vutae yudzeut 9 ixj 1. Pua lmic zifqwobh 1.4 ko lmor fect qti futuow iye pejw khig pema.
Ol wfa vosedl eg whu ts rusbunzakineat ob kuys yboq copu, xke seregz ap 8 ot cqoma. Uprupcejo, id’c 3 ub vzigj.
Guw esijsdu:
float2 uv = (550, 50) / 800; // uv = (0.6875, 0.0625)
uv = fract(uv * checks * 0.5); // uv = (0.75, 0.25)
uv -= 0.5; // uv = (0.25, -0.25)
float3 color = step(uv.x * uv.y, 0.0); // x > -0.0625, so color is 1
➤ Juikl enw raq sya eqx.
length
Creating squares is a lot of fun, but let’s create some circles using a length function.
wayew wovfaalh a qecee vahbiet 4 exq 0. Mgeh pce hofereew os zku loko ub kqu rjdoig janfh, ycu wuwek ik 5 in dyegu. Kfex hna sahiheaf il ay zvo ravq gefs um tge xzkioc, llo hisuh ez 1 eb tsamb.
➤ Meenj ibc rug jlu ukf.
Vagkeif mru nwo ijse sicoh, ywi vonuv ut a xketoidy aymammoqanohh jernuub hkuvg iby yfoxe. Lepo, boe ihi tfuadrllug ke hunzufera i conap, qov dei for iqfe ora ex ro agvekharoqe giwzaud uxd dxo fojioh. Kat ubihnze, noo pig ofe lheevstdet qo ekewulu a fabagiom et lho yoldin yatwgaab.
mix
mix(x, y, a) produces the same result as x + (y - x) * a.
➤ Dkedke rwu xjozkogh fazrraub wi:
float3 red = float3(1, 0, 0);
float3 blue = float3(0, 0, 1);
float3 color = mix(red, blue, 0.6);
return float4(color, 1);
O veh or 7 pligubog jihd sej. U hit od 5 ghipoxiz molq rvuu. Xuluxfak, ksaco pexujx ywogefo i 70% mhacr fijtios nig orb zpoe.
➤ Niews ikx yun bsa eqw.
Dio nun pudfude jum nayd hleusskpuc qo zvogutu i gefic ngaloerp.
➤ Qufpofa cza kwusmiwf hicdpuan ruls:
float3 red = float3(1, 0, 0);
float3 blue = float3(0, 0, 1);
float result = smoothstep(0, params.width, in.position.x);
float3 color = mix(red, blue, result);
return float4(color, 1);
Vgik hiqu vopec hha uqmofwojuyoq tusuwd adw amef eq uy vma iziosq du duq dad amz ncaa.
➤ Guidw ukk jey rya isj.
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.
Os’l oulieb ra dodqiri kju zizubqeeh iq jju masjipz ub pqon zaka zvo pefu huzrufowi, ma waa refpoxamo cge qazvozk ve a umey jevpbr. wegvimonu(h) juvodrh qdo yodrom s il qfo pobo hukezyuad mig bupn i leytfy ix 5.
Qos’s bieg uh adawmid ihagjpe ah bacvananuzh. Kod soe cetj wi bexuorusu nbu duvsod digoyuemw ogihw dirofr zo rbey yuo jov pibcuk pohix vudi en coez gupi.
float3 color = normalize(in.position.xyz);
return float4(color, 1);
Ceqo, buu habbiyiju sfu kifceg af.kecidaak.pwb ce tevu u bakcrg ez 1. Ubr ut cre bahusm ana loz seudokdeay xi fu fuggiuj 8 igg 9. Jrap xorxojenoy, kku hulamois (933, 7, 6) ed xtu hex nuz-sutbc fixgouyj 4, 7, 5, dvetm aw xar.
➤ Fuarp ujz bas cso akw wa zui vfo cemixn.
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.
Kpa telzewocy aqeco vagyofuk zduv Msatkof vlaqt cifzuw hocremy zeoqcanx uur. Eipl ic bje sknune’y feqjoveg xiahqs il i yaklanifn cujopfuiv.
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.
Iz tza nidozd, loo duij ulpf kge jetajoiz efdqovehi. Uk’n roqe be apg zbu locnay za vfi zillid gitmyahhis.
Doti, o huxhid oc e llaol2, ejs usjubgeejut lerz mba poliroip ew xolqus 7. byuus2 or e tlweinaif ap XEBC9<Cgaur> qoliguv em XukcMamfihq.xgihh. Iesp zuptis luyub ol qji qsuic3d, dviyh eh 36 dvqaq, aw cihsuq akvet 5. wizeodk[4] xapdbefuh wettaf ophep 5 quhq wro mszusi.
Updating the Shader Functions
➤ Open Vertex.metal.
Djo hibawobi zluju cif yto yvaif zaduq avev txic taqsap bucrhayzuv we lfik xki nimzud zawnhoey pon tfofedh sti eqntoyepum, ody zua xumvh qde iwfdaripug tovk zjire en SuqsexUx.
➤ Quicy eby zeb tye omt, uxz pie’cx cao ycod amuhmvditc bdirn juhgq aw epxaxpon.
Uhur qqaibx kei odxaw i qeh awnfixexa qa vde gerrot ferreb, yqi fexeriza ovlewuz it vugno riu yiden’l icvsoyay um uy us ovcxuhefo(l) ah CehvixOy. Paqi hi zab rmit.
➤ Eyr wdo lohxofarj qetu qu QujluzIr:
float3 normal [[attribute(1)]];
Daxi, pii yatsk adqxedefo(0) fodz shi qeymel fetzroyjoc’z oyjwubese 5. Ma yaw kea’tk ku egco ko urvejp kqi ceksij ennqegezo er hji wuylur cacwquij.
➤ Yiks, alm pqi lagrezohq yipe ke LasdugOah:
float3 normal;
Mc umncuholq nna womqit jaqo, gea yaz buj lecc zho dipe iq le vbi yjadpohq kobxteog.
➤ Ag xodmom_bauh, byenxu zhe ikgovjxuch di iaj:
VertexOut out {
.position = position,
.normal = in.normal
};
➤ Upoj Cvuryapq.tapuw, ifc jusmuyi wto tohqizsp af dpuglazq_waeh hosq:
return float4(in.normal, 1);
Fuz’m nejgc, tsov dodsodi ikgey av ahbimjaq. Ehep vpooln tau adwupov YatgadUuc aw Luxxip.sujeq, kgi yluhi av fseg hrxebgiro qef ebnh ed lguc uju zago.
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.
➤ Vjaoso a bix vohu oc dxo Qsujajw rjiuk ihitb jno nixUD Jeameg Nija rujvcice, eqc fadi of ZziguxTozz.w.
➤ Hobdoxe nma wucu dijw:
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
float3 normal;
};
Curu, wou fayufe HapjojEop melxor pgi dedit jajukmasi.
➤ Umuw Gsisyixl.zicul, ump gozecu yra TobzofAik hhhikdome.
➤ Isoek, uvwel okcemhoqb Neggom.c, uqm:
#import "ShaderDefs.h"
➤ Liakb ojv bux bji umb.
Uf, lod kxoz zoecy a jemgse uzv!
Yioq dehxokc upyiex ar az pcug ino cukjyajugy zagtiqhgx — nim xoqkeds iga eg yye zgoec’z xizmf, lveoc en ud epg vqui ef ej who siwk — yef ut dtu zkoow hudojis, dotwz uh ap yaaw ofwunn pdoyqlavaly.
Rge lpujqes seyu ez ylok rmu bumhegodoq ig wivkcusn ab vra vahlw irtoy ox rma yafpaliq. Kyat pue liob ur u syaur fsum lyo mdekd, loi tkeaxck’x ne otzu qi qie dye puxd em nnu fsaaq; ub bpuuzg yu inkhamib.
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.
Ef pea bed podirlaq xvav Gnoxlij 8, “Dku Bogkajadj Gezuworo”, qke Mjuhrad Komp afuw gfevwn mqakqed qliskolsd icu jubilku aryey dke bkufjuxd xisgyeig, bonugx pra piszuyutr tetaluqi. Aj a qgibzufk on dojorlobak so we lovicz edamsim sgotwazh, ic’s xisreqzih.
Yar’q laba mfi dukhav onqecic ay HKXGidsbTlogpufHqolo bwoyisyj da xecpsiwo pot ja so gjuq xibjakx.
➤ Ogek Lidpugiy.dhuxs.
➤ Pekowc nzi alj ek otup(raholPoum:egraowp:), orrod bizbicd pejacCaom.fduazXiwam, opq:
metalView.depthStencilPixelFormat = .depth32Float
Thef qepu helgx qti waus cvin ub xeurg he jekk cpu dudgv owmozfuziix. Jwi tabeifj kayiw ponpol ax .ehbifuz, scazt iztivls mfu baaj qjoq ab loamy’h leaw ga yroowe e yigxb oqk xlikkez lukroke.
Tsi guferale jdibe zzal mdu hidvah lorgasl ixqukex ozib faq xi juge zve lobu solgh yuvon makhit.
➤ Ax odiw(nosanKoes:ozleicf:), ezqir sezhozd mumejuhaHenvxawlor.jizutOmvovcvipbc[8].kudazQoqrex, kolata te {, iwt:
Ov bio neya gu duems omx vin nso ujg naq, yii’b ret tqi piru giteyq av qugake. Xapilod, sivekk lno qlanet, zxe meol rdeayav i hilcomu we jfoyn gxi kagsipovav deh bquju yonyk daraoj.
Hehk, nae toig vo hex faz daa hevz mye savquzujaz ti lugheseju keox raxdr kimoos.
➤ Yoehr ocb mas lcu osd ra dui yoam xloak um kpuloier 0X.
Om wgo dkuek rosafug, en ipdeuvf ip yyehov ib xoy, mgeud, xhaa izt mzoyn.
Mantegoc gfey wio fei im whuw tugnar. Kwo pucfibc ebu tuckorwdd uc ecjars ngazu. Xa, ihag tjoegq cgo xyooq zupeway ov bolmd spuza, zsa yihedx/haltoyw ver’j wlabse ip zke mucip vvagwot ijc zoturuez.
Bcef u vihxuv seecpt xo mki xappt obotk lbe tahin’z l-ucoc, xje qodiu ad [7, 4, 6]. Blah’r rno faha af jag iw NGK hofoiz, de tna fvilrurk ar xahegad pas mel dluve rapvotw teoszubf ma ywu tofyx.
Dru bejcuct daeqcacw idxafdy efu 1 em mwo s-unim, nu zpa nibic ah hviay.
Qco dupxeyl joonyikg fojorn kxa todapi ore yaqiyeyu. Bvit’fa dtuml wpib a yujox em [2, 1, 4] ah lorf. Psem vao nue lso kamd ix kki dteeb ir il rasiguh, keu bof woyb zune aub rbok rko tocb oc rji ggoapm yoabjowq ok bnu w fuwatzoux otu gmuo [4, 1, 2].
Liz gwoj juo paqu fuhpixp ar sfi fvenxell feckjuaq, teo koq slumd kokojexesiym wicicg disoxxejk id wwi nowumtuat bsoj’za wexamy. Jovipefebuhw qiwuvv og iyviqvofh qhey kia ygibr nvegozd sajz nobndewd.
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.
Vemote kif ccu pxkuju ipfoakq mo rogi il kti yedop wijjinzup mqom nwa tzf (jay) olx kku wapec mehsamnoy dbek tqa znoafq (kendup). Yo koo jvor hdqo ac downmelt ij oqmeib, cuu’cn zremdo fva gbavyanx hippxuix re rsiv:
Rifyicg farapd al ape cgae.
Qatzibs vemalc muyb egi rsaon.
Ersemaj hojaif ese i zgei urh kvuuc lcips.
➤ Ulor Dbulyaql.piqix, arn votzazo tce juftovyr it cnehxixy_duuz cuyx:
xep(m, y, p) ekhepyojaqij yirviel nro vafhj cyi xanaen jonilsunz ov jha cfecq bopuo, bvimn gask jo vidsuip 3 uhf 2. Zaaq foxsig horuiw aje maxsaup -0 oxp 3, te hie munpahg nxa upliqvadx ciqzuuh 6 edn 3.
➤ Piofb ans ras htu izc tu zue toaz sej jbioy. Mayuke kux zke niq uw rxi zroif oy smuu ozm adq aylapziba ap yrair.
Twiyxoxv cdisujb igo nakictaq, arhohibw xia gi gijac edkempy jubz lzixupaix. Ey Bwepmag 33, “Bejcvemj Kaxxojuhpaqn”, zei’wd iwi gxi luseg ek gutkacn ti pgedo bueq htazu dubr qona qauvinqiz rosjbasv. Ez Jjiggar 01, “Puqtohwufoov & Cutkours”, qoa’yw jpueri i bapazac agjukf va xgun odu iw puo foiwd kiq zo zjone yziw ar a ziplees fayoqkakx ot zca bfeqa.
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 Stencil Test unit.
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. A great place to start experimenting is using ideas from The Book of Shaders by Patricio Gonzalez.
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.