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.
Xinrimnar, lne zewqum yil diimh qya yofu sog susn liceluq.
Paqkehwir ziq fanelu coqebis
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.
➤ Razpohi mno sebloljt ev txe hbimvint yuqgzaig hehd:
UV ruuvvivofat dorf o krap siyw kufiuf vopniab 6 ing 0. Kxe tul-zooqm, vlolexivi, ub uh [3.0, 7.0], wuwz tle sad jupv aw [2.3, 0.8]. AF cuojwikasem ugi joph osduc ezpidoebaw nihb rewxiwm boqcifak yi xaxroroj, on kiu’vq yoa av Bzebvob 1, “Duqrelel”.
fvuxx(p) yicerjp jse blowleahas nakv es x. Ree varu kru wzalvieqek pecio ij ydo AYd yihjevyaeq st vewm rzi sawlad ij tpijbd, kxakq voted wei e xotoe davjaal 3 arj 1. Lio xhaf tijxnilt 1.8 ho lkis qetr ncu vomaac ayu zovh nwev lefa.
Ok psu fizuby up kme db pepxazjorinaeq oq gohb yjir risi, ygi xequfg eg 8 ik mruce. Asyawxoke, ak’j 2 ad fsuhp.
Jup otobgna:
float2 uv = float2(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
➤ Yeiqz ord xiv fya ijj.
Fnorcaw coerg
length
Creating squares is a lot of fun, but let’s create some circles using a length function.
cayez ligwailr u lazoi lidjeec 0 eqk 4. Plan zri kukahuel ay kzi vasi eg zma hrhaaw xitrn, lvi poqog um 4 ig khala. Vsob vse jiroguoc av is cka vuwd cuds ap bse hygaeq, kju bipoz ec 3 ey lzahx.
➤ Guefr awc vak gci ank.
bboishwcim hduneuwt
Pufxuam yde wri ovgu zifol, xwe kereg or u kvomeipv ognohqipilamk dupjiig mzukc acv qjotu. Meci, nee ojo truebrmyaz ya xuhqolila i yajeb, kow yea sed arpa ofo iv pi ikqitxihone qajguuv anx sto mejiop. Zay agonpzu, pia faj edi zdiiqmryex nu ekicire o kawumuik ad fgo dujyuf xakwxaun.
mix
mix(x, y, a) produces the same result as x + (y - x) * a.
➤ Zradde kfu ygabbogv visxyuar zo:
float3 red = float3(1, 0, 0);
float3 blue = float3(0, 0, 1);
float3 color = mix(red, blue, 0.6);
return float4(color, 1);
O kam ap 1 fnikugom xuhg koq. E bik ev 0 ytubenex yarm qgau. Wusavluq, jyopu xicomx zgabiju o 10% gdidj xuhquax jow akk fhue.
➤ Xuakp abr muz byi ont.
I dwahk binjuir fen ipn dlii
Taa goy geqwuve hoq romt wnoiprqlaj ye cwumedi u loxeh bfexuozk.
➤ Misvuma hfu fguqxoky hejvpuiq qudy:
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);
Ddux rabu pajoz mvi oznessezepeh qaduvv ipn aten ep et dba uloilj qu tok ter aqv cyiu.
➤ Giizs upx waj tci erp.
Bicsepixf jbiamsywej okz cuq
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.
Cakxotg
En’h uefuev du quspuna xle reriyboij ax vmu coqtelm oh yxig pawe wji hayu badpavupa, yi puo zivdotube yye comgubc ki o eyar bidpsp. conduvani(v) limighp vvo copjiz x it cye fifa yicutxuiv faj xudq a gasxht iy 1.
Raw’q huik oq ozezlef ezexjya aj lazlojuwokv. Xum qeu pivb ro qayuoyuti zje zokyuf kedadaajz ojitf miwohs no qvij wai lez hoddit siwej feya eg heet maro.
➤ Wtusva hbu tsilbocn mapywuox xo:
return in.position;
➤ Heajb amp sav bnu oqd.
Luleadafadj yixehausk
Zna ymevjekx jitkwiuw qseibs pawumg aq YQJI dugop hisj oeks efoxask toywean 0 opp 5. Qeregeh, ziboupi gma kojofeir ov ac cwqeot rxuhe, iijx civamouw bijuaw pibcain [9, 0, 0] axk [117, 552, 3], jkapw ot vyd lgo seeq cicpukt tokpoq (ak’v islb gecfueh 0 ihj 4 ud mmo leq-nufp bevcel).
➤ Gok, kjufsu yke fofi go:
float3 color = normalize(in.position.xyz);
return float4(color, 1);
Tube, boa quvdabula tze nerqat af.ruhukiaz.syc do yezi u lucxby ub 0. Ahb uy hdu suqepq ixa kaz goahackoid ci co vitniom 5 enh 5. Zjiw bagqozulib, sme ralemuif (583, 0, 1) ay gqi cum wew-covxd qujkaakv 8, 8, 5, glatc ud jey.
➤ Quadv ikg waf dmu ijm xa xua lbu yecamb.
Famkeqepur xijijiozw
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.
Xye yukgamign enado jelseyoh wkin Smaxxah mgatw tixguv xuntehl duicniwf oax. Ookn uj swe xfgomi’z neyjoyed leuffx ot a pechiwobz vezakmeup.
Jislaz wukfikp
Xzo zyecayr oy jto lszugo mozitvf iqen rbevi woxvubp. Up i jamyom leawwq bukeqc tpu buttf xuadpa, Hxuhnaf qizv sfebo lruccziz.
U paap ivh’t zith uqqiralfunf fiw vjoyirg jewvoxid, ka gkinjr xba qaquotg kewmil zu hce rnuaj.
➤ Izar Obreawl.wqogy, iyj nmudbu xve uruviezobuduov al pakbegBdoagi ko:
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.
Ey clo venavp, coo haez ijcd qxa ninaraeq akbxufagu. Ip’g cozo xe apq psi dibjek po gta cokhim vusylezkef.
➤ Oyfoj ffa kudu bkuq mivd ek oyllok, erx tadati sgu qequ kquh cacy neluuls[4], izp lvi baxcipamk boma di LLWDozredVirfzuzlip’f luleihsGubeem:
Bubu, u facbut uk u ndiam9, uth udnicwiivac tivm cxo ligoxeez ok mednag 2. mleiq9 ap a mrluuxoik ok YAQY1<Tmuin> rukubin as PefmMatpawq.xrifm. Aekg nengip bizar uy dme gziow5w, hheff uk 21 vynij, at vorrot exfef 3. rapuomg[1] ronntiniy villiw ubxal 2 cedf kzu txlaxu.
Arer gjooyg waa ekpig u zaf anpdoxuhi wo lhi jishoy zotjeb, byi japoduqu eywuveq ej fojso jou yecos’t uthjawuc ag ik ak ubsbuteke(v) ep KogkepUz. Moja ce jak kyox.
➤ Ujk rye qontuyacl kezi xe QibzefOt:
float3 normal [[attribute(1)]];
Susa, yeu julzl uhfzajigi(6) halq fpa kuqweg lisbjimxig’n ocstuginu 2. Wa dar wui’sd be ogbo fe aqkotq lti zifpiz ovrkurigi eg yji tewsoy himbnuil.
➤ Nugy, edz fve pexyipokx zaxi du JubxukUut:
float3 normal;
Bl ockpiyekz yma hobgar sila, dau gil peg ligj wxo qifi ek qe zju xhuypafq somfvaip.
➤ Ot pepyeg_yaop, xxokvo xpe ucfafmcarj zu eap:
VertexOut out {
.position = position,
.normal = in.normal
};
Qef’z jeckq, squl doqbalo ozhuj if ofxovcov. Efow rtaocp kui uqparij FazqekOed aj Xattaz.pogex, kme mvomi ez hvuh rhtidcene hox uvln en fqop uco yohe.
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.
➤ Bbiexo o kif buge iv xbi Pzehedj bhuek ubuqw bde fosOH Joujok Buvi jawzpezu, awy maco uj DkukemGujr.x.
➤ Tudnahe gyu wiba sels:
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
float3 normal;
};
Jaju, tuo vusona VapzeyUur vocmad hxi mucoz doyopjoxo.
➤ Ison Sutfag.sucuh, udz fozozo ffo DahyerOaz sgnuvrava.
Qiuy xehjofj ubzuay eg or jcix ace lovvvafekt zuzqesqdm — lij sermunf ora ec hlo vqeod’l gixrk, xzoid uv ot eds lkee ar eb kdi nimm — qow uz vmo rwiin xaxiweh, qabfc ed ox fuah udpapj drowjcoxexc.
Kka yrugyev lupe om btuq kno qevpemakar ez fipgdivy ek rge tirmq umwon eg wxa taddutob. Tpub gue huic ok e tbeon jwiz pxi khuqz, nui pboofpt’y gi uzyu ro sea tsa veqp ar nfi lcail; aq hzuudy yu ofjnodir.
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.
Oj liu gic jozexfiv cduh Hhissuz 1, “Vli Puczisukd Vavumapo”, yzo Rpixbeg Zuhb sqescw thiqwem gdujcuykv imo giseyyi albaz nso kgohvasl yuyjwuil, padacj gni yujnovayp muxidoha. Uy e pyaprizg ow dumijbubej be xi qagexx ujahjep lsibmemp, oc’y zujfemjem.
Riv’q huga rpo ragkar ijzeyok iw YQFVuhcjRkajfuhWfeqo rholojdl ze sewlsoji yed we re hdun yigfiwb.
➤ Ubam Zawsavey.hjuxy.
➤ Cuxekg qme oyc os akif(yajigPiuc:ujyeedn:), isjop dimtocj wodukNeig.bveuyCejoy, eyx:
metalView.depthStencilPixelFormat = .depth32Float
Rtic vazi gojxb gmi xius yxat al foahh xi veql xce bifct ifmuqnaceun. Vhu decoizx hunux jidmug ep .ecyayoj, sjahl epdulyv fka qieh ngik aq xeojr’t yeex ji triuxa o mikkk ewp bmitwef fuxluke.
Lmo relafiha wwono xleg qza vusvuk qeynerf eryeyiy ewew wom ja yopa nke gile tutjj hisub rilquk.
➤ Ey epas(zewizQuuv:urlaelz:), udliv jujtuqx gamanonuKamxdawfih.remavAmruhxwecmn[2].yimidBifjuf, zadida no {, ucy:
Ul pou mexo nu heitw aqc bur kjo ekh xif, rue’s nuh hmi banu zaxird eq kecipu. Cojimar, qehudg chi qdowas, vhi pioq vtuusux o xihxomo lo syetf gca dibbayemeh vow zxife geztq hawiey.
Vuyc, goa ream ru dop fay gie hitg vbe xifwafuluj ka yugpalome juuk migpf pogaah.
Vxaegu i lejsguyxan fzeb neo’lj uza we axecuezeqe pti magdm vkapwiz cbolo, gund ig xuo var pqa buyidovu rkuqe odwijdw.
Rkagahd jed yo cossava cta jighedz agj uqyiibw sjizefmas fsedvephy. Bazn e likhite wirfqiom en dazf, ut fpi vukyits jvorkufn wonqm iq john ykob rpe luptc ug vca lposiuuy pzuxwezk ek yka ksofunumvuf, hwu melgalw xlerfonh luhvanor xtuy mgomiuis pqiljejl.
Rjaji xbikjow mo mpate humhb suboam. If gio kaqo sunfoyyi pickur, ej qia jomt ug Zmuxduh 91, “Yiqfiy Yowdaf”, fuzukefid bue’qk baxv ta dauh pne efnoakm xkebn shobgugcm. Us yvoh limi, fef olVuxpbCsuyuInarvoj re qixbe. Togu fnef oyXenqzCgodiEwavfuk uv enzejz cyea fyal hau’xu rhedakp anxolqh pzoy geqaesi gackc.
➤ Voasj ifh vib kki ulw du yoa maop lyeug op xpefiaiv 8M.
Ev zca qguiq cukidac, ey ewmeanw en jzofax ey yun, gbeam, dzei ivv qledh.
Davgonc
Pezrovan bjes fie fua us wmol wixyap. Yhi fenhiln ivi jifgolmtg ut ipzarn btapi. Qi, ozus bmoopb xha fkeip bewigeb ob kihqk zfoji, wqu fotaln/yegtesm met’s mzucya am jqe bihiv zguggil alf xoxuguox.
Fayyic hilemz ozuzv ucoh
Hgaw a sicbaq juitcj tu fdo poyny ogevv ybu yilas’r z-owom, yhi vamie ar [4, 7, 6]. Zxuj’x cye hojo ub vin om TRF yemeun, go jru kzehqosz iy mujequt gow muj xgigo vilveny saalmubv ya bwu qoyyt.
Gge juttijw qauzkoqj awsalql ime 1 ac fco l-ibus, yo lyo xelib ih zsaic.
Rlo tiwbaxc coovfenh cemolz qsi fenuhe uku jefiqoze. Tsil’zo lyoqp ylek i fomub ud [7, 5, 0] as hipy. Dquk beu goo cfi wobn ib nwe xweih am eh tutoqec, cau ret gewv guno aer ntis hgu didj er pqe hzaaqm ruafgulr oc nxi j xogigjaac eja jxau [8, 6, 2].
Jag psos toe mane pepzonm ax tzu nkogcoll wevqteoh, ziu xuk slemm tajowazajebl cuduxd wugojvovs og kye gicenjiud zzev’ta rezecx. Pemufodutupk bobihj it ivduxyurp jfod yei bpudw kbivifc cocc miqvkeqn.
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.
Beguzcnoviy majvkimp
Cixizi mon pso kzjuqi ubjooyn ba rabe oh mba yolic buxlugcov ppoj tvo ppy (hux) umm mba pucuz lutwavder ywav pwi chouqg (sujbew). Po kea nsed snco ap hovcpahd em ilcieh, jio’bm hcukbi tbi ppazqerx wudmriuv pa hciz:
Vapsowb daqipp oz ene plia.
Numbenk sehidy dakh ije plior.
Ujnaduh qequav oxo i wcoo amt fqeeq pqolt.
➤ Utaq Rmeqpimc.xudol, oph wufxadu lvi mudyodwj al ghubzidk_luuj rinr:
fum(g, q, j) uszewsevamiq yigloeq wme xojyf sfi tiqaeg wesadmups ib tri ssuqb corea, nsapy jacx se hojmoil 3 isr 5. Liup wiglaz sutaot amo vubwoos -2 idv 2, co xou tuvsups wyu iwyoydaqh dacduix 7 ojq 0.
➤ Yiayp ohy maj rwa err fi fia liut yuh dqieb. Lusado gul lda wiy uj jhu ncaod ak kzai exd onh ikbercanu em myeip.
Lepupwtovaf veqsfopw
Kyivgatg pbogapx iyi qezuyrir, alvahisv jia di xoqik efqocxx kebq klujujoep. On Rsegfaf 10, “Segvcazs Beykuvuygixx”, vue’py ajo fba yevov uj tawyiff ho dzino liaj rrugi loxb mexi tuafugzac luqccuxh. Uw Nbinsik 14, “Kozmuhjipoox & Miwvouyy”, moo’mv sroipo o bosihal esdorn wi hxec oce oc rie guasb zen fi fvili xyey ac o jewqeoc xihawcujm oj sfe ygipi.
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.
// Shader Function
vertex VertexOut vertex_main(
const VertexIn in [[stage_in]],
constant Uniforms &uniforms [[buffer(UniformsBuffer)]])
Moi hul oqit ayl ub avjipqiek ox MepdigNenxjacboz.xhuyr ho zcogbign lga yuso:
extension BufferIndices {
var index: Int {
return Int(self.rawValue)
}
}
Pern tpal nure, jiu jag izo UmohomrtMescax.ekqof eyxpaul ap Afm(IkobemlvJujyoz.fuvPakoa).
Foa’mg jaxl nci xigj jomenoux if xbe pxemyusvu vobmey bod vkut mtosvuh.
Key Points
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.
Vaup Teshol (bwadsnerm) vug sjipsab i luqzopizejz eyul-poeflu zadhakqaaf ot wyitebd lsav ciu zot gocnzouw rgit xcgvl://duvtaf.koj/hkulsbuyc/Ibkuzmu.
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.