Question
i have a scene and i have to implement a reflective surface and shadow into the scene using openGL I have the Main cpp, frag
i have a scene and i have to implement a reflective surface and shadow into the scene using openGL
I have the Main cpp, frag shader and vertex shader below
Main CPP
#include
#include "GL/glew.h"
#include "GL/3dgl.h"
#include "GL/glut.h"
#include "GL/freeglut_ext.h"
#pragma comment (lib, "glew32.lib")
using namespace std;
using namespace _3dgl;
// 3D models
C3dglModel table;
C3dglModel vase;
C3dglModel chicken;
C3dglModel lamp;
unsigned nPyramidBuf = 0;
// texture ids
GLuint idTexWood;
GLuint idTexCloth;
GLuint idTexNone;
// GLSL Program
C3dglProgram Program;
// camera position (for first person type camera navigation)
float matrixView[16]; // The View Matrix
float angleTilt = 0; // Tilt Angle
float angleRot = 0.1f; // Camera orbiting angle
float deltaX = 0, deltaY = 0, deltaZ = 0; // Camera movement values
// light switches
int nAmbient = 1, nDir = 1, nPoint1 = 1, nPoint2 = 1;
bool init()
{
// rendering states
glEnable(GL_DEPTH_TEST); // depth test is necessary for most 3D scenes
glEnable(GL_NORMALIZE); // normalization is needed by AssImp library models
glShadeModel(GL_SMOOTH); // smooth shading mode is the default one; try GL_FLAT here!
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // this is the default one; try GL_LINE!
// Initialise Shaders
C3dglShader VertexShader;
C3dglShader FragmentShader;
if (!VertexShader.Create(GL_VERTEX_SHADER)) return false;
if (!VertexShader.LoadFromFile("shaders/basic.vert")) return false;
if (!VertexShader.Compile()) return false;
if (!FragmentShader.Create(GL_FRAGMENT_SHADER)) return false;
if (!FragmentShader.LoadFromFile("shaders/basic.frag")) return false;
if (!FragmentShader.Compile()) return false;
if (!Program.Create()) return false;
if (!Program.Attach(VertexShader)) return false;
if (!Program.Attach(FragmentShader)) return false;
if (!Program.Link()) return false;
if (!Program.Use(true)) return false;
// glut additional setup
glutSetVertexAttribCoord3(Program.GetAttribLocation("aVertex"));
glutSetVertexAttribNormal(Program.GetAttribLocation("aNormal"));
// load your 3D models here!
if (!table.load("models\\table.obj")) return false;
if (!vase.load("models\\vase.obj")) return false;
if (!chicken.load("models\\chicken.obj")) return false;
if (!lamp.load("models\\lamp.obj")) return false;
// Initialise the View Matrix (initial position of the camera)
glMatrixMode(GL_MODELVIEW);
angleTilt = 15;
glLoadIdentity();
glRotatef(angleTilt, 1, 0, 0);
gluLookAt(0.0, 5.0, 6.0,
0.0, 5.0, 0.0,
0.0, 1.0, 0.0);
glGetFloatv(GL_MODELVIEW_MATRIX, matrixView);
// create pyramid
float vermals[] = {
-4, 0,-4, 0, 4,-7, 4, 0,-4, 0, 4,-7, 0, 7, 0, 0, 4,-7,
-4, 0, 4, 0, 4, 7, 4, 0, 4, 0, 4, 7, 0, 7, 0, 0, 4, 7,
-4, 0,-4,-7, 4, 0,-4, 0, 4,-7, 4, 0, 0, 7, 0,-7, 4, 0,
4, 0,-4, 7, 4, 0, 4, 0, 4, 7, 4, 0, 0, 7, 0, 7, 4, 0,
-4, 0,-4, 0,-1, 0,-4, 0, 4, 0,-1, 0, 4, 0,-4, 0,-1, 0,
4, 0, 4, 0,-1, 0,-4, 0, 4, 0,-1, 0, 4, 0,-4, 0,-1, 0 };
// Generate 1 buffer name
glGenBuffers(1, &nPyramidBuf);
// Bind (activate) the buffer
glBindBuffer(GL_ARRAY_BUFFER, nPyramidBuf);
// Send data to the buffer
glBufferData(GL_ARRAY_BUFFER, sizeof(vermals), vermals, GL_STATIC_DRAW);
// Setup Lights
Program.SendUniform("lightAmbient.on", nAmbient);
Program.SendUniform("lightAmbient.color", 0.025, 0.025, 0.025);
Program.SendUniform("lightEmissive.on", 0);
Program.SendUniform("lightEmissive.color", 1.0, 1.0, 1.0);
Program.SendUniform("materialAmbient", 1.0, 1.0, 1.0);
Program.SendUniform("lightDir.on", nDir);
Program.SendUniform("lightDir.direction", 1.0, 0.5, 1.0);
Program.SendUniform("lightDir.diffuse", 0.3, 0.3, 0.3); // dimmed white light
Program.SendUniform("lightPoint1.on", nPoint1);
Program.SendUniform("lightPoint1.position", -2.95, 4.24, -1.0);
Program.SendUniform("lightPoint1.diffuse", 0.5, 0.5, 0.5);
Program.SendUniform("lightPoint1.specular", 1.0, 1.0, 1.0);
Program.SendUniform("lightPoint2.on", nPoint2);
Program.SendUniform("lightPoint2.position", 1.05, 4.24, 1.0);
Program.SendUniform("lightPoint2.diffuse", 0.5, 0.5, 0.5);
Program.SendUniform("lightPoint2.specular", 1.0, 1.0, 1.0);
Program.SendUniform("materialSpecular", 0.0, 0.0, 0.0);
Program.SendUniform("shininess", 3.0);
// create & load textures
C3dglBitmap bm;
glActiveTexture(GL_TEXTURE0);
// cloth texture
bm.Load("models/cloth.png", GL_RGBA);
if (!bm.GetBits()) return false;
glGenTextures(1, &idTexCloth);
glBindTexture(GL_TEXTURE_2D, idTexCloth);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bm.GetWidth(), bm.GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, bm.GetBits());
// wood texture
bm.Load("models/oak.png", GL_RGBA);
if (!bm.GetBits()) return false;
glGenTextures(1, &idTexWood);
glBindTexture(GL_TEXTURE_2D, idTexWood);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bm.GetWidth(), bm.GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, bm.GetBits());
// none (simple-white) texture
glGenTextures(1, &idTexNone);
glBindTexture(GL_TEXTURE_2D, idTexNone);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
BYTE bytes[] = { 255, 255, 255 };
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_BGR, GL_UNSIGNED_BYTE, &bytes);
// Send the texture info to the shaders
Program.SendUniform("texture0", 0);
cout << endl;
cout << "Use:" << endl;
cout << " WASD or arrow key to navigate" << endl;
cout << " QE or PgUp/Dn to move the camera up and down" << endl;
cout << " Shift+AD or arrow key to auto-orbit" << endl;
cout << " Drag the mouse to look around" << endl;
cout << endl;
cout << " 1 to switch the lamp #1 on/off" << endl;
cout << " 2 to switch the lamp #2 on/off" << endl;
cout << " 9 to switch directional light on/off" << endl;
cout << " 0 to switch ambient light on/off" << endl;
cout << endl;
return true;
}
void done()
{
}
void render()
{
// this global variable controls the animation
static float theta = 0.0f;
// clear screen and buffers
glClearColor(0.18f, 0.25f, 0.22f, 1.0f); // deep grey background
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// setup the View Matrix (camera)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(angleTilt, 1, 0, 0); // switch tilt off
glTranslatef(deltaX, deltaY, deltaZ); // animate camera motion (controlled by WASD keys)
glRotatef(-angleTilt, 1, 0, 0); // switch tilt on
glMultMatrixf(matrixView);
glRotatef(angleRot, 0.0, 1.0, 0.0); // animate camera orbiting
glGetFloatv(GL_MODELVIEW_MATRIX, matrixView);
// setup View Matrix
float matrix[16];
glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
Program.SendUniform("matrixView", matrix);
// setup materials
Program.SendUniform("materialSpecular", 0.0, 0.0, 0.0);
// table & chairs
glPushMatrix();
glScalef(0.004f, 0.004f, 0.004f);
Program.SendUniform("materialDiffuse", 1.0, 1.0, 1.0);
Program.SendUniform("materialAmbient", 1.0, 1.0, 1.0);
glBindTexture(GL_TEXTURE_2D, idTexWood);
table.render(1);
glBindTexture(GL_TEXTURE_2D, idTexCloth);
table.render(0);
glRotatef(180, 0, 1, 0);
table.render(0);
glTranslatef(250, 0, 0);
glRotatef(90, 0, 1, 0);
table.render(0);
glTranslatef(0, 0, -500);
glRotatef(180, 0, 1, 0);
table.render(0);
glPopMatrix();
// vase
glPushMatrix();
Program.SendUniform("materialDiffuse", 0.2, 0.4, 0.8);
Program.SendUniform("materialAmbient", 0.2, 0.4, 0.8);
Program.SendUniform("materialSpecular", 1.0, 1.0, 1.0);
glBindTexture(GL_TEXTURE_2D, idTexNone);
glTranslatef(0, 3, 0);
glScalef(0.12f, 0.12f, 0.12f);
vase.render();
glPopMatrix();
// teapot
glPushMatrix();
Program.SendUniform("materialDiffuse", 0.1, 0.8, 0.3);
Program.SendUniform("materialAmbient", 0.1, 0.8, 0.3);
Program.SendUniform("materialSpecular", 1.0, 1.0, 1.0);
glTranslatef(1.8f, 3.4f, 0.0f);
glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
Program.SendUniform("matrixModelView", matrix);
glutSolidTeapot(0.5);
glPopMatrix();
// pyramid
glPushMatrix();
Program.SendUniform("materialDiffuse", 1.0, 0.2, 0.2);
Program.SendUniform("materialAmbient", 1.0, 0.2, 0.2);
Program.SendUniform("materialSpecular", 0.0, 0.0, 0.0);
glTranslatef(-0.9f, 3.7f, -1.2f);
glRotatef(180, 1, 0, 0);
glRotatef(-4*theta, 0, 1, 0);
glScalef(0.1f, 0.1f, 0.1f);
glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
Program.SendUniform("matrixModelView", matrix);
GLuint attribVertex = Program.GetAttribLocation("aVertex");
GLuint attribNormal = Program.GetAttribLocation("aNormal");
glBindBuffer(GL_ARRAY_BUFFER, nPyramidBuf);
glEnableVertexAttribArray(attribVertex);
glEnableVertexAttribArray(attribNormal);
glVertexAttribPointer(attribVertex, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), 0);
glVertexAttribPointer(attribNormal, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3*sizeof(float)));
glDrawArrays(GL_TRIANGLES, 0, 18);
glDisableVertexAttribArray(GL_VERTEX_ARRAY);
glDisableVertexAttribArray(GL_NORMAL_ARRAY);
// chicken
Program.SendUniform("materialDiffuse", 0.8, 0.8, 0.2);
Program.SendUniform("materialAmbient", 0.8, 0.8, 0.2);
Program.SendUniform("materialSpecular", 0.6, 0.6, 1.0);
glTranslatef(0, -5, 0);
glScalef(0.2f, 0.2f, 0.2f);
glRotatef(180, 1, 0, 0);
chicken.render();
glPopMatrix();
// lamp 1
glPushMatrix();
glTranslatef(-2.2f, 3.075f, -1.0f);
glScalef(0.02f, 0.02f, 0.02f);
lamp.render();
glPopMatrix();
// lamp 2
Program.SendUniform("materialDiffuse", 0.8, 0.8, 0.2);
Program.SendUniform("materialAmbient", 0.8, 0.8, 0.2);
Program.SendUniform("materialSpecular", 0.0, 0.0, 0.0);
glPushMatrix();
glTranslatef(1.8f, 3.075f, 1.0f);
glScalef(0.02f, 0.02f, 0.02f);
lamp.render();
glPopMatrix();
// light bulb 1
Program.SendUniform("materialDiffuse", 0.8, 0.8, 0.8);
Program.SendUniform("materialAmbient", 0.8, 0.8, 0.8);
Program.SendUniform("lightEmissive.on", nPoint1);
glPushMatrix();
glTranslatef(-2.95f, 4.24f, -1.0f);
glScalef(0.1f, 0.1f, 0.1f);
glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
Program.SendUniform("matrixModelView", matrix);
glutSolidSphere(1, 32, 32);
glPopMatrix();
// light bulb 2
Program.SendUniform("materialDiffuse", 0.8, 0.8, 0.8);
Program.SendUniform("materialAmbient", 0.8, 0.8, 0.8);
Program.SendUniform("lightEmissive.on", nPoint2);
glPushMatrix();
glTranslatef(1.05f, 4.24f, 1.0f);
glScalef(0.1f, 0.1f, 0.1f);
glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
Program.SendUniform("matrixModelView", matrix);
glutSolidSphere(1, 32, 32);
Program.SendUniform("lightEmissive.on", 0);
glPopMatrix();
// essential for double-buffering technique
glutSwapBuffers();
// proceed the animation
static GLint prev_time = 0;
int time = glutGet(GLUT_ELAPSED_TIME);
theta += (time - prev_time) * 0.01f;
prev_time = time;
glutPostRedisplay();
}
// called before window opened or resized - to setup the Projection Matrix
void reshape(int w, int h)
{
// find screen aspect ratio
float ratio = w * 1.0f / h; // we hope that h is not zero
// setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glViewport(0, 0, w, h);
gluPerspective(60.0, ratio, 0.02, 1000.0);
float matrix[16];
glGetFloatv(GL_PROJECTION_MATRIX, matrix);
Program.SendUniform("matrixProjection", matrix);
}
// Handle WASDQE keys
void onKeyDown(unsigned char key, int x, int y)
{
switch (tolower(key))
{
case 'w': deltaZ = max(deltaZ * 1.05f, 0.01f); break;
case 's': deltaZ = min(deltaZ * 1.05f, -0.01f); break;
case 'a': deltaX = max(deltaX * 1.05f, 0.01f); angleRot = 0.1f; break;
case 'd': deltaX = min(deltaX * 1.05f, -0.01f); angleRot = -0.1f; break;
case 'e': deltaY = max(deltaY * 1.05f, 0.01f); break;
case 'q': deltaY = min(deltaY * 1.05f, -0.01f); break;
case '1': nPoint1 = 1 - nPoint1; Program.SendUniform("lightPoint1.on", nPoint1); return;
case '2': nPoint2 = 1 - nPoint2; Program.SendUniform("lightPoint2.on", nPoint2); return;
case '9': nDir = 1 - nDir; Program.SendUniform("lightDir.on", nDir); return;
case '0': nAmbient = 1 - nAmbient; Program.SendUniform("lightAmbient.on", nAmbient); return;
}
// speed limit
deltaX = max(-0.15f, min(0.15f, deltaX));
deltaY = max(-0.15f, min(0.15f, deltaY));
deltaZ = max(-0.15f, min(0.15f, deltaZ));
// stop orbiting
if ((glutGetModifiers() & GLUT_ACTIVE_SHIFT) == 0) angleRot = 0;
}
// Handle WASDQE keys (key up)
void onKeyUp(unsigned char key, int x, int y)
{
switch (tolower(key))
{
case 'w':
case 's': deltaZ = 0; break;
case 'a':
case 'd': deltaX = 0; break;
case 'q':
case 'e': deltaY = 0; break;
case ' ': deltaY = 0; break;
}
}
// Handle arrow keys and Alt+F4
void onSpecDown(int key, int x, int y)
{
switch (key)
{
case GLUT_KEY_F4: if ((glutGetModifiers() & GLUT_ACTIVE_ALT) != 0) exit(0); break;
case GLUT_KEY_UP: onKeyDown('w', x, y); break;
case GLUT_KEY_DOWN: onKeyDown('s', x, y); break;
case GLUT_KEY_LEFT: onKeyDown('a', x, y); break;
case GLUT_KEY_RIGHT: onKeyDown('d', x, y); break;
case GLUT_KEY_PAGE_UP: onKeyDown('q', x, y); break;
case GLUT_KEY_PAGE_DOWN:onKeyDown('e', x, y); break;
case GLUT_KEY_F11: glutFullScreenToggle();
}
}
// Handle arrow keys (key up)
void onSpecUp(int key, int x, int y)
{
switch (key)
{
case GLUT_KEY_UP: onKeyUp('w', x, y); break;
case GLUT_KEY_DOWN: onKeyUp('s', x, y); break;
case GLUT_KEY_LEFT: onKeyUp('a', x, y); break;
case GLUT_KEY_RIGHT: onKeyUp('d', x, y); break;
case GLUT_KEY_PAGE_UP: onKeyUp('q', x, y); break;
case GLUT_KEY_PAGE_DOWN:onKeyUp('e', x, y); break;
}
}
// Handle mouse click
void onMouse(int button, int state, int x, int y)
{
int cx = glutGet(GLUT_WINDOW_WIDTH) / 2;
int cy = glutGet(GLUT_WINDOW_HEIGHT) / 2;
if (state == GLUT_DOWN)
{
glutSetCursor(GLUT_CURSOR_CROSSHAIR);
glutWarpPointer(cx, cy);
}
else
glutSetCursor(GLUT_CURSOR_INHERIT);
}
// handle mouse move
void onMotion(int x, int y)
{
int cx = glutGet(GLUT_WINDOW_WIDTH) / 2;
int cy = glutGet(GLUT_WINDOW_HEIGHT) / 2;
if (x == cx && y == cy)
return; // caused by glutWarpPointer
float amp = 0.25;
float deltaTilt = amp * (y - cy);
float deltaPan = amp * (x - cx);
glutWarpPointer(cx, cy);
// handle camera tilt (mouse move up & down)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(deltaTilt, 1, 0, 0);
glMultMatrixf(matrixView);
glGetFloatv(GL_MODELVIEW_MATRIX, matrixView);
angleTilt += deltaTilt;
// handle camera pan (mouse move left & right)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(angleTilt, 1, 0, 0);
glRotatef(deltaPan, 0, 1, 0);
glRotatef(-angleTilt, 1, 0, 0);
glMultMatrixf(matrixView);
glGetFloatv(GL_MODELVIEW_MATRIX, matrixView);
}
int main(int argc, char **argv)
{
// init GLUT and create Window
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100, 100);
glutInitWindowSize(800, 600);
glutCreateWindow("CI5520 3D Graphics Programming");
// init glew
GLenum err = glewInit();
if (GLEW_OK != err)
{
cerr << "GLEW Error: " << glewGetErrorString(err) << endl;
return 0;
}
cout << "Using GLEW " << glewGetString(GLEW_VERSION) << endl;
// register callbacks
glutDisplayFunc(render);
glutReshapeFunc(reshape);
glutKeyboardFunc(onKeyDown);
glutSpecialFunc(onSpecDown);
glutKeyboardUpFunc(onKeyUp);
glutSpecialUpFunc(onSpecUp);
glutMouseFunc(onMouse);
glutMotionFunc(onMotion);
cout << "Vendor: " << glGetString(GL_VENDOR) << endl;
cout << "Renderer: " << glGetString(GL_RENDERER) << endl;
cout << "Version: " << glGetString(GL_VERSION) << endl;
// init light and everything not a GLUT or callback function!
if (!init())
{
cerr << "Application failed to initialise" << endl;
return 0;
}
// enter GLUT event processing cycle
glutMainLoop();
done();
return 1;
}
FRAGMENT SHADER
// FRAGMENT SHADER
#version 330
in vec4 color;
in vec4 position;
in vec3 normal;
in vec2 texCoord0;
out vec4 outColor;
// Materials
uniform vec3 materialAmbient;
uniform vec3 materialDiffuse;
uniform vec3 materialSpecular;
uniform float shininess;
// View Matrix
uniform mat4 matrixView;
// Texture
uniform sampler2D texture0;
struct POINT
{
int on;
vec3 position;
vec3 diffuse;
vec3 specular;
};
uniform POINT lightPoint1, lightPoint2;
vec4 PointLight(POINT light)
{
// Calculate Directional Light
vec4 color = vec4(0, 0, 0, 0);
// diffuse light
vec3 L = normalize(matrixView * vec4(light.position, 1) - position).xyz;
float NdotL = dot(normal, L);
if (NdotL > 0)
color += vec4(materialDiffuse * light.diffuse, 1) * NdotL;
// specular light
vec3 V = normalize(-position.xyz);
vec3 R = reflect(-L, normal);
float RdotV = dot(R, V);
if (NdotL > 0 && RdotV > 0)
color += vec4(materialSpecular * light.specular * pow(RdotV, shininess), 1);
return color;
}
void main(void)
{
outColor = color;
if (lightPoint1.on == 1)
outColor += PointLight(lightPoint1);
if (lightPoint2.on == 1)
outColor += PointLight(lightPoint2);
outColor *= texture(texture0, texCoord0);
}
Vertex shader
// VERTEX SHADER
#version 330
// Matrices
uniform mat4 matrixProjection;
uniform mat4 matrixView;
uniform mat4 matrixModelView;
// Materials
uniform vec3 materialAmbient;
uniform vec3 materialDiffuse;
uniform vec3 materialSpecular;
uniform float shininess;
layout (location = 0) in vec3 aVertex;
layout (location = 2) in vec3 aNormal;
layout (location = 3) in vec2 aTexCoord;
out vec4 color;
out vec4 position;
out vec3 normal;
out vec2 texCoord0;
// Light declarations
struct AMBIENT
{
int on;
vec3 color;
};
uniform AMBIENT lightAmbient, lightEmissive;
struct DIRECTIONAL
{
int on;
vec3 direction;
vec3 diffuse;
};
uniform DIRECTIONAL lightDir;
vec4 AmbientLight(AMBIENT light)
{
// Calculate Ambient Light
return vec4(materialAmbient * light.color, 1);
}
vec4 DirectionalLight(DIRECTIONAL light)
{
// Calculate Directional Light
vec4 color = vec4(0, 0, 0, 0);
vec3 L = normalize(mat3(matrixView) * light.direction);
float NdotL = dot(normal, L);
if (NdotL > 0)
color += vec4(materialDiffuse * light.diffuse, 1) * NdotL;
return color;
}
void main(void)
{
// calculate position
position = matrixModelView * vec4(aVertex, 1.0);
gl_Position = matrixProjection * position;
// calculate normal
normal = normalize(mat3(matrixModelView) * aNormal);
// calculate texture coordinate
texCoord0 = aTexCoord;
// calculate light
color = vec4(0, 0, 0, 1);
if (lightAmbient.on == 1)
color += AmbientLight(lightAmbient);
if (lightEmissive.on == 1)
color += AmbientLight(lightEmissive);
if (lightDir.on == 1)
color += DirectionalLight(lightDir);
//if (lightPoint1.on == 1)
// color += PointLight(lightPoint1);
//if (lightPoint2.on == 1)
// color += PointLight(lightPoint2);
}
Step by Step Solution
There are 3 Steps involved in it
Step: 1
Get Instant Access to Expert-Tailored Solutions
See step-by-step solutions with expert insights and AI powered tools for academic success
Step: 2
Step: 3
Ace Your Homework with AI
Get the answers you need in no time with our AI-driven, step-by-step assistance
Get Started