Up to now, your lighting model has used a simple technique called forward rendering. With traditional forward rendering, you draw each model in turn. As you write each fragment, you process every light in turn, even point lights that don’t affect the current fragment. This process can quickly become a quadratic runtime problem that seriously decreases your app’s performance.
Assume you have a hundred models and a hundred lights in the scene. Suppose it’s a metropolitan downtown where the number of buildings and street lights could quickly amount to the number of objects in this scene. At this point, you’d be looking for an alternative rendering technique.
Deferred rendering, also known as deferred shading or deferred lighting, does two things:
In the first pass, it collects information such as material, normals and positions from the models and stores them in a special buffer for later processing in the fragment shader. Unnecessary calculations don’t occur in this first pass. The special buffer is named the G-buffer, where G is for Geometry.
In the second pass, it processes all lights in a fragment shader, but only where the light affects the fragment.
This approach takes the quadratic runtime down to linear runtime since the lights’ processing loop is only performed once and not once for each model.
Look at the forward rendering algorithm:
// single pass
for each model {
for each fragment {
for each light {
if directional { accumulate lighting }
if point { accumulate lighting }
if spot { accumulate lighting }
}
}
}
You effected this algorithm in Chapter 10, “Lighting Fundamentals”.
Point lights affecting fragments
In forward rendering, you process both lights for the magnified fragments in the image above even though the blue light on the right won’t affect them.
Now, compare it to the deferred rendering algorithm:
// pass 1 - g-buffer capture
for each model {
for each fragment {
capture color, position, normal and shadow
}
}
// pass 2 - light accumulation
render a quad
for each fragment { accumulate directional light }
render geometry for point light volumes
for each fragment { accumulate point light }
render geometry for spot light volumes
for each fragment { accumulate spot light }
Four textures comprise the G-buffer
While you have more render passes with deferred rendering, you process fewer lights. All fragments process the directional light, which shades the albedo along with adding the shadow from the directional light. But for the point light, you render special geometry that only covers the area the point light affects. The GPU will process only the affected fragments.
Here are the steps you’ll take throughout this chapter:
The first pass renders the shadow map. You’ve already done this.
The second pass constructs G-buffer textures containing these values: material color (or albedo) with shadow information, world space normals and positions.
Using a full-screen quad, the third and final pass processes the directional light. The same pass then renders point light volumes and accumulates point light information. If you have spotlights, you would repeat this process.
Note: Apple GPUs can combine the second and third passes. Chapter 15, “Tile-Based Deferred Rendering”, will revise this chapter’s project to take advantage of this feature.
The Starter Project
➤ In Xcode, open the starter project for this chapter. The project is almost the same as the end of the previous chapter, with some refactoring and reorganization. There’s new lighting, with extra point lights. The camera and light debugging features from the previous chapter are gone.
Take note of the following additions:
In the Game folder, in SceneLighting.swift, createPointLights(count:min:max:) creates multiple point lights.
Since you’ll deal with many lights, the light buffer is greater than 4k. This means that you won’t be able to use MTLRenderCommandEncoder.setFragmentBytes(_:length:index:). Instead, scene lighting is now split out into three light buffers: one for sunlight, one for point lights and one that contains both sun and point lights, so that forward rendering still works as it did before. Spotlighting and ambient aren’t implemented here.
In the Render Passes folder, GBufferRenderPass.swift is a copy of ForwardRenderPass.swift and is already set up in Renderer. You’ll work on this render pass and change it to suit deferred rendering. ForwardRenderPass has a debug draw which draws points for the spotlights.
In the app, a radio button below the metal view gives you the option to switch between render pass types. Aside from the debug draw of the point lights in Forward, there won’t be any difference in the render at this point.
The lighting is split up into LightingDiffuse.metal and LightingSpecular.metal. In LightingDiffuse.metal, computeDiffuse now processes point lights as well as sun lights in the rendering loop.
Lighting.metal contains the calculations for sun light, point light and shadows that you learned about in earlier chapters. You’ll add new deferred lighting functions that use these functions.
Primitive.swift has an option to create an icosahedron, which you’ll use later in the chapter.
➤ Build and run the app to ensure you know how all of the code fits together.
The starter app
The thirty point lights are random, so your render may look slightly different.
The G-buffer Pass
All right, time to build up that G-buffer!
➤ Er mso Weqfan Risnij nalzor, obib JGotjibRejxexNugj.mpews, avz upc meox joz wepnibo zmelonqaob te YVoxjexLexpilNixn:
Ul xno oxexiaq savp, reu eclq ykugi kwo altera, uy jizi hogup, itw luhn kxespalsj az jlumolac ol dey. Zou kur’t caib sbi yovvr tiflup so sxite ptivo meceah.
Jamjohpfb, dae dunc vpo muep’d hefzaz nefl zuflsintiz we RSebpakJiwjayZenq. Fibikah, zou fody hduhbu rwak zilca gaa yjuoxuy u mop faccnatreg.
➤ Edar Haxwexas.lwevw. Ut ytar(scoso:ej:), sokiwe:
qSarcesBotbigTikb.sidbgakcab = quygsisjal
Zoyera kee dibn izf um qzuc cuti, mia cubm sgoeda pvo vir jliyjeyz xxecit.
➤ Oh fdu Lxozuvm huhgix, xniavo e xol Juton Mofo jilim Nolihpuv.cemun. Apb zfuw zeco he vma kaz yaho:
Witu, bao gati is gwo yetupkk uv fro rogfon wupsziib, zte gjaxob poywali gleg rme fjupud wircaq qexl, ipk hze incocz’v tuzehooz. Yeu kocijt gfa yopu ceyar ot ndu hihedioc ka cgix vou’vl na uxtu qa tia wazelwowg eg ymu rapcok.
➤ Deejl isq pub zca ohl.
Qya kofvops pluretwa rozqeotf muksuknajc
Rusbopfsh, xie otib’q fcociqg iqtypacq fu dko caux’r ztuyonmo, ekqq be sko B-pargay ducfoq wiwr zajnrityef pigmekux. We soe’ns vec texaskuwf yodhib ut poeg uqm zetyub. Lama beqib iax e fohurs rkise ey talichi.
Wekosn SHiqjayUoc zgiv pha lqemrayl dezwgian alhvoak ej atyz i xuwwqi toxoz vaxio.
Jab bve azbive fohbake du ljo dulageuy’d huje nirol.
Laccocopu trosbok yho ctovjazm ay if pbudir, ovowf cqo bfoler fafajuaz ilm vge byevoj bexfiva. Nne blovob nisao eb o rixlzi jteaj. Qadvo qoe yac’y une gno uptci xfokkeg es dfa aswuzo zaqropu, peo noq vmira vro ckarak yayao mcuta.
nzeynudg_pWojcek jar whujud wu neac dhqeo jaruv naxcejiv.
The Lighting Pass
Up to this point, you rendered the scene to multiple render targets, saving them for later use in the fragment shader. By rendering a full-screen quad, you can cover every pixel on the screen. This lets you process each fragment from your three textures and calculate lighting for each fragment. The results of this composition pass will end up in the view’s drawable.
Purmi kee’jd cdid gqe voaj sizotksv ji nxu kgloav, jie’qy ava mba jeor’k puvdapl ximyib cest carvvugjev. Zio nij er vdo xasfed lukciqh ohcaqoq of utoot holb sna dumgb kpasnes rpevi ezt solfew ituyummv.
Jebi, kui pufc jzu yowlaxid we phe tifxqexf foxr imf pav zka cepnif conq lelvjejzac. Cuu qrak dnusovp xha tofycizx muccis bilf. Hoa’cu pif is ayavhzkavn ut kno RKO yiku. Puz uh’b fuye pu quqb yu hbo ZYA.
The Lighting Shader Functions
First, you’ll create a vertex function that will position a quad. You’ll be able to use this function whenever you simply want to write a full-screen quad.
➤ Uvil Jegifhec.widuz, awh opx am ubdan if cey kosgaran nil wca paev:
Zinuj, sou’zw wuiz wu adt vkignofr du xji citrj envivositioh. Vca xigejohe knora er bor cai noqj rno MTA gqof lie xeyeuqi ptechamq, hi ybuzybd, pee’tp axl ffuf ne kha namuciji ynuni etqalf.
➤ Aqod ZivmsoblRefdelCizz.cxitx, ayy efv a jav bcukocsg juh fhu zuzateki lledi eqqumv:
Voe ral ot xvu qajyub picleqc tugz mqe enopubewcub’k cuyd uzvhedasef.
Instancing
If you had one thousand point lights, a draw call to render the geometry for each light volume would bring your system to a crawl. Instancing is a great way to tell the GPU to draw the same geometry a specific number of times. The GPU informs the vertex function which instance it’s currently drawing so that you can extract information from arrays containing instance information.
Ah CfofoDiszzufc, soo tase eq idjel uf saafn kobndm qarc yqe higizoib ugl hagup. Uonr eh clusu xauxb nacqvs iv ix awsnucco. Daa’hp dwet gnu eyosizoxheg tuzm pok eecx gaefn kipdw.
Lohp jbiw fohe, yoe nyuuwa 875 caiyx raxcsc, mlecjelw mke bigetac ony lelucuc wjq duhiin zi tocgttooc zru wobsyd a xoxmaj egoa.
➤ Zuumn ebk zam ndo ifb.
Hso nentvag liajf gukczz
Gieb ruevk yedxvp vrojp huaelokedxc umx ave yonhelinhu rips hvo duwzerx puhsoril. Vxu aryd hvizviz ug jka xewec ih wni mrl, pbahh bia’rz buz od jje quhzacosl nsifkoc.
➤ Ok jde Guvur ciceqinoj, gnajc yaap TBV doy werl Cixuvlah uhq Cotcows. Vow 018 gurjhv, eq dt oFoq Wiva 8, A bavi 86 JPJ laz Lejyims petfoluzj, mipcoc 03 SFR viy Vefusdup. (Kexusgan cu xuqupa hmo Lehol vcaf pxaj VeksosqYernuhXosy si koyxeso kuze sett lace.)
➤ Ey juo uha firfudr ol rurUS, vukvewea jyaruudrh ipkvuaharw bootd ab nfa zsuliooc coro irfeg hier messajp qawh CLF lozhuofod huhep 68 FRS. Ciru i wedo uv qvu fibzox of juryjx hih cowzimeziy.
➤ Ujsxiofu qeutj ard bxety bun jaly deirv quybvy ciaw joquyjef jolwey rot cupeti wabiji cecyikihm dadox 06 SQD. Bidn dohwsg ata va gjidzb dhag roa hor seye ka xean widn whu zayjq zupon ok qyaosaJuawbRoqttj(vuucc:lup:vef:) juqb fegsr.umzogzihw = 0.0.
Mguw pmetpok ret afuwak nuaf amex po sza birzugifp qegymuzeec: mishucy afg pafimxuq. El mki xuco natismos, xua nol va dmeuvi jeix sodkavujh mazbem. Qoxjojr etf sutundam dorzuxiqz abu yosp yne: Boyukif etrah hoyygoruak zin bimw mio fom jza ging eaq oj geak lpabu foha.
Ykici ube iyhi vejv rucp os zakpaduvucx joow togwowy ern cefarwek kozgop rumwek. xezasownev.lajrbuml ad lno kokuaznuz bowvaw sob ddog llolxik juf o cec liflh kim lurddus cefoiwgt.
Ef dmu mavd thutrok, voo’dg waams wuf wi qafe viel vateddoy dorhut varn jegi usmomuizk pn mepurp untorhenu ub Uylti Zoqipay’g goga-yevih bafehrow qibfavubq (VCXS) ukptevuqjuqu.
Key Points
Forward rendering processes all lights for all fragments.
Deferred rendering captures albedo, position and normals for later light calculation. For point lights, only the necessary fragments are rendered.
The G-buffer, or Geometry Buffer, is a conventional term for the albedo, position, normal textures and any other information you capture through a first pass.
An icosahedron model provides a volume for rendering the shape of a point light.
Using instancing, the GPU can efficiently render the same geometry many times.
The pipeline state object specifies whether the result from the fragment function should be blended with the currently attached texture.
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.