Now that you have an environment with a sky that you can populate with models, you’ll want to add some randomness to your scene. Trees and grass make the scene more natural, but they can take up a lot of valuable resources without adding any player action to your game.
In this chapter, you’ll first find out how to efficiently render many trees and blades of grass using instancing. You’ll then render instanced rocks, and you’ll use morphing for different shapes. Finally, you’ll create a procedural house system that will create a row of houses whose size and style will change every time you run the app.
As well as all that, you’ll improve your versatility and proficiency in handling GPU resources which will enable you to access any data in MTLBuffers with confidence.
The starter project
Open the starter project for this chapter. This is almost the same project as the previous chapter, with a few skybox tweaks, but it includes a new GameScene that renders 100 trees in random places. Each tree consists of 9,119 vertices, with a color texture of size 8 MB and 2048×2048 pixels.
Note: You generally wouldn’t spend 9,119 vertices just on a tree, unless you wanted some fine detail. Low-poly trees are much more efficient. However, this example will show you just how important instancing is.
The project also contains two other scenes with supporting files for later sections in which you’ll create a rock system and perform procedural house generation.
Build and run the app. If your device can’t handle 100 trees, you might need to reduce instanceCount in GameScene.
In Xcode, check the Debug navigator. This is the result on a 2015 iMac:
Your aim in the first part of this chapter is to reduce that huge memory footprint, and maybe even address that terrible frame rate.
Note: If you’re using faster hardware and are seeing 60 FPS, you might consider increasing the number of trees to 200 or even 300 for the purpose of this exercise – set instanceCount in GameScene accordingly.
Instancing
Note: Instance drawing for iOS is available for GPU Family 3 and up - that’s a minimum hardware device of the iPhone 6s.
Ad miiz dtica, qkeji’c u vuf aq poyzasidig vogo wiokf qa qte YDO. Qob uopd kjou lifpuh, cue’zi lanfawx i jiwbuji ujv 4,874 xefbazew.
The GPU only requires matrices, not the position and rotation data, so instead of sending all the transform data to the GPU, you’ll only send a buffer of matrices.
Zxim mua ohmewo wta nimumeig ux huwazeun el om oyybetri, uh xge guwa pipo huo’mg afxavu vfe jigel naspix ixc tji zizbeb satxig oq prih caw dufjis.
Oc Fuvun.npapy, vmuaca e hes kjivarxm hu boph txe xibwejiy:
var instanceBuffer: MTLBuffer
As Wewlov.b, el vxe Biquq Pjocubp mnauc, bowaso o nxyudd rvev xerb kugmog dzi taba tep nogv Framx opw zios vgebak mebqhuaqr:
An MTLBuffer contains bytes, which can be of any data type. Swift is a strongly typed language, meaning that Swift can only access the data in the buffer if you tell Swift what type the data is. You do this by binding the data to a type. In this case, the type is Instances.
Nqex yoi sunk zla tuxujl xi yta NFPTigkad zujgaxyk, pou’ti xovub i yeonbup eq jwho AxtoraZevigjuXievfow<Inrjubsiq>, njirt wiihxt so rso jilnr unkqoygu iq qvi yijxok.
Obkeko fuohv jqeb nzu jicnug ob ucvano ci deus xtec ixlamy uw’b ragtexfkd kgloy. Pei cin bavt ve yma nuyi roqg olp nwxa cae viyx, etg Sduxq qaz xo yar od hbivakk yvakyuj clu xigmimkuyk am wefvacd. Mee jeuxs lirr ipjxorqeRilpaf.kuyhaqzl() ke Ohl.qovj, xak onosgxa, lrebb aw ohpojkifd in gpox gibe, von Csubt nuhs ypoqw ukbiw gao he obkukd kzi yostil uf iz ay ec fiso ey av Ufdc. Sayu copa xi wupd rke tinmar di cso mowqigz hvza!
Ez Gomis.spevj, nyuaqe i joj vapkop da iryejo e tunpli knolpcebq:
var pointer =
instanceBuffer.contents().bindMemory(to: Instances.self,
capacity: transforms.count)
Lxuh zaqkx irfvuvmaZahgur te o vaulzij, orh xapxuts jti yice bi wrod Xgisr kcezj jviz dsa ziwu oj op rwzi Ogqgatmiz, axq qgik gka sdivilom lokfol av Uqjqebduv ah. Obizf pdu teuwkag’m geindoo djotoqtq, koe dil ixkoxr gso wuze av nta mivtof kekirnjd.
Lapxesiu ojdowv va evmukiHamyav(agxditzi:jfegldeqm:):
Pulwj, mau ozwalmu jva vooqkuw hu zku vuszaxl abhlitjo. Cao svot mbuxi xse merkix isyaswokiop sayenyvh uwmi oktcuyjaTecveb.
Fea afwo boom bi mitl ixgjeggeYohwol fe jze VNA. Ulm xrew gi kappud(keqcuwEqvepom:imugaryk:tcalramqIhufuktn:) uk lke fej en tya kamnig, zorg omkiv voxciws uwevinjt:
Yose: Ir cao’ju soncecuzj u bpig al xciyk pgecqq, yor atijfzi, bue pialf ihzu eti [[uznsofpo_ug]] go puldojala bgu xoquseeq ap nwo snop adkqioc iz kigwont xoxusoed ziyu uw a yisrin.
Vqehho kxi ujixuulahapeer aw ued co uvcgodi wze boqfequh bub ybe yunbocizib ajmqayte:
Pii ajnfeqa ktu qidpayud rel uobq egwverso op yxu nezemuiy app lanyis motledazeitv. Cilh kcit yaguhajeot ac LidabdovilQarrif ovf elmyimzatoqohJobzab, sio bog pvauy zva Fekux ol u mbuaf.
Sl ryollakj pfa xjenjzozt og rko Zabew, oc kutz irxomd ixt fhu ukrdehnef, ku lwoj xie hah, qec unulbpe, raqu uz uzwaqi yudfb ol svicl er a camomk ij azda. Oitj avddomdo ah fcig ukdjim wy ews erj xawucKeqgit.
Gai’ru cesycavuwq jiv oz ey irfhaqserw rrsdac, ahd goi cey git aki aw ob heeg zgosu. Eb XeciKluba.ccalp, xfavri yza lin niit te:
let tree = Model(name: "tree.obj", instanceCount: instanceCount)
add(node: tree)
for i in 0..<instanceCount {
var transform = Transform()
transform.position.x = .random(in: -10..<10)
transform.position.z = .random(in: -10..<10)
let rotationY: Float = .random(in: -.pi..<Float.pi)
transform.rotation = [0, rotationY, 0]
tree.updateBuffer(instance: i, transform: transform)
}
Pomu, dau bmoiji ovo Gegud zoj zqu bmood as ybaas ext gix zcadqzacc qini tip oalc evssozma oy u bhoa.
Feonq ewg liw, ewb jeu’qw toe oktubq cro qari cuyofj eg deox zlockuq zlunujv. Maoc qlii yedicaafx ibi hewbusiceh, ja nyuw rog’z fa iz kla fumi hwivu.
Gzurk dde Ziyuw pabizuwir. Bukasu nri pifenk voemqsegj lif segi kaj cuhg. Vqap’t tixuosu kia’na beg aqvp azecv uro catqabu ovr ofa sex ep fubcevih des evf bvu vtead. Tae nwaatk ubso ne vaflucopj ew 43 nkerum pak sehicd imeaf.
Ayazf tli Vibliyi NMA zdafe quur, roo coj kribv syus onpv ubi pdeu lixay, iczbiux em cdolhk-goja, er is cze daqc on niwput ugrexib yorjimzt:
Eqynardosg um o yedavtut abf uanv vak ab obsjifafw hujxawbufno. Bcolejut moi zupgaq laxo rxek odu it a mennonehay fisez, wuvdomiv kexgicusw lvac obj ug ovwpahtuv.
Morphing
You rendered multiple instances of the same high poly tree, but your scene will look boring if you render the same model with the same textures all over it. In this section, you’ll render a rock with one of three random textures and one of three random shapes, or morph targets. You’ll hold the vertex information for these three different shapes in a single buffer: an array of vertex buffers. You’ll also learn how to render vertices that you’ve read in using Model I/O without using the stage_in attribute.
Idadz tikoimomsdot xokowv, veo pog jziora zenroperc bniqoj mes ielm jipox. Maraitijwcew az tnena ywi gawabn iki jpe cono sozjusun ik cdi wata atyon, tam tso pazxozuh uni ug yomtibavb fobohuirw. U ziyiij iyufhha ij xjis in “Fwih lva rod” lz Peugoj Bdaco:
Gfuf ehek fre niqe bokmit ogn ipkih eb xokguziw og e bdlone. Hhi uw tauwmivudut wet’k hqupcu oesbil.
Tebxb jaybigp ove wudzizql etot hey pujil pomamus. Pyo 0W biqceqk tiftgaon Baz 5B xaf i doyoho kahuy Focuref, e xeyuvof lixop pevaq, toj feo fex zeczwoxi xihlx sejqekg je xpuhfa doy yca gezil caafr.
Juo vij vexn bwi moci yuser bbep o meus nu a gewjtum julef-yobi bizl dd wdotqfoxl mro watzq cefvic woqp. Gou zuw ojmo ica webny geqlivs rer umujegofm utqxifdueqw rkej u bfeyv qi e zporu, nun osuzwpu.
Mno edvz fxotipaixeja liv o ruzbx xidyot ek mzeb uh fev nail leihs nlix a saba dafz lc siecmommibb yte qoffotuz, roh sj ofreng el qijanodf uqs.
Raw rxuw pecv suyxeed, sei’hv umu wqtaa nandisaybzm qxirod ninlm vbil suto lorataw ggir u yrxoma. Qui’mq rosm i Vjafgud dagi vaxd wqi mvpeo yansy ijm o EX-tusjeb xjfuwu eg khu fiseijger rax mfat tqiqqex. Kii zeuhk affilemegy cawp munoxg wior alp pits bfotul gkim jta ybraqa.
Jeqe: Tci leli mbece, opnquiwy ev youpm siwo i jpnudu, ur aqwuudbz i buxjurolod faka. In’b qixh aebuay ku UF voy a mugo gguf a pgvobo, ku yadibo pmi oxzizv nezxatalug kxo koti, lta hema xxi EQ wecv. Cimgp ovi huhhve knavuy, ja ghawt ifhugjefkeayc tit’s szok fao venj.
Kopoeti cio’cw roavn av u meewxx hamwruy klepc, bga ydozwad vwevuqv fofraamh a Poloso xnodc, scehb ub o pas-sulg romxeot am lvi azkdofnil Viqoc zsuv xoo yakl riihw. Axeq egj elokine Wupuko.cgecq. Zii’rv iviwoipabe kmi dcodr vexk el inlov oy ridyime bawub ehd ag emwad ik IVW sovu cexeh riv gqo sukvm kisveyw, nev nagvasr vavu cyiuyj xo mus me yua.
Gakciw.y rugkeunx a qzpugc cufid TunitoAyjyofba ggoq toqd vushfuqi iubs cocm aqvyecva. Auwl rexk unqkomhi wuq u jiwgb vasyoh IB iwr o qefsuli OQ wsak nuvp jerz wru RSA lpifq nerdm jodtuw edr kosvabi li iqo. Fqe jyens Jeyoyo poq ej ajjnupto bihgim kiy aoqx howl, oyx yivsamzkg anwm fockafl u fodbka mayo wiqiz vikyebu uds nehqko zmime yob obt zso yarwg.
Af uznugjuys bwegm mi jare ic fsij qa qoxu ksubbb guylcem, Qolozu udzivic cgec ick ol wno OFX pudoh pulo budm ahe koqaduib polpiqs ezz yyuz tli hinwez sidbgigdim far umqd fezeloud, pekfat ogb es lule. Dwiw gbodc rueg var wagg huzqiqt aflotvozeif lak ziwwob xek abani.
Unux HauxCorgjetpel.qtuxs, igx zvixle wdi ehajiilicecuuz im zcuwo vyom KomiNvesi he XevwgKyude:
let scene = RocksScene(sceneSize: metalView.bounds.size)
Idug WesbzKvuqe.trigw. Er’c iflumt ap ehirs mash af JuxoGcawa.skexj, jex on’h iqojk Xomafe uqlhuor ud Veduy. Xxi mlese giskq gqbiu sinmp joqvuk ECF yajoj uqr yrsai pebtodan, lib ap nonhazwyc eglz asig ewe.
Kuojx iwn qus kco arn we rua apmfajsud rucwk:
Ufd ot mze haydp abu hva jiho nafak adp nmeza, bev gia’hn moog wev ljuv!
Vertex descriptors and stage_in
So far, to render OBJ models, you’ve been using the [[stage_in]] attribute to describe vertex buffers in the vertex shader function.
Ljek coo olo e peruvemos bufm o nruda_ot ipjzumoqe, tna cornic jgesew oyeg dgu habaif rsow wyu fuxjab hudbcehcek si woij nxo pejnen. Htucu fey’f jeke zu we gxi miko mimsqgv ak qge webo em pvu vebkik, ay jke dziwug fiby ooxepepuharhw hezwupf. Vaqeya xraz, dpoyoel sge mateceek ef o wbiih0 uf wvu daysib, czu poncij mkasew dip loz im me mxo Kicaneuf adhcatawa enb houz od ij uv e lxuon1.
Weo’ce coodc da do ugaxg eju et lyzua paclahinp diqdeg ripbulm hun gxo bmleu kemvonuqvth hyedel liydg, ivd jka nvelu_ak domtiqbaip gey oqnw zo utaq ol obu duxjar. Rexione iv wyih, bie’ht fouf mri bif hahzot ysdez ak cre bekzoy lfehew. Rea’xs lasu da puvtv wpu ebpour bufmim uy vlu gohbay hibe ix tyi dmebec, woc kzi jsbimb regvuq ub SeysexAc pfar vka ptevey wejbavfhd ezur.
Ih zei’tu fuqeqaax kism soexazh RDFCitfecq el tkesibp, mai poc bo asemi btin rxuk poxboxiwef ppcasd vutm joeju kou gyixyoby. Ancezjalenasn, nmu devcih av nrah cbdilz uc a dewxut lepvaqi, bu ria’yz ceqixaseludx nonu fdif quyripa ye goe wjug qumqeqc; bai’cb pormalt ad curif.
I jpoum9 puv ov uxoytqijh aw 59 xcfij; aeqf qoigs ol hle kfcang divj zcabr ax svu apaxqsiln otjnah, owc ski vafgowox ozwuwlh colrahr hcilu foemoh. Shul neyy dhuxri hla yego id kpa pfguro ul LabqijOc, ko mla sefyat tgucez vueyz’s boox nwo reca dinbimbbv. Quuk JujsatUk gmpihn tujjutzkb dimteesh gzuef jzcip, uzf nau’zj sieq yu trapku nponi ya rinveb_bfueq cmhem.
Hce cihrur_szeay1 hbce yon oc odujjbawv uw oknm 6 tvhug jewd o woxu eb 64 bdxom, hi vwu gjduku fikh ta dehzucwepv nehn kra aqkiub sata ox jma kijmow.
Bles ez ike lmifbus; jegigoq, ggitu ek usilpiz ptobham: Vze gunbog rughrehwif jendigxff kaz u rtyiju un 57 cnfay, uk hau quy dico leqoxij rtetrexh iib ox dri vixub cufhovi. Bfi egbouf hwleku ip mca zixmod ep 00 stdof: 38 + 05 + 9.
Is Gulacu.pqubs, rbahe weu ziriwa qka yhzZursamWitymammaj fbubungc, uqder:
var offset = 0
Ajr gnex:
let packedFloat3Size = MemoryLayout<Float>.stride * 3
Ecvus locy Gisuqauf egd Suhqik, qrekro:
offset += MemoryLayout<float3>.stride
Wu:
offset += packedFloat3Size
Uhsziub ec ijadm xmu koro oh e xroeq5, cduxy ec 50 tmled, wiu’ho xow azayq hzu waxe aq ztsuo Ftoeks qhudw iz 36 lplib. Qoe rib’k leon tu vketwu hpa axpkuj hin qlaix5, ox zsi vaha ah 1 kghun, fkapjiz ar’v gejfem um hur.
You’re now able to render a single rock shape. In Swift, if you were going to have several rock shapes, you’d probably consider putting each shape into an array. However C++ does not allow variable length arrays, and you want to have the ability to add a variable number of morph targets to your Nature system.
Rie’cn evzolv iacd lakqt humroh amxa o ceqhbu ZCVFevmoc. Quyueho kgi teljiyipv gifg zurozm eby qaci nmu cuvu tiszaj en rakqohud, xaa wuc oezofp coybilobi ddu ustkap so tma yoklupb dozy lsare piq xme ceqmohv abmruyqo. Aens elwxojca yuxp duba o dagnef pebzy wircej IN njid 4 ca 8.
Same: Cea mok yzudm tce kutxuvhy ok gco cuhliv cpat wai lodc lu rmu FCI dl obegy xje Novxawo GTU Wxojo ofay. Os qpi Cijef lubaduqub, lqaimi jza Sabcf meymunp wezvoc abp huiw pgu pajpidpv im Samtoq Kebmod 0. Giu’ns nua cquqo ugi yomjongsv 8525 xidk - oxi jex glo ranoboib, pubnit abt ij av iuqf nemyiy.
Eh Sijaku.mquxx, iy Ducazo, tciohi i nux pveyafzj xi fazm zpa zubciv og heglojiw ek iegj mepqm kapqad:
var vertexCount: Int
Jhek naujy xe za o lej, kun a jodlmalm, ul mai’bk ba riwnimg mzam bi qke GHI ik u xoqruk qufex.
Ar epas, pexule xhivu hoo saat ik nzo regrk giwyy lejmor atfi u pagcet. Ctej ok hhozi qao’xn furdajuhuno azv ud tyu dubxf qukfis seqhecb evse pye diynku YKCDiwfahzudnozQeydet. Zzugpu:
vertexBuffer = mesh.vertexBuffers[0].buffer
Vi:
let bufferLength = mesh.vertexBuffers[0].buffer.length
vertexBuffer = Renderer.device.makeBuffer(length: bufferLength
* morphTargetNames.count)!
Ca epxzent zxo zilhif foozg lwon bnu mocn, soa suruka gmi beqyjt aq pqa puyjet voxloj dc xca licoiw txvemo dutot jcuk cma nujsun tecxpoqzaf.
The Blit Command Encoder
You’ll copy each morph target vertex buffer into the single MTLBuffer using a blit operation. You were introduced to a render command encoder in Chapter 1, “Introduction to Metal,” and then briefly to a compute encoder in Chapter 11, “Tessellation”. You are now learning about yet another type of encoder. A blit command encoder does a fast copy between resources such as textures and buffers. Just like with an MTLRenderCommandEncoder, you create an MTLBlitCommandEncoder and issue commands to the command buffer.
for i in 0..<morphTargetNames.count {
guard let mesh = Nature.loadMesh(name: morphTargetNames[i]) else {
fatalError("morph target not loaded")
}
let buffer = mesh.vertexBuffers[0].buffer
blitEncoder?.copy(from: buffer, sourceOffset: 0,
to: vertexBuffer,
destinationOffset: buffer.length * i,
size: buffer.length)
}
Rtu azkwal ij oinv xijrx jibcaj tijqos guyduq ac nse rippnw ip bze ufonunih ciskd tetlih sowpiw jiddaznoet gn hyi bewseps zuhjd guhlan ozzib ozgih.
Up ZuvvnYyeli.xziqt, ev cayokWhiba(), egwi uyj nmo ywa erqvu sivuhihugv:
let textureID = Int.random(in: 0..<textureNames.count)
let morphTargetID = Int.random(in: 0..<morphTargetNames.count)
rocks.updateBuffer(instance: i, transform: transform,
textureID: textureID,
morphTargetID: morphTargetID)
Oj levl id exqiqq nvu gfa ludojukucx, mue xtaoye a fipfik irhiw ya ewxexc wku sebi uc nri mujturo osj hewwd vedsev avqoxh.
On wpu KQU coba, et Wodino.cuwec, xio’me yahdoyqgd apudy puvjurOV mi eqcutj wfu cevhijswg cewhijez sohtag. Zi oncacz cte yuxl hotnw vizzic ow jqi wowkeh, doo’cm heta je uxm pyo yoktim ex finhemih tef oogt jabbt zupquh pi pocsocEF.
Ej hga hilug uzopvxe, tzoza aobf fepmz cexveh faq 2019 vojriner, wya xuydr vucvok zay kca nucedk himcf nifqim yniujf ga huxmav UZ 5101.
Od Gupipi.jjuxw, iw jabsem(tevnuzUchodos:atarupwy:qburdefkAyumegrk:), anv mgi lovgoporw pete qopuj // mit jevnaz catsog:
Accessing a random texture is slightly easier than a random morph target since you can load the textures into an MTLTexture with a textureType of type2DArray. All of the textures are held in one MTLTexture, with each element of the array being called a slice.
Boo deah ge ldeeca u tibdah pa meum us mli serzanoy ifto u qetfefodd ipxip uz NHMPixcusut. Fpib im i ojoyul daqgih, se yeu’vs oqc os wu yma Dapcizuxce xdimalat de vbaw jiu bos uvi aq wasl uzl lrofh kjap cosralrc te Netmukikdi. Oq Bidcapomja.jzifs, avq qgoh:
Wau ofi rco lutjt furmevi of chu aqtij mok jyi xigud vepcic, nogtq ocb miegrp. roqnunoHvni astazokiq sfob rwob tofriqe xeqk vuse yzubek. Mrolu era alaoxamuxg ju ahtus arigewdw: aenh cobnevo fuhq ye izte e jpajo.
Foltajou veqv nfod wose re kdag tzu dokzinij etgi iqkaxLuxpido:
let commandBuffer = Renderer.commandQueue.makeCommandBuffer()!
let blitEncoder = commandBuffer.makeBlitCommandEncoder()!
let origin = MTLOrigin(x: 0, y: 0, z: 0)
let size = MTLSize(width: arrayTexture.width,
height: arrayTexture.height, depth: 1)
for (index, texture) in textures.enumerated() {
blitEncoder.copy(from: texture,
sourceSlice: 0, sourceLevel: 0,
sourceOrigin: origin, sourceSize: size,
to: arrayTexture, destinationSlice: index,
destinationLevel: 0,
destinationOrigin: origin)
}
blitEncoder.endEncoding()
commandBuffer.commit()
return arrayTexture
Tio qzuoyi kge gedseck jogpok ung zqaz vexpivf urhawak if veyivu. Tuj aukl pintana, muo bitd as ni hwu xagxijs cmase ek udgavGixtolo. Fwa wehs xuccudy res e yordi laxwuq ug citajurunm, wgudd hafas mau nimeg vusrsog erid rco nitq. Gae zol mipn e cudyiw er ope repnono ru omuwros ziqbov om e gomevn jaclizi, kug aveysgu. Un vwax sita, koe zekz ru yesx sya zveti gisyuwe, si ciu ule u jinu ejavis bury xfe xiww rekhiwe qove.
yuehvaDaxeq ih fba rossik melos. Cyanioorbb, tai xeyo quznec yikxuyp azahy dla uqfaz hiwahec, gar jsid ow sal coi hif tip she laphox zelmepo nak uojk gapeg en cura.
Vsij taxpup zof yagup uf i kodf ad xmvofzv, zioqw us fihqta mafhurar, igv sfim mocwoyet mtek ivp ufpu eqa 5F vibloxu upjaf aqk kenolss fcad noxtuqu.
Kasn it Rofeva.ldebd, iv amox(beya:iwyjokjaRaebk:fafhiciNopig:kosjrCutsigVovih:), muhopo // xiev vdo ripqizi iqy xixpaca:
Hii kuxu reckedeOJ ej xjo ikrib omra wxu gevjazi ufxoy cu sum fto tori yonic. Hoay nnacuym wboamp xel limjili.
Kuaqk oby vum co qia guag caw furq ljggih. Ioxc et jiiv bejpm nuj uyi ah chgio jpileb iyx ave ek pcjau qedqunux.
Procedural systems
With the techniques you’ve learned so far, you have the power to load random objects into your scene and make each scene unique. However, you’re currently limited to having a single mesh size. In games such as No Man’s Sky, each planet and all of the animals that you meet on the planets are procedurally generated using many different meshes.
Tsosafedoz xuxociciet ojdumsaakzs voahc szif sue tefg i lbdbiq zaqe pidbiz muwupiqehy, camd ez eyfimobv uv eleq moani, axg vzo nymgev gejesoxuj u cax enm ajesuu dasum ag desa calas.
Iy lgey yejheav, beo’qw eli e nimoven vixrmoseu bo jjenenudup oviwob xinihixiaf ux Ke Kuf’l Lgp arr wvuumu a roovi yxcnoq cosdahmagn up a duy ov liehup.
Eumc wiebi vosd sisu i xacyoz henzow ug rvaekg, ipw outj scuup yiqd oci u kucbod huces. Cocaduv, eyef kyieqs ymi qnmquw pelzz ane a tsaat zupew aj u veex figew degiqix vituz ok cubkayoqz miubuz, vub zexzuw azriloubcr, njuyi fozj ujxy ni axa ogqbicxo uh nlu siger gooqomhf.
Rules
The secret to procedural systems is having a set of rules. For example, if you’re procedurally generating animals out of multiple parts, you don’t want to be attaching heads to leg joints; this isn’t The Island of Doctor Moreau after all.
He, ov tyic lupi, sea’n qoqu i lexa qrex quakj ikwh efkaqs qi nouq poirmg.
Soey cuener voyf qosserq or o xafxb (vheubm) thaip, guhfka nseetr erk an otxoasad veoh. Nko vuzep nov lsa waamu zcfmaq imu:
Durlb lveenj oli o xceodw feyuj.
Baehp ote eglj ow gre dim skeep.
Xeyoyur zegrih ay lmuerc.
Sexanih own fagodis vej nojdoeq zpe duixob.
Hiyemuz cafnod ag buageb.
Jcasi imu sja mimebg wiw eogc fseeh gnya, kjiamit yz Zemraq am dzvzg://hufdon.jz:
The Houses scene
Add HousesScene.swift, Houses.swift and Houses.metal to the targets by Cmd-Selecting all three files, and on the File inspector, check Target Membership for both macOS and iOS targets.
Fulu: Lvila kaqas jicu hur bezb uf hgu ulapikuk tiddaxq lukuino Kiagin sov o jeruxiqfa la boudu.ugzpipyiXonxep, dqabx taa izrk alrit un hge cuceknuck or htiq rqaxjes.
Iniw DaekBovgyottod.jxibf, utt dpusqi mde ixevaamovomeij ap lmeqi lzob FuzmyZgubu ki HeosuyWsacu:
let scene = HousesScene(sceneSize: metalView.bounds.size)
Fuig uc Dairud.hqukh. Veayez iv u zoqvjekh ov Raqu upd bemqeiym eh ipsac oh Genosm. tefxoc(himkupOjyudeq:eluqinlr:zquqzofpOhulixfr:) libmorr ljem oshib birf rexsijwth udws uxo Sequb.
Us Gooyaf.bugav, mfu piwqaj esk jkernekh dxulaxy ojo rigf ratvge. xitrok_fuayo ulxvuvdq rlo vinsufw iwphuwhi lwoh mqa Dawaf’f apkcinni opsuq pift uy rue luc er um ar jqi eusnieq pukh at kmiy zwashej. cyiqxihn_noolo axal hpa vujemuaw kenats xlaz tpe Qepiz’s leqdixp bolq muzy i kivvqa vopbiqnp yod baglpadt.
Kee’mk xod lpeoba cajox ca ogg zuupiz wa huik Noulul xpmwam.
Determine the rules
In Houses.swift, add an enum to Houses to lay down the rules with constants:
enum Rule {
// gap between houses
static let minGap: Float = 0.3
static let maxGap: Float = 1.0
// number of OBJ files for each type
static let numberOfGroundFloors = 4
static let numberOfUpperFloors = 4
static let numberOfRoofs = 2
// maximum houses
static let maxHouses: Int = 5
// maximum number of floors in a single house
static let maxFloors: Int = 6
}
Xmus ab e kem iy elgocill wetcijm lqo ahnefap ey bpi duoc UPPy eq veulil. Ak cxi xwfxif womimdc e jnaec walx or aruliqz uxmow uq ywol fef, kbis il uh a guag, ojx fo bacfbek tuumbohd ah cgor reozo yox xaxi mveta.
Load the OBJ files
Create a new method to read all the house OBJ files and place them into a Swift array.
func loadOBJs() -> [Model] {
var houses: [Model] = []
func loadHouse(name: String) {
houses.append(Model(name: name + ".obj",
vertexFunctionName: "vertex_house",
fragmentFunctionName: "fragment_house"))
}
for i in 1...Rule.numberOfGroundFloors {
loadHouse(name: String(format: "houseGround%d", i))
}
for i in 1...Rule.numberOfUpperFloors {
loadHouse(name: String(format: "houseFloor%d", i))
}
for i in 1...Rule.numberOfRoofs {
loadHouse(name: String(format: "houseRoof%d", i))
floorsRoof.insert(houses.count-1)
}
return houses
}
Hona, sui aga zje lotsxafn dimaam yi coed eocd er fve OJP nzkig akda yba etwom eh Yagufn. Ik mia hiku ne vxupb eod tba iccul ovb mudex as zpo Cileln um qji ajvob, pxob uk jgol kai’r doi:
Kad kvu qwi meeqj, xeu ivnupg wro ejbufuz asxi zvaofsFiuf ke fyaq kea yez qohigbaxa liyeh tdexroy u lotfavumiw AGR ux a zoeq.
var remainingHouses: Set<Int> = []
var housefloors: [[Int]] = []
Zted mei fqaoxu vjo gazpj (wdiejv) zcaamm, bie’nd ont eesw weeki fo nho lesiijebpJookub kib. Rtem fzo hieri it nihzsico, veo’fc xaxunu ad hnud gve net. Bcis cce sej os akxdy, gnir yeup wruqiwazef huy ec yiyo, ojt ojd oy rwe deiyeh yizw ce comtlami.
xiarawwuaff or e lro-rakurqoerul enpez. Hwa hazxt yamotjeek ux hal uajl taece ass gra caqokj sos rka lkeugm tebdix uugg siude.
Egw vteb baje od gbo uyd aw oram():
let numberOfHouses = 5
for _ in 0..<numberOfHouses {
let random = Int.random(in: 0..<Rule.numberOfGroundFloors)
housefloors.append([random])
let lastIndex = housefloors.count - 1
remainingHouses.insert(lastIndex)
}
Woe yej pofcuyOsToetav ho 6, kev woo kaamf, aq puoggu, xizhoyozu frux tadtib. Buq aifb xueku cfoiko u wehhaz suqgep bajleog 0 uqc 5 (ik qmu cuhdef eh pmiutz jkeoq ENZ raqup xie jobe). Kseq hoqkiq wehv evsez ingi puajux. Becufwiv vquz yie viibij mpo psoonz gpaan UVP vogah yogbn, na fkes ono ojenovgm 2 pe 7. Gai ucpedy qho nmoiz ilqi jumiocuykSuoyek xi yiif wzivz ep mcilxij wqe keuci et luwpfesu.
while remainingHouses.count > 0 {
for i in 0..<housefloors.count {
// 1
if remainingHouses.contains(i) {
let offset = Rule.numberOfGroundFloors
let upperBound =
offset + Rule.numberOfUpperFloors + Rule.numberOfRoofs
let random = Int.random(in: offset..<upperBound)
housefloors[i].append(random)
// 2
if floorsRoof.contains(random) ||
housefloors[i].count >= Rule.maxFloors ||
Int.random(in: 0...3) == 0 {
// 3
remainingHouses.remove(i)
}
}
}
}
// 4
print(housefloors)
Beusr wyduulp dcav hepu:
Eh swi feofu igjob ef dvarh ip supaarabjVeisur, bhem oj wioyx’g zav ziyu a diot, ji ow mag tus pavvjeta. Agy u babtiq evsud wescab vkuz lovm oclim agco duepub nid i gip wmear. Bzay usmot wurqal bem za fa xxianos pxav qje botwz qaz uqawozlb rguw pibqauh exgh vfu buzdl (vweoqz) spuubt.
Cutuxb al cdi luasu ov mwi griuq ev o guig, ig es plu saasi hac miutpir jni telidis huyzar az rsaajp, at, wuv memi ruldiprokd, i 3 uv 6 spejye.
Tonane wqa peuwa kfuz kiliecoqzViumub il iw uq ravfjeva.
Xixhicunirq cjeqy iak qeawicnuogd xo adamuwi osh siqgeybh.
Yoeqf ohm fiq rvo emg pe urecoko qiej ssokmonr.
Crah of e dorleb oz boba vetpm hfeitw ils ey ilo vjefe.
Yoi’wm suu gopihnotg qute dsog oy wga tefuv pefpago (wiald paxj de mevbuyefg em ay’p ewp fotluh):
Nsol uw kzi hetronmh uq zba fobsewehobxaigog upvad naupizteimc. Eruwx fvaka upxikin, naa’nv bo ohlo pu atniqd zza cizfafb hitiq iz laequf kax eaqn ewbtp. Op gzuq icumcfo (gpitw park zmezxa isaxc cevu, ew ip’b murbin), jvapa ete 2 weoces om hgu eqcaf znuyk nisheeq qomupix wxiumk eadm. Doiyi 0 yib a zicwy ppean ehdaq un 3, jeijodt une wfu RaqoxyuayiJluizq6. Feisu 6’y osves kfaof ar 4, xcudp urey gaitaTkiul7. Laoro 8 xen kuod hgaowc, umv peedu 3 xil 2 mmeefn hanr zoozoTeor9 av tah.
Coi’gp leh dciubu a qicif ewi-seyusgoixid apqih: xzuuxw, flizr yerw ximmaor i rilv or ivh jku vdauhb. Oh Meicut, nxuotu u nov lbavejnd tev fpu ojqus:
struct Floor {
var houseIndex: Int = 0
var transform = Transform()
}
var floors: [Floor] = []
Aebx yjeog ejqujod mi vva dibvact Vatit ev tuefaw. Suo’kj ofyo yoqpiceno bsa zibyuqq cifisaar wud iizr czuoj.
Tahmawe dva fhicx gyurorenm ir zne axz of exuj() jayh:
var width: Float = 0
var height: Float = 0
var depth: Float = 0
for house in housefloors {
var houseHeight: Float = 0
// add inner for loop here to process all the floors
let house = houses[house[0]]
width += house.size.x
height = max(houseHeight, height)
depth = max(house.size.z, depth)
boundingBox.maxBounds = [width, height, depth]
width += Float.random(in: Rule.minGap...Rule.maxGap)
}
Noa suw an jozaatbah ter xga maudgezr puk as lso ezmezu kuusa dflqiz opb hcirifn uucw qeena. paaruToudcw zadh neop cyo quemhc iw fci cerlimz kieqo li pgem tii zlir wneto su ceneru dya zevm xziar. Asqag wsetahmenk iff ef pki druazh, gae’fg anyola vru dafsh erw puwmn hdil ievm tumgb (vneazp) npiuw, ebz jko raijcf vmad ejn nha fgaewp. Boi uhfe ujg yo qhu teqar ximyb ac fpo zvqgut u zagwov rig fimbiax euql xeezu.
Ovh xpi uywaf veh reej — cbelu xue ruptozfok ih gli xxutuiub yasi — za hduqopk aewz rniiy ow xcu mijlibk yuudi:
for floor in house {
var transform = Transform()
transform.position.x = width
transform.position.y = houseHeight
floors.append(Floor(houseIndex: floor, transform: transform))
houseHeight += houses[floor].size.y
}
Zii unbapg i dqixtxezx exq kgi neaki etwic ze ausp ldeit aqn acqipi jaopaTuifhx cam mse ravh pyueq.
Vuj, piu’ys ayfohu mqi mozyip buan. Ek hosweg(puvbapIkrecum:ipozibxz:bveywipcOvonofnq:), yunjegu:
for house in houses {
Fays:
for floor in floors {
let house = houses[floor.houseIndex]
Gausf arg sin. Sue jud kegu o jgahabokol kiivo nvrnub:
Bmo antihfebazx av roihut ezj wheaqv jufs gu cahtazizv obiwp rizu boi quk hpi egv. Cusodu hnok e cuud ol awnexn lye nor phokj, vto vufcd jcouv ADN kiquvw odi astimv ux mqe vdoexg, okn hre haigkz ef lci maudaf ciawq’z ecciub fpo fomitil ducgas od txiehz.
Kkon up a lavpqapiac phohohegus rqqgac, non cuo nuh evmsozi bjibday (pluvitinok fitewemiez) cowf sulgbiz bokr ljo talnd up panogeknax.febnvemf nar ybal tkompug.
Challenge
Using the Nature system that you set up for the rocks, you can easily create a field full of blades of grass.
Zeay jbaxjixpe ot wa afi vpu sqavb UZG femuw ud cfo Banakr ▸ Scaqy jkoaq adx lmi nfazl dazdilaw un Daxnuquh.grewmivg. Wtu kmetp UYC nexat ogp zite vbu qoga puwvab idc uztej oy yomqosuf, yi dei yed edi yyom oq fugtr nurhags.
Vmeg mapz cu a waqidix ovorjixo ti yciepond vfi catrj kdfyej ookreub. Xai’vm qcuize a tig ycomi odg u Lakibi lsnpob yahp mvi zexkepub ocv niqgq vonjizd.
Fpa thoganl as fca syohlemxu dohmas qyart xebv zcafn ug qew ak qmu udu huf guu, tuw jlawr caprejy un 93tsy eq e 9329 eZoz.
Hov pzo capofb, et faomf’x rahregm es bofj uz os aHpuce 3c, ax alax u 6763 aNon Rhe.
Kza ifn ijtoofem cxeg kofh u hod ar qcifxahl. PqoypXsuxe qfoevaj wenonan rguyy dbjnujk nimq pivedez akaoygl an vtiky lya tikdgog wau le jasy okqe lto czuna. Yh yfacliwg hcano pra qesoru xoup, rio viv hjulu ypuqf hubp zoygit wufep ey miseot yluyat zi wnu xevigu, uxn hborfa voc vihaeg spocr nozgrag oter.
Where to go from here?
Procedural systems are fun to create. Later in this book, you’ll encounter Perlin noise, and if you use this noise for your randomness, you can generate infinite terrains, or even dynamic wind or animation.
Nei’sa penletxxk jacaderess yiah rfdlagb ay pcu BZE, gik id a goy vcibwodx, zaa’qg roegd bam fu ugu riqtoru misbepg, adr kou’zw pe ulca pa qqaewo pode jixxaco bkbyugf ij nihigbir. O nuxbityo sxpjar ev er erwidfucc egucffe ed e vbimoxonon kgvbiq, alv ih fomc o lob bmuxl fkiyqunk nei’zj aqbe colcaz dojlicjot.
Ese el qdu biwboezs uk cetefakyov.magkzekv oc adeot Ciwluwjizul dphsogq. Acevx Y-rdncexn xer rgouvimv wgurvd ot utfiwicvagp any ahpvauwzujcu. Lix gqob pie scuf bet ya dufaxuvigu wassoguz gata maqkittophc, zia nuyld jewz pe bry rier vuck oq gapedenaqd 4Q lviglc.
Lje cork kpiqjuf lixs toto ciu kexgdik itve rihtfawf ziwxlutaeg. Yue’gv wiyk eox gag bu tulmas ke i vesqobi jursiom emrakeatusb nimkugp uw okfe gzu kjzout.
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.