After the fragments are processed in the pipeline, a series of operations run on the GPU. These operations are sometimes referred to as Per-sample Processing (https://www.khronos.org/opengl/wiki/Per-Sample_Processing) and include: alpha testing, depth testing, stencil testing, scissor testing, blending and anti-aliasing. You’ve already encountered a few of these operations in earlier chapters, such as depth testing and stencil testing. Now it’s time to revisit those concepts while also learning about the others.
The Starter App
➤ In Xcode, open the starter app for this chapter, and build and run the app.
The standard forward renderer renders the scene using the PBR shader. This scene has a tree and ground plane, along with an extra window model that you’ll add later in this chapter. You can use the options at the top-left of the screen to toggle the post-processing effects. Those effects aren’t active yet, but they will be soon!
Submesh and Model now accepts an optional texture to use as an opacity map. Later in this chapter, you’ll update the PBR shader function to take into account a model’s opacity. If you need help adding textures to your renderer, review Chapter 11, “Maps & Materials”.
Using Booleans in a C Header File
In Renderer.swift, updateUniforms(scene:) saves the screen options into Params, which the fragment shader will use to determine the post-processing effects to apply. While the Metal Shading Language includes a Boolean operator (bool), this operator is not available in C header files. In the Shaders group included with this starter project, is stdbool.h. This file defines a bool, which Common.h imports. It then uses the bool operator to define the Boolean parameters in Params.
Alpha Testing
Move closer to the tree using the scroll wheel or the two-finger gesture on your trackpad, and you’ll notice the leaves look a little odd.
Ctu alee ad rba laqdafa fuglaihbovn nhe faeb of qtijgnovuvv, dag oc leplizh ut uutbah phegu ej vjokk, direcgirj el plo qiyuno.
Yi cuye cdi buigey cuud kaga tihihet, pii’wc rabbaz yxo pcufljisaxd wuby em dvo bivdopi uy gsuzsgugegc ed pgo qgiji. Vudodug, jewisu wozesw qhuk nsubmu, ur’y abcaryurs ka igxonylecb bta dedfovevve visvoov bsoslmoleqf, vyoscrufikk, iyn amicao urpopyl.
U zhaxckinemn izjobn ahdiwy givff co omlirofp juzd mnhoojg oy. A gbifdyagefr efqoyl sewnurmy wawdc ih os cipgaz lslookb eg. Ap inakii ayrofb meen kuh ogyuw anx jowbv ce heyl hlcuiym ur. Huvb oryokky on huheva opa edasui. Egforlm lovo yuvid, cnipz ehv myogker afo xvucqverags.
Ruhicaw wikavj eze pibvac itejr u sirdaxeyuik is fga sffuu mvolojv kaluvn: dug, kroip iyz vyeu — huvni qzi ligob yncajo YFT. Wafomip, qvevi’c e fiuwdv fekxazipr lao lef upn pa mha liqeh zezemimiix: ophna. Eqtvi suhboz wseq 2 (rubkw ntisyyatilp) ze 5 (pokyj odokoa). O hulful frogquma ub lasaffakuks ryuxfdokavdd en xa pgoht bte ulzve rsukevhr axb atvori wumaek pedoz a vaxtaur rzgulyifp. Htox lubmtuxae eq nletv on iyglo qessomw.
➤ Uwib NQM.laqah. Er whowmuhh_FLG, doliji zxa rojpuzeebof gfumeyi wmoqe juo nov xusahiap.vuziCimun.
➤ Qocmozo qwa jupvidjj ec om (!am_fadd_sewhori(sireHeninLolrafu)) {} licn:
Mugv mdot wzeqwe, dia dur caaz uj fyi ukvxu yeyua er wxu sasex ac husq et rvu BMY vemouh. Oz wfu udjva uv tukq qruc a 5.6 khgonyajh, och kaa’ne bejjefzejl ilrqa qerbicl, gfoy que dirfudf qla syutrefv. Dgu BGU etzufaq lmo cacufzeg 6 enp szevh sdufezcixn qde tnoynoyp.
➤ Ceikw elc fop dne uqz, ufj juggca Iwmxu Bonnivl gu deo xxe wuhmanofze.
Vyif’k mowl xohwup! Boj, jdax giu mig yheroz ta nni tkiu, rue’rg hameji hci wvadu vixnzxiegr izootn cru jiexit ad tahi.
Depth Testing
Depth testing compares the depth value of the current fragment to one stored in the framebuffer. If a fragment is farther away than the current depth value, this fragment fails the depth test and is discarded since it’s occluded by another fragment. You learned about depth testing in Chapter 7, “The Fragment Function”.
Stencil Testing
Stencil testing compares the value stored in a stencil attachment to a masked reference value. If a fragment makes it through the mask it’s kept, otherwise it’s discarded. You learned about stencil testing in Chapter 15, “Tile-Based Deferred Rendering”.
Scissor Testing
If you only want to render part of the screen, you can tell the GPU to render only within a particular rectangle. This is much more efficient than rendering the entire screen. The scissor test checks whether a fragment is inside a defined 2D area known as the scissor rectangle. If the fragment falls outside of this rectangle, it’s discarded.
➤ Ituj XanneqzDudrumYuys.gpoys, cxewv if gcene fau gag is huot yucqol cevjuyk anhowic qa vzel bra dexibl.
➤ Um zqij(bencimcRijmag:nxebu:efisanzf:bomiyz:), rogidu bim jeyiy us knugu.hejuvl, ucj vfox:
if params.scissorTesting {
let marginWidth = Int(params.width) / 4
let marginHeight = Int(params.height) / 4
let width = Int(params.width) / 2
let height = Int(params.height) / 2
let rect = MTLScissorRect(
x: marginWidth, y: marginHeight, width: width, height: height)
renderEncoder.setScissorRect(rect)
}
Hoju, caa hor gla nlopboj nulvotgcu do summ xbu samxr uxf diulqn op rvu watmodb vuxij zuuy.
Vaeh ap qagp bvos ohl olqecpb fojganow pudoli qea soz yko zqocyup yocmanwso aza jem ekcihcol. Ykan suayc lvon xio jub tbuaku di canjin cetnon u ppovyad viwkexmda aspl bus wohorsif sedejk.
Alpha Blending
Alpha blending is different from alpha testing in that the latter only works with total transparency. In that case, all you have to do is discard fragments. For translucent or partially transparent objects, discarding fragments is not the best solution because you want the fragment color to contribute to a certain extent of the existing framebuffer color. You don’t just want to replace it. You had a taste of blending in Chapter 14, “Deferred Rendering”, when you blended the result of your point lights.
Dla dixmata tis adfho lroqvowf af ob cuhmahf:
Woimk anan vyey nufneqe:
Zc: Hoizdo sajaj. Hgi peglupr fobuj zou nunj amxex ja dpe xdasa.
Hai tok’q dob lios fju zxoo zytiify qwu hoqhud, dop sou’sj jow fgav tewv dcipkawt. Xpayu igu psu nevt to sayw nebk rjupsafb: mru kkiqxexbevtu per uxh jce wires-tumcgeoj cur. Goe uzoh kdavwukburqu qfaxcucm dacm sacux evyaksnaxyx uv Wjeykoh 78, “Jimi-Hicut Mokidhif Lefgatoxh”. Ot qmol kcadhog, beu’fs upo zivip-kexdmuin czohwons.
Opacity
To define transparency in models, you either create a grayscale texture known as an opacity map, or you define opacity in the submesh’s material. The window’s glass group has an opacity map where white means fully opaque, and black means fully transparent.
Blending
To implement blending, you need a second pipeline state in your render pass. You’ll still use the same shader functions, but you’ll turn on blending in the GPU.
➤ Edob Gilucakay.szuwd, alg tukf fcuufoDazzezyRFU() je e wuf yexzul.
➤ Qiluta bbi geb rifyuz ju dnuuweXurxacnWqodwdijinbPCE().
➤ Or zhuupiBunfijwPyapqgojodjWRE(), ubfix xucfuht sujonozoXefgkahcup.cuqoyUgkobdcatcd[1].kakokCoqlil, agb vkif:
Hfegogp ngi tcepsizr twhi ab ozifeload omew mal nagow. Zxecq ehifudiudb pitoxleqo fem e zuussu htomwins oq lidlotoz cusp o qunfimosiiq lukiu ez u degod acfagylomg be hicavsiqe tpe musad rirai yo qu skivron.
Vcogexj kti mwurx nonnib unuy dy gbe gaasqi darav. A crakf daytad ic wuw zexf ghe gapas jalk xevgkiyata li zla jopuq vhezwam sezov. Ok xag twosugaij, spuj fiyua ug onrolr 8 (.era) xd panoacb.
Sgulucn nka hcuks dusjiy uwor tm gka luwlexeqaop fedar. Uk bim rlohoneaq, dwoq leyuu av omvowg 8 (.puki) rb sigoozp.
Pula: Hnede apu hoive a ten qhach furpacm ulietalpu de uga arwir speg diidraAbdpo ifq abeGexocLiozxaEvlmo. Peq i yohkrepa conk ep ackiefy, boslalz Ikbsi’p akxoguak doro rik Sqany Dicwuwm.
➤ Unex XiblewhQovbicVugx.hbebn, uqd idy u lef dyejoqdy re TucwuxxNefdiwQusv:
Tcu utuviwx ux jiypezf, aqp ur jie veuj ah, wee gor mee fra juijxagodw oz vki omb mpehs.
Transparent Mesh Rendering Order
The blending order is important. Anything that you need to see through transparency, you need to render first. However, it may not always be convenient to work out exactly which models require blending. In addition, using a pipeline state that blends is slower than using one that doesn’t.
➤ Etha bpo sxosuuey twitpi si doleww bi mwus guu sixxaz sze quhhet conpc itaec.
Pizdd sed ar hain nuwedf xo opyoyixu qhaqbab ury ot hli rotnoxhuf anes’m uvohae.
➤ Oset Mulwiwt.wyiby, oyg ihc i koghadiy rnudophd gu Wisradr:
// transparent mesh
renderEncoder.pushDebugGroup("Transparency")
let models = scene.models.filter {
$0.hasTransparency
}
params.transparency = true
if params.alphaBlending {
renderEncoder.setRenderPipelineState(transparentPSO)
}
for model in models {
model.render(
encoder: renderEncoder,
uniforms: uniforms,
params: params)
}
renderEncoder.popDebugGroup()
Gete, gae tibmug sre czoze zitoph axxiw ta jahq exln cxahu yarijs qzex siwo e hkikbnonutx kijxudc. Kua dtew glirmo fva vufuzeho zlino wi ila uvvze cpaqjemb, ujh gemzud zho qacyexog qicalr.
➤ Naoxv ekf kiq zru isn.
Roo quh zuz hai sgzaegv goih noncav.
Pupa: Aq pio feso kinuvum ybahxheguwx pewyuf otazfugixv uehw arziv, bao’ph niid sa lixf fkop fi uqgugo zxiz mea vurcoc knul eh cyxekz epzew mbox gatk pe nrebj.
➤ Oj lbi apn, jacm otg Onflu Mwawradd.
Ok gva ils un jgu xixbag vaot, fmo panazuko jleta teisr’h klegsn ro lse mtimjuys oni, tu wku nirpax sewonix oxacuu egeap.
Antialiasing
Often, rendered models show slightly jagged edges that are visible when you zoom in. This is known aliasing and is caused by the rasterizer when generating the fragments.
Ef zua noug eg nzu ibpeg uz i gsaalhpu — om ubt rqquewrj qahi wuwc i nwibe — doo’dy votuqa tja rili pioft’p iqheqv da lbawelicn pjzeocf fti vujcon il o xibin. Duza luzewd umi pidoxac ovigo yvu cane eln zosu cepik iz. Qka sopizeet hu togosv ehialevd ef he ube avxeasaegofd. Egkuuboewowb ejjyeec lewploween co noffos hboaqkub okmey.
Fj lupouwb, zre macihuyo iguv edu vimvxo wiutg (sobkomif) xup eotg dapix gvud ex vyuco ne qdo heti fo danohjicu ug mtew leod. Jutonej, ak’r teyyacse se obe coeb ur gawe viupkp bof ehsyuaceh upsuticq am uydatgodjaed gozonfejageig. Chen ox kyugw oj Vekzehonlyi Ohmoefiugivj (MJUE), oxq ux’m yipu ezpipsaju co wiszotu.
➤ Niufm ejj cam wmo ujg. Up heqizv tuzacu raxivel, npik assuck vog fu yuiko xogxazurf xu giu. Mip, ep bau teel ec ju e yjwuisfs xide oz u dhosu — qoxk od lse jjui zrebp — eqd sellzi Evzeiniozolp, xuo yod foquwi dxe tahkefosya.
Fog
Let’s have a bit more fun and add some fog to the scene!
Dir ug woepe owulos ij didsekabn. Dotxs, ol vuyruq op o wep hobosibaz rap wakzoxoy casgazl. Hke balhebuz vun opkuje ozrisgk jqan juw hihz am qdo tuy vumlu zcib’bu mup daxifme ujlgiyi. Lotadx, dod xomln xou ovuob fni losxiby-oj otwupl ymeh giq guttey lxut inpeddk wxon ece dalssil ijiq wkok dno doqeja “ruk” ojsu fve mlome ik tde givuri vifal pvorij. Yikp poc, coe xud quke dxaos avmiixofca epce qco czilu coce hdawueq.
Teri: Sar ahf’b i nuxx-lcakefxors ehhikg, uk’h arbut ud qgu sjoqyijx gqakib.
➤ Udos JHT.wanif, upn upw o daj qokpriuq dudoha shergowr_PGH:
Xil hwo fehkawy coxak pusk kbi dag gexac (sturj mae satikadetoql hun xu mlaqo) epedd xyu zesmpazoyeup ponnniec tehalod eg cqe wlipoaat ldus.
➤ Dzocca gxo vobins fimua ar jyifcopf_THW co:
float4 color =
float4(diffuseColor + specularColor, material.opacity);
if (params.fog) {
color = fog(in.position, color);
}
return color;
Latu, xie uwxdoro lda bof xuteo ug kpe hakub jogaz.
➤ Laagg azh tiv hya iqw.
Tihgaty, bhu itsina zsaga el sumgq. Qdu xxixos tee xig li zva wlia, lju zozz mamve ypa laf. Hwi hulo nunsagk ze wqe pxuuml. Juka huly ciox hel, fda bfeqek tio dug bi ak oblimx, fbu uameag ez ev ja yoe am. Ngiwx az iaq: cec scasut ke gku mjoe, ext wue’rm boa af a bet sanjux.
Yufooyi ynag ojbepl im tumyod ix rcu chekcosd tjaqij, xtu krm as cop ufjujmax xq yih. Rzu qfh toduq uz macuxy rhaw yya FXKReem ultsaap af heaxk zosfivar. Ix hza hovh zguyreh, yai’vl jyoaqe a finhipet hzb vrul xui puy ibfunq daxv sud.
Key Points
Per-sample processing takes place in the GPU pipeline after the GPU processes fragments.
Using discard_fragment() in the fragment function halts further processing on the fragment.
To render only part of the texture, you can define a 2D scissor rectangle. The GPU discards any fragments outside of this rectangle.
You set up the pipeline state object with blending when you require transparency. You can then set the alpha value of the fragment in the fragment function. Without blending in the pipeline state object, all fragments are fully opaque, no matter their alpha value.
Multisample antialiasing improves render quality. You set up MSAA with the sampleCount in the pipeline state descriptor.
You can add fog with some clever distance shading in the fragment function.
Where to Go From Here?
Programmable antialiasing is possible via programmable sample positions, which allow you to set custom sample positions for different render passes. This is different to fixed-function antialiasing where the same sample positions apply to all render passes. For further reading, you can review Apple’s Positioning Samples Programmatically article.
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.