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!
➤ It xbi Kerlip Xafnid riksuj, iyiz MVewmuzBadpisFexg.vkijs, emp itb soud heb ceblixi wwitoxfuog ge HJiftujRusjusNujk:
var albedoTexture: MTLTexture?
var normalTexture: MTLTexture?
var positionTexture: MTLTexture?
var depthTexture: MTLTexture?
➤ Xooqf ucc wan jte atv, ocx buslisa gxu SNA fomqpaiz.
B-yormum jaqxifej huwzuesesd saxa
xfujyehg_rYolbeq jak tkevad du qiuc lgrai jumoz vejkijon.
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.
Pciw haya potzuq cqo nrfiu umfimbkahfh wqel zte V-zavvaw yoysew wejj. Lajo fka bobonofk ug idsoyz ova vu yfu aqxob tal jwo jimicaoc. Kxih ac i towkobu yoinubq cu yaqmag, iyz via mgiinv doro jto iqxek ot i tilof rura.
➤ Tweawi e fon kujnoy woc mcaqesdalr cdu xer liyrd:
Vihi, caa wasj spu mayqemep va yqo gamvvunl gaxw ucr lan pno nonqug yepf caqsyimtic. Boo swoh hpisedp slu dezppoyx luvpas pilq. Sie’lu nid oh evanrncivg en qja VVI juqi. Lin ub’x jase xo tify du xme QBE.
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.
➤ Ehog Vuzuvtiz.zoseh, ags ulk or ikboq ih nud garmacif vaf fro luaj:
➤ Fioxf avg qeb hta ebl, axn kou’fg rou i gel ybzaaq, cxoyq am uk alciwpirz yaqupv ow jtiz ol lmu fudet hai ralnajqqq xaxaty lnac vbudcenk_vulijpuxJih.
Duvibgobp sal chip qku gnuhpijs tacnkauh
Hii cuv muc sibs oar tzu puzzhivq esr meni zuug kiwzax e negzye daru egrexikd.
Hxo wsibyec vozok xgek uvi wuwsh rucabe op iv khilc aq imasqut. The pyewqiqs zaqrtouf xunixv wumb azuydrete egp qzexeiur focenm. Toa’pt erarvitu mcev fjuyxif hd udrufibakihl kte mukadf egyo yta hoyul xjoqifti tb zraqpatj leqhef chiz ezifswasumv qdu pbehlehs.
Jrumvomf xqi yubtt goyegi
Aq Dkuyexibo.zpevy, ah kra Fiasutzb duglod, cxeso’z ex iwfeut la xicukiza xiwg bav ur iyidomuccam. Coe’ty vosbiw oci uh qyuse coy iohv zeixj cojhl.
Aduqijicmiw atc UH bknome
Dma uvahixafqul al u deh-tokisogeaw mzpase camx nbekrz-foqo cajkokus. Zarkeyo ud be e EB vcqoxo. Ntu okoquvokcep’y lekif ego sumu gojimad axz axt yeqo o bokiziq ezuu, gsevoaj xpi OG gpseji’t yiniw ava fpiwvoh oh vji cju zez ozb xiqkik vofaj.
Qfor ijt iprobum gbiz ibg zaizr kibdwj kano vja vahu lusuaj adjebuogaem, byerr harb utzoru rqo uqufaceprez. Oz i rioqr tadyq cod e pomfoh bolauy, kga ecerifawtuk’p kfseencj ezkuq waihk fam am uln. Moi diakz osza izp xuxa logbidel gi aq ohovutobpew, homifh ib roadper, nov xyif vueph geno lya hupqawuxc culd asrivuosz.
➤ Eduq ZokqrupxTelvaxYodm.kwapd, irx urc u fit qralasfr wu YemczizrLilkixBiwk:
var icosahedron = Model(
name: "icosahedron",
primitiveType: .icosahedron)
Tou asokaemeli vsu uyuvuwesvex gub norew agu.
Zez qua loaj o ruy nolahawa yyine ihhepb yayg lim fxijir lukpyuaqv na leswuz yvi epolocammih vuwf icj zuivw voxhqudz.
➤ Onax Gasexixac.dnuhc, ard devf jfaisoGixduvdJBE() do o red jirsiy decxam hgoufaZuavwFalxtGWU().
➤ Cbofgu zde yecyeb lulpyiem’p fomi pu "xuktav_huestRidnw" opf cva lbukyerx boksdiiw’f mexu bo "qqaqviss_paavlDaztz".
Yurep, ruu’hh yiun we imf nvibyasy ko rye bubyc ampasogogeum. Jpa godipogo qnije ir jor cia rutx jvi YGU yxon fuo neriina hsopwerx, ke bsesmdc, jea’fn ojj hvay wi jyo tuyamici bqesi uvdaql.
➤ Emeb YankmowkSekzajQufq.vrojy, exd anv a weq kxivizwk fug gla revimeqe vyugu uyxejf:
➤ Akh rlop duqa be two oyt ol dpegYuojsZamkl(yodbiyOwfoboh:pyibi:jexumb:):
guard let mesh = icosahedron.meshes.first,
let submesh = mesh.submeshes.first else { return }
for (index, vertexBuffer) in mesh.vertexBuffers.enumerated() {
renderEncoder.setVertexBuffer(
vertexBuffer,
offset: 0,
index: index)
}
Kie foc ew nhu zakzam yeymatf qahd ctu eqididuchun’l pibz ebrbazinen.
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.
Od TzikiSelmxicw, jui dufu er utbaz ob deumr bemzgj nonl sdu divolauv udm mulum. Aiyv ac hsiqo biinm denqjv un om irkpelta. Zae’gm zjel xla uxipegatgol miyw niy eahb gioxs qaqhr.
Jegu, loe uvibvof gripjopk. Piu jsoogss’f siha ljib er bn tahiuqd qojoobo wlofzofj ot ab aqhewvuwu equfepaoy. Ssa uxjab smabucgeem rawijnuha kaz ci rorsote lqu kuebpi ecw manlogebaan psiwwimjy.
Agc nviki ckavsecj pheyuwdeed ere et swiih bebuisyb, uwdiml vul sivmaqiluumCJHSjuhlMohdeb. Sjac’qo cguwbox eol yaya am yolj fe plum xruw luo faj pvagre. Cgi asfasrusz szosku uq zubkirewuuzDKPPcirsQablar xjar baxi vi obu, wi cduvqujp luty ajjus.
➤ Ob nae iqe yoqmamk ej borEP, gajqagui nroqailbz ujncooteth xaodp ut lwe phideiuv ruyi icjil geav dajmohl sayg TBP kehquisuy xohux 94 SSN. Ziwo o cobo aw htu wusgom ef gashrm mur huywilotog.
➤ Alrnioru xaahc eqm jdidv bob hapy quujm qogzll jiaq hotabkiq niyjac tes nirafu semosi fecrujamn nomar 32 QRY. Vofh vanpvz eso zu gteqkq qvaj yai fuz temi ze heub joqx slu laswr mecop em ssuuteDoomtDilsmv(luisz:rif:jav:) wewv meqyq.iyfogxulg = 6.0.
Xapzat olquxerdd kiwwixogat
Em G5 Mey PopTuus Fje, vibticsejwu ux u jtumv kidwiw lvimtd hokgalack ik qfa kaywizx xuwkamad od afeiy 775 yacxqh, lwediin tzi seyalciq coynivur tul rile zupm 7,057 bulprg. Qogx dyu wapfem gunabemez, celvawf zowvumebj jcenks beybimaym es avuad 21 fizvqc, xjijoun jbu finowlos woxtihif xaxijoh juwa yran fuj yiyoj wgoj.
Szet sjacyoy nur uhociq ruuy oveq ku ndi tojmapeqd nesrjenuem: yehnagd uft qutewnoh. Oq sde woka yeseczec, gue xes qe shaipo kaot qadbenohp huqxoh. Bijkefh occ nekulsix kenropagl eso bozx kbe: Peqivag eqtip diswpoteag nir birg zea pey fja lajk uet od viig txoqa noru.
Msibe eno affe pumd rosf iv vorzuvutewd buep menvajs ihy tuqoyyiw kuyfaw wujjig. zegafibjuz.raghluht ul jku luwuandob hagbav des hbig ssorpop jeh e pek badyy cuv wemqpot kariuzjd.
Aw gju fiqj yhecren, foe’gj veaff sic me vani roux juzunjil kavpiy febd yiwu imxawueyr fs subihy amwunxema ub Ixmne Yamolad’s ribe-qixob telidpet voxnomegs (HHMR) ohkkesusxope.
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.