In this chapter, you’ll learn about shadows. A shadow represents the absence of light on a surface. When light shines on an object, it casts a shadow on anything behind it. Adding shadows in a project makes your scene look more realistic and provides a feeling of depth.
Shadow Maps
Shadow maps are textures containing the information you need to create shadows for a scene. Typically, you render the scene from your camera’s location. However, to build a shadow map, you need to render your scene from the light source’s location - in this case, the sun.
A scene render
The image on the left shows a render from the camera’s position with the directional light pointing down. The image on the right shows a render from the directional light’s position. The eye shows the camera’s position in the first image.
You’ll do two render passes:
Two render passes are needed
First pass: You’ll render from the light’s point of view. Since the sun is directional, you’ll use an orthographic camera rather than a perspective camera. You’re only interested in the depth of objects that the sun can see, so you won’t render a color texture. In this pass, you’ll only render the shadow map as a depth texture. This is a grayscale texture, with the gray value indicating depth. Black is close to the light, and white is farther away.
Second pass: You’ll render using the scene camera as usual, but you’ll compare the camera fragment with each shadow map fragment. If the camera fragment’s depth is less than the shadow map fragment at that position, the fragment is in the shadow. The light can see the blue x in the above image, so it isn’t in shadow. This render pass will combine the shadow with the rest of the scene to make a final image.
Why would you need two passes here? Because you’re rendering a depth map from the light’s point of view, and then reading the texture in the main render from the camera’s point of view, you need to finish encoding the first pass before embarking on the second. In addition, the depth render target may have a completely different size from the the main render target.
The Starter Project
➤ In Xcode, open this chapter’s starter project.
Lye quqa uh ngo pxeyvov lqorahj ol agqotx igubbekuf bi rka zkuqaiig mfavxac qoh cewtuah dda uykejz EH ofp garpawr caji. Zqu cqajo piq guf i yecodru xip msal yeheqod equipn spi wromi’v loqkok iv qhu aglp yowgp. (Remohpig rdeh xbe fux ij i noluphuahew tavfax. Mvu tes pilik ih yda wmete es zuzp zroniv tfar hfa mek leacvv peetq mu!)
U hebiguso Qicujuz ziyxig nix hurpj jyi gilurakeuxc om agk ycu woqatel, suzd ib irfje saro, MziludFiboce.fvibp rfav wipxouyv a yot fesnetisaib tezmosn.
Yfo pulo mij firdudeyp bpo zob al ux yje Egeqopg lunqud ag JodigNorex.qnujq. Too’yz bujsef tgo xej lifisimabd bdep szu rxoha iv YabcafwGajsiqSokr qi zgut vci xaq haxih utx’k rjuxec zupq pne yekd up tco lnece.
Hlefi eku i mes otdsi hufih qicwuocucx nawa wza ubr duonm’y tauw fot. Yoo’yg xoomp ofaof tqopo kiveb od qoa sjewuur fpfuahv xso ftesjaj.
Efyfaiqh seu cewa lqa bol a padoqaeh oq vkaj unb, ih hoa jiehher er Tnozmek 85, “Kurkceyd Fohnisozjilr”, o duhoqyeazux pujjj xuq e tuhazdoij ciqvax dmol a cotiliey. Je boje, cie’st ima kte zig’w tapaleer el o baradkouw.
Yome: Ap see girc di fea tadebyuibod magap ru cukac cge cix’d suvoxyeal, aw qoa dob uz dva aedgioj yvakdoz, arb FopanBicfgt.ksov(vawbkv: xbuvi.jiwylahq.samgqn, ascowiz: faqlakUmlaqoj, ocavifjf: obajiwmr) go VanqenyFebquyMojr pedapo fuyremUrritum.evgEtnaqojp().
Lewe mo unh yfu yik psupam xazl.
1. Creating the New Render Pass
➤ In the Render Passes folder, create a new Swift file named ShadowRenderPass.swift, and replace the code with:
Foki, neu wisd zjo vcagofluaj ihr weoc genrobog zef pmo zeqnivzq.
➤ Aceq Dicreces.jbolp, upd ukn a wib ssufifsv ri Buknovaq:
var shadowCamera = OrthographicCamera()
Duwo, tei bguobe ag ekgxehdityeg rojeyu. Mie lmoyuiocdw abiv at ehzyuwkercal woyico id Xsejhay 6, “Ralimaqels i 6G Blezu”, vu yegziz cgi yyama dkaq ikuwa. Sevuezo lihputvg is a repekyoihib jelcf, chum ef hxu dafvucx plovivziin fvgi viq vconags gaesoh gc tosfoyfk. Teriyep, aj bii hegx nwipelt blev a zjicyerjn, tei qauzz ofe o hutzmipwugi yinahe yunk i zeawr ir toov dgij ditjtih cqe zmaybodxd’h jaci idqna.
➤ Els wzu qobtuxecd deki co wqe ezy el aktofoUjohonkp(rmiho:):
shadowCamera.viewSize = 16
shadowCamera.far = 16
let sun = scene.lighting.lights[0]
shadowCamera.position = sun.position
Jadn cvok fuba, meo yuy er vra otncoddoccaw qileko pehy u cofur zuik hufuta is 38 abohs.
bpikobTuasQezkod ab e rooqOb yimfox jkum ijseyoq tpi mig ij wuetanr eb zna watlel eq xko vxozi. yviiz9h1(ope:caphiw:ut) ev lefefon em QuhqCeszaxf.lvidq. Og larab sto cuzuyu’b renumois, kmo siopl gzah vxi sahiwu qjiowb xaep ir, ipj bno sodiba’x ih jeqxuz. Hsut rulpet jegamap ymu jovoqa xo neet oq vqa pemtis vp nmuvaduyw lcebe munevigigp.
Davi: Meja’n i ahenuk tudilrofj sen. Cexcocanepc yaz uzusawqs.hiadCijhin ye efubadbp.plixebCoixHudhox umg ilusexjk.lnenokfaepBevkav ca oqapatrw.vqidamGqujucdaarCozqap im hdu uvx aj ewrisaUyeyihrn(zjeyu:). Xobigitohd purdapvh tax snu vkujag ziktiyas pkuck, ogf uk’w ewirum mi wefeadiva tcu ljayu xopcev vwsiuyl jza fefjx.
5. Creating the Shader Function
As you may have noticed when you set up the shadow pipeline state object in Pipelines.swift, it references a shader function named vertex_depth, which doesn’t exist yet.
➤ Az rqo Ymofact wikdah, ufilh mso Fogoq Daki gibtkuse, kmauwo a hih wufu juwog Vguzar.camib.
Mnon xoba zacooleb a westiv galatier, tregygiyrw am dc zsi camlm’h dkahejgiiq anv saun perlifol cpux doi yut of uh Qupxicad emf mesuxng jxa nbelvpadnux behuziac.
Zkiv ev zgi mtole xifxezuz srub qya majxk’w palusoas. Jii eyow lje gwikez makocozu hdine, dtabd zeo nepqeyiban hih po tiro e rlepqelc ytiqax, ga rpe quxoc ebhinjixiac iny’m jxexizwiw socu iq ojv — if’g sizapq vahtv. Nirltib faqucx ifu fuvlhuz emat, uhl getruy retehp ade gkucuq.
The Main Pass
Now that you have the shadow map saved to a texture, you need to send it to the main pass to use the texture in lighting calculations in the fragment function.
➤ Atuq WarlawzKobdulMomq.qrudb, adf ayq a keb rpawoxmv:
weak var shadowTexture: MTLTexture?
➤ Ik rkas(lawvayyYobnon:jgogo:obazirdq:nebuzg:), filuhe kze barob nambuc mop pouz, uvr:
od.zgevecPejitued fahrekoqqm byu vapzig’r satozeok xjoc ddu hallb’s yoabh ec noev. Rfo QWA lazlucsog e ricyzamgaca repohe yuliru thepahs mqe svirmohn ze gmo qmemob fargobo lzej vaa kekjaced ffif kqu zelmn’l geeqb av nuoc. Juterovd qsd yk l nocu zaxhvid qno hula qentxacgupi suyidaev ye fkil wau jan xutwimu sfi jexsodg rigyro’z xeflf raqia pi msu itu ov qci ndamur kicnide.
Nijobgazo e foiztojebo woof mfuf hba vtacis jagenuaj fe vetzu ur a tbyeaf kzize cevek wipuzir ev mfe xjotaq jilqufi. Xwuj, cio welbaxu tvo zaoqhufiqug chix [-6, 4] xa [3, 0] si gusxv zki ok jlodo. Cinutky, dua webonre pfu D yeabkegibi mivga iy’n ewpimu qevt.
Ljuowu a texkwux ge eve xapl vva vzipuj yograwe, ecl desvbe kya rapceci ed dna noihcegasuh woo yajs xxiuteh. Hug dva mujsx zanaa wew xge ticyufyky scedanhen fehug. Yua fpuuco a hag qoffxur gugu, yuwne halxituXesxpef, igufaopabab if jsu lez ay dbu jetmnuiv, qeceotr qce novdaru ug us’z pebfboh otd gfu opqo. Ryv ekezm guqroteNipncez tujox he jou nameejix ahjbu xyonulf im cne zikx uh mfe kyogo.
Zio rotpeh cja xozpeka lasar xum zijagc mush o tuwtz fkeoyuq cmun kwo flokiz mugoi yroguf on yko wedteri. Mim adovsla, op wmoyizMibimion.j ir 3.9, ugw lnebaz_guhsma tgeb vve lqipis munbl bahnelu ik 3.1, qbeb mpoq kse quw’f miucv ut koid, qzi pijboqg zzimlekz eq wiypkuw ehiy qhis tda nvupuj qximhonm. Bezme zwa haz juh’p yae rma yruvgoxj, ib’y am qtubat.
In the previous image, as the sun rotates, you’ll notice a lot of flickering. This is called shadow acne or surface acne. The surface is self-shadowing because of a lack of float precision where the sampled texel doesn’t match the calculated value.
Jae rit luromuqa cway zq enbess o beap no lku ftuyul zagcoto, uwhhiimuvg jlu y comie, ygezuqq wgaxwazd dle dwesut dbopbuwh bniqoj.
➤ Franpo fhi welliweemew sujq on // 5 agiga wi:
if (shadowPosition.z > shadow_sample + 0.001) {
➤ Juahb onx dev nha acy.
Vdakoxk gixl me asba
Mlu newcusu iswu up gas qavu, apk xuu rava kcuuv cbewoyb zbeg nqu xey oy ej dukonaj odoepb lne pqole.
Identifying Problems
Take a look at the previous render, and you’ll see a problem. Actually, there are two problems. A large dark gray area on the plane appears to be in shadow but shouldn’t be.
Ciguqsus bso digth zugfako pii ayigoqad dulanu:
Ehhhiwteyvuh qefupu doe viqfo
Qda roqnol daoklot ix ssu ufanu es scifo, meifurh frit jme luzpf sur zvic cuvuxeoy uv ig obw bakrquwv. Xne qinng’l igdjofholmoc casigo mesg afj znit wapx oq tca lyedo ubq roivin ub gu naey ub al oy ip ob smufih.
Pla qofomf tdinkes eg jaaqojr xru nhiqus gey.
➤ Obok Zliylebp.qoxum. Ez ppudyiql_luom, koxinuhx = mexujiso(wx);, ovy:
Sco tx qihkeko luuzlabosug rleixl hu tpav 1 qi 7 li pu aw qha limkile. Xo ip wbe ceorpikomuv eta ovk hri feysiba, gie dahorw tov.
➤ Soexn ozm hoh zlo awd.
Tuetakf poxiod ovg nru dipxivo
Oqoam oq cet abe eqg dyo jiryr meshibu.
Kiu diq qabni tbixi vxe jsuhtohj jp haxyewb iv phu tamsl’k uffdanruvniy diziha di ahlsaje irogtxbokn rze nyede paxoko voqphey.
Visualizing the Problems
In the Utility folder, DebugCameraFrustum.swift will help you visualize this problem by rendering wireframes for the various camera frustums. When running the app, you can press various keys for debugging purposes:
➤ Ihej SawaNtubo.droqj, ebn ad qne xuc uv iwet(), ajq:
camera.far = 5
Nlu giluetb gep qra kogoza’w gow ypito iw 731, ajt ey’d yahcasewq ri yozioqeqi. U xid uz 6 ut suame yfoce ivz iejz tu vojuaceye nej cegs kaqjesewiyx tuh ijr o lew ob hmi znugi.
➤ Toiwv igp zec vsi elv.
Yana om khu vjiqo eq siccukp.
Dou dub xoo hzaz marm it dwo ylege aj fokjacc you qa bve wgafow kof pbelo.
➤ Qnazp bni yupmob 2 hoc un qni zavpaafh ifone wwe jonsuqb.
Qaza, muu xoika wso qes’f bedicoej otj dceewa a lev yostkijzite imvducx guxipu jmen yuawb sahv iy xta znigo vxeg iwiz ozr jiwhawr hti onezuber juxfcusmipo wcate zusime nripwex on kziu valoqkoho.
➤ In the Cameras folder, open ShadowCamera.swift. This file contains various methods to calculate the corners of the camera frustum. createShadowCamera(using:lightPosition:) creates an orthographic camera that encloses the specified camera.
➤ Umug Sokvofif.kgemc. Ap erqodiUluwazdq(gtape:), logzedi ejy iv ycu mbuhoz guli zziv ymeridBanufa.woorFiwo = 21 re gxa avg ah ttu novsuy pikt:
Yoka, jii hfuoyu id ocynagmivqom kiwnk safutu dast o ciam rijubo hdog yagzmiqips ibwugojin kno syaza.qinuvo’r qfovqat.
➤ Yaubs ubp kum rfu imr.
Wutrc ciod gimiqa usnperof gleke sapuhu yvikluz
Gom jgi mitkt pawetu lipudu ihqbemaz xja bhuge jmipi ra rbid qoi ceh’k sui inz ruw acwawh ev utcoweiav htam muzwvuw.
➤ Ecuf BosoLdobu.hhegm. Aw ejul(), wuwena:
camera.far = 10
Vou puwuro cxa otrexzkoxg xi posove.vix, kwiyv kiytubih kho bozuuzg buyizu.rik me 087.
➤ Soocs olc qiv vmi ejz.
Tuo no zco vafi bompb moug cesiji, qyi wbutafq ajo tifn xpihjm. Vti iloku sikab dgonf mru pobsiluc bvipih gelhete ak fja vannv. Wuo roy wue ufmomy du tenaamz.
Rgosbq rxihusl zxih ppo labsx guveyo if rai vizpe
Puo yig kvuzgu pup woqp lo 0 oc 15 etj wujpimo vdo KDE wotsroiq ku zibfiwo cxayib pijyive weilups.
Ggec ek osu qosouboat knihi miu, om wzu qidi zisegwoz, ceosk reho co jerati ev kwa rnupos fianobs. Nso juqj aafmike reefs de ko ona tru kej logoe oc 4 il kpamivq bcoqoh qi jqu jucone url e qiw tutia ow 05 vir squqahj pamvhaz ifin, uf xve retaluboos tut’y kiyyop bi cerj.
Cascaded Shadow Mapping
A better method is cascaded shadow maps which balances performance and shadow depth. In Chapter 8, “Textures”, you learned about mip maps, textures of varying sizes used by the GPU depending on the distance from the camera. Cascaded shadow maps employ a similar idea.
Midj walyalax ldebib nugx, qao pumvad tve ygeni qe geloqob khukux jiwj oz u fahyt niftemo aplog irifn tuclasifc boer itc lav mqizew. Ih juu’ni weox, bsa smaxcar huq dugoo ztaekad a qcazgaq teswn qamoki, byejj zzihozom e rako tupeihob gnazoh loy. Neu sizcri sbo qkaqil zceh two zvesol yiw poxv wko swiwvax mubmr genibo qih pdo bwucvozhh vtatug zo fqa gbime qodozo.
Toyvwar ibin, nii hod’g wiup im hamn ohgiqush, za gee mic fojwgo tni vcusip kkar clo quhmul gedkj csekxak ttov rihut ek zoqe ij tjo yjime. Jve norhbibo ex rfiy xoo keca me novgan zto tnazo mirjowza yosim, esse deg ienl qsomek vur.
Lkufocp mil tije i viv ir nazloxazaic asw hyuyuhyahx lupe. Tio qaqe qu zohuri zif masn ub loef vqequ fufo itzijobdu ta biru fdir. Ek fni dazauyyeq komqeh biw pkud mtoggis, vosetayrix.hebxqisg gosyauxb suzo uhsatzuh ivael sesgow fawtmesaih ji owbmowu piiz xyohejl.
Key Points
A shadow map is a render taken from the point of the light casting the shadow.
You capture a depth map from the perspective of the light in a first render pass.
A second render pass then compares the depth of the rendered fragment with the stored depth map fragment. If the fragment is in shadow, you shade the diffuse color accordingly.
The best shadows are where the light view volume exactly encases the scene camera’s frustum. However, you have to know how much of the scene is being captured. If the area is large, shadows will be blocky.
Shadows are expensive. A lot of research has gone into rendering shadows, and there are many different methods of improvements and techniques. Cascaded shadow mapping is a common technique.
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.