МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ «ЛЬВІВСЬКА ПОЛІТЕХНІКА»
ІКТА
кафедра ЗІ
Графічно-розрахункова робота
з курсу «Програмування комп’ютерної графіки»
Мета роботи - набути практичних навиків в складанні програм для побудови зображень на екрані комп’ютера за допомогою засобів мови С# та бібліотеки OpenGL.
Короткі теоретичні відомості.
OPENGL обчислює колір кожного пікселя в результуючій, сцені, що відображується, міститься в буфері кадру. Частка цього розрахунку залежить від того, яке освітлення використовується в сцені, і як об'єкти сцени відображають і поглинають світло. Як приклад цьому пригадаєте, що океан (або море, або річка – взагалі кажучи, будь-яке водоймище) має різний колір в сонячний або в хмарний день. Присутність світла або хмар визначає, чи буде вода виглядати яскраво сині або брудно зеленою. По правді кажучи, більшість об'єктів взагалі не виглядають тривимірними, якщо вони не освітлені. Скажімо, неосвітлена сфера нічим не відрізняється від двовимірного круга. Освітлення будь-якого об'єкту залежить від двох чинників. Перший - це матеріал, з якого зроблений об'єкт. Другий - це світло, яким він освітлений.
Завдання
Для виконання завдання необхідно створити відповідно до варіанту сцену (фотореалістичність не потрібна). Оцінка, виставляється за завдання, залежить від виконання сцени, і використаних в ній засобів. Максимальна оцінка 30 балів.
30.
Космічна станція
Текст програми
using System;
using System.Collections.Generic;
using Tao.FreeGlut;
using OpenGL;
namespace ROZRAHA_SVIAT
{
class Program
{
private static int width = 1280, height = 720;
private static System.Diagnostics.Stopwatch watch;
private static ShaderProgram program;
private static VBO<Vector3> cube, cubeNormals, cubeTangents, square, square2, square3, square4, square5, square6;
private static VBO<Vector2> cubeUV;
private static VBO<int> cubeTriangles, squareElements, squareElements2, squareElements3, squareElements4, squareElements5, squareElements6;
private static Texture brickDiffuse, brickNormals, background;
private static bool lighting = true, fullscreen = false, normalMapping = false ,alpha = false;
private static bool left, right, up, down, space;
private static Camera camera;
private static BMFont font;
private static ShaderProgram fontProgram;
private static FontVAO information;
static void Main(string[] args)
{
Glut.glutInit();
Glut.glutInitDisplayMode(Glut.GLUT_DOUBLE | Glut.GLUT_DEPTH | Glut.GLUT_MULTISAMPLE);
Glut.glutInitWindowSize(width, height);
Glut.glutCreateWindow("Rozrahunkova_robota");
Glut.glutIdleFunc(OnRenderFrame);
Glut.glutDisplayFunc(OnDisplay);
Glut.glutKeyboardFunc(OnKeyboardDown);
Glut.glutKeyboardUpFunc(OnKeyboardUp);
Glut.glutCloseFunc(OnClose);
Glut.glutReshapeFunc(OnReshape);
Glut.glutMouseFunc(OnMouse);
Glut.glutMotionFunc(OnMove);
Gl.Enable(EnableCap.Blend);
Gl.Enable(EnableCap.DepthTest);
program = new ShaderProgram(VertexShader, FragmentShader);
// Створення камери
camera = new Camera(new Vector3(0, 0, 10), Quaternion.Identity);
camera.SetDirection(new Vector3(0, 0, -1));
program.Use();
program["projection_matrix"].SetValue(Matrix4.CreatePerspectiveFieldOfView(0.45f, (float)width / height, 0.1f, 1000f));
//program["view_matrix"].SetValue(Matrix4.LookAt(new Vector3(0, 0, 10), Vector3.Zero, Vector3.Up));
program["light_direction"].SetValue(new Vector3(0, 0, 1));
program["enable_lighting"].SetValue(lighting);
program["normalTexture"].SetValue(1);
program["enable_mapping"].SetValue(normalMapping);
brickDiffuse = new Texture("borg.png");
brickNormals = new Texture("borg2.png");
background = new Texture("background.png");
Vector3[] vertices = new Vector3[] {
new Vector3(1, 1, -1), new Vector3(-1, 1, -1), new Vector3(-1, 1, 1), new Vector3(1, 1, 1), // верх
new Vector3(1, -1, 1), new Vector3(-1, -1, 1), new Vector3(-1, -1, -1), new Vector3(1, -1, -1), // низ
new Vector3(1, 1, 1), new Vector3(-1, 1, 1), new Vector3(-1, -1, 1), new Vector3(1, -1, 1), // Перед
new Vector3(1, -1, -1), new Vector3(-1, -1, -1), new Vector3(-1, 1, -1), new Vector3(1, 1, -1), // зад
new Vector3(-1, 1, 1), new Vector3(-1, 1, -1), new Vector3(-1, -1, -1), new Vector3(-1, -1, 1), // Ліво
new Vector3(1, 1, -1), new Vector3(1, 1, 1), new Vector3(1, -1, 1), new Vector3(1, -1, -1) }; // право
cube = new VBO<Vector3>(vertices);
//ініціалізація квадратів космосу
square = new VBO<Vector3>(new Vector3[] { new Vector3(100, 100, -100), new Vector3(-100, 100, -100), new Vector3(-100, 100, 100), new Vector3(100, 100, 100) });
squareElements = new VBO<int>(new int[] { 0, 1, 2, 3 }, BufferTarget.ElementArrayBuffer);
square2 = new VBO<Vector3>(new Vector3[] { new Vector3(100, -100, 100), new Vector3(-100, -100, 100), new Vector3(-100, -100, -100), new Vector3(100, -100, -100) });
squareElements2 = new VBO<int>(new int[] { 0, 1, 2, 3 }, BufferTarget.ElementArrayBuffer);
square3 = new VBO<Vector3>(new Vector3[] { new Vector3(100, 100, 100), new Vector3(-100, 100, 100), new Vector3(-100, -100, 100), new Vector3(100, -100, 100) });
squareElements3 = new VBO<int>(new int[] { 0, 1, 2, 3 }, BufferTarget.ElementArrayBuffer);
square4 = new VBO<Vector3>(new Vector3[] { new Vector3(100, -100, -100), new Vector3(-100, -100, -100), new Vector3(-100, 100, -100), new Vector3(100, 100, -100) });
squareElements4 = new VBO<int>(new int[] { 0, 1, 2, 3 }, BufferTarget.ElementArrayBuffer);
square5 = new VBO<Vector3>(new Vector3[] { new Vector3(-100, 100, -100), new Vector3(-100, 100, -100), new Vector3(-100, -100, -100), new Vector3(-100, -100, 100) });
squareElements5 = new VBO<int>(new int[] { 0, 1, 2, 3 }, BufferTarget.ElementArrayBuffer);
square6 = new VBO<Vector3>(new Vector3[] { new Vector3(100, 100, -100), new Vector3(100, 100, 100), new Vector3(100, -100, 100), new Vector3(100, -100, -100) });
squareElements6 = new VBO<int>(new int[] { 0, 1, 2, 3 }, BufferTarget.ElementArrayBuffer);
Vector2[] uvs = new Vector2[] {
new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1),
new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1),
new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1),
new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1),
new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1),
new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1) };
cubeUV = new VBO<Vector2>(uvs);
List<int> triangles = new List<int>();
for (int i = 0; i < 6; i++)
{
triangles.Add(i * 4);
triangles.Add(i * 4 + 1);
triangles.Add(i * 4 + 2);
triangles.Add(i * 4);
triangles.Add(i * 4 + 2);
triangles.Add(i * 4 + 3);
}
cubeTriangles = new VBO<int>(triangles.ToArray(), BufferTarget.ElementArrayBuffer);
Vector3[] normals = Geometry.CalculateNormals(vertices, triangles.ToArray());
cubeNormals = new VBO<Vector3>(normals);
Vector3[] tangents = CalculateTangents(vertices, normals, triangles.ToArray(), uvs);
cubeTangents = new VBO<Vector3>(tangents);
font = new BMFont("font24.fnt", "font24.png");
fontProgram = new ShaderProgram(BMFont.FontVertexSource, BMFont.FontFragmentSource);
fontProgram.Use();
fontProgram["ortho_matrix"].SetValue(Matrix4.CreateOrthographic(width, height, 0, 1000));
fontProgram["color"].SetValue(new Vector3(1, 1, 1));
information = font.CreateString(fontProgram, "Rozrahunkova robota");
watch = System.Diagnostics.Stopwatch.StartNew();
Glut.glutMainLoop();
}
/// <summary>
/// </summary>
private static Vector3[] CalculateTangents(Vector3[] vertices, Vector3[] normals, int[] triangles, Vector2[] uvs)
{
Vector3[] tangents = new Vector3[vertices.Length];
Vector3[] tangentData = new Vector3[vertices.Length];
for (int i = 0; i < triangles.Length / 3; i++)
{
Vector3 v1 = vertices[triangles[i * 3]];
Vector3 v2 = vertices[triangles[i * 3 + 1]];
Vector3 v3 = vertices[triangles[i * 3 + 2]];
Vector2 w1 = uvs[triangles[i * 3]];
Vector2 w2 = uvs[triangles[i * 3] + 1];
Vector2 w3 = uvs[triangles[i * 3] + 2];
float x1 = v2.x - v1.x;
float x2 = v3.x - v1.x;
float y1 = v2.y - v1.y;
float y2 = v3.y - v1.y;
float z1 = v2.z - v1.z;
float z2 = v3.z - v1.z;
float s1 = w2.x - w1.x;
float s2 = w3.x - w1.x;
float t1 = w2.y - w1.y;
float t2 = w3.y - w1.y;
float r = 1.0f / (s1 * t2 - s2 * t1);
Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
tangents[triangles[i * 3]] += sdir;
tangents[triangles[i * 3 + 1]] += sdir;
tangents[triangles[i * 3 + 2]] += sdir;
}
for (int i = 0; i < vertices.Length; i++)
tangentData[i] = (tangents[i] - normals[i] * Vector3.Dot(normals[i], tangents[i])).Normalize();
return tangentData;
}
private static void OnClose()
{
cube.Dispose();
cubeNormals.Dispose();
cubeUV.Dispose();
cubeTriangles.Dispose();
brickDiffuse.Dispose();
brickNormals.Dispose();
background.Dispose();
program.DisposeChildren = true;
program.Dispose();
fontProgram.DisposeChildren = true;
fontProgram.Dispose();
font.FontTexture.Dispose();
information.Dispose();
square.Dispose(); square2.Dispose(); square3.Dispose(); square4.Dispose(); square5.Dispose(); square6.Dispose();
squareElements.Dispose(); squareElements2.Dispose(); squareElements3.Dispose(); squareElements4.Dispose(); squareElements5.Dispose(); squareElements6.Dispose();
}
private static void OnDisplay()
{
}
private static bool mouseDown = false;
private static int downX, downY;
private static int prevX, prevY;
private static void OnMouse(int button, int state, int x, int y)
{
if (button != Glut.GLUT_RIGHT_BUTTON) return;
mouseDown = (state == Glut.GLUT_DOWN);
if (mouseDown)
{
Glut.glutSetCursor(Glut.GLUT_CURSOR_NONE);
prevX = downX = x;
prevY = downY = y;
}
else
{
Glut.glutSetCursor(Glut.GLUT_CURSOR_LEFT_ARROW);
Glut.glutWarpPointer(downX, downY);
}
}
private static void OnMove(int x, int y)
{
if (x == prevX && y == prevY) return;
if (mouseDown)
{
float yaw = (prevX - x) * 0.002f;
camera.Yaw(yaw);
float pitch = (prevY - y) * 0.002f;
camera.Pitch(pitch);
prevX = x;
prevY = y;
}
if (x < 0) Glut.glutWarpPointer(prevX = width, y);
else if (x > width) Glut.glutWarpPointer(prevX = 0, y);
if (y < 0) Glut.glutWarpPointer(x, prevY = height);
else if (y > height) Glut.glutWarpPointer(x, prevY = 0);
}
private static void OnRenderFrame()
{
watch.Stop();
float deltaTime = (float)watch.ElapsedTicks / System.Diagnostics.Stopwatch.Frequency;
watch.Restart();
// оновлення руху камери
if (down) camera.MoveRelative(Vector3.UnitZ * deltaTime * 5);
if (up) camera.MoveRelative(-Vector3.UnitZ * deltaTime * 5);
if (left) camera.MoveRelative(-Vector3.UnitX * deltaTime * 5);
if (right) camera.MoveRelative(Vector3.UnitX * deltaTime * 5);
if (space) camera.MoveRelative(Vector3.Up * deltaTime * 3);
Gl.Viewport(0, 0, width, height);
Gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
// Перевіряю чи підключені шейдера
Gl.UseProgram(program);
Gl.ActiveTexture(TextureUnit.Texture1);
Gl.BindTexture(brickNormals);
Gl.ActiveTexture(TextureUnit.Texture0);
Gl.BindTexture(brickDiffuse);
// привязую камеру до космічної станції
program["view_matrix"].SetValue(camera.ViewMatrix);
// Матриця куба. Побудова куба
program["model_matrix"].SetValue(Matrix4.Identity);
program["enable_lighting"].SetValue(lighting);
program["enable_mapping"].SetValue(normalMapping);
Gl.BindBufferToShaderAttribute(cube, program, "vertexPosition");
Gl.BindBufferToShaderAttribute(cubeNormals, program, "vertexNormal");
Gl.BindBufferToShaderAttribute(cubeTangents, program, "vertexTangent");
Gl.BindBufferToShaderAttribute(cubeUV, program, "vertexUV");
Gl.BindBuffer(cubeTriangles);
Gl.DrawElements(BeginMode.Triangles, cubeTriangles.Count, DrawElementsType.UnsignedInt, IntPtr.Zero);
Gl.BindTexture(background);
// Космос. Головний квадрат
program["model_matrix"].SetValue(Matrix4.CreateTranslation(new Vector3(1.5f, 0, 0)));
Gl.BindBufferToShaderAttribute(square, program, "vertexPosition");
Gl.BindBuffer(squareElements);
Gl.DrawElements(BeginMode.Quads, squareElements.Count, DrawElementsType.UnsignedInt, IntPtr.Zero);
#region Square
program["model_matrix"].SetValue(Matrix4.CreateTranslation(new Vector3(1.5f, 0, 0)));
// bind the vertex attribute arrays for the square (the easy way)
Gl.BindBufferToShaderAttribute(square2, program, "vertexPosition");
Gl.BindBuffer(squareElements2);
// draw the square
Gl.DrawElements(BeginMode.Quads, squareElements2.Count, DrawElementsType.UnsignedInt, IntPtr.Zero);
// transform the square
program["model_matrix"].SetValue(Matrix4.CreateTranslation(new Vector3(1.5f, 0, 0)));
// bind the vertex attribute arrays for the square (the easy way)
Gl.BindBufferToShaderAttribute(square3, program, "vertexPosition");
Gl.BindBuffer(squareElements3);
// draw the square
Gl.DrawElements(BeginMode.Quads, squareElements3.Count, DrawElementsType.UnsignedInt, IntPtr.Zero);
// transform the square
program["model_matrix"].SetValue(Matrix4.CreateTranslation(new Vector3(1.5f, 0, 0)));
// bind the vertex attribute arrays for the square (the easy way)
Gl.BindBufferToShaderAttribute(square4, program, "vertexPosition");
Gl.BindBuffer(squareElements4);
// draw the square
Gl.DrawElements(BeginMode.Quads, squareElements4.Count, DrawElementsType.UnsignedInt, IntPtr.Zero);
// transform the square
program["model_matrix"].SetValue(Matrix4.CreateTranslation(new Vector3(1.5f, 0, 0)));
// bind the vertex attribute arrays for the square (the easy way)
Gl.BindBufferToShaderAttribute(square5, program, "vertexPosition");
Gl.BindBuffer(squareElements5);
// draw the square
Gl.DrawElements(BeginMode.Quads, squareElements5.Count, DrawElementsType.UnsignedInt, IntPtr.Zero);
// transform the square
program["model_matrix"].SetValue(Matrix4.CreateTranslation(new Vector3(1.5f, 0, 0)));
// bind the vertex attribute arrays for the square (the easy way)
Gl.BindBufferToShaderAttribute(square6, program, "vertexPosition");
Gl.BindBuffer(squareElements6);
// draw the square
Gl.DrawElements(BeginMode.Quads, squareElements6.Count, DrawElementsType.UnsignedInt, IntPtr.Zero);
#endregion
// Шрифти
Gl.UseProgram(fontProgram.ProgramID);
Gl.BindTexture(font.FontTexture);
information.Draw();
Glut.glutSwapBuffers();
}
private static void OnReshape(int width, int height)
{
Program.width = width;
Program.height = height;
Gl.UseProgram(program.ProgramID);
program["projection_matrix"].SetValue(Matrix4.CreatePerspectiveFieldOfView(0.45f, (float)width / height, 0.1f, 1000f));
Gl.UseProgram(fontProgram.ProgramID);
fontProgram["ortho_matrix"].SetValue(Matrix4.CreateOrthographic(width, height, 0, 1000));
information.Position = new Vector2(-width / 2 + 10, height / 2 - font.Height - 10);
}
private static void OnKeyboardDown(byte key, int x, int y)
{
if (key == 'w') up = true;
else if (key == 's') down = true;
else if (key == 'd') right = true;
else if (key == 'a') left = true;
else if (key == ' ') space = true;
else if (key == 27) Glut.glutLeaveMainLoop();
}
private static void OnKeyboardUp(byte key, int x, int y)
{
if (key == 'w') up = false;
else if (key == 's') down = false;
else if (key == 'd') right = false;
else if (key == 'a') left = false;
else if (key == ' ') space = false;
else if (key == 'l') lighting = !lighting;
else if (key == 'm') normalMapping = !normalMapping;
else if (key == 'f')
{
fullscreen = !fullscreen;
if (fullscreen) Glut.glutFullScreen();
else
{
Glut.glutPositionWindow(0, 0);
Glut.glutReshapeWindow(1280, 720);
}
}
else if (key == 'b')
{
alpha = !alpha;
if (alpha)
{
Gl.Enable(EnableCap.Blend);
Gl.Disable(EnableCap.DepthTest);
}
else
{
Gl.Disable(EnableCap.Blend);
Gl.Enable(EnableCap.DepthTest);
}
}
}
public static string VertexShader = @"
#version 130
in vec3 vertexPosition;
in vec3 vertexNormal;
in vec3 vertexTangent;
in vec2 vertexUV;
uniform vec3 light_direction;
out vec3 normal;
out vec2 uv;
out vec3 light;
uniform mat4 projection_matrix;
uniform mat4 view_matrix;
uniform mat4 model_matrix;
uniform bool enable_mapping;
void main(void)
{
normal = normalize((model_matrix * vec4(floor(vertexNormal), 0)).xyz);
uv = vertexUV;
mat3 tbnMatrix = mat3(vertexTangent, cross(vertexTangent, normal), normal);
light = (enable_mapping ? light_direction * tbnMatrix : light_direction);
gl_Position = projection_matrix * view_matrix * model_matrix * vec4(vertexPosition, 1);
}
";
public static string FragmentShader = @"
#version 130
uniform sampler2D colorTexture;
uniform sampler2D normalTexture;
uniform bool enable_lighting;
uniform mat4 model_matrix;
uniform bool enable_mapping;
in vec3 normal;
in vec2 uv;
in vec3 light;
out vec4 fragment;
void main(void)
{
vec3 fragmentNormal = texture2D(normalTexture, uv).xyz * 2 - 1;
vec3 selectedNormal = (enable_mapping ? fragmentNormal : normal);
float diffuse = max(dot(selectedNormal, light), 0);
float ambient = 0.3;
float lighting = (enable_lighting ? max(diffuse, ambient) : 1);
fragment = vec4(lighting * texture2D(colorTexture, uv).xyz, 0.5);
}
";
}
}
Результат виконання програми
Висновок: набув практичних навиків в складанні програм для побудови зображень на екрані комп’ютера за допомогою засобів мови С# та бібліотеки OpenGL.