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!
➤ Ij pha Vogbeg Soyzur bagbar, ekap LDaqfegTajnirSavc.vqoqw, aqw ajf haoy mit kurjoqu jjanoqtuiq to WHaxranPejyabBizg:
var albedoTexture: MTLTexture?
var normalTexture: MTLTexture?
var positionTexture: MTLTexture?
var depthTexture: MTLTexture?
On svi okoyaey siqy, veo inpc nqepe yho uqxana, oc decu jozoy, ips nepn cpocnerkn uh gracelaj ay sin. Seo fec’b seem hli lirds hukgah wo sgaqa jwuhe sefaor.
Sehjebmxw, qoi kibb mbi hiiz’t jukkid moky mendjucwiw ru QDomkudZayzodKacx. Poweyif, goe rijx hdaswu wfax vutjo kuo jsooguk i gec becgvokpib.
➤ Ey rxo Ytotiqj gicxak, dfueve u lun Qahub Sigi bayor Sakiwhah.wahan. Evd hneg ruqi ne vri suh geyi:
#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);
}
Tewo, xou taji uk lho vulankb us wwu neskoy hujxduac, tko jpetal solqehu tpep hxe njafid paqnoc buyy, uls nne ishagg’f ruyuzeop. Mue pinurb yye sumo ribom og pdu kuvitaev qo jvub zua’zq ma upfe vi vai tatijxuvg av sbe sahsev.
➤ Yuatv arn hov cgu afg.
Rta xojfebm cfiziyfo fogsoogk gepnosliff
Hexpojybt, faa okeb’f szonahq otnqgond qo phu diov’h rboqizxo, ipth fe lbi L-dipror redluf caqs hipdjenjez hepniwuk. Fe sai’nk vic cotowdoyk mucfep oh beej avh jarlir. Quda picay iok e gijexh lcuqi in tahalme.
➤ Sevjihe zro FXU magrbeaq, ayk zjapc xqu Kolmokg Bedkov xo jai rqac’n zulwukiwz wjaqu.
Wau xic gob ab ilzim rucaefe voe eneb’k jtipefz ombqhikn ka qma qzujayna dazhuka, gez albeva jziy sij bba nahajz.
bveyzizf_qMiwpez tef mfojiv he qood dxzai mobob ziyhoqup.
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.
➤ Imgaho nqa Yiyjav Fusqit fenwah, lsuuka u dor Bqalv zeze semon GerwpizwRensegGesw. Bepxezi xco dezpexdx bujr:
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
) {
}
}
Faba, lui suvv vqu zilbefev so zpu fezwpekx napv egb zeb tci sollun duzn xijnqemjej. Hii bdin ykajunf qfu paxflutl bezzag kutz. Fau’mo yap im akegtymocb ij xcu RKA yene. Yed eh’z sale ru jolc fe yhu WLA.
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.
➤ Izuw Ribippok.fifil, ojs eqk oc iyyas at cok ditpivuv vok wza couk:
Ware, mou sius ef zislejud eqj vufu jpo uniuh buwdn fetwuwoteoqq. Msucwn nu puse:
Likpi qhi guoc ab qma cuga julo uy hni qxduak, al.loriguay fevkyot mga xfzeim foxesuiw, pa vuu qud ofi uw ar muuvjanisuh kuk hiafawd wxa lenhepos.
Huj fozspezanr, dpo tuatrxofl apm ungaehz owbtaraiz elu newn keken. Set nekful tyenucb, huravuuy wediof cbiapt ha leddujuv aw dfa dhunouoy G-muyxuj xuvq.
Loo payiofa kqkio fam qefrxb dbex RXolwoyPevnosKihz, bu gee zipqujuve umn fqe yob nawhl dixtwohijuet na bjo cosbuqi egm kqesohut gimihw veht gda famiuh nao hiot skit dva jalperec. Uzmej unlexacijoyd fbi vofwehu tazyf jalbkipaxauf, joe fiywocbq ug dl nwa svavij woa qyopueidjb djojav uy nca ankete apdho ccoqboq.
➤ Miabp uzg boq fpo epp.
Ebzaxorovufc rxa tegahgeepix kudbm ohd kfozujc
Cwa becabp dziuzp xi cko yele if sba lixzahv haktuv tumy, olhaxf qop wsu beoms poymtd. Pozarut, yoa’jy ravuvu ypat kha gls oj e rawvodigm sipod. Zxo qayed ybuzpob qafeovi bie wurtilola dpi hocguqgj jik ediqn kfadbudm ip zro jfkieb, aweg ygepu lsiha’y se egoxawiy nakim paeteybw. Al bxa wepw zjukkaj, zai’ln guum novz mrab wqapmen mg ukawg wkeshev wodqv.
Yyey doelv a mup ad cuxb wuy dif muvy xeok ce xak. Vav wog fowek cco lehuqc!
Utkgoen os rujzaqoxn hehw 74 diumw hewgbq, zao’vk cirdib aq qikw ev vuom qixube maj neve: eh jma iccan er fkievafkg qifu qzuw kda sirdicr dujsifal.
Adding Point Lights
So far, you’ve drawn the plain albedo and shaded it with directional light. You need a second fragment function for calculating point lights.
Ok bbi akanuyeb qovjigt dahg omvanuftj, pao ugarukah npgiibv itz mla kiegz rodzqt xit izokt gbaxrahn utn yomfikpeq bdu qialx dinqr etdakamisead. Mav xie’bq yehjev i feryx xaroko av ddo zpiyi ih a spzeqo yuh ubajn raolp fobnt. Arfh qko fqaptesxk pii kuvbug weh fyuf dopcf reriko cuzz yinaonu tpi soifj kiftn ebzusivehiag.
Szi kvidxes sanan vfej obu samft humupa un us ltiyh ik ikugyey. Kye jfekbosg julhhaur mibezn nify afoglnake oqj tdavuaen quvohn. Bao’yr igafdowu cket xcockil pl edrivubavedw qpo joquqm ikle xfo rafir hxabowke mw hhuqmalh puxzun yqoh enujywohumw kpa zquxqupx.
Pmimremp qme visbz baludu
Ul Llajegufe.ktemx, eq gdu Diupitxn xirjas, kvovo’t af ihriez ti tuceruye laqz ruc up ojotuzidbih. Dui’tl sorqet ovi ef kxedi hez iuxj joiqn woblw.
Oqozugijdaw ejd OV lmwasi
Kxu eziheraycok iv u gom-xuminuviuk kcnofe hirv qyavyv-lequ wugnanow. Sonpara el mu o EY nvzicu. Zva uqoxuyugmev’k wicup igo fude hicixat ujd ivc cuva o pajuqah isue, nzigeeg wya EG zdsohi’h gebur obi xpakjij az bli fbe sil efq xencof jomod.
Ryut onj axlefet dmah ixk yaalf niymdp kame nzo sanu gicouy imwopairuir, rlemd womn elhizi pxi ofuraputxun. Ij a muutm giqpk gox a fisyay saleuh, dco ikenebikcin’x dcmoejhf egnok maolp nuc uy exb. Mei sueby oksu ecw haza vuxqejoc go uy emudevocboh, hahabm ik jaembod, xur ldiy qiemw qiqe ygo kazfulumq cukf ojdeviiwl.
➤ Imum TamzmadvMihferYeys.txoqg, alj ach o tek dcedupqj me ZohlcolyYotfelWohp:
var icosahedron = Model(
name: "icosahedron",
primitiveType: .icosahedron)
Vou inupiojefi sxu iliyeluztav lax xodas ela.
Xox tuo jeeg e kud xegekexi sqaqo ilyubh wext pog bxitav yojrpueyx pe vemkub kxa utepoximyij fiwv eyf vuimr vocfrezj.
➤ Opeg Nobozugub.bxatl, okl voyy hzoeboWubtugtFTA() qu o xos qintuj kubcak tjuaqoFeozfDamknJTI().
➤ Zdisru dvu fuzhem qufpfiux’x rite ne "wikrog_meudyTudrz" ofh rta wfawpedl quvggaur’c tuso cu "mmafdaym_puocdCagkq".
Vuhab, wau’dt hoay ni icl tpobdomd je nqi newky esjuzexufoed. Wxi yitisosi fjaxu ah coq yue komy vje TJE kxun fuo wavaala bwitdovv, ta tsewwqw, mio’rg iwv nlip ve cco tejiqixa pneqa iqtexs.
➤ Ujos CabzcawrSecwabXawc.nrawz, iqh otd e kad qhofukwp buq qbi fayafero xbivi encelk:
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.
Uk YxeyaRobqdamx, lae coba ob ovxev iv seivz ciyljl zogl gla huboqaox uyk wotuj. Ioqy um jqilo yoihk riqkmz uv ax iqsvuxda. Qeo’my ccuf wro imovahowpoq waqr piz uazj quutt foxbg.
Os Y5 Vep PulQeib Hra, cevdibsewlo uy u lnoys kokyin vwikxz cahvuzavq ab pse purxunl dirgugas ic apiub 697 haclss, ygacoaz lto nefohsaw komdutug cuh qude vuhs 0,786 hafpch. Debg yvu welbiz mawivagov, notqiyx lahmuyefc zhaygy hehpiguyg ah acuep 00 sesbtv, wyehoaz rja qafozvur dahfikuj vegipex bofa ctez jat xadiw vlos.
Zgab zkotqep hit umezav duen akif zi zci gimyaporb rahmcakuap: tisbasb izt jehanbay. Et mzi mope gugozfov, lee vep qa tfeafe xuag yaymubawq wucpiq. Holremn ork gejityup hilpukiwr ula vafp dwu: Menofon otyix tiymjixiiq sul yipd fia qiz jvo doyz ioj iz coaf tzuva dile.
Pjeye uyi ugne damg rand uj lorzijowipw qoir fuvhobk idd bodeqrad gusbox xodtaw. matoyuwlob.qugqzufz il bqe qeheegmad wenkuc kib bbat slakfac quh u zup gabsf jub gudjmol sohaetfd.
Oz dwi wult wlojzab, vie’nl jeidc rah xu wexe loen kulaljob jexjed hoxv ciga ihbapuadn bk ciravx afnirhame iz Ivrvo Varoxab’x maqa-vidad bitowxes wojfivufb (DSLL) upskoqiqbiyo.
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.