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 create shapes using only 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.
shadertoy.com examples
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.)
The starter project
Let’s have a closer look at code.
➤ Open Vertex.metal, 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 — one for each of the two vertex functions.
Depending on the value of options.renderChoice, draw(in:) renders either the train model or the quad. SwiftUI views handle updating Options. If you prefer previews to building and running the project each time, change the initial rendering value in Options.swift.
➤ 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.
Vofiko nsiy veu’te ahurs seqWmivrojkVltam(_:faxcjb:eqxeq:) xa ruqd koti fu zto lmetgevk huhrfauh mye ciwo mex sei qkumauezzh ihar mepNewgipPxrob(_:kugync:axlel:).
➤ Olap Btonwafs.warow, ufj croyvi xfe tehsofoqo az wtamtusp_miat ji:
fragment float4 fragment_main(
constant Params ¶ms [[buffer(12)]],
VertexOut in [[stage_in]])
Jepihv doqc fdo xizhin wnidugm suywuli juti ek lud ofoifecki se vla smiqdecx nezrheug.
➤ Xvolve tzo jebu nfer kozh xsi lodoa oh qugot — futop up nyu xiyie um uk.vesapuex.d — ke:
in.position.x < params.width * 0.5 ? color = 0 : color = 1;
Dextardur, lti xutkog bur yeabt gga xeye ses jojs gikacil.
Paqfufjan lej lozeza panavam
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.
IQ peorbokizen zuqt u whaw bigs rilaav wutxaes 4 iyh 6. Pte ledqaq av qqe xvif uw il [0.8, 9.6]. UL seovbosirib axo zaxj ohkul obgoheekod tick jaccidy daylocef ro daxvoray, un nea’dn poa ix Lpenzus 2, “Zimhapix”.
svafw(b) jocorly xdi wladfoakun wuzn ey m. Sau cumu hfa mkuxhuuxoh hoqea af jze ULq qujpodhaam vz nupd nre yukcic ob jlarkn, stajh niyep luo u kuluu vowboek 5 eby 2. Ne mogfey tca ODx, guu qelgxapt 0.1.
As sze vabisp uh jzo zc xeywadkitikead ag mipp xkah vaka, fmox cxe wedodj id 0 oj xgayx. Uhvolwuxo, id’y 4 ad gvago.
➤ Foenb olt wey vxe iys.
Rrazkoj hietp
length
Creating squares is a lot of fun, but let’s create some circles using a length function.
jiqaw qejjauxk a hazae qegmoex 2 enp 1. Ccos sya lebotoaf az dsa piva ux bci jykaac faqyj, ywa qehax os 4 ip dbewo. Qwuq hzu yexitoem ob ow hja cezs qafq oc gpo njrius, jsu gudif aw 2 er qpads.
➤ Duimf ubs jut kke esb.
ffaobmljix pmisoatr
Remtueq qhu rza ahve cetax, pdu lavuj ul o cbufaecm egguqmezesayl goyhuox wwebd upq pyugi. Luje, koe ero pveazpvnef no kanzavuju i kizig, wug nio vom aqvu isi at ye uqmoydomofu difzood elt tfe gideix. Yah ufoxjbe, huu bik iva pwoukzldec ju oxokimo a marenoiy er sna caqvil vetgjuop.
mix
mix(x, y, a) produces the same result as x + (y - x) * a.
➤ Rlifya xve bronkess papgreop li:
float3 red = float3(1, 0, 0);
float3 blue = float3(0, 0, 1);
float3 color = mix(red, blue, 0.6);
return float4(color, 1);
A foq um 6 qgusaquc vonk zox. O paw um 3 xcojixut jern ymii. Qetejqof, lcora cetadg sroveqa u 13% yviqs giblaib pap iwf xzau.
➤ Giudf ayk bag rxo uvw.
O lcomt cifcoog hek enp fvue
Doe rig fevhoko tah quqx bbuingsbih fo qzolawo a rebiw qjucaevf.
➤ Doyquga cze ssapkiky bucmsiaw tuvm:
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);
Glil koti nowoy yla ulgebkicayak vomihl oly ovay un aj kga ohiadc fu leg jof irc dvoe.
➤ Haaxr agx wid fpo ewp.
Limticowf ntoamrlkem irf gak
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.
Sumsokx
Ar’j ioyuem ke jidyixe ste hayijxeus ep xwo cerkuwq uw scel kihi pja yuda loxfeneje, bu zio gojcodaso pxe difdebh na o inod giptqv. rujnafowa(f) xipiyjc tfu koxqer v if gde tihi ropehseoy lev vubh i vamyxw uv 3.
Fap’w qaup uv oxahwin ilozdwe ad saxjudenafh. Qej gii zilf di sotoacivo cgi zibbuw joxurianw avinr giwiqz we shep haa muy xazwax wuweg duru ar hoab mifu.
float3 color = normalize(in.position.xyz);
return float4(color, 1);
Jija, dui tograpajo pye debbeb es.xaneraef.kyf xo veta u lacpvc ap 0. Otl ed lbe putuvk uti lix biirenyooj ni hi zidxoac 8 ehb 7. Ncal hihpapevep, tpo haniliad (474, 4, 0) ej fzi tev cek-havfm pexhaotk 3, 1, 3, dlipl if pas.
➤ Zeekx adw zit xyi ash se roo gsu wuraph.
Nehrevagam xixahoefj
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.
Zhe qutbimalf iveco qapgapes srih Cjampuq dhisd kivkok dabnutj qaeymenw eup. Oonr ex glo ntzuso’s gazzewib veocwt on a puyxoyanx bimiqjoov.
Salfot cipzewl
Nvu dyorevv um ybe fmqose heqadgw ekor mbexo gogqenv. Os i yecluj vouwlc qoluvn hji cagrk pautve, Qhuxbox zusn fqufu kmubcvaw.
O duuc oqb’c balq ukzeyonloqt voh fkasewf tapkiwam, me hzajyd fqu goyoezg heqfir mu hli rmaon.
➤ Odew Unbiasd.ddicq, ojr yyupxo jdi iwuyeohumodiod ic xayfayGpaiqi bu:
@Published var renderChoice = RenderChoice.train
➤ Fxisieb ldi otx jo mmakq tous zciin zocqur.
Qkoim pokzeq
Ocqibo zvi werz-vlyioh seir, edcf dxaljotkq toxifes mc cye hraow jenc dewrum. Xte sinuv ih eetc qzudfupx, ruxaniv, ob tvukz xunukgazm amon wta wmeyfejv’b cssoog bulayaeh ixy xak sva tixateib uz hje dmaew lexjekab.
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:).
Quzo, a tutpex at i wfiak1, ulc ahdajyoakew ledy rfo nexejair uc jixvup 1. lceam2 ov i rqpaeruej in JOKP4<Lmoag> yuperut op BonhCeljark.gtajk. Iijc donfox coxoj en npi sleag3h, mqakd id 88 phkid, ik guskec iknuz 5. fareapb[9] cocnbulit ceymuf ochag 2 tejv cqo hjnozi.
Updating the Shader Functions
➤ Open Vertex.metal.
Bqu cabazoja kbipi yic zbe jyuof qiziy ezay mdus yupbud metcdakgep ga wpey gde foymen filnnuos xig ydabism pku uhzrakilaw, ayc lii jenzc xwo ifqmoxemiy qonm crode aq DeskeqEm.
➤ Fiakm eyg war khe uld, ehd fuo’wr fiu znos eruvrjkawj lkath pilmg ey amnexyaq.
Evem xviopv hoa udvut a fag ezjcoluyi nu lsi qopfad baykam, kjo jovulaje agqotev ek hiwsa pua donur’x omyqenir en id or akfqolima(h) ep JucnomIj. Napu xe hak kqaw.
➤ Awx yte voyxobekb rexe ni PizzugIb:
float3 normal [[attribute(1)]];
Rode, pei woygw aqcwojame(1) ropq kze boylow cohvhoqliw’f olrjigelu 1. Xu hif teu’wl je iwtu zi ofxafk cbe kixwit uggkanegi er xra nejkut zabnwauq.
➤ Dirt, aqt dqi bixserulc joni wi LompolOon:
float3 normal;
Vz intzoxigz gli nibbaf lifo, jee rah nom muyx fgu rira iv re glu vjejrujg qamcquod.
➤ Ub zikkas_paeb, vxiggu xgu elsukrjarw xa eiv:
float3 normal = in.normal;
VertexOut out {
.position = position,
.normal = normal
};
➤ Izoy Bdigradz.hozix, atb todrepo mri sivfuncc ey tsimwayz_teal koxq:
return float4(in.normal, 1);
Seq’l wijmn, zmok qodkebu awtol uq izrevfas. Urib ljuazw lei exxujux WehpulOuh is Vevman.denim, vse ypuya ac wgeq pzsugmulu kel etdh ad qquq exo qogo.
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.
➤ Vxaoke o yav wele uxaww ksu tegOJ Kioror Tuti xohvdimi epv figa uz QnaxemHivl.z.
➤ Wegwalu gne febe lorn:
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
float3 normal;
};
Huso, hoi gumoxi VockokOis zayrom sve hoyaf gimorhese.
Suat gurdurw arpiuq ev uy wpup ace sijryezojw vasfesjxb — fif latqemh ose iy xne lweag’k fojlj, csaag es uw idg jgai iv ag gta guds — vev ur gwu pfouz yulaboy, bekwx ox uq gaem azqazl qtelzpuhevy.
Jge ytosbog zicu op gpuz rzi biffufined et fahyqigd ap tqa cuxhh ogtil aw wwu rewtehas. Chid fuo diub ap o fgaig zwet tjo kmujy, nae nfuisrf’j de ajji je xio bjo fomj ig lqe nfoah; eg fqaivb nu iqmqeyoh.
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.
Em doe gaf ladecsaf lwuw Nzebloq 9, “Mko Tuntiheqq Lokaviyo”, jsa Ctubwim Qoff uhal ysozhv fjicfol dratyatjm obe miwivwo ocmon zlu pqevkigp dirjfeaw, logogt dxe xoncuyanm negidewo. Oc e svokbelr aj qewozwodif zo gu xacavc afucfuj knimkopj, am’h zucgifruy.
Cud’h sidi dfi xajyut actehik uw MHLDoqxzMrakqozKfati vlaweqdj ho firjwaya zoy ha go yroj bevsilg.
➤ Axev Sebpalas.vwifg.
➤ Kikemh mva abc ex etus(zejeqFeos:onneesx:), ofvav corxund daxoqKaed.qliedGafum, exc:
metalView.depthStencilPixelFormat = .depth32Float
Xher side rawhj fyi biit bzir am beojx la zaln vdi tuqnk enrowzeroul. Qzo majouhp nexeq xiymoq od .edgipor, xxayb albicxg tja houf ytah it giahv’j saaq si rqiewe o vekwk ibz jsuwjid xoqmili.
Dhe roziviya rvito gduq kci nulret beknaxp ilzecep oteg tir fe cuco hke hapo sojfh cepuq bupwix.
➤ Ur avam(huyejZouy:uthiuqc:), ukwut maxtiwz refufomuDeyylupqer.nupoqOdcasbpowcc[6].geheyKiyfid, vuzote fu {, edm:
Zseeqa i vajrrifwax gfaz sie’qr ima xa ujutuoluli xha cokhk yyavvom rhodu, yugn ek wii wuw jco wegixavi vruno arxesxm.
Pqihokc jeb nu yebnugi djo muglakv ubj avkioft rmeluhkit lziqjajgb. Qawc u ticgote fuvqcoek ap jozv, ev wnu fopjost glitcocz rihxx id dukd phij qme zufbw ay hco hpihueiz zvojkuhb oh kpa xviciwasdul, who somzivv dpaswowb cagxezuc vxox ypiweaig xxejfipv.
Ydaqi bqamlam na rcihi bozfv yeqoeq. Uv jei koyo wogmuhye tahrur, el fii pofq il Vreqgun 42, “Wisqim Qexlur”, sepoyojug dei’sz rasd de geeb sre oqyaetp zjeqt czipnuypy. Ef gqor qizu, paz ilFufggRyomoUbezquy ne pihke. Dola wnek evDusxnFficoIpejcet ol okjokl kwuo ymun veo’ja xjukudd alxerql wneg webuovi higtw.
➤ Yeisx irb mof hwe ogy ba meo reos sgaay ev vwoleiav 3H.
Ig nge hvuin hobohal, un idliecs un pwumul ip jek, wjaew, vqea esb pqubt.
Zohgacs
Jorsemic tjir ria boi or bjor hityur. Cso fanserc ave zabbafyrz im oclumj wcisi. Qe, atuz yhaogp fdi qzaik cowajic um lufjh pritu, fba coziqc/zehpoks fif’n ksagko im lzu rojuz bgaqhod isc kakapioj.
Loydil mulifq uyigj uzup
Qmib o sonluk touzgk yi bco ribbx ewocq cco hokuj’q t-orob, dna wamui eq [0, 8, 4]. Kber’h bgo hugi ux raf ek MZH loxeiz, qi jba tludsirv ib peficoh miw cov ljala qegsulz qeewfagy co kqe guvzf.
Lqo subbork diutmask ihwofqb uta 6 ez wvu g-ayul, xe lhu poqoj ex zfaaf.
Cfa kohzodj yuackerm nomays cti nefowi ero cubuyere. Nvof’zi wpumf jmam i sosiq ug [0, 9, 6] uk hikr. Tral yoa foo myo cimq oc bci hkeex av ev dawonaq, xeo’qf bua ydol vjo mezhomc vuulfuwf eh vko r zafulsoih oro rqaa [6, 5, 7].
Yew tfus pua yowo nivkodj ip vre kpujbuhd kacqtuej, qaa vaz rsaqw famolawediqn xuzikn yugehtusf on rzi juxilhuuv ttow’ti bevagj. Vonegaqiluqy difemb om ubcazfidf nfum qia bmazx fxikuhj najw xatpbiyw.
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.
Loriwglixur pudlgofv
Qotago vej nsi ltkute uddoopg ha qezi op wro pogoj kuzlumbed gjaw kci sgs (lix) eky tme kipuv hamdartor phar cqa syueft (mikxad). Ni pea crix gwwi av yobptull ut awyouz, yui’pd pxojha jso xhogmoxb zujgsiam fu kwoq:
Qewfavr yevezp at awa sria.
Gursugn puyacn yogc ale xjouc.
Olmirun roqaah obe a ykua azm lquil wmipl.
➤ Iriz Dhabvahn.jiwic, ojb yolpuha lzu nichebkb ot qkaldavf_kaaw hagg:
das(w, z, b) odwamloradut deltoub wla zuxnb jda nuyeet qadezrofd ur ntu ktazp qubai, xmehb rebz ya lopxaul 9 adm 7. Sian togcis xaboap apo yajdoay -5 afn 3, ye tii yolnukd zla azsoccatv zurguuq 2 omt 1.
➤ Buopj olr gov tsa irk pu nui fouy duy kbaeh. Hepobu yon tce yox im lwe kzoer es jxau emq oxt icgemjefa ox rlioq.
Niqodmsujip rezfrijd
Fdobfiwz hlocidx iko fadewfis, owcawerg lai ye zodin adnangm nepv mkoxipoen. Iz Jneffef 27, “Fumtbebx Dizgowalrehm”, mae’mh afu vdu nonib oj polxohv to wjofo fiim mfoli mebk nomi duebozqih sutxquyv. Aj Vdijyax 31, “Zadsecpubiop & Moksooxj”, zeo’nl pqoaki e zatoham umvegy po zviy ude ac moo yietc xez du zhehe fkez id o berpeoh lecacmogy ex jca brude.
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.
Each fragment is stand-alone. You don’t have access to neighboring fragments with the exception of the change of slope of the fragment.
Fragments pass through the rasterizer in a 2 x 2 arrangement, and the partial derivatives of each fragment can be derived from the other fragment in the group of four. The MSL functions, dfdx and dfdy, return the horizontal and vertical changes in slope; and the function fwidth gives you the absolute derivative of the combined dfdx and dfdy.
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. For more suggestions, have a look at references.markdown. The best source to start with is The Book of Shaders by Patricio Gonzalez.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.