Assignment 4: Gleaming the Cube
Due by: Friday, November 17, 2023 at 11:59pm
Assignment 4 is a programming assignment intended to focus on writing shader code.

Specification
The goal of this assignment is to create 80 cubes that will bounce around a scene. There will also be 3 point lights that also move around randomly and are modeled as spheres. There will also be a skybox. Lighting the cubes should be done with shader code, not BasicEffect
. You should also implement basic camera controls as in Assignment 3.
Cubes
You may base the opaque cubes on some of the cube code from the previous assignment, or you may use the cube model given below. If you define the cube yourself, you will probably need to define it with VertexPositionNormalTexture
vertices, even though you will not need textures. Each cube should have a different random color. You can generate a random color by creating a Random
object and calling its nextDouble()
method. One of the Color
constructors takes 3 float
values between 0 and 1 (exactly the range that nextDouble()
provides).
You will only need a single vertex buffer or model no matter how many cubes you have. Nevertheless, you'll need to keep track of where each cube is so that you can change the world transform when drawing it. Likewise, you'll need to keep track of its velocity so that you can update its position. Finally, you'll need to store the color for each cube as well.
I used the following data structure, but you may use your own.
struct Cube { public Vector3 position; public float scale; public Color color; public Vector3 velocity; }
To draw each cube, set the world transform to take into account the scale and position (translation) of each cube. Next, set a color property on your Effect
object to the color of the cube so that you can retrieve it inside your shader code. You will need to convert the color to a Vector3
or a Vector4
, using the appropriate method. Finally, draw the model or the vertex buffer.
Lights
You will also need to keep track of the location of the lights. I use a data structure similar to Cube
. Naturally, you may use a different one if you prefer. You should only have 3 lights, but an array is still a good idea. You can generate the light colors in a way similar to the way you created cube colors, but each component of the light colors should be between 0.5 and 1 (instead of 0 and 1) since a dark light isn't very useful.
struct Light { public Vector3 position; public Color color; public Vector3 velocity; }
Use the sphere model below to create single 3D sphere object. You can draw it three times in different positions to show where the three lights are.
Although you're using shaders for the cubes, you may use a BasicEffect
for the light spheres. Turn on default lighting, but then add an emissive component with the color of each light. Emissive lighting adds a glowing effect, and will make your lights take on their color wherever they would otherwise be shadowed. It doesn't exactly match what a light would look like, but it's easy to implement. I set the specular color to the zero vector, and I set the diffuse and specular color of the first directional light to the zero vector as well.
All of my lights have a radius of 10 units. The lights move as well, using bounce code that is virtually identical to the cube bounce code. Annoyingly, the sphere model has a radius of 3.0 units. Thus, you will need to scale it by 10/3f to have the lights appear to have a radius of 10 when you draw them.
Skybox
Now that you've done a skybox the painful way, we can do it the easy way. The shader code for a skybox is almost trivial. It doesn't use any lighting, and even the texture is easy if you have a specially created cube texture.
Use the skybox shader code from class to render a skybox. The following two cube textures can be used for such a purpose. Feel free to use either one. When you add one to your content pipeline, make sure you set the PremultiplyAlpha value to False
. You only need to set the three transforms and the camera position to render a skybox using an appropriate size cube. Note that you will need to turn culling off (or to cull clockwise faces) to render a skybox.
Shader Code
The shader code for drawing the boxes should be very similar to the point light shader discussed in class. It's probably easiest for you to have an array of float3
values for the positions of the three lights and an array of float4
values for the colors of the three lights. Declaring these is done in C-style rather than Java-style.
float3 LightPosition[3]; float4 LightColor[3];
You'll need to set appropriate parameters in your C# code so that your shader gets the right information. For arrays like these, there's a version of the SetValue()
method that takes a whole array.
Make sure that you set the object color, the transforms (including the world inverse transform), the camera position, and the light colors and positions. The process of shading with the three lights is very similar to the point light example in class except that you are adding the contributions of three lights instead of one. Lighting is additive, so all you need to do is add up the lighting effects and saturate them. My example also uses a light radius of 250 units.
Making all of this code work takes a relatively large number of shader registers. Many of the examples we've been using in class have compiled code to Shader Model 2. I needed the larger number of registers provided by Shader Model 4. The difference is small: the instructions you give to compile the techniques. My final technique looked like the following.
technique PointLight { pass Pass1 { VertexShader = compile vs_4_0 VertexShaderFunction(); PixelShader = compile ps_4_0 PixelShaderFunction(); } }
Unfortunately, using Shader Model 4 also requires a change inside MonoGame. You need to set your GraphicsDevice
graphics profile to HiDef. You can do this as follows, just after you create it in your game constructor.
graphicsDeviceManager.GraphicsProfile = GraphicsProfile.HiDef;
Position and Movement
Each of the cubes (except for the skybox) should have a random size, random position, and random velocity (random direction and magnitude). Initializing these values is not difficult.
- Scale and shift the output of the
nextDouble()
method to make values for x, y, and z between -90 and 90. - Scale and shift the output of the
nextDouble()
method to make velocities for x, y, and z between -1 and 1. - Normalize the velocity vector.
- Multiply the velocity vector by a random number between 5 and 10.
- Make a random scale between 2 and 10 for the box size. (Assuming a unit cube, 2 gives edge lengths of 4 and 10 gives edge lengths of 20.)
The lights should also have a random position and velocity but a fixed radius of 10 units.
Before each render, use gameTime.ElapsedGameTime.Milliseconds/1000f
to calculate the seconds that have passed since the previous render, and update the objects using that time. They can be updated by adding their velocities times the time that has passed to their positions. If they hit a wall, the appropriate component of their velocity should be inverted, e.g. if a box hits the top, its y velocity should be multiplied by -1. After such an impact, you may wish to change the location of the cube so that it is no longer intersecting the wall. Note: Cube locations are at their centers, but their sides hit the walls!
Sample Implementation
You can download a sample implementation here to get a feel for how my code works. You don't need to match it exactly.
Hints
- Get your camera and skybox working before everything else.
- Get cubes rendering with dummy lighting (or lighting turned off) and moving around before starting on the lights.
- Work incrementally. Get something working and then move on to the next thing. It's impossible to get everything right the first time, and it's hard to know what's going wrong if you don't go a step at a time.
- Start early and ask for help when you need it.
Turn In
Zip up your entire project and solution. Upload that zip file to Blackboard. All work must be submitted by Friday, November 17, 2023 at 11:59pm. Grace days are not available for assignments.
All work must be done individually. You may discuss general concepts with your classmates, but it is never acceptable for you to look at someone else's code. Please refer to the course policies if you have any questions about academic integrity. If you have trouble with the assignment, I am always available for assistance.
Grading
Your grade will be determined by the following categories.
Category | Weight |
---|---|
Cube creation and movement | 30% |
Skybox | 10% |
Camera movement | 10% |
Shader rendering | 30% |
Light movement and sphere rendering | 20% |