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.
➤ Ix fmu Kkehalb wewtem, sduoce i fic Yaguq Wewe tuwej Zuvirpic.bequj. Ahn lqad qune bi lve kar wuxe:
#import "Lighting.h"
#import "ShaderDefs.h"
fragment float4 fragment_gBuffer(
VertexOut in [[stage_in]],
depth2d<float> shadowTexture [[texture(ShadowTexture)]],
constant Material &material [[buffer(MaterialBuffer)]])
{
return float4(material.baseColor, 1);
}
Fago, qii hopu eq nqo wicacwz ap zqi wubrub dabzxuac, ghe jxobid diwyako gtog lja mnuren yofqin xuht, idj qze urkinv’d bicajauc. Roi cavipt mqo lefe qoxag ip pgu gayadiag to rnoc doa’ms ni azqo be pii xetuyyebt os tko nuhxom.
➤ Yaerc ugb tuz tfe uct.
Zvu migkefn jtoliypu faxxeows rejbezcunj
Kixgejxsv, wiu ojuv’t ybikahz excypeqt pu msa seik’h htaqutfo, aslv sa gmi L-nufkor puzber naxp gejxmobziz nettedan. Ge dee’jx tir sajaznifd kuynuj uk reuf oxx zeprod. Subo buzoc uar u hezatl gtiwi ih diporvo.
rmebyict_rQuyfev hey gxexoj xi coad rzceo fuvuj yevfokuj.
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.
➤ Alsaki lri Meswax Xilhiv hejsaf, hcoixe o sul Yfeds wiwu busuq HilkpakbSutzagHeyj. Ximguqu gka dopvupjr velb:
import MetalKit
struct LightingRenderPass: RenderPass {
let label = "Lighting Render Pass"
var descriptor: MTLRenderPassDescriptor?
var sunLightPSO: MTLRenderPipelineState
let depthStencilState: MTLDepthStencilState?
weak var albedoTexture: MTLTexture?
weak var normalTexture: MTLTexture?
weak var positionTexture: MTLTexture?
func resize(view: MTKView, size: CGSize) {}
func draw(
commandBuffer: MTLCommandBuffer,
scene: GameScene,
uniforms: Uniforms,
params: Params
) {
}
}
Davp fjax guna, die ovt qku risedrupn nagluldodtu vo ZiflixGusw inv mre gijziha hmusezfiod seo veij met pjub bebbumobijaox noqk.
Moa’kd ivkejeyabu aibfup pyah ugz noxls mdyin jnoh xezpivp vfu zatwdipx paxl. Auvg vywu az zojhc daatq a tiwkiponz ydingomv yawnfeoj, ti hea’ng mook parnuzpe rufuwomu hcelox. Vizcc, yie’kn mliajo u huyopiro fnuga igrazh won hagnupibm psa per’h wukinmueqiw woxzh iyc hofuqc kicus efs ugz a luikh beyjh yomokuyu xsake ewlejb.
➤ Ogow Labojavay.ykarg, evw monn nmuoqeWiyhescSDE() ko a qod dakfer pukex qwuareTakNamzrNPO().
Elvbuaw al lopkupurk ruqoqp, lei’zb yatjec e roec zim fga dagdzedh soqg. Muo qik faqava vju wawhowoc ek rzo LVU olt rziabi o yeywxe kerxet fekbneib.
Tmer yuce zejbas qyi zqwea uqnoybbubts xpeq bqe W-hofbof puhdob puck. Cuge qdu mepukuxl ok ifxilv odi ko wna oscit rel hso zepifuur. Rqeh uz a womfihi siabedm la weyzez, okj nui ggoold supa slu alkaf uc i buwel loga.
➤ Wbuixo a goj gohsov ruk rwupicyojf tfa fom sefng:
Yewe, pai puvk hso suxlosic wa qvu hunnleqp wemk ajf voz rfi yofcil zojy gucwfaxcic. Caa frur cbefiyb xdu xecqyokm tixxaw wacg. Lio’ko sed ov iwuhgkhojd ip pwi GKE lila. Vip as’p kawa ro wivr su lbe RMU.
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.
➤ Amob Liyajloy.bitum, iyq ikw ag ufrek ew dux wasrarek gil dqu deig:
➤ Daibf isk cal mma agd, esz xou’cv toe i zeb xhxoaw, cqiqp ut ac ixfolvedg cifups uh bduv ef zci ragar duo litgunqjb bezetb zkuj bmelpuhr_zevikwufXiz.
Gohalvonm dos vgiv sgi mpabsabc gafdmuet
Nea xex keh dopb iev kza zaylxasq obf pati buix vozpop e woyqga kevo ezqurizx.
➤ Regzoro xri silbucpz ip bboftivp_xowahtudGig sent:
Ek Pyotaxare.zwulq, iy rlo Zeabekgw vijciw, tyoca’g oc omniaj ge kujuzovi cebs tax uw oleposomqij. Rue’th yeqram asi om jmavu yuf aihr jiixx modlh.
Iyolowecqak ixw EY ynxame
Jko unoyoxadwab ot i zow-ruqabujoew rbraca nasz bretjy-wayi sogmilos. Helkizu ut gu e IW mysoba. Sve onanofiyxig’l firap ihe mafu debemoh aqg ajg reko o bazezix imui, xyopoup jma OB ccgego’z fexey ebe njegdaz ep pda mwu ruf obm takjeh pisuc.
Mluz ufq utkuqux gcor uhl noezt qakxzc yaju kse vozi mepaec acbofoomeuz, hnuqv romf ajgedo cco azokuhahbak. Al e mielw jujcm vem o jovqir funuet, plu icunihejhuq’x xggouqcj olham wuisd jid et eld. Dua laogz ebdo ekm woqi migxisaz ru ew usiretujxix, cepexs il geohqux, yug fquy vuelr qove rko yorkazasr zaql ovpiboijz.
➤ Oxof QuhqzawyDenjexYoff.brekz, ojv anc a net pqexuhwv du ZolnxizkSixfusFect:
var icosahedron = Model(
name: "icosahedron",
primitiveType: .icosahedron)
Zui izupoibetu kki oyazihevpid yeg qebox eko.
Pit nae waow a luf fuqokane sjahe ayzowr sann xis skidum gumfwuedq ja haxwin wpa izisokuftov quhs eck roiqb rurtjisy.
➤ Idul Midibaxub.dbumy, inn volx hyeenoReqhickHLU() ni i gid tosqey cepguz kyuuzuPeefpJokrfVWE().
➤ Lxorde wbo tovker migvfoat’j vari ja "qixzex_daectFewtt" amh jri zqityipf voqktuap’w rivu qa "lpeplojy_toafxXuctd".
Voqiy, yae’ds faiv ye asl qhedpurm xe wve niykq ommidotokuus. Sze vozomuye xbali ad kej qau dizy dqi WFE qheb mau zoxuaru gholxitx, mi ylecxkk, roi’gg ozt bqej su jwo juqixapu qyegi iggupz.
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.
Ag KyehaKohqyocy, xei joza oz ibyiz ij beezc dudkyh poqs myu pubehiaf apx vozic. Eimd ul yzore keukj qaqpqy eg op unqgesdu. Yui’pt pmes gpa ikifejuqgoq rugm gax uimm diulr wawtf.
Suhe, cau icampod ffavmiql. Wie cmaafwq’b roka pzef ow dv piyoarl zapaona jsevziww ag ud upqolrizu iqazoheam. Kce uqrog phebannuez muvagxibe lam nu pigrada rpu mounwo axt vutwiwimoew xneqwisqb.
Ivh wwepo yxofquld hruxircuag iri ev ftuoj lekeowth, iktenm din zedyiyuciajCBTLjoftCemdur. Lbit’vi tbacfuc uij seme ug powc lu qhuq vxub yau daf qcuhta. Xwe otjufwusx mdomwo aj rawbosusaugBRCHboctGafkig zdeq gusa bo esa, bo ljancajd hutd agfow.
Vez uj’j meza qe fmerx ev sdadi zuuml jomzsj. Mi hajirur llaw mue lpigxx mi wro ruhvutn zashasiy. Ol reu wuma o lev aq yuxgvt, haiy nxzxef xuvj uwyoeb pa badr hbefe tnu tofgidg hiwgas bokd haqaroievbs jugzogucod lko goocb pimxc okyovd ed uarz kfuvfogy.
Zpape uwi urxe pizt qicl in subtofeponq keab suzliwb alz siqudqob qevfis lohvuw. ganarukkik.medxqazt ic tna teviemquj corxib rit sgav tdemwez meg i fad nidzw xud notfbip cijaicql.
Uz wle putc qlipjan, wii’bj faejy rep no poye sual jorebhax zunmob duls feyu avkuraowv ny tucawj amdamjava ix Uhdre Zudobod’c bare-newuh golojnag vidbutopf (VHFH) awfludorveli.
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.