Dartmouth College - COSC 77 – Computer Graphics
Phong-Shaded Solar System Animation
Create an animation of a simplified solar system (e.g., Sun, Earth, Moon) with Phong shading using OpenGL (or a modern graphics API wrapper like LWJGL for Java, or WebGL for JavaScript if specified). The planets should orbit and rotate. Implement vertex and fragment shaders for lighting. Allow basic keyboard camera controls.
Our Suggested Approach
1. Understand Modern OpenGL Rendering Pipeline (Programmable Shaders):
- Vertex Shader: Processes each vertex. Responsible for transformations (model, view, projection matrices) and passing attributes (like normals, texture coordinates) to the fragment shader.
- Fragment Shader: Processes each pixel (fragment). Responsible for calculating the final color of the fragment, typically by implementing lighting models like Phong.
- Vertex Buffer Objects (VBOs): Store vertex data (positions, normals, colors, texture coordinates) on the GPU.
- Vertex Array Objects (VAOs): Configure how vertex attributes are read from VBOs.
- Shader Programs: Linked vertex and fragment shaders.
- Uniforms: Variables passed from the CPU application to shaders (e.g., transformation matrices, light properties, camera position).
2. Scene Setup and Object Modeling:
- Celestial Bodies: Represent Sun, Earth, Moon as spheres. Create sphere geometry (vertices, normals, and indices for
glDrawElements
).
- Transformations:
- Model Matrix: Translates, rotates, scales each celestial body to its correct position and orientation in world space.
- View Matrix: Transforms world space coordinates to camera/view space (camera position and orientation).
- Projection Matrix: Transforms view space to clip space (perspective or orthographic projection).
- Hierarchy: Moon orbits Earth, Earth orbits Sun. This can be managed by composing model matrices (e.g., Moon's matrix = Earth's matrix * Moon's local orbit/rotation).
3. Implementing Phong Shading in Shaders:
- Phong Reflection Model:
Color = Ambient + Diffuse + Specular
- Vertex Shader Responsibilities:
- Transform vertex position to clip space:
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertexPosition, 1.0);
- Transform vertex normal to view space:
viewSpaceNormal = mat3(transpose(inverse(modelViewMatrix))) * vertexNormal;
(modelViewMatrix is view*model)
- Calculate vertex position in view space:
viewSpacePosition = vec3(modelViewMatrix * vec4(vertexPosition, 1.0));
- Pass
viewSpaceNormal
andviewSpacePosition
to the fragment shader (asvarying
orout
variables).
- Fragment Shader Responsibilities (per fragment):
- Normalize the incoming
viewSpaceNormal
.
- Light Properties (Uniforms): Light position (in view space), light color (ambient, diffuse, specular).
- Material Properties (Uniforms): Object's ambient, diffuse, specular reflectivity (colors), and shininess coefficient.
- Ambient Term:
ambientColor = materialAmbientColor * lightAmbientColor;
- Diffuse Term:
- Light direction vector
L = normalize(lightPosition_viewSpace - fragmentPosition_viewSpace);
diffuseIntensity = max(dot(viewSpaceNormal, L), 0.0);
diffuseColor = materialDiffuseColor * lightDiffuseColor * diffuseIntensity;
- Specular Term:
- View direction vector
V = normalize(-fragmentPosition_viewSpace);
(from fragment to eye)
- Reflection vector
R = reflect(-L, viewSpaceNormal);
specularIntensity = pow(max(dot(R, V), 0.0), materialShininess);
specularColor = materialSpecularColor * lightSpecularColor * specularIntensity;
- Final Color:
gl_FragColor = vec4(ambientColor + diffuseColor + specularColor, 1.0);
(Clamp if necessary).
4. Animation Loop:
- In each frame:
- Update time.
- Calculate new positions/rotations for Earth and Moon based on time (orbital angles, rotation angles).
- Update model matrices accordingly.
- Update view matrix based on camera controls.
- Clear screen buffers (
glClear
).
- Use the shader program.
- Set uniforms (matrices, light properties, material properties, camera position for specular calculation if needed).
- Bind VAO for Sun, draw Sun.
- Bind VAO for Earth, update its model matrix uniform, draw Earth.
- Bind VAO for Moon, update its model matrix uniform, draw Moon.
- Swap buffers.
5. Keyboard Camera Controls:
- Implement functions to modify camera position (e.g., move forward/backward, strafe left/right, move up/down) or camera target/angles (pan, tilt, zoom) based on keyboard input.
- Recalculate the view matrix when camera parameters change.
Illustrative Code Snippet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
// --- Vertex Shader (Conceptual GLSL) --- /* #version 330 core layout (location = 0) in vec3 aPos; // Vertex position layout (location = 1) in vec3 aNormal; // Vertex normal out vec3 FragPos_viewSpace; // Output to fragment shader out vec3 Normal_viewSpace; // Output to fragment shader uniform mat4 model; uniform mat4 view; uniform mat4 projection; uniform mat3 normalMatrix; // transpose(inverse(mat3(model*view))) or mat3(model*view) if no non-uniform scaling void main() { vec4 viewPos = view * model * vec4(aPos, 1.0); FragPos_viewSpace = vec3(viewPos); // Normal_viewSpace = normalize(normalMatrix * aNormal); // More robust Normal_viewSpace = normalize(mat3(view * model) * aNormal); // Simpler if no non-uniform scaling gl_Position = projection * viewPos; } */ // --- Fragment Shader (Conceptual GLSL for Phong) --- /* #version 330 core out vec4 FragColor; in vec3 FragPos_viewSpace; in vec3 Normal_viewSpace; struct Material { vec3 ambient; vec3 diffuse; vec3 specular; float shininess; }; uniform Material material; struct Light { vec3 position_viewSpace; // Light position in view space vec3 ambient; vec3 diffuse; vec3 specular; }; uniform Light light; uniform vec3 viewPos_viewSpace; // Camera position in view space void main() { // Ambient vec3 ambient = light.ambient * material.ambient; // Diffuse vec3 norm = normalize(Normal_viewSpace); vec3 lightDir = normalize(light.position_viewSpace - FragPos_viewSpace); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = light.diffuse * (diff * material.diffuse); // Specular vec3 viewDir = normalize(viewPos_viewSpace - FragPos_viewSpace); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); vec3 specular = light.specular * (spec * material.specular); FragColor = vec4(ambient + diffuse + specular, 1.0); } */ // --- Main Application Loop (e.g., C++ with GLFW/GLEW or Java with LWJGL - Conceptual) --- /* void mainLoop() { while (!windowShouldClose()) { float currentTime = getTime(); // processInput(window, camera); // Update camera based on keyboard // Update planet positions/rotations float earthOrbitAngle = currentTime * 0.5f; mat4 earthModel = translate(mat4(1.0f), vec3(cos(earthOrbitAngle)*EARTH_ORBIT_RADIUS, 0, sin(earthOrbitAngle)*EARTH_ORBIT_RADIUS)); earthModel = rotate(earthModel, currentTime * EARTH_ROTATION_SPEED, vec3(0,1,0)); float moonOrbitAngle = currentTime * 2.0f; mat4 moonLocalTransform = translate(mat4(1.0f), vec3(cos(moonOrbitAngle)*MOON_ORBIT_RADIUS, 0, sin(moonOrbitAngle)*MOON_ORBIT_RADIUS)); mat4 moonModel = earthModel * moonLocalTransform; // Moon orbits Earth moonModel = rotate(moonModel, currentTime * MOON_ROTATION_SPEED, vec3(0,1,0)); mat4 sunModel = scale(mat4(1.0f), vec3(SUN_SCALE)); // Sun at origin, maybe rotating // Get view and projection matrices from camera // mat4 view = camera.GetViewMatrix(); // mat4 projection = perspective(fov, aspect_ratio, near_plane, far_plane); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); shaderProgram.use(); // shaderProgram.setMat4("projection", projection); // shaderProgram.setMat4("view", view); // shaderProgram.setVec3("viewPos_viewSpace", camera.Position_viewSpace); // Or transform camera.Position to view space // shaderProgram.setLightProperties(...); // Set light uniforms // Draw Sun // shaderProgram.setMaterialProperties_Sun(...); // shaderProgram.setMat4("model", sunModel); //glBindVertexArray(sunVAO); //glDrawElements(...) / glDrawArrays(...); // Draw Earth // shaderProgram.setMaterialProperties_Earth(...); // shaderProgram.setMat4("model", earthModel); // glBindVertexArray(earthVAO); // glDrawElements(...); // Draw Moon // ... same for Moon with moonModel ... swapBuffers(); pollEvents(); } } */
Explanation & Key Points
Programmable Shaders (Vertex and Fragment)
Modern OpenGL relies on shaders written in GLSL. The Vertex Shader transforms each vertex's position and prepares data (like normals) for the next stage. The Fragment Shader calculates the final color of each pixel, implementing the lighting model.
Transformation Matrices (Model, View, Projection)
Objects are positioned, oriented, and scaled in the scene using a Model matrix. The View matrix transforms the scene from world coordinates to the camera's viewpoint. The Projection matrix (usually perspective) defines the viewing frustum and maps 3D coordinates to 2D screen coordinates.
Phong Shading Model
Phong shading calculates lighting by combining three components for each light source: Ambient (general background light), Diffuse (light scattered सरफेस from a surface, dependent on light angle), and Specular (shiny highlights, dependent on light angle and view angle). These are calculated per-fragment for smooth results.
Normal Vectors and Normal Matrix
Normals are vectors perpendicular to a surface, crucial for lighting calculations. When model transformations (especially non-uniform scaling) are applied, normals must be transformed correctly using the transpose of the inverse of the upper-left 3x3 part of the model-view matrix (the 'normal matrix').
Hierarchical Transformations for Orbits
To make the Moon orbit the Earth, and the Earth orbit the Sun, hierarchical transformations are used. The Moon's final position is calculated by applying its local orbital/rotational transform relative to the Earth's already transformed position.
Animation Loop
The main game/animation loop updates object positions (based on time for orbits and rotations), processes user input for camera control, clears the screen, sets up shader uniforms (like matrices and light/material properties), and then draws each object.
Vertex Data (VAOs/VBOs)
Sphere geometry (vertex positions, normals) is defined and uploaded to the GPU using Vertex Buffer Objects (VBOs). Vertex Array Objects (VAOs) configure how this data is laid out and accessed by the vertex shader.
Keyboard Camera Control
Implementing camera controls allows the user to move around the scene (e.g., zoom, pan, orbit). This involves updating the View matrix based on keyboard inputs.
Key Concepts to Master
3D Graphics Pipeline
OpenGL/WebGL
Shaders (GLSL - Vertex & Fragment)
Phong Shading (Ambient, Diffuse, Specular)
Transformation Matrices (Model, View, Projection)
Normal Vectors & Normal Matrix
Hierarchical Modeling
Animation Loop
Vertex Buffer Objects (VBOs)
Vertex Array Objects (VAOs)
Uniform Variables in Shaders
Vector Math (dot product, normalize, reflect)
How Our Tutors Elevate Your Learning
- OpenGL/Graphics API Setup: Helping you set up the OpenGL context, load shaders, create VBOs/VAOs, and draw basic shapes.
- Shader Writing (GLSL): Guiding you through writing the vertex shader for transformations and the fragment shader for Phong lighting calculations (dot products, normalization, etc.).
- Matrix Transformations: Explaining matrix multiplication order for model, view, and projection, and how to implement hierarchical transformations for the orbits.
- Lighting Logic: Clarifying how the ambient, diffuse, and specular components are calculated and combined, and how to pass light/material properties as uniforms.
- Debugging Shaders: Shader debugging can be tricky. Helping you use tools (like RenderDoc if available for your platform) or print-debugging techniques (e.g., outputting intermediate values as colors) to find issues.
- Camera Implementation: Assisting with the math and logic for implementing keyboard-based camera controls (e.g., updating camera position/target and recalculating the view matrix).
- Generating Sphere Geometry: Discussing algorithms or providing code snippets to generate vertex and normal data for a sphere.