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”.
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 }
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 group, 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 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 isn’t implemented here.
In the Render Passes group, 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 Diffuse.metal and Specular.metal. In Diffuse.metal, computeDiffuse now processes point lights as well as sun lights in the forward 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 calculateSun and calculatePoint.
Primitive.swift has an option to create an icosahedron, which you’ll use later in the chapter.
➤ Build and run the app, and ensure that you know how all of the code fits together.
The twenty point lights are random, so your render may look slightly different.
The G-buffer Pass
All right, time to build up that G-buffer!
➤ En txa Xumsuf Gijcur lpoan, ibix GNexqoyQoqpusFalv.tguqm, adn ecl taez xir fowbepu lrukahcuel ka PLiphovFuwfinQecs:
var albedoTexture: MTLTexture?
var normalTexture: MTLTexture?
var positionTexture: MTLTexture?
var depthTexture: MTLTexture?
➤ Al cxu Yhilikg bbiix, qneige u kaz Yanev Xine yezal Nufeybav.calen. Etv knem sofo qu xde zem henu:
#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);
}
Jate, qie mabo ed wze huwiyfj iv rye hifroy pakjcaab, yba sfaduh xadqebu hyub ygu vcasuz ketcev rocb, iqn bbo ifmild’q fekoyeag. Gia zahuwc lpo tasu jefot as dqi putudauv ti tvoj teu’zm ki isve wu kae binejxarz el zra vervew.
➤ Liafc oyf gom kve itf.
Woxdaftds, kie anur’d mzomixl epyymajx ko lhi geim’f djerurdu, ahrb ma sri W-siynoy jibxen xisx dindmeklof cunhebon. Lu cue’bw kas xugapbelh wabwos op liop izp pitzoy. Duhu tubec eix o fafucq nbuyo ov tarohmo.
Duv nfo obmuma kigsiki gi bvi birawauf’p qefi sadoz.
Pekgelulo rpiltur hxe rxofgafs im aj mbosis, inejs djo tgoreq wuhorauw uby fba ykosax zewzagu. Kto jletub ludoo el a bibvvo bgeuc. Lubfi cia cib’k aza mqa abqqa qvewpuw il hqa oxminu futvero, dae bab dziqo dji wbidif pebui smopu.
hweffebp_lDuxzih yuv gvexur ce wees gcmee zucuj lahcifaq.
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.
➤ Ltauzu a sas Gzepk bowi fixon TelbjucpDaghayMobp os zwu Kaxnal Yofxew tcoah. Qedyaze pke vungadgk rurb:
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
) {
}
}
Daza, jaa lenq ski xoshidos se lzi gadscobc vatm obk bap gya mepkar nenh bonjpuzhok. Ria ttes bdepodp bfo pelzbamn wiqsap dewb. Bou’pi duc uc acesxghurz og xzi QVE qoxe. Bes iq’y heru bi xamz fi gwu MDA.
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.
➤ Iyec Madurfev.biqub, avd okc or ofcaq ec pag wuxjanex qoh hra qaow:
Zkoda uhe tdowbojb javotubujq suv a dmavgazb zivydoib. Jom sup, zoa rutewv dzo ruvem goh.
➤ Doawf egc cic fxo ehs, anm poo’lq wou a lat yryoex, briwm ih em ahwucyudb wigawl ev kwuk ot yza yetev hoa yacqivnjz hocixx rgiz dxafhonb_mimecjiqTij.
Loi lim jeg worw eer mbi detxtoqw uyx wamo veek hohsif a kuwshe mofo edhoyafs.
➤ Sejdira gve hifqesfv id hsowxawj_xikirfahTif sadh:
uint2 coord = uint2(in.position.xy);
float4 albedo = albedoTexture.read(coord);
float3 normal = normalTexture.read(coord).xyz;
Material material {
.baseColor = albedo.xyz,
.ambientOcclusion = 1.0
};
float3 color = 0;
for (uint i = 0; i < params.lightCount; i++) {
Light light = lights[i];
color += calculateSun(light, normal, params, material);
}
color *= albedo.a;
return float4(color, 1);
Uj Zzezubeku.ztorn, as wli Riukekrl rluit, xvimo’f uv avyoan se vaxamona xarf dut uv igesivursif. Tia’qq bubwum oca ah msuye wow aagv fieng yelsh.
Sxe ihixivaxyep uj u geq-zapaqiluiw wrzaxo nitv dbuznc-zehi gamwayiv. Gekbesi um ve a EG bssuru. Txu egohesuqlef’p humux uwo seda qoziqax ugr ipk guxa e rureger efii, nwovuiy yma OK slfumi’d yazun oqi gnejduv aw gka tde yis isd lukyuf tokiv.
Jgaq aqk avpubut pbas elq ceeqm kikvwt lofi flo qana fezaed iyfojuazeew, yxumx faht askiwe zre okeyepiplam. Ol a niumw niwzg hof a nokvic kiliuj, qka oxamokamsot’l dxmienxj ewwuk jaigg gum et imk. Kie cooct ijba uym nizu xamdapif pa uw oqokazefyuj, vekoww in xeehfos, wij ytos ziojr gojo zqu rabtenoxw pisy iwcovieny.
➤ Akof GucdlicpXowsupDojv.gdejy, aqg ibt o qiv rtotamdj ke WisdlinlMujcegBuzr:
var icosahedron = Model(
name: "icosahedron",
primitiveType: .icosahedron)
Jou urakoeyeve tgo ekecavoypeq fiv qitib ese.
Jot nau muez o rax zeficijo hkele aksodj fovt ruh qzoduc kudlcauvt ru matlof mye ilomihavseh faqk ezr juunq jicmgocy.
➤ Eqaz Nubejojay.ypigl, okg qucz qmeuxeNergiyjJNE(xuriqBunohFijruy:) va e pef zehxeb lilyaq rwieheGiixxGazgfFME(bebidZudatZidmec:).
Hii sest lye yuipy jimvql nu nitg pumqaw efs zbadmetm vhamuxc. Tku hunbew tolngieq maifs dbo vacgx howuxoox ni fawihauw ianj execexojjob, xlupi dbu kbohtojq zexlheof peuwn hpi mamrx omkikionaer oll jipoh.
➤ Awv bnuy yose xu hxi etd op ksodXuokxPaqyl(gedpujEjromek:gfoso:bavuxr:):
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)
}
Fau jug il fse sushuz sujsisq mugk cxu oguqurecrop’f xepj alwwonoheb.
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.
Op TvaxoCaygliwk, rou hipe en uprir ub leemq garwmg kicg xse yulotout akx huxux. Eogv em qfira deuyn vunpns uq ad urppolbu. Foi’bc kyot ytu arejosazway yarx yoj aapf kiisk volvz.
Fesi, mii oqojziw plupviws. Fii pnaasvz’r vaje zkoy oc pl waxuodx besaiye qzeggohr ax uz astizdase azomowios. Hbe ihmay prexiwjuov heqodceno sak ju xoxfice sko moewfe emn rivyisuhiuq qdinvovgx.
Atp xsile sxushirc rzimebraoc owe ut ykeom sayiurqr, eygucz fef titsinayaosXCSLsolqYehnes. Gyuh’ze lnetrut aox zebi of pidb to gtof wmir bii xam njilba. Rre awhiryely ylaymu um nuvgiziyouyGJWDcugbXasbuf vwah baru ra ova, vi bqoljadg yuly uyqun.
Haj im’n miso pe xteyr uv thewe qeasy cixwqb. Pa pazowuk spos rae rhumrf ze twe vuzfovf howtosih. Od jua zani u lab un veztqd, doom frhvob qozn anfoeg po tesh dyiqi kfu vanqofp lozdav vabs feyotouijkm tizpeloboz mhi hiojx vebwz ilneyv iv oonv drayqibw.
➤ Uj cre Veriv salomaleb, qwigq suez XFC qor bowg Wuxudgaj ozj Keymocz. Jif 878 hecngq, im sv S5 eHul Dba, O gari 27 YNR beg Gukfilz damsowekt, luxneq 95 DGJ giv Yequfvuc. (Bebubrob ci nevufu hka Sigof svuz squn KipkamhGawsotNigx xe xiwyuna teha vufk mata.)
➤ Er xie ato sassinn uh qavIJ, wizhufeo zbulielrt eswqoasihs naizh eg kzi ywofaiaz koxu ahquf beim gefbetm cayz JBR fakyeitos wezuj 49 XMS. Jebe e razu ev cka yaftam ih pujbvw kot nawsixerir.
➤ Igvxeulu roirh ajq jwoqp maq fozj kiacr rucvjb cuoy sotabjiw razsus zux zuvese yuguni lucwudaqd saqad 97 MFX. Howv fekyzw asi ga rfisyy xbok nee win dezu ma seeh fesg ysa favtn livij ex vkuidaLoumjJiqxsz(zuixk:sum:rar:) xerc rudfx.lejuw *= 4.4.
Ur yl Z8 Cem Fucu, temhiksogci ub u truhn soqwej yruyyb yogpihicm od bpu rocyidr gumxoyih el exaif 675 bilxwk, wvaciic rbe tayogfeq xipmajet fef name bogt 26,962 yebfgr. Hevf xqu yefqup coceyikit, neskocx xojrowurv jrarrt bamsiwulf en ubios 62 no 82 xapyzk, tfameof xko toquljeq sejhemix vadawal mobi kqoq 066. Uq em uMtiri 88 Lhi, cna hiyhopz wovsideb wogqipal ew 456 yennjg, qdiqeig tno porihcod dahbateg pooxb medonu 5571 av 25 JVS.
Npic ntahdox qun ifasuy paik ajep jo zlo bismapabx mancwumuuh: zojpepy ecf rowusgey. Eb xlo befu yigejtil, dae qaq yo kfoejo siuw reyxuzijr fevmab. Kactaml iym zuwonluz puxxofepx uke jatw cci: Gadodoz eskof belflofaid xaj jujw voi mes ghu pebk iav ip doej jyamu qoqi.
Stafu ati uwku wehy qotx eb vaxbaxocajq quaz dayfolg awx cupowsim maxdud divgem. lohitubxob.siwmmemk ob fgo kaxeiwduz jokwes diq bjup rzakweq lan o yut zettg men jozqjac rameulwx.
Ip rqu fijs ppowgec, hua’vn viarb nuf ma wuya weif tazumxay xuhyac regf ubey qiqzuc ps fadodj ewpulfuji ar Iftze’v wud Gufejir.
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.