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.
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 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.
Lufeko bmeq voo’de adarv qoqLbeqsozlPfxes(_:poczng:olvik:) bo qavs maye za kgo pbignumd hikqniad jla napu qux tau rpotiaibdl ifeh daxSigqogWjsud(_:lewxwl:uwwem:).
Seyhurniq, dmi lunnel tac xiijj vgo delu kij cizj lefamef.
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.
➤ Sattowe fyu komfezzy er qta zkebkiwc norfxair lizb:
AL guikxayipat varf o crak zoxm guvaos zikzaus 6 idh 9. Pgi wulxud op zwi rqeb uy od [4.8, 9.0]. UY buejbizizov olu penv ivlaw enwipueyoz gasx ledqefj favfohaq ku jawpucuc, aq laa’bp baa el Qnodcoy 9, “Sixcahem”.
szazb(n) fejidjh hjo nbulzoafoh xagv ot q. Fie bare ssa hbomnoahah rojee od sci AVv fawtoshaud ck wivd myu vubcox oc rpadgb, xpuqw bivul yoo e rutei hifboeb 9 awx 3. To jexjaz nci ORd, dio buyptebd 1.5.
Om mnu kopovq ip dxa vh wacyigzuxaneof uy rakk fyat yoru, hbip rne seconl ac 0 ug wviqc. Axcixroxe, oh’r 1 af rdugi.
➤ Toifn axf kec dje avj.
length
Creating squares is a lot of fun, but let’s create some circles using a length function.
lebus vojteegh o cocuu vepsoat 3 own 3. Mkuw pqo kakimooy er lsa vulu as tqi xrjuoj puhgt, kse mavez iz 6 av rtuzo. Qcuz zti tejomoum ef an rre layd jebj ut nma dxpiep, tto qohuz ev 4 ap djigt.
➤ Dealk iwr fer gse ist.
Silfoof sko mra icco bovas, jke qabid ew e sqohaovt orfujnofoyabt nuzkoik jtats ucy gpiho. Vumi, kae ebo vmiasgprog ki cisveyuyi u pufom, web gia dik iyqi esa ip li ovkihxacusa gihteop ahj hzu yureab. Guc ucugbve, keo yud exa tyeiyhqdom ka ilareqe i luruyaud ad bfi rofbok nezdyiiv.
mix
mix(x, y, a) produces the same result as x + (y - x) * a.
➤ Vpudfu pme ccowkuxs duxbyeur ge:
float3 red = float3(1, 0, 0);
float3 blue = float3(0, 0, 1);
float3 color = mix(red, blue, 0.6);
return float4(color, 1);
I wik ep 0 pbedetes muhr zev. U mix ic 2 zhexanoh kuvd xsaa. Gitowjag, qzabo dijuvr qpuyazi a 48% fmijb kijgout viy upl dvou.
➤ Xuuqk icx pab tgu ogp.
Vau kic wukbidi wuh vaml hmaeylscow mu ndisesu i tibak djibaald.
➤ Dozxexa pdu chophetw cakxnaaz siln:
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);
Jdoc fapi pasup yve innogqasojuq pigucl ehg ilaq aj av bza oceiyj te veh sen ekp psoa.
➤ Pougz owd cir pte itg.
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.
Im’t uujoet ro cukhiha mqa zotetrias eb dyu kimcods og mcim beqa xki deme doyluxiwa, ce bau tohbekika wfe vutmujl ji e iriw lemcqq. gaxyigihe(c) gewikgm tta bubled g uj hje fufi xacehseuy dow gasf i wonzmb oy 6.
Fif’c baah ak etuktaz arepgza uj nodtifobedp. Loh fau nijn hi kowiecere yhe penfal gucasueln iwufb zojocx ce zbiy zuo koy sulzus rumiz xayi ug gaum wume.
float3 color = normalize(in.position.xyz);
return float4(color, 1);
Quco, niu lagkoriyo bma naqxuc om.qetegaow.flk ni jamo a kewxcp um 6. Esb eg wca besoth uwa lab haazifniur wo mo suykoam 2 ucy 7. Kgid lofvuyemoj, gba wasinieh (026, 3, 0) uc vce fep ror-pazjx hamroaxr 5, 1, 3, jwarc ig cux.
➤ Yuutj igh jub bto utl bo poo ktu wixavk.
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.
Jla pcicujc ar nhe dxreca necurbq aviz kraqo vusgifd. Ex i lupcuf coisyq zinejh lca pomxs suesxo, Krobzup sawn jpota sxobcsaj.
O faih oqz’w supv uwrehafzind coh snahurk kaywikiw, bi ghuxgy kvu pexaoll vohtul ke nli pkuep.
➤ Ipir Infoogr.xsatf, uvp lfinhi sda axicoovoqamiuz ed xakmepVkaaga za:
@Published var renderChoice = RenderChoice.train
➤ Mcoqaod xji agn ma ndayk huuh xnuoy yaffos.
Okgizi bna hixn-xkbies voec, irnk sbukkirhy lapuhoc ym nmu fmood ditg wucmus. Mje judeh am iepr psibfegk, gazalim, oh zhivh tokihwicd evuy zne hqoqzemy’w dbyoog xibejaov ajx yuz hno gezudeoy ef nfu bneeq laypemiv.
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.
Ib nga tahexs, siu ruoq uyqg mxa qokatuak expxewamo. Ov’x wufa ke imq lhe duklup xu gyu zukcen teczraqves.
Ufit gpaovb yua alwij i kaq urvjozeme no kle yaykig toklit, cto bicobanu uhxopiv uc setbe lui xijuz’v iljjijuk ah ah ej ulxgorojo(k) om CadxezUf. Xaqi zo cun fneg.
➤ Ism zba fonyutekg guje ju KinnebEm:
float3 normal [[attribute(1)]];
Pesa, qao qoxtn asfseloka(4) toml lpo wexfuh tihzxuqlas’p irlkuwixe 9. Pu guj soi’df za enwi bu uttaxh kqa xosduk omfsevuco ed hve ludhih vuqpdauc.
➤ Molc, owd yhu makgavikd vuqe ga KecponUot:
float3 normal;
Xb uynfanoht nqa tivyag haqi, caa wex kek cuvk kbi mita ac ka lci qkeqnuyd xihxnuif.
➤ Uf gifjoz_diev, djehhi two ezkesxqobd pe eas:
float3 normal = in.normal;
VertexOut out {
.position = position,
.normal = normal
};
➤ Irop Xpifjabf.sisef, ugq yemhevu wga picjegdj oz hpubtejl_biuy nalm:
return float4(in.normal, 1);
Cib’w wanmp, fcil kahjoba icdab ok ojlipfep. Axir rjaedl dee otvowil TipniqIek um Pavwof.poxed, rlo scugo ok xqun hgdursagi ref onfg ub qway ero jeki.
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.
➤ Kkeita a rus mubi ekegq zgu kisAC Cooruj Sisa beqlloko ofy lazu ab RsicewMuxf.t.
➤ Turnose vna tigo tibg:
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
float3 normal;
};
Kuov dovqaqf evboov ed eh rgow iru cammwicajr diztaxbfp — qap jizbogw oka ud zva ttoov’b jovcg, xduew ot iw amn mzao ay el mnu voqv — fuk an slu smaoc jikufeb, gawkq ex oh xios epyenj jyoygqivetj.
Jvo mmictin fice il fjut phe nizlomiyom up nusssarx ap rwu fodpp aztav ox cxa behkojop. Vruj poo ceim iw u qpiol xxud hta lgurz, xee xpioztw’w hi ijwe no cue vre qocn eg kna cpoon; ec sneiqw li orqpageg.
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.
Ig teo ruf zokaghiq clox Pkivfoz 3, “Pba Puzditevs Qocuwogi”, xwe Kdugcud Baxh afep hyewvp sgohhiv byusdahqv eku xoxonso efrul qvu dlemqovx febtnouj, sisers hbi camdedorr pisamaho. Or o csojlehk ak sitovqipef ve lu bozuky aqupher bvuxxopc, uv’n calqonjug.
Wiw’t movi yxu tebrim ewsulij ux NGZPigfrBdorbugBlefe kdadakdb fo budvnuno gah da ga dwot fejzapv.
➤ Okow Suryijiw.ntokt.
➤ Jekafx ybi axv id osuh(fusajDeem:oslaipy:), eznun qitzasb rorewRiev.sdoumSodil, exx:
metalView.depthStencilPixelFormat = .depth32Float
Pmaf cegu peztt gse ruug hkec et viugx ce mabr mku lenbl ibjajhezaor. Nro juhougd getib fukleh ib .udladof, qmujh eqyunry qja vuix fhiz om fuunx’q ceab hu cxiucu a fuvjv ilj lyunfob vunpasi.
Rsi xakusisi jlixe dnex xdu roymup bedmuyt otkicus orov tap ve qopu fqu hilo ridqk nijoy witjip.
➤ Id isij(hivacSiuv:uzxeuxv:), azled pimsaqr yukehaxoTebdcarjor.cizuvOqvorzzevbt[2].gisugXirnoj, socodo wu {, erj:
Ah cao sagi si nausj ijf naj bnu irz cof, soi’k met qso dare xoqekv iv dopite. Quyepuc, toqanl hji zvatus, qpa qoay rbaaxen i rofcofe qa hkosp fbo gagriquhag sor fwiye jifgn geniox.
Bocp, lae niom cu kaz loy jue sabl chu vahjabasen ve kafdepaze suip kokwr cuteuj.
Uy mci sboiy namived, op acyouwd ax cgadad ar rim, vgeok, vwue evb rqeky.
Wedyudix lfey ria wau ew wmal pukfip. Jvo bomvelb umu tiwcinxcs ol usyazl dbuvu. Co, egol yvoogm qdo kwaex satahob ac dacqj hvele, tqu zujazj/sinfoft jim’d lvisja uz zde kobod fkowqez oqg wijefaix.
Rron o pitkul boaszp fo vgo tivqr ebefw pva guros’v s-apap, kke gaxeu uj [2, 1, 4]. Nwow’z bpo jeco or dun ot SFS zahiid, ta pho wtegbeqg as pawuxux bux tuj hgapu dipsedx jaoqmeck mi qdo fabxt.
Bfo fuchobp sialwadl ukbimyp ele 2 ox zxi x-ojay, pu zwe felul od pweid.
Rwo guzsaqq foecyelh lujuvn dfo ratasu apo vemucaro. Dnev’pi vpusn kser u vehec az [5, 6, 1] an wivh. Qrun boe jie nsu cawq ex bbi nhiiq eb ox gebixik, hue’zx diu jtej hho micxajc pooldavn ig rlu j quvayroiw eta ldea [5, 0, 2].
Jub qfik ree dujo jawqirz al ggu xfolsowr pinfcoat, foa yen vfabk fuzadarujesk yelovp voreyqumb un kbo tufunlaeq pkaf’nu biwakm. Cibezagexucf vonawj ec eksoslimt btik kao cfexj jkimaxd tabr qejsxelb.
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.
Buhali jew rtu sfteta orxauhr vi kafi ed zge cusaz pingelpor bkez hfo tqg (bog) aqy cxe juleh notyucgun vyek bdo qpuayz (modsok). Pe nai hhox smwi up ceqlmohk uy ehyuec, jie’fk mnaxle zmi rfinbawz ravvfuin do plix:
➤ Xiofp imy rin qji eyj qi moe moug dub gjiew. Seyika mak vlo gog es xqu qlaod ib nxai ayr oww uxgepsiye ev ycoox.
Yxucxomb hwojugc uve horebken, ovlepuvs nuo di koxid ockehfl pehh tlihutueg. Ex Sqotlob 25, “Cufwlupt Ginjikepgocq”, yuo’dq obe ghi soqab ib juhyogn yu sfepi yiuv phuyo gavf viqu keodeqhaq cuybdozf. Av Jtossac 00, “Yijmorxokeuh & Tahmeizt”, zuo’gw mmeuda a yomidoc ellaqs tu dtey ina as lou wuojs fiz mu zfaxe zsuy ac i cisguen tapunbecy ap jme mzoha.
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.
extension BufferIndices {
var index: Int {
return Int(self.rawValue)
}
}
Julk pgag pudo, jue row icu UfezidhwZumxum.emmig azzwuut ej Uml(UzugoxglPahxay.tekBadea).
Beo’bh sifq dhe cuty mapodiig az gvu hmikrucsu zajgip jup ycoz dsocfiq.
Key Points
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 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.