Låt oss bygga en 3D-grafikmotor Dynamisk belysning

Hej! Det här är den sista artikeln i vår serie om grunderna för 3D-programvaru grafiksystem, och den här gången ser vi på dynamisk belysning! Innan du blir för upphetsad eller orolig för hur svårt en uppgift som dynamisk belysning kan vara, slappna av. Vi kommer bara att täcka den mest grundläggande formen av dynamisk belysning för tillfället (eftersom hela ämnet är enormt och andra har lyckats fylla hela böckerna på konceptet).

Specifikt kommer vi att bygga en enda punkt, singelfärg, dynamiskt belysningssystem med fast belysningsradie som gör det möjligt för oss att börja dabbling i ämnet. Innan vi går in på det, låt oss ta en titt på några av våra tidigare färdiga klasser som vi ska använda.


Recap

Vår dynamiska belysning kommer att hanteras punkt-för-punkt som de är prepped för att dras till skärmen. Det innebär att vi kommer att göra omfattande användning av två av våra tidigare klasser: The Punkt klass och Kamera klass. Så här ser de ut:

Point Class Variables: num tuple [3]; // (x, y, z) Operatörer: Peka AddVectorToPoint (Vector); Punkt SubtraherVectorFromPoint (Vector); Vector SubtractPointFromPoint (Punkt); Null SetPointToPoint (Point); Funktioner: drawPoint; // rita en punkt i sin position tuple Kameraklass Vars: int minX, maxX; int minY, maxY; int minZ, maxZ; array objectsInWorld; // en grupp av alla existerande objekt Funktioner: null drawScene (); // drar alla nödvändiga objekt till skärmen

Med hjälp av denna information, låt oss sätta ihop vår grundläggande ljusklass.


Vårt ljusklass

Ett exempel på dynamisk belysning. Källa: http://redeyeware.zxq.net

Vår belysningsklass behöver några saker för att göra den funktionell - nämligen en position, en färg, en typ och en intensitet (eller ljusradie).

Som jag sa tidigare kommer vår belysning att beräknas på en punkt för punkt innan varje punkt ritas. Förhöjningarna av detta är att det är enklare för hur vi har vår motor organiserad, och att det också skiftar mer av vårt programs belastning till systemets processor. Om du skulle förberäkna din belysning skulle den istället flytta lasten till systemets hårddisk och beroende på hur din motor är utformad kan den vara enklare eller mer komplex.

Med allt detta i åtanke kan vår klass se ut så här:

Belysningsklass Variabler: num position [3]; // (x, y, z) num röd = 255; // vad som ska läggas till en poängs r-värde vid full intensitet num green = 255; // vad som ska läggas till en punkts g-värde vid full intensitet num blue = 255; // vad som ska läggas till ett punkts b-värde vid full intensitetsträng lightType = "point"; // typen av belysningsnummerradius = 50; // ljusets radie i pixlar

För närvarande kommer vi att lämna alla sina värden inskriven för enkelhet, men om du upptäcker att du vill utvidga belysningsklassens funktionalitet, kan du enkelt få varje värde att ändras genom antingen funktioner, en konstruktör etc.

All den viktiga matematiken för vår dynamiska belysning kommer dock att hända inom vår kamera klass, så låt oss ta en titt på det.


Lights? Kamera? Motor.

Ett annat exempel på dynamisk belysning. Källa: http://blog.illuminatelabs.com/2010/04/hdr-and-baked-lighting.html

Vi ska lägga till en ny variabel till vår kameraklass nu, som vi ska använda för att lagra vår ljuskälla. För tillfället lagrar variabeln bara en belysningsinstans, men den kan lätt uppskalas för att möjliggöra fler ljuspunkter.

Strax innan en punkt ritas, kommer vi att kolla för att se om det ligger inom ljusets radie. När vi väl vet att det ligger inom ljusets område måste vi hitta avståndet mellan punkten och ljusets position, och då måste vi justera punktens färg baserat på det avståndet.

Med allt detta i åtanke kan vi lägga till kod som liknar detta till vår kamera drawScene () fungera:

om (currentPoint.x> = (light.x - light.radius)) // om punkten ligger inom ljusets vänstra gränsen om (currentPoint.x <= (light.x + light.radius)) //if the point is within the light's right bound if(currentPoint.y >= (light.y - light.radius)) // om punkten ligger inom ljusets övre gräns om (currentPoint.y <= (light.y + light.radius)) //if the point is within the light's bottom bound //calculate distance between point and light (distance) //calculate percentage of light to apply (percentage = distance / radius) point.red += (light.red * percentage); //add the light's red, scaled to distance point.green += (light.green * percentage); //add the light's green, scaled to distance point.blue += (light.blue * percentage); //add the light's blue, scaled to distance    

Som du kan se är vår metod för att justera en punkts färg för närvarande inte så avancerad (det finns många som är om du vill använda dem istället). Beroende på punktens avstånd från ljusets mitt lyser vi dess färg med en procentandel. Vår belysningsmetod svarar inte alls för skuggning för närvarande, så områden som ligger långt ifrån ljuset blir inte mörkare och objekten kommer inte att blockera ljus från andra föremål som kan ligga bakom dem.


Följ ljuset

För vårt program den här gången kommer vi att ha några före gjorda former på skärmen. Dessa kan vara allt du vill, men för vårt exempel kommer jag bara att använda några enkla punkter. Nu när en användare klickar någonstans på skärmen kommer vi att skapa en ljuskälla vid den tiden. Nästa gång de klickar kommer vi att flytta punkten till den positionen och så vidare. Detta gör det möjligt för oss att se vår dynamiska belysning i åtgärd!

Så här ser ditt program ut:

main // setup för ditt favoritgrafik API här // inställning för tangentbordsinmatning (kanske inte krävs) här var kamera = ny kamera (); // skapa en instans av kameraklassen // ställa in kamerans vy space camera.minX = 0; camera.maxX = screenWidth; camera.minY = 0; camera.maxY = screenHeight; camera.minZ = 0; camera.maxZ = 100; // rita initiala objekt och sätt dem in i kamerans utrymme medan (tangent! = esc) om (mouseClick) om (förstklicka) // skapa det ursprungliga ljusobjektet vid musposition annat // ändra ljusobjektets position  camera.drawScene (); 

Nu ska du kunna uppleva din dynamiska belysning i åtgärd, och förhoppningsvis se hur mycket djup det kan lägga till i en spelmotor. Kolla in min demo här - använd en och S nycklar till skala, och Y, H, U, J, jag och K tangenterna för att rotera.


Slutsats

Medan vår dynamiska belysning är enkel kan det verkligen förbättras om du känner dig benägen att göra det. Vissa saker som skulle vara ganska coola, och också ganska enkla att lägga till är:

  • justerbar ljusradie
  • justerbar belysning färg (istället för att tända en färg jämnt, lätta den med en bråkdel av sin inställda färg)
  • blockera ljuset från att resa förbi fasta föremål
  • hantera flera ljuspunkter
  • lägg till en skugga på alla punkter utanför ljusets radie
  • Experimentera med andra former av ljus (riktning, kon, etc.)

Tack för att du kollade in vår serie, Låt oss bygga en 3D-spelmotor. Det har varit bra att skriva dessa artiklar, och kom ihåg att om du har några frågor, var snäll och fråga dem i kommentarerna nedan!

Du kan också få extra hjälp på Envato Studio, där du kan hitta massor av fantastiska 3D Design & Modeling-tjänster till överkomliga priser. 

3D Design & Modeling tjänster på Envato Studio