OGE & Internship Complete!

It is finally the last day of my internship and in less than 3 days I will be back in Toronto.  It was a stellar experience working abroad and a learned a lot about:

  • starting a business in Hong Kong
  • running a small business / startup company
  • wi-fi and gadgets
  • hotel management and the hospitality industry
  • dealing with clients
  • building and designing a website
  • and much more…

Unfortunately my boss had to leave to Shenzhen, China yesterday so he was not here for my last day.  However, yesterday he sat down with me for what seemed like an hour where we talked about his business.

My boss is a veteran of the IT industry working in it since the first computer came out.  He has worked in places like IBM, AT&T and Juniper and he said because of those places he had learned a lot about the IT and Networking industry.  He has several business’ but DNET solution is a company that he is looking to make it with.  He only recently started the business, however he is moving in the right direction and he has a solid business plan.

He spoke a lot about his time at IBM where they trained him and gave him a lot of essential skills that he uses in developing DNET that he would have never gained in school.  This interests me since I have been curious about starting a business sometime in the near future.  He advised me in making sure to work in the industry first if possible to gain a better understanding about how it works and most importantly I can build a network of friends in that industry.  He said his network that he built during his time working was extremely valuable when he was starting his own business.

A great piece of advice he offered me was a few tips for starting a business.  Before you want to start your business you first need to figure out where this business will be in 2-5 years and what you want to do with it.  For example: do you want to create a product and try to get a bigger company to acquire your entire company and sell your IP and move onto the next idea or build your company for several years and aim for an IPO.  He also gave me a lot of advice on how to initially finance and build a proper budget for your business using some tips he learned at IBM.

Overall, I have thoroughly enjoyed my time here and I have made several contacts here in Hong Kong that I intend to keep in touch with.

Web Dev Update

So the website was completed and it is now live and available at : http://www.dnetsolution.com/

Earlier this week I said I would post at post mortem of sorts about my progress with developing this website since it was one of my main and largest tasks to do over this summer in comparison to the other things I have done.

I shall go into detail of each page individually:

Home Page

Home Page
Home Page

The old home page had a lot of unneeded info on it and it made it a bit difficult to look at to understand exactly what DNET Solution is.  For my homepage I decided to strip it away and follow a really minimal approach to everything.

Home Page 2
Home Page 2

For the rest of the homepage I highlighted the main product which was the Supermedia where DNet makes its main profit from.  For the footer, I also kept it simple by having a simple and sweet ‘about us’ statement and a few outgoing links to other DNet portals.

About Page

About Page
About Page

The about page defaulted to the Contact us subpage because I felt that it had the most important information to help a client reach DNET and a form to send us any email really quickly so the person would not have to load up their email.

Our Story is a small subpage with a bit of  a company blurb, nothing too fancy.  Gallery has a few pictures of DNet products and a nice lightbox display that fades the entire screen when you click each image as it opens up in full screen.

Also note that the subpages do not need to be loaded separately.  They were preloaded to make browsing faster since they all technically exist on the same page.

Product Page

Product Page
Product Page

This page was really hard to do since the old version was super clunky.  I decided to stick with my minimal approach and remove a lot of hindrances and highlight all the products at the top with a collage, then have each product section at the bottom in an accordion that the user can click and interact with.  Rather then loading another page for each, I just placed each product neatly hidden in each button.

Product page, Accordian
Product page, Accordion

The accordion works really nicely to hide each product neatly into the buttons and it allows for a very fast way to view each product. When you click on the other “Specification” button you are able to download the a data sheet for each product.

Download Page

Download Page
Download Page

I didn’t want to keep this page, But I ended up having to since my boss was adamant about having a page to send his clients to, so they may download important documents about our products.

News Page

NEWS Page
NEWS Page

I made this page entirely out of embedding a tumblr blog that I set up so that my boss did not have to keep updating the website when he wanted to make a change to the news section.  It feeds info from a DNET tumblr page and it auto updates with every new post.  It is a lot easier to use tumblr then to update a website, which is why I chose to do it this way.

Quote Page

Quote Page
Quote Page

Another really simple page that just allows a user to enter data into a form so that an email will be sent to the sales team to approach them.

 

Aside from that that is pretty much the website I built in about a week and a half.

Singapore Was Awesome!

Guten Tag!

This past weekend a few of us were able to get Friday off from work and we went to both Malaysia and Singapore!  We visited Kuala Lumpur and went up to the Twin Towers and got an awesome view of the city from a variety of angles.  Singapore was amazing, definitely my new favourite city.  We only had a day and an half in Singapore, but we packed it with a lot of activities.  From riding a Segway on the beach, going on a duck boat tour, a nighttime safari and so much more.

Aside from that I have been put in charge of revamping our current website and making it look alot better than it currently does.  My other job is to make it easy enough to update if you have no experience in web programming.  I have a couple ideas of what I want to do with the website, but I have no clue on how I will implement it for now.  I personally have little to no experience in web programming so this should be an exciting and challenging task.  It works out well since I have to make my own portfolio website later on this year before I enter the job market.

I will likely try to do something using HTML 5 as I have seen some really cool web pages set up using that. I have also been looking into WebGL and JQUERY for special effects and to do things like parallax scrolling and what not.  The more time I spend researching on web development the more I get excited at all the possibilities.  Normally in game programming, when you start its difficult to show people your work easily.  Especially if you are still very new to programming due to all the technicalities involved in making an .exe run on several machines.  However with web pages you can have a website up and running in an hour, a day if you are new to web development.

Week 2!

SALUT!

This was a shorter work week than normal because we had the Monday July 1st as a statutory holiday.  Not only is it Canada day, but it is also the Hong Kong SAR day.  The global edge crew including myself spent the weekend volunteering at the Canada Day celebration by the Canadian Chamber of Commerce.  I was pretty busy and I regret not taking enough photos, if I manage to find some that others took I shall post them here.

Aside from that this week has been a bit slower than last week.  On Wednesday I had a chance to visit ASAP Creative & Communication.  They are interested in partnering with DNET Solution to hopefully sell our hardware in Taiwan.  I was glad my boss took me to the meeting so I could see first hand how business was done in Hong Kong.  It turns out that my boss knew the Director for over 20 years.  He was telling me that in Hong Kong relationships are very important, it’s mostly how business is run.  My Boss’s goal was to get the Director to showcase the product to her husband who has a company in Taiwan who would eventually tell his colleagues and through that their business would spread and they would be able to sell the hardware.  Most of the meeting was in Cantonese so I couldn’t gauge anything but their facial expressions and reactions.

The other main task this week is trying to integrate another companies tech with our multimedia panel.  Essentially that tech is an Android based media server that would be the perfect harmony to our companies hardware.  Our main job is to figure out if the tech can either fit in and be integrated into our hardware to offer the full hardware and software experience to the customer.  Otherwise if we can’t integrate it we have to add it on as a package deal when we try and sell our hardware to our clients.  I personally hope that we can integrate it because our current software is pretty primitive and from the demonstration I saw last week, I like their version a lot more.

Mainly because it is developed using the Android 4.2 OS, as a software (game) developer I can see myself trying to convince my boss that games can be developed specifically for that system.

On July 8th, I had the privilege of visiting a press and industry launch of “Tomorrow’s Guestroom” at the Icon Hotel and the Polytech Uni. DNET won the award so myself along with almost everyone in our office went to the press conference to showcase our hardware to both the press and industry.  It was really exciting to meet several people from the hospitality industry and it was a bit awkward since almost everyone there was 2-3 times my age.  It was great to see how fast paced things in Asia are.  I never knew how slow business was in Canada until I came here.  I guess it really helps that China is so close that we don’t have to wait long to hear from manufacturers.  In Canada since the distribution channels are mostly further apart from one another, I guess it slows down business.  It is really amazing that a small company can manufacture hardware and prototype so fast!

 

Thats all for now!

End of Week 1

Aloha!

I finished my first week of work here at DNET and it was a really interesting week.  I can safely say that I have not had two days at work where I did the same thing.

I had some downtime at work and I didn’t exactly want to sit around and browse the internet and I was a bit lazy to work on my personal programming projects. Since I am more then familiar with the Adobe creative suite I offered my Photoshop skills to them and within the hour I was working with Illustrator editing some of their technical documentation.  Granted  had little to no Illustrator experience before, but I lot of what I knew from Photoshop helped me out.  My colleagues seemed to be pleased and I felt less useless :D.

Aside from the office work, on Thursday I had the opportunity to head to the Polytech University’s School of Hotel Tourism and Management to set up some tech.  DNET entered a competition based upon building tech for “tomorrow’s guest room”.   Several other companies also applied but DNET managed to win with their DN-7000 model that I talked about in my last post.  So I spent some time during the week making sure I could install and debug it so that it worked when we had to install them in 3 model guestrooms.

Panorama from the hotel room

There were several categories in the competition however I didn’t get time to see them all since we managed to install and demonstrate all our devices before everyone else came in.  I saw some other people installing a VOIP operated system that worked with the display in the room to help the user use the hotel’s services while also being able to reserve tables and order online.  The menu of the restaurant’s would show up on the TV while a map of the seating arrangements would also show up on screen.

Oh yeah the hotel even had self cleaning toilets! They had a button that would… clean your behind for you also. Haha.  I was more amazed by this then a grown man should have been.  There were a couple other buttons on it as well, but they all made to many sounds when I pressed them and I didn’t want the people outside to wonder what I was doing in there.

THE FUTURE!

I was really impressed by Hong Kong’s use of technology.  Especially in the science park! Every place I went to was working with the latest software updates (Android 4.2, Windows 8 and whatever version of iOS and OS).  I am used to working for larger North American corporations who find it more cost effective to deploy older software that they still have licenses for while not updating to the latest version.  I can understand why they do that: they have other systems that have been tested and depend on those pieces of software and the workforce is already trained with that version. IE: if it ain’t broke, don’t fix it.  I really don’t like that mentality though.  In the Science park there is a lot of innovation and collaboration going on that is possible with never software versions that allow them to do some really futuristic stuff.

More on that part later!

Next time I’ll likely talk about the “google glass” augmented reality talk I went to on Friday.  Oh and since July 1st is also Hong Kong SAR day, we get the day of work! But the global edge crew will be volunteering at LKF street in this Canada Day festival.  There is so much going on and I am loving it!

LATER!

Component Based Engine Architecture

This year we have had a lot of discussion going on about how we would like to go about building our game engine.  With about a week and a month left before its due, most of us have decided to build it using a component based engine architecture.

Basics

This post was helpful for a good 5 min intro to the topic.  Here is pretty much a summary of it:

Generic Engine Architecture

The base concept is rather than having a giant monolithic  class based game structure (above), we want to inherit from a base class that gives everything full functionality.  Imagine the base architecture of Unity.  Everything starts of as a GameObject.  You can add scripts, models, and other functionality to it by dragging items onto it.

The problem with the monolithic class structure is that classes end up having the same functionality and in return things get really messy.  Most games get around this by making a really large base class with lots of functionality while having the subclasses turn functionality on and off.  This becomes a problem when adding new features because items have to be able to coexist with one another.  Since we pretty much add functionality to our game engine the entire year, adding functionality has to be really simple and we don’t want to waste the precious time we have taking one step forward and two steps back.

Say we have three main game items in a component system: a Player, Enemy and Item.

  • Player
    • Spatial
    • Rendering
    • KeyboardMover
    • Health
  • Enemy
    • Spatial
    • Renderer
    • AIMover
    • Health
  • Item
    • Spatial
    • Renderer
    • PhysicsMover

Spatial Component – Allowing it to exist in the world
Renderer Component – Allow objects to be drawn into the screen
Movement Component – We may want to control the player through a keyboard system, enemy through AI and the Item through world physics
Misc Components – We may want some objects behaving different from one another.  Like giving some objects health.

Our component class would look something like

  • Component
    • Spatial
    • Renderer
    • Health
    • Mover
      • KeyboardMover
      • AIMover
      • PhysicsMover

Summary

We are currently working on some sort of implementation of this system.  We have split up into 2-3 engine programmers and 1 person working on getting animation to work using a skeletal animation system.  That latter job will most likely be mine, and it shall be quite the interesting job to take on!  I have already done some research with the mesh skinning shader and we have a rigged character to test with.  All thats left is to make a demo that will work.

References:

[1] – https://github.com/shrt/ComponentKit
[
2] – https://github.com/PushButtonLabs/PushButtonEngine
[
3] – http://stackoverflow.com/questions/1901251/component-based-game-engine-design

Homework Questions!

Its been a while since I have posted about anything to do with this class.  This is because of the shotgun of work being blown my way recently. 3rd year game dev = hardest year yet.  Its hard in the sense that there are not enough hours in a day that allow me to accomplish everything required of me.  So its gotten to the point where some work gets prioritized over others and the rest falls through the cracks.

Ogre

Anyways moving onto Ogre.  Its an amazing engine from what I have seen of it.  The only issue is that it requires a higher level of programming skill to debug.  Last year the errors were solvable through simple debugging methods (stepping through, print statements, etc.).  This year you really have to have a good handle of how your code gets compiled and what it is asking the engine to do.  Which involves using the call stack.  I don’t really know how to use the call stack that well, so this is an EXCELLENT learning opportunity.

Homework Questions

So the easy question deadline got pushed to Monday.  I managed to finish 4 easy questions (1-2. 4-5).

Easy_1 was easy.  The key was understanding how Ogre worked.  Create a Scene node pointer for your entity to attach to.  Create a entity pointer that you attach to a prefab cube and all thats left are the two lights.

Easy_2 was a bit more work.  This question was mostly about understanding the hierarchy of the parent-child relationship of scene nodes.  We know that we have to create 10 separate cubes for the robot arm.  Right away you should think about 10 scene nodes and 1 entities.  But it would be stupid to make them all children of the scene manager.  So you would just make them all children of whatever entity they would be rotating around.  The only problem about only having 10 scene nodes is that the rotations look fairly wonkey.  You need to have rotation joints halfway in between each entity.

Easy_4 was probably the most ‘difficult’.  Since it involved dealing with .material files.  They are still simple however.  Once you figure out how to edit the resource.cfg file to load resources into your game, the next step is the .material file.  Its pretty much a plaintext structure that is easily readable. You just need like 5 lines of code to create your textured material for your ingame object.

scene_blend alpha_blend
lighting off

This goes after pass.  scene_blend does something with the alpha values, and I had to turn the lighting off because I was having an issue with the object turning black for some odd reason (Thanks Kevin! @Iceninja77).

texture leaf.png
filtering trilinear
colour_op_ex source1

This goes after texture_unit.  This pretty much textures your object.

The rest of the code involves making 4 planes rotated at 90, 180, 270 increments textured to look like an imposter and calling the createBillboardSet command to create your billboard.

Easy_5 was probably the one I finished the fastest.  TwoLoc has an interesting dynamic texture class and a base example called BlitTexture something that is a huge help for this.  If you use this as your base project and solve a small error you are worthy to use it!  There is a function randCol(x,y) that can access the data of a pixel on the plane/billboard that you would like your texture to be put on.  You can modify the RGBA values given a row and column point.  So be creative and make something cool!  I was lazy so i just made a few designs using a for loop.  Some people made mini-games, drawing tools and even an etch-sketch!

 

Shaders 103 – Lighting

Hello!

By now you should know what shaders are, and how they work.  You should also know how to integrate them into your code.  Since I have spent a lot of time putting lighting and what not into our game, I have become a bit of an expert with it.  So today I am going to go over how to do some fragment based lighting.

Changes from OpenGL and Movement Matrices

While I didn’t do the lighting in our game last semester, you can’t take old OpenGL code with lighting and a whole bunch of glTranslate and glRotate calls and expect it to work.

The first thing we are going to have to do is build a whole bunch of matrix functions that build a perspective, look at, rotation, translation, multiplication, invert and transform matrices.  When you download the CG API some of the sample code does have these functions build in, but they expect you to know what they do and how they work.

Here is how we will now be rendering objects instead of using the ‘gl’ draw calls.

/*** Render brass solid sphere ***/

setBrassMaterial();

/* modelView = rotateMatrix * translateMatrix */
makeRotateMatrix(70, 1, 1, 1, rotateMatrix);
makeTranslateMatrix(2, 0, 0, translateMatrix);
multMatrix(modelMatrix, translateMatrix, rotateMatrix);

/* invModelMatrix = inverse(modelMatrix) */
invertMatrix(invModelMatrix, modelMatrix);

/* Transform world-space eye and light positions to sphere's object-space. */
transform(objSpaceEyePosition, invModelMatrix, eyePosition);
cgSetParameter3fv(myCgFragmentParam_eyePosition, objSpaceEyePosition);
transform(objSpaceLightPosition, invModelMatrix, lightPosition);
cgSetParameter3fv(myCgFragmentParam_lightPosition, objSpaceLightPosition);

/* modelViewMatrix = viewMatrix * modelMatrix */
multMatrix(modelViewMatrix, viewMatrix, modelMatrix);

/* modelViewProj = projectionMatrix * modelViewMatrix */
multMatrix(modelViewProjMatrix, myProjectionMatrix, modelViewMatrix);

/* Set matrix parameter with row-major matrix. */
cgSetMatrixParameterfr(myCgVertexParam_modelViewProj, modelViewProjMatrix);
cgUpdateProgramParameters(myCgVertexProgram);
cgUpdateProgramParameters(myCgFragmentProgram);
glutSolidSphere(2.0, 40, 40);

Now this may seem like a lot, but it is necessary for working with shaders.

The beginning where we call the setBrassMaterial() function is where we set the objects parameters.   We will get to that a bit later.  For now think of it as your glColor call.

The first part where we create the matrix using a simple rotation and translation matrix is fairly simple.  You would just pass on those parameters as if you were doing a normal glRotate or glTranslate call.  You can replace these with variables so you can move these.  For now this object is stationary so we do not need it to move

However the next part is where you  multiply them to get your modelMatrix and invert it to get your final matrix.  This is so we can calculate lighting with respect to the sphere object.  We then update our eye and light Cg parameters that we will see later.

The last bit of code creates the modelView matrix and actually draws the sphere.

Using Materials

The book uses this method of creating functions that set the emissive, ambient, diffuse, specular and shininess values.  Like this:

static void setBrassMaterial(void)
{

const float brassEmissive[3] = {0.0, 0.0, 0.0},
brassAmbient[3] = {0.33, 0.22, 0.03},
brassDiffuse[3] = {0.78, 0.57, 0.11},
brassSpecular[3] = {0.99, 0.91, 0.81},
brassShininess = 27.8;

cgSetParameter3fv(myCgFragmentParam_Ke, brassEmissive);
checkForCgError("setting Ke parameter");
cgSetParameter3fv(myCgFragmentParam_Ka, brassAmbient);
checkForCgError("setting Ka parameter");
cgSetParameter3fv(myCgFragmentParam_Kd, brassDiffuse);
checkForCgError("setting Kd parameter");
cgSetParameter3fv(myCgFragmentParam_Ks, brassSpecular);
checkForCgError("setting Ks parameter");
cgSetParameter1f(myCgFragmentParam_shininess, brassShininess);
checkForCgError("setting shininess parameter");

}

So this function just sets the colour of each of the light parameters that we want.  Using this we can make several material functions for different objects and control them independently in whatever way we want.  You can make a character, enemy and level material.  Right before you load your character, you can make their lighting bright so that they stand out.  For enemies, you can give them a bit of a red highlight to show the player that they pose a threat.

What to Initialise

Now we are in our initCg() function let us break it down into a vertex and fragment area.

Vertex Initialisation

myCgVertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);
cgGLSetOptimalOptions(myCgVertexProfile);
checkForCgError("selecting vertex profile");

myCgVertexProgram =
cgCreateProgramFromFile(
myCgContext,              /* Cg runtime context */
CG_SOURCE,                /* Program in human-readable form */
myVertexProgramFileName,  /* Name of file containing program */
myCgVertexProfile,        /* Profile: OpenGL ARB vertex program */
myVertexProgramName,      /* Entry function name */
NULL);                    /* No extra compiler options */
checkForCgError("creating vertex program from file");
cgGLLoadProgram(myCgVertexProgram);
checkForCgError("loading vertex program");

#define GET_VERTEX_PARAM(name) \
myCgVertexParam_##name = \
cgGetNamedParameter(myCgVertexProgram, #name); \
checkForCgError("could not get " #name " parameter");

GET_VERTEX_PARAM(modelViewProj);

This is a fairly simple vertex initialisation.  The main point is to see that we are passing the modelViewProj matrix.  If you go back up to our draw code you can see where we update myCgVertexParam_modelViewProj parameter.

Vertex Shader Code

void v_fragmentLighting(
float4 position : POSITION,
float3 normal   : NORMAL,

out float4 oPosition : POSITION,
out float3 objectPos : TEXCOORD0,
out float3 oNormal   : TEXCOORD1,

uniform float4x4 modelViewProj)
{
oPosition = mul(modelViewProj, position);
objectPos = position.xyz;
oNormal = normal;
}

You can still see that this vertex shader is still simple.  We take our model view matrix and multiply that by our position and output both our position and our object position.

Fragment Initialisation

#define GET_FRAGMENT_PARAM(name) \
myCgFragmentParam_##name = \
cgGetNamedParameter(myCgFragmentProgram, #name); \
checkForCgError("could not get " #name " parameter");

GET_FRAGMENT_PARAM(globalAmbient);
GET_FRAGMENT_PARAM(lightColor);
GET_FRAGMENT_PARAM(lightPosition);
GET_FRAGMENT_PARAM(eyePosition);
GET_FRAGMENT_PARAM(Ke);
GET_FRAGMENT_PARAM(Ka);
GET_FRAGMENT_PARAM(Kd);
GET_FRAGMENT_PARAM(Ks);
GET_FRAGMENT_PARAM(shininess);

/* Set light source color parameters once. */
cgSetParameter3fv(myCgFragmentParam_globalAmbient, myGlobalAmbient);
cgSetParameter3fv(myCgFragmentParam_lightColor, myLightColor);

This not the full code for the initialisation.  This smidgen of code contains the new parameters that we will be passing into our fragment shader to compute our lighting.

Fragment Shader Code

void basicLight(
float4 position : TEXCOORD0,
float3 normal   : TEXCOORD1,

out float4 color : COLOR,

uniform float3 globalAmbient,
uniform float3 lightColor,
uniform float3 lightPosition,
uniform float3 eyePosition,
uniform float3 Ke,
uniform float3 Ka,
uniform float3 Kd,
uniform float3 Ks,
uniform float shininess)
{
float3 P = position.xyz;
float3 N = normalize(normal);

// Compute emissive term
float3 emissive = Ke;

// Compute ambient term
float3 ambient = Ka * globalAmbient;

// Compute the diffuse term
float3 L = normalize(lightPosition - P);
float diffuseLight = max(dot(L, N), 0);
float3 diffuse = Kd * lightColor * diffuseLight;

// Compute the specular term
float3 V = normalize(eyePosition - P);
float3 H = normalize(L + V);
float specularLight = pow(max(dot(H, N), 0), shininess);
if (diffuseLight <= 0) specularLight = 0;
float3 specular = Ks * lightColor * specularLight;

color.xyz = emissive + ambient + diffuse + specular;
color.w = 1;
}

This code takes in our parameters that we pass in our C++ code to compute emissive, ambient, diffuse and specular lighting.  Emissive and ambient are fairly easy to compute, however diffuse and specular require some more work.

Emissive Light

Emissive is the light that is emitted or given off by a surface.  This can be used to stimulate glowing
Equation: emissive = Ke
Ke is the materials emissive color

Ambient Light

Ambient or ambience is light that has bounced around from different objects.  This can be used to make your environments better.  You can have a grey ambient for smoggy cities or a nice bright yellow ambient for forests and nature environments.
Equation: ambient = Ka * globalAmbient
Ka is the material’s ambient reflectance
globalAmbient is the color of the incoming ambient light

Diffuse Light 1

Diffuse light is reflected off a surface equally in all directions.  Even if an object has small nooks and crannies, the light will bounce of its rough texture
Equation: diffuse = Kd * lightColor * max(N dot L, 0)
Kd is the material’s diffuse color
lightColor is the color of the incoming diffuse light
N is the normalised surface normal
L is the normalised vector toward the light source
P is the point being shaded

Diffuse Lighting 2
Specular Light 1

Specular lighting is light scattered from a surface around the mirror direction.  It is only seen on very shiny and metallic materials.  Unlike the above types of light, Specular depends on where the viewer is looking at for it to work.  It also takes into account how shiny a surface is.
Equation:  specular = Ks * lightColor * facing * (max(N dot H, 0))^shininess
Kd is the materials specular color
lightColor is the color of the incoming specular light
N is the normalized surface normal
V is the normalized vector toward the viewpoint
L is the normalized vector  toward the light source
H is the normalized vector that is halfway between V and L
P is the point being shaded
facing is 1 is N dot L is greater then 0 and 0 otherwise

Specular Light 2

Then you add all the lights together and that is lighting in a nutshell.

Fragment Lighting

Thank your for reading,
– Moose