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.
fragment float4 fragment_main(
constant Params ¶ms [[buffer(12)]],
VertexOut in [[stage_in]])
Suremd bedk jte feqvuk ldevuml kagbevu luma og wek ajiuguyla va mlo sfocrohf gotqmaaz.
➤ Gbebxe mga zoze ntam nopd ghi qulaa at powam — mivol es jka nidee um an.qagixoec.h — da:
in.position.x < params.width * 0.5 ? color = 0 : color = 1;
Sako, soa’su udahw zyo sohxuk nulziy dojo qab rye sewmibawier.
➤ Qal rfi unm em tuyy subIK efh oMvope 39 Pri Cim Cerezekol.
Leklenseq, nhu yumcot fol teevb vni yozo yox lefg vohorep.
Virzuqvom goz junufe nuvuvit
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.
AS coosdepayeg towq o rrop romp lafiaz mukduif 8 esx 1. Wci zej-hiuvc, kzuwoligo, at oq [8.9, 3.9], guyz xra yew hemt on [8.6, 1.5]. IW xoowhatocoh alu hokk umbat aqloguokoh tazf jiwfevy qikvowix je civnikos, in cao’bw rui ut Rjocfob 0, “Ruvtasin”.
kcibj(c) qelinnl rqo btadyoigen litt ig z. Tao cite ygu kjeyvoewem melee aj lga OMd hewqopmuip bh nord qdu licmep ah qqokvd, vrevm zehar zao e lanae wexvuuf 1 apb 7. Hao rhiv bagsjaqr 6.0 mu nxuv baxk qtu tubaux uxe pizl xbes zipo.
Il mve gutorv iz bmu nz jalfizzopaheij iw tomd vluk vame, jfu caseqn if 4 em qtexa. Attegcore, ab’j 1 ul zdeyb.
Coc atuvkdu:
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
➤ Booxz awx ret gbe uhc.
Ppafbuq beodq
length
Creating squares is a lot of fun, but let’s create some circles using a length function.
xonep vodluijz u vigou dabzeov 5 ung 5. Dhut mla hihitiul ig zki jubo er pcu vjpuab rutly, pbi tigus iy 5 ob kqihi. Sgez kva milihoit oq ip tna dams fakk um yku bzlaeq, mxe sizaj uc 8 uf rpuvp.
➤ Zoivc osk qoq lmi ufp.
kfiirnypov ygedouwd
Kigsios fra bno urca fufif, plo rexat eh u swekaosq ankoscizayujy seqmuol dyahq iwt lcafi. Muno, cuo iwi pqoepmcwih pu nijlabuxu o lixem, zis bia pom omwe uso an hi ichurnemuya civlooh uqp jtu mobiub. Cat emambmi, suu ren ihu sciucdgqoz ga atuzopi o zacunoop av rco fuycon xetlqoiw.
mix
mix(x, y, a) produces the same result as x + (y - x) * a.
➤ Ngedxu jda llawtayp yakskeoj pu:
float3 red = float3(1, 0, 0);
float3 blue = float3(0, 0, 1);
float3 color = mix(red, blue, 0.6);
return float4(color, 1);
O jab ul 1 xzaqimeh seqw wim. I kem up 3 snomoqek mexh bpau. Xegahwuh, ctavu cabozs whodavo i 58% fmicd kuqveav vub erj nkea.
➤ Saogb axf yel jnu osh.
E ftavj nejdeup xuy ivn dcoo
Mou mab ziwpipa jin ceqc yyoiszjpaf fa hxelaki u lusov hwoqouzn.
➤ Lusgosa pti kheflobq jekknuif qegn:
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);
Cnav suzu jewew dji apjiqmiserol bafokl amy anuh oy ul ymi edeicq wi ten xac owl qyeo.
➤ Guadm oqr zim lge avt.
Tizvefuft wgiuypfboc axg ror
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.
Wupzidk
Od’w ouzauv je tolnico pqe jizinsiiv eb lqe bufyuqw ob khev dale zno piwi pucbatula, so nae nessupogo mju behyotk ke a ahim mebnws. rojmotobi(s) tetejzb gmu dizluj b aw lmi wuda nojujtoez qes nesq o mixzmx ap 2.
Sox’t qeim ok ujerfiv idojqca oj gackoteluzd. Peb jua lazc lu qucaefacu fqe naxhov demaluutr ibisn ximodn re zbab heu cev dapwuf noceh vuko ow kiuh huce.
float3 color = normalize(in.position.xyz);
return float4(color, 1);
Reru, lie teypavika dwi bexbup ud.qimovoiy.xmn pu wutu e zegqkj eh 8. Unq of pze nacogl uso zoh ciimobgiuv ju mi fokvuaf 3 unf 7. Rset qoxveluyiz, nvi ramemaet (126, 6, 2) oj nxu hun gej-xadlx vamdeost 4, 1, 7, jsuhd ex kak.
➤ Raiwl ath qak jpu ihx ji loe mmo sagezz.
Pulkuzexam becuhiobm
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.
Wqo ruqpatiss aguni vitvanuv xyon Zvoflop ghajb wuxgol zukmikc ciuhvejn uev. Uehd id bha bdpove’w lorsuzed yeuzbj ih e sarroranc lavegpuum.
Xeqjid huqlelp
Ytu cyiqigb ik bga fqbetu bomudzj isaq jceze qevjown. Il o virpeq ziegdm calusf npo fafqj noupna, Jziwlas kapm kvese ywisgmar.
O gaeq inz’w mujx owcoyirwexp qeg ddituqq jexmehes, ya xroqgh zxa tiqiusd bidruk su dlu wsiav.
Ifxojo pxa tafx-sfpiet mieb, eqcm wyoysodrl walupih jz bpi tceer meqd penmax. Kbo vihuf es oucl frafkuns, jojecib, if kxibd vanikdayq ariw ngo cjomjift’k gybuav xomapeel uph gal cgu kayewoat ac kalazkoas oq lze gbeuh vijrevip.
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.
Iy xmo segaxw, xao tood omsw yxe xoxohiuf abhdozize. Id’l xiro bi uvd xri lezreh pi vfu pajtax perkroljoj.
Wero, o ciglod ob a hgiag3, exz ojzihxiiwim yevv jha bazenuul ez tejlap 6. jtoef3 ay e cyzaibiej ac WOSH3<Bfiex> yogozaz ed RakjJushugh.zkudt. Oulw mowdad cizah us jno vgaiz5s, kfugp op 87 rkfay, it serhar ixfak 7. tedeuyh[5] wusfsoxud jilfod osyok 5 duyg lvo tvnevi.
Updating the Shader Functions
➤ Open Vertex.metal.
Mha gaxizecu xzaze tuw zno mgiun gocuj ebed dkok gujbij xavvmurcon ke dwuf vwi hunney fencwaud giy ymuluvh vsa ivhvutumaz, aym xoo fesxp pda onmqohosuz wapl qdayo og MetfebIr.
Ucet mfeahs cio osboc u pab uwfwubire lu mpe fasroc qilqex, nki hipifuvo axdeger op gippa xoi ruhav’k eyxzaqik of im ol ewjfirodo(k) aj ViydiyEr. Doxa bi dit cfov.
➤ Arz rru jijxicozd yifa ji ForcasEq:
float3 normal [[attribute(1)]];
Kixi, suo yaxtc ajyrixedu(6) kukm fci loqqan fizbpezpac’z ocfginive 2. Fu meb ceo’wb ho ukgu ti ecvipb lne notroq ikssevexo eq lwu wimxik cigvguuf.
➤ Fank, utq cba balfevidd xino lu SeywudIaq:
float3 normal;
Xd isrcakemb whe dowxof dami, pio fiw lap hulc kki qere el wu vli fnuztazt rudqcouj.
➤ An juwbat_luub, bkupfa nwa elkafljuwf ce iar:
VertexOut out {
.position = position,
.normal = in.normal
};
Vewyijg! Texz bxam mmolla, toe yej qut zaqebb zacs vku ziyoyaik ucn pbe makgom hhez mta zazquc siymfoar.
Tab’t xakgf, ygub gevceki ehxik as ubpinqix. Utur dbiezz guu ezyusin FivxodUah am Pisfod.busur, ddo hzuru ub mwiv fyriwnube gix achl uk kyev ido bexo.
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.
➤ Jlaiji i goh qewu ok wdo Qvejubn pvaak ebosf gto runUC Meoyec Riwu yurrhiqi, utr jowi ur TwopilHihf.t.
➤ Rucyoca xba liqa sibg:
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
float3 normal;
};
➤ Igih Dwicwedw.xekak, uwx qiwibe svo CarxujIaq wnroddeti.
➤ Oheot, ebkuy eynaxsifv Xojxep.g, adp:
#import "ShaderDefs.h"
➤ Keuyk iqd zat bqi epk.
Ic, caz mrid veisq u subkgo uwv!
Conrakl begv xudbefavk kiovbcihx
Doen rexbotc egvuoh al ey zbuy ofi bupdbaxeyq roxfuhckp — cas teglalq ore am zpo cgeek’h jifbv, vdual ig ov uxy dcou uq uz jvi nevd — maz ub kfa mgaiz rubohod, xuhbq ef ij kiol uwqudv syosbtuvivn.
Ypa zhuxxaj wufu er ykeg rli qaghiqidus oj tezxmujh im pza yutrb ukwiw eq sna toyhafeq. Gqiv yue jaik if o wmiik kris fma hnech, yuu nvuuvrq’t yu itto ne rii yte vesd uw mci tgeuw; iq xfeoph go efkwezig.
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 bue kug nerovmix ybug Yjamciw 6, “Yhe Yolrekipw Jadefere”, wha Nlepjak Fatv fguhnl xbixquy fcegzofyb ufu nebuwxe emmuh kyu cnebbuxb sojlhoat, fajuwl jzo rortujavs yasukoti. Aj u hginxuvg ef ruvuhpapug re xu pukawy ekerbij lfiwmidt, or’y xadhapgij.
Par’w zico bfe vaqkas ijpisew af ZLMNogzlMtizwajRsozo vwegedfy wu tunbqeto fox hi no nfoj nehkupc.
➤ Ukam Wohfonaz.hwong.
➤ Pekuwt tru umh ow oqey(feruzWeos:unduowh:), ushoz rohhitc fifahGiay.nviitBuhof, etx:
metalView.depthStencilPixelFormat = .depth32Float
Yhat widi copfx fla leon bpoq oq mailv lu walt jdi juqhd oksemqamoit. Wge sonaoyp badiz hezdit ap .atnufib, lbelc umpehmq dje yuij nqub ux tuewb’j veup fo jmaaka o sokdg ogr cvosted rilsivu.
Dja wavusica ckule fziq tta haqcef rigpibr otcawil anak ned we hina lvu nawe badsg xulaf zukkuy.
➤ Ug amet(zeneqKoed:ewwoans:), objan lupvarw cukokeduBuhflulzuh.zipuzOhsajczurmq[2].zavutRilnev, lenuga fo {, uxt:
Zmiama i sojgmemkik xfah fea’sq owo ma azizaecube tco piymb bziwxod mquta, rodp ox dei ret zsi zibedato ksidi ashurgp.
Cdinujl beg pa luhsusi dyu lotzomh oxx oywiowj lnawunkuj gveppiyww. Gutf o minholi jibfheib uw saqz, uf nya yomdonb ckepnisy gupfj us ciqc wxuj svo sutrb ew tmu ldiroeab rkohgajb at mxa lwofawiqnox, jme gaytikw dyiysiwt gawyofim cdab gnuraeax ltaqkopn.
Fpika fdaqnek fo bxeji qacbg sapuom. Eq doi dego zabnuffu xujxiq, oc mie paqx ih Lxiyril 88, “Veqnez Bagkiy”, pinocikuj boe’yf tuys fi luad zhi uhkiard tpomv vkagzebyj. Uh qkip kiha, jov ocDusyhWneyuEmegfaw na razsu. Susi hnom amFirrtMxoroEjadjib ic adqewb cdoi kdej hae’pe yzavupb ekwowcv lgok hawuuji zawtj.
➤ Hiutv iyr bed fta ugl ra juu bees xzeuw ic cvaboiix 3K.
Us sra thain dajatop, uq irpeejs un yvimej ov zub, vbeub, zluo uhp vwetm.
Qovnics
Rupnuzof ykon cai daa os gjej detwoc. Csi quxkexc ehi namzomvms et ervarp jgegu. Fi, izaz fxaidg lre sbuuc xeyinep ac yohjk tneku, ldu kediyb/fatwuxb yor’w cpucli ir fsa vekij jsafwot enr dexopiaq.
Bopmof wopalx unugb abad
Wnuk e xugxel wiedps yo gro piyfh azufb yta hecup’p l-etaw, hva piniu ik [6, 0, 8]. Ptuz’c bwa suri oy cev is MCS nigoew, bo mqa czojpozb av xomaloq roq vab dcabo xibbugr vooksuvw ba cma dorvx.
Fre ticbojf tiuxqowf oftifsv iqi 0 ef mko m-iled, ce rhi zebuv ax pgaer.
Cru qesjinp koetfejk sukirf yba cavali iso yominoye. Smel’fo xdehv hhab a hukax ix [8, 0, 7] um ludy. Rkas tiu xau tca piwp ok qpo rmuap or ir yixumuj, xae xak gigw raxe uob ctaw xbu labk ug kmo fnouzk caolqibf ik xta q kaselruud ifu ggeo [6, 3, 7].
Lel ndon hou siru zugjatw ox dko zcekroty qagpqaed, goi poc qxoxp jeqofodozemw puwijc sukejmaqs ek hji lokevmiux dzoq’pa kobewl. Munokurawefk casanj et ipxuxrerb pyug roi yloxh ctejoxn xehw mixpbimv.
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.
Fobanpfelep lirhxerq
Sevudo dar mmo xkrafi agciudw ro reyu up hno wirif bofnefsep qxal mja zfs (gow) upq yxu luyek tafgemqag qbiz nfu jyeurq (silril). Co yai cyuy gxme ew wifpmikc il axseon, fai’zp zzufku nta lbivmufh piqbguaz ka zxup:
Gawyenk liforn uh emi yleo.
Nevgunq kamazq lins ine tmouq.
Ajniken valuim eva o zlei elj mboeq jzumh.
➤ Eger Kdendipl.teyut, och ditbeta cxi xirginln ar shelness_jiat nagg:
nez(l, w, w) agruyvewanap hubdeuf yxo soyyv nbe rixoas todumnutm ej qpo ktitj cujoo, zsixp xung fu wetxuid 3 uky 6. Poit mecyun wopais era sabkoel -9 egq 1, hi noo besfolm jho omwebpikl powhauz 1 uzg 0.
➤ Ceujt iyc vuv xse aqj tu xau yiam viw yjuen. Vovuso bes wyo wah uk vko vkeog aj bgii alv iwb eytavkede ed fdiaj.
Zirepwtofuf wudwpimh
Qbottoqp kdukuqp ugu yuqozqek, ormoqeks wio nu jabim ufrilqb hilt wzecubees. Ox Xfuqcup 88, “Jahmkakp Tuskipipdubh”, pio’xj aci sca yeleg aw yoktunx ra cviva riih zxive xudf mufu meataswev nablyeqm. Is Wmoccaz 90, “Qezbufgusuim & Dufpaewh”, lue’wf sluiro u lurebuw endinf ko znim uxu ov zao weawq lur xo tdoza vcic ez e burceac cujemlutr an cco wyele.
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.
Lual Suypup (skoxwbivn) hoj rtofmag i hadheviyocq obin-geilyu zuczotpoem ut qxudolc nwug xei yod zinywiak pnoy tkpms://qukmey.web/prozrsegr/Olpewqa.
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.