skybox but its mirrored

This commit is contained in:
OutsiderAK 2022-01-21 19:41:34 +01:00
parent 490155f6ff
commit 8454ed0005
26 changed files with 1015 additions and 99 deletions

View File

@ -1,29 +0,0 @@
0. Do tej pory nasze obiekty znajdowały się głównie w płaszczyźnie X-Z, w której poruszał i obracał się statek (i kamera). Teraz nie będziemy się już ograniczać do ruchu na płaszczyźnie. Planety będą znajdować się w dowolnych miejscach trójwymiarowej przestrzeni. Aby wygodnie poruszać się w trzech wymiarach, stworzymy ruch kamery oparty na kwaternionach.
1. Wyświetl większą liczbę planet (np. 10) w przestrzeni trójwymiarowej (tak, aby nie znajdowały się tylko w płaszczyźnie). Możesz wylosować w funkcji init() pozycje planet (używając np. funkcji vec3 glm::ballRand(float R), która zwraca losowy wektor w kuli o promieniu R), zapisać je w tablicy lub w std::vector i w funkcji renderScene() umieścić pętlę iterującą po tablicy, w której wywoływane będzie rysowanie planety o zadanej pozycji.
2. Chcemy stworzyć kamerę, w której ruch myszą góra-dół spowoduje obrót kamery wokół lokalnej osi X, a ruch lewo-prawo - obrót kamery wokół lokalnej osi Y. Należy zacząć od usunięcia zawartości funkcji createCameraMatrix(), którą w tym zadaniu napiszemy od nowa.
a) W głównym pliku C++ pojawiła się nowa funkcja void mouse(int x, int y), która jest wywoływana przy każdej zmianie pozycji kursora w oknie. Stwórz zmienną (lub zmienne) globalne, w której po zakończeniu funkcji mouse() będzie znajdować się różnica między poprzednią a aktualną pozycją kursora (osobno względem osi X i Y ekranu). Różnica ta zostanie wykorzystana do kontrolowania obrotu kamery.
b) W funkcji createCameraMatrix() oblicz kwaternion reprezentujący rotację spowodowaną przez ruch kursora między poprzednią a aktualną klatką animacji (zmienne z punktu 2a). Możesz użyć funkcji glm::quat glm::angleAxis(float angle, glm::vec3 axis), która zwraca kwaternion reprezentujący obrót wokół podanej osi o podany kąt. Oblicz obrót dla osi X (wynikający z ruchu myszki w kierunku Y) i dla osi Y (wynikający z ruchu myszki w kierunku X) i połącz je, mnożąc ze sobą kwaterniony (kolejność mnożenia macierzy jak zwykle nie jest dowolna. Jednak w tym przypadku - ponieważ są to tylko inkrementalne, a nie całościowe obroty - nie ma do dużego znaczenia). Po obliczeniu macierzy zmiany rotacji, należy wyzerować zmienne z podpunktu a)!
c) Oblicz nowy obrót (zmienna globalna rotation), poprzez:
rotation = rotationChange * rotation; // rotationChange to kwaternion z podpunktu b)
Z powodu potencjalnych niedokładności numerycznych, otrzymany kwaternion należy znormalizować (funkcja glm::quat glm::normalize(glm::quat q)).
d) W plikach Camera.h i Camera.cpp znajduje się nowa funkcja Core::createViewMatrixQuat(), która generuje macierz kamery z pozycji i kwaterniona zawierającego obrót, której należy teraz użyć w miejsce używanego wcześniej Core::createViewMatrix().
e) Należy także w każdym wywołaniu funkcji createCameraMatrix() aktualizować wartości funkcji cameraDir i cameraSide (aby poprawnie działało przesuwanie kamery w funkcji keyboard()). cameraDir i cameraSide to z definicji wektory, które w przestrzeni kamery mają postać (0,0,-1) i (1,0,0). Aby uzyskać te wektory w przestrzeni świata (bo tam wykonujemy obliczenia przesuwające cameraPos), należy je przekształcić przez ODWROTNY obrót kamery. Można to zrobić mnożąc je przez (odwrócony) kwaternion rotation.
Pseudokod:
cameraDir = odwrotnoscRotation * (0, 0, -1)
Odwrotność kwaterniona można uzyskać funkcją glm::quat glm::inverse(glm::quat q).
3. Popraw kod przyczepiający statek do kamery.
a) Macierz obrotu będąca składową shipModelMatrix obliczona przez "glm::rotate(-cameraAngle, glm::vec3(0,1,0))" musi zostać zastąpiona inną macierzą, wynikającą z nowego, bardziej skomplikowanego obrotu kamery zapisanego w kwaternionie. Macierz obrotu 4x4 można uzyskać z kwaterniona używając funkcji glm::mat4 glm::mat4_cast(glm::quat q). Znów jednak należy użyć ODWROTNEGO kwaterniona obrotu kamery (* - wyjaśnienie na dole).
4. Dodaj obrót kamery wokół trzeciej osi (lokalnej osi Z) przy użyciu przycisków Z i X na klawiaturze.
(*)Macierz obrotu kamery działa z przestrzeni świata do przestrzeni kamery. Ponieważ chcemy, aby statek był "tożsamy z kamerą", to jego macierz świata powinna być właściwie przekształceniem z przestrzeni kamery do przestrzeni świata - czyli ODWROTNOŚCIĄ przekształcenia kamery. Można to samo rozumowanie zastosować do pełnej macierzy kamery (razem z translacją), ale my akurat robimy translację statku w osobnym kroku).

View File

@ -14,8 +14,10 @@
<ClCompile Include="src\Bubble.cpp" />
<ClCompile Include="src\Camera.cpp" />
<ClCompile Include="src\main_6_1.cpp" />
<ClCompile Include="src\Model.cpp" />
<ClCompile Include="src\Render_Utils.cpp" />
<ClCompile Include="src\Shader_Loader.cpp" />
<ClCompile Include="src\Skybox.cpp" />
<ClCompile Include="src\SOIL\image_DXT.c" />
<ClCompile Include="src\SOIL\image_helper.c" />
<ClCompile Include="src\SOIL\SOIL.c" />
@ -25,9 +27,13 @@
<ItemGroup>
<ClInclude Include="src\Bubble.h" />
<ClInclude Include="src\Camera.h" />
<ClInclude Include="src\Mesh.h" />
<ClInclude Include="src\Model.h" />
<ClInclude Include="src\objload.h" />
<ClInclude Include="src\Render_Utils.h" />
<ClInclude Include="src\Shader.h" />
<ClInclude Include="src\Shader_Loader.h" />
<ClInclude Include="src\Skybox.h" />
<ClInclude Include="src\SOIL\image_DXT.h" />
<ClInclude Include="src\SOIL\image_helper.h" />
<ClInclude Include="src\SOIL\SOIL.h" />
@ -39,6 +45,10 @@
<ItemGroup>
<None Include="shaders\shader_color.frag" />
<None Include="shaders\shader_color.vert" />
<None Include="shaders\shader_cube.frag" />
<None Include="shaders\shader_cube.vert" />
<None Include="shaders\shader_test.frag" />
<None Include="shaders\shader_test.vert" />
<None Include="shaders\shader_tex.frag" />
<None Include="shaders\shader_tex.vert" />
</ItemGroup>

View File

@ -51,6 +51,12 @@
<ClCompile Include="src\Bubble.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\Skybox.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\Model.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\objload.h">
@ -59,9 +65,6 @@
<ClInclude Include="src\Render_Utils.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="src\Shader_Loader.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="src\Camera.h">
<Filter>Source Files</Filter>
</ClInclude>
@ -89,6 +92,21 @@
<ClInclude Include="src\Bubble.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="src\Shader_Loader.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="src\Skybox.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="src\Model.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="src\Mesh.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="src\Shader.h">
<Filter>Source Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="shaders\shader_color.frag">
@ -103,5 +121,17 @@
<None Include="shaders\shader_tex.vert">
<Filter>Shader Files</Filter>
</None>
<None Include="shaders\shader_cube.frag">
<Filter>Shader Files</Filter>
</None>
<None Include="shaders\shader_cube.vert">
<Filter>Shader Files</Filter>
</None>
<None Include="shaders\shader_test.frag">
<Filter>Shader Files</Filter>
</None>
<None Include="shaders\shader_test.vert">
<Filter>Shader Files</Filter>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,10 @@
#version 430 core
out vec4 FragColor;
in vec3 TexCoords;
uniform samplerCube skybox;
void main()
{
FragColor = texture(skybox,TexCoords);
}

View File

@ -0,0 +1,14 @@
#version 430 core
layout(location = 0) in vec3 vertexPosition;
out vec3 TexCoords;
uniform mat4 projection;
uniform mat4 view;
void main()
{
TexCoords = vertexPosition;
gl_Position = projection * view * vec4(vertexPosition, 1.0);
}

View File

@ -0,0 +1,152 @@
#version 430 core
struct Light {
float ambient;
float diffuse;
float specular;
float constant;
float linear;
float quadratic;
};
struct PointLight{
vec3 position;
float ambient;
float diffuse;
float specular;
float constant;
float linear;
float quadratic;
};
uniform sampler2D texture_diffuse;
uniform sampler2D texture_specular;
uniform sampler2D texture_normal;
uniform vec3 lightDir;
uniform vec3 cameraPos;
uniform float Distroyed;
in vec2 interpTexCoord;
in vec3 lightDirTS;
//in vec3 lightDirTS2;
in vec3 viewDirTS;
in vec3 vertPosition;
in vec3 lightPosition;
in vec3 viewDirection;
vec4 CalcDirLight(vec3 LightDir,Light light,vec3 viewDir, float distance)
{
float attenuation = 1.0/(light.constant + light.linear * distance + light.quadratic * (distance*distance));
vec3 L = normalize(-lightDirTS);
vec3 V = normalize(viewDirTS);
vec3 N = normalize(vec3(0,0,1));
vec3 R = reflect(-normalize(L), N);
N = texture2D(texture_normal, interpTexCoord).rgb;
N = N * 2 - 1;
N = normalize(N);
light.diffuse = max(0, dot(N, L));
vec3 diffuse = light.diffuse *texture2D(texture_diffuse, interpTexCoord).rgb;
float specular_pow = 5;
light.specular = pow(max(0, dot(R, V)), specular_pow);
vec3 specular = light.specular *texture2D(texture_specular, interpTexCoord).rgb;
vec3 color = texture2D(texture_diffuse, interpTexCoord).rgb;
diffuse *=attenuation;
specular *=attenuation;
vec3 lightColor = vec3(1);
vec3 shadedColor = color * diffuse + lightColor * specular;
float ambient = light.ambient;
ambient *=attenuation;
vec4 mixColor = vec4(mix(color, shadedColor, 1.0 - ambient), 1.0) * 0.6f;
return mixColor;
}
vec4 CalcPointLight(PointLight light, vec3 fragPos, vec3 viewDir)
{
vec3 N = normalize(vec3(0,0,1));
N = texture2D(texture_normal, interpTexCoord).rgb;
N = N * 2 - 1;
N = normalize(N);
vec3 lightDir = normalize(light.position - fragPos);
light.diffuse = max(dot(N, lightDir), 0.0);
vec3 reflectDir = reflect(-lightDir, N);
float specular_pow = 5;
light.specular = pow(max(dot(viewDir, reflectDir), 0.0), specular_pow);
//float distance = length(light.position - fragPos);
float distance = 0.05;
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
vec3 diffuse = light.diffuse *texture2D(texture_diffuse, interpTexCoord).rgb;
vec3 specular = light.specular *texture2D(texture_specular, interpTexCoord).rgb;
vec3 color = texture2D(texture_diffuse, interpTexCoord).rgb;
diffuse *=attenuation;
specular *=attenuation;
float ambient = light.ambient;
ambient *=attenuation;
vec3 lightColor = vec3(1);
vec3 shadedColor = color * diffuse + lightColor * specular;
vec4 mixColor = vec4(mix(color, shadedColor, 1.0 - ambient), 1.0) * 0.6f;
return mixColor;
}
void main()
{
Light light;
light.constant = 1;
light.linear = 0.7;
light.ambient = 0.7;
light.quadratic = 1.8;
float distance = 0.05;
PointLight pointlight;
pointlight.constant = 1;
pointlight.linear = 0.7;
pointlight.ambient = 0.7;
pointlight.quadratic = 1.8;
pointlight.position = lightPosition;
vec3 viewDir = viewDirection;
vec3 fragPos = vertPosition;
//float attenuation = 1.0/(light.constant + light.linear * distance + light.quadratic * (distance*distance));
//float distance2 = length(lightPosition - vertPosition);
//float secondDistance = normalize(-distance2);
vec4 Color = CalcDirLight(lightDirTS, light, viewDirTS ,distance);
//Color += 2*CalcDirLight(lightDirTS2, light, viewDirTS , distance2);
Color += CalcPointLight(pointlight,fragPos,viewDir);
float distroy = Distroyed;
float distroyed2 = 1.0;
if(distroy == 1.0)
{
Color = vec4(vec3(0.0,0.5,0.2), 1.0);
vec4 toAvoid = vec4(vec3(0.0,0.5,0.2), 1.0);
if(Color.rgb == toAvoid.rgb)
discard;
}
//vec4 color = vec4(vec3(0.0,0.5,0.2), 1.0);
//vec4 toAvoid = vec4(vec3(0.0,0.5,0.2), 1.0);
//if(color.rgb == toAvoid.rgb)
// discard;
gl_FragColor = Color;
//gl_FragColor = color;
}

View File

@ -0,0 +1,50 @@
#version 430 core
layout(location = 0) in vec3 vertexPosition;
layout(location = 1) in vec2 vertexTexCoord;
layout(location = 2) in vec3 vertexNormal;
layout(location = 3) in vec3 vertexTangent;
layout(location = 4) in vec3 vertexBitangent;
uniform mat4 modelViewProjectionMatrix;
uniform mat4 modelMatrix;
uniform vec3 lightDir;
uniform vec3 cameraPos;
out vec2 interpTexCoord;
out vec3 lightDirTS;
//out vec3 lightDirTS2;
out vec3 viewDirTS;
out vec3 vertPosition;
out vec3 lightPosition;
out vec3 viewDirection;
//out float distToCamera;
void main()
{
vec3 lightPos = vec3(-5.0,7.0,0.0);
vec3 vertPos;
gl_Position = modelViewProjectionMatrix * vec4(vertexPosition, 1.0);
//distToCamera = gl_Position.w;
vertPos = (modelMatrix * vec4(vertexPosition, 1.0)).xyz;
vec3 normal = vec3(modelMatrix * vec4(vertexNormal, 0.0));
vec3 tangent = vec3(modelMatrix * vec4(vertexTangent, 0.0));
vec3 bitangent = vec3(modelMatrix * vec4(vertexBitangent, 0.0));
mat3x3 TBN = transpose(mat3(tangent, bitangent, normal));
vec3 lightDir2 = normalize(vertPos - lightPos);
vec3 viewDir = normalize(cameraPos - vertPos);
lightDirTS = TBN * lightDir;
//lightDirTS2 = TBN * lightDir;
viewDirTS = TBN * viewDir;
vertPosition = vertPos;
lightPosition = lightPos;
viewDirection = viewDir;
interpTexCoord = vertexTexCoord;
}

158
cw 6/src/Mesh.h Normal file
View File

@ -0,0 +1,158 @@
#pragma once
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
#include "Shader.h"
#include "glew.h"
#include "glm.hpp"
#include "assimp/types.h"
//#include <glm/gtc/matrix_transform.hpp>
using namespace std;
struct Vertex
{
// Position
glm::vec3 Position;
// Normal
glm::vec3 Normal;
// TexCoords
glm::vec2 TexCoords;
glm::vec3 Tangent;
glm::vec3 Bitangent;
};
struct Texture
{
GLuint id;
string type;
aiString path;
};
class Mesh
{
public:
/* Mesh Data */
vector<Vertex> vertices;
vector<GLuint> indices;
vector<Texture> textures;
/* Functions */
// Constructor
Mesh( vector<Vertex> vertices, vector<GLuint> indices, vector<Texture> textures )
{
this->vertices = vertices;
this->indices = indices;
this->textures = textures;
// Now that we have all the required data, set the vertex buffers and its attribute pointers.
this->setupMesh( );
}
// Render the mesh
void Draw( GLuint shader )
{
// Bind appropriate textures
GLuint diffuseNr = 1;
GLuint specularNr = 1;
GLuint normalNr = 1;
for( GLuint i = 0; i < this->textures.size( ); i++ )
{
glActiveTexture( GL_TEXTURE0 + i ); // Active proper texture unit before binding
// Retrieve texture number (the N in diffuse_textureN)
//stringstream ss;
//string number;
string name = this->textures[i].type;
//
//if( name == "texture_diffuse" )
//{
// ss << diffuseNr++; // Transfer GLuint to stream
//}
//else if( name == "texture_specular" )
//{
// ss << specularNr++; // Transfer GLuint to stream
//}
//else if (name == "texture_normal")
//{
// ss << normalNr++;
//}
//number = ss.str( );
// Now set the sampler to the correct texture unit
glUniform1i( glGetUniformLocation( shader, ( name //+ number
).c_str( ) ), i );
// And finally bind the texture
glBindTexture( GL_TEXTURE_2D, this->textures[i].id );
}
// Also set each mesh's shininess property to a default value (if you want you could extend this to another mesh property and possibly change this value)
//glUniform1f( glGetUniformLocation( shader, "material.shininess" ), 16.0f );
// Draw mesh
glBindVertexArray( this->VAO );
glDrawElements( GL_TRIANGLES, this->indices.size( ), GL_UNSIGNED_INT, 0 );
glBindVertexArray( 0 );
// Always good practice to set everything back to defaults once configured.
for ( GLuint i = 0; i < this->textures.size( ); i++ )
{
glActiveTexture( GL_TEXTURE0 + i );
glBindTexture( GL_TEXTURE_2D, 0 );
}
}
private:
/* Render data */
GLuint VAO, VBO, EBO;
/* Functions */
// Initializes all the buffer objects/arrays
void setupMesh( )
{
// Create buffers/arrays
glGenVertexArrays( 1, &this->VAO );
glGenBuffers( 1, &this->VBO );
glGenBuffers( 1, &this->EBO );
glBindVertexArray( this->VAO );
// Load data into vertex buffers
glBindBuffer( GL_ARRAY_BUFFER, this->VBO );
// A great thing about structs is that their memory layout is sequential for all its items.
// The effect is that we can simply pass a pointer to the struct and it translates perfectly to a glm::vec3/2 array which
// again translates to 3/2 floats which translates to a byte array.
glBufferData( GL_ARRAY_BUFFER, this->vertices.size( ) * sizeof( Vertex ), &this->vertices[0], GL_STATIC_DRAW );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, this->EBO );
glBufferData( GL_ELEMENT_ARRAY_BUFFER, this->indices.size( ) * sizeof( GLuint ), &this->indices[0], GL_STATIC_DRAW );
// Set the vertex attribute pointers
// Vertex Positions
glEnableVertexAttribArray( 0 );
glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), ( GLvoid * )0 );
// Vertex Normals
glEnableVertexAttribArray( 1 );
glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), ( GLvoid * )offsetof( Vertex, Normal ) );
// Vertex Texture Coords
glEnableVertexAttribArray(2);
glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE, sizeof( Vertex ), ( GLvoid * )offsetof( Vertex, TexCoords ) );
//w wypadku gdy nie ma tangentow i bitangentow maja one wartosc 0.f w vertexie
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, Tangent));
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, Bitangent));
glBindVertexArray( 0 );
}
};

249
cw 6/src/Model.cpp Normal file
View File

@ -0,0 +1,249 @@
#pragma once
#include "Model.h"
using namespace std;
Model::Model(GLchar* path)
{
this->loadModel(path);
}
// Draws the model, and thus all its meshes
void Model::Draw(GLuint shader)
{
for (GLuint i = 0; i < this->meshes.size(); i++)
{
this->meshes[i].Draw(shader);
}
}
vector<glm::vec3> Model::getVertexPosition()
{
vector<glm::vec3> vectorOfVertPos;
for (int i = 0; i < meshes[0].vertices.size(); i++)
vectorOfVertPos.push_back(meshes[0].vertices[i].Position);
return vectorOfVertPos;
}
/* Model Data */
// Stores all the textures loaded so far, optimization to make sure textures aren't loaded more than once.
/* Functions */
// Loads a model with supported ASSIMP extensions from file and stores the resulting meshes in the meshes vector.
void Model::loadModel(string path)
{
// Read file via ASSIMP
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace);
// Check for errors
if (!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // if is Not Zero
{
cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << endl;
return;
}
// Retrieve the directory path of the filepath
this->directory = path.substr(0, path.find_last_of('/'));
// Process ASSIMP's root node recursively
this->processNode(scene->mRootNode, scene);
}
// Processes a node in a recursive fashion. Processes each individual mesh located at the node and repeats this process on its children nodes (if any).
void Model::processNode(aiNode* node, const aiScene* scene)
{
std::cout << scene->mMaterials << endl;
// Process each mesh located at the current node
for (GLuint i = 0; i < node->mNumMeshes; i++)
{
// The node object only contains indices to index the actual objects in the scene.
// The scene contains all the data, node is just to keep stuff organized (like relations between nodes).
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
this->meshes.push_back(this->processMesh(mesh, scene));
}
// After we've processed all of the meshes (if any) we then recursively process each of the children nodes
for (GLuint i = 0; i < node->mNumChildren; i++)
{
this->processNode(node->mChildren[i], scene);
}
}
Mesh Model::processMesh(aiMesh* mesh, const aiScene* scene)
{
// Data to fill
vector<Vertex> vertices;
vector<GLuint> indices;
vector<Texture> textures;
// Walk through each of the mesh's vertices
for (GLuint i = 0; i < mesh->mNumVertices; i++)
{
Vertex vertex;
glm::vec3 vector; // We declare a placeholder vector since assimp uses its own vector class that doesn't directly convert to glm's vec3 class so we transfer the data to this placeholder glm::vec3 first.
// Positions
// Positions
vector.x = mesh->mVertices[i].x;
vector.y = mesh->mVertices[i].y;
vector.z = mesh->mVertices[i].z;
vertex.Position = vector;
// Normals
vector.x = mesh->mNormals[i].x;
vector.y = mesh->mNormals[i].y;
vector.z = mesh->mNormals[i].z;
vertex.Normal = vector;
//Tangent
if (mesh->HasTangentsAndBitangents())
{
vector.x = mesh->mTangents[i].x;
vector.y = mesh->mTangents[i].y;
vector.z = mesh->mTangents[i].z;
vertex.Tangent = vector;
vector.x = mesh->mBitangents[i].x;
vector.y = mesh->mBitangents[i].y;
vector.z = mesh->mBitangents[i].z;
vertex.Bitangent = vector;
}
else
{
vertex.Tangent = glm::vec3(0.f);
vertex.Bitangent = glm::vec3(0.f);
}
// Texture Coordinates
if (mesh->mTextureCoords[0]) // Does the mesh contain texture coordinates?
{
glm::vec2 vec;
// A vertex can contain up to 8 different texture coordinates. We thus make the assumption that we won't
// use models where a vertex can have multiple texture coordinates so we always take the first set (0).
vec.x = mesh->mTextureCoords[0][i].x;
vec.y = mesh->mTextureCoords[0][i].y;
vertex.TexCoords = vec;
}
else
{
vertex.TexCoords = glm::vec2(0.0f, 0.0f);
}
vertices.push_back(vertex);
}
// Now wak through each of the mesh's faces (a face is a mesh its triangle) and retrieve the corresponding vertex indices.
for (GLuint i = 0; i < mesh->mNumFaces; i++)
{
aiFace face = mesh->mFaces[i];
// Retrieve all indices of the face and store them in the indices vector
for (GLuint j = 0; j < face.mNumIndices; j++)
{
indices.push_back(face.mIndices[j]);
}
}
// Process materials
if (mesh->mMaterialIndex >= 0)
{
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
// We assume a convention for sampler names in the shaders. Each diffuse texture should be named
// as 'texture_diffuseN' where N is a sequential number ranging from 1 to MAX_SAMPLER_NUMBER.
// Same applies to other texture as the following list summarizes:
// Diffuse: texture_diffuseN
// Specular: texture_specularN
// Normal: texture_normalN
// 1. Diffuse maps
vector<Texture> diffuseMaps = this->loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");
textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
// 2. Specular maps
vector<Texture> specularMaps = this->loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");
textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
// 3. Normal maps
vector<Texture> normalMaps = this->loadMaterialTextures(material, aiTextureType_HEIGHT, "texture_normal");
textures.insert(textures.end(), normalMaps.begin(), normalMaps.end());
}
// Return a mesh object created from the extracted mesh daata
return Mesh(vertices, indices, textures);
}
// Checks all material textures of a given type and loads the textures if they're not loaded yet.
// The required info is returned as a Texture struct.
vector<Texture> Model::loadMaterialTextures(aiMaterial* mat, aiTextureType type, string typeName)
{
vector<Texture> textures;
int tmp = mat->GetTextureCount(type);
//std::cout << tmp << endl;
for (GLuint i = 0; i < tmp; i++)
{
aiString str;
mat->GetTexture(type, i, &str);
// Check if texture was loaded before and if so, continue to next iteration: skip loading a new texture
GLboolean skip = false;
for (GLuint j = 0; j < textures_loaded.size(); j++)
{
if (textures_loaded[j].path == str)
{
textures.push_back(textures_loaded[j]);
skip = true; // A texture with the same filepath has already been loaded, continue to next one. (optimization)
break;
}
}
if (!skip)
{ // If texture hasn't been loaded already, load it
Texture texture;
texture.id = TextureFromFile(str.C_Str(), this->directory);
texture.type = typeName;
texture.path = str;
textures.push_back(texture);
//std::cout << textures.size()<<endl;
this->textures_loaded.push_back(texture); // Store it as texture loaded for entire model, to ensure we won't unnecesery load duplicate textures.
}
}
return textures;
}
GLint TextureFromFile(const char* path, string directory)
{
//Generate texture ID and load texture data
string filename = string(path);
filename = directory + "\\" + filename;
GLuint textureID;
glGenTextures(1, &textureID);
int width, height;
unsigned char* image = SOIL_load_image(filename.c_str(), &width, &height, 0, SOIL_LOAD_RGB);
if (image == NULL)
{
std::cout << "error with loading image, by SOIL" << endl;
}
// Assign texture to ID
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
//std::cout << "GL ERRORS: " << glGetError();
glGenerateMipmap(GL_TEXTURE_2D); //skalowanie szczegolow tekstury zalezne od polozenia kamery
// Parameters
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
SOIL_free_image_data(image);
return textureID;
}

57
cw 6/src/Model.h Normal file
View File

@ -0,0 +1,57 @@
#pragma once
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <map>
#include <vector>
#include "glew.h"
#include <glm.hpp>
#include <gtc/matrix_transform.hpp>
#include "SOIL/SOIL.h"
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <assimp/material.h>
#include "Texture.h"
#include "Mesh.h"
using namespace std;
GLint TextureFromFile( const char *path, string directory );
class Model
{
public:
/* Functions */
// Constructor, expects a filepath to a 3D model.
Model(GLchar* path);
// Draws the model, and thus all its meshes
void Draw(GLuint shader);
vector<glm::vec3> getVertexPosition();
private:
/* Model Data */
vector<Mesh> meshes;
string directory;
vector<Texture> textures_loaded; // Stores all the textures loaded so far, optimization to make sure textures aren't loaded more than once.
/* Functions */
// Loads a model with supported ASSIMP extensions from file and stores the resulting meshes in the meshes vector.
void loadModel(string path);
// Processes a node in a recursive fashion. Processes each individual mesh located at the node and repeats this process on its children nodes (if any).
void processNode(aiNode* node, const aiScene* scene);
Mesh processMesh(aiMesh* mesh, const aiScene* scene);
// Checks all material textures of a given type and loads the textures if they're not loaded yet.
// The required info is returned as a Texture struct.
vector<Texture> loadMaterialTextures(aiMaterial* mat, aiTextureType type, string typeName);
};

98
cw 6/src/Shader.h Normal file
View File

@ -0,0 +1,98 @@
#ifndef SHADER_H
#define SHADER_H
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include "glew.h"
class Shader
{
public:
GLuint Program;
// Constructor generates the shader on the fly
Shader( const GLchar *vertexPath, const GLchar *fragmentPath )
{
// 1. Retrieve the vertex/fragment source code from filePath
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
// ensures ifstream objects can throw exceptions:
vShaderFile.exceptions ( std::ifstream::badbit );
fShaderFile.exceptions ( std::ifstream::badbit );
try
{
// Open files
vShaderFile.open( vertexPath );
fShaderFile.open( fragmentPath );
std::stringstream vShaderStream, fShaderStream;
// Read file's buffer contents into streams
vShaderStream << vShaderFile.rdbuf( );
fShaderStream << fShaderFile.rdbuf( );
// close file handlers
vShaderFile.close( );
fShaderFile.close( );
// Convert stream into string
vertexCode = vShaderStream.str( );
fragmentCode = fShaderStream.str( );
}
catch ( std::ifstream::failure e )
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}
const GLchar *vShaderCode = vertexCode.c_str( );
const GLchar *fShaderCode = fragmentCode.c_str( );
// 2. Compile shaders
GLuint vertex, fragment;
GLint success;
GLchar infoLog[512];
// Vertex Shader
vertex = glCreateShader( GL_VERTEX_SHADER );
glShaderSource( vertex, 1, &vShaderCode, NULL );
glCompileShader( vertex );
// Print compile errors if any
glGetShaderiv( vertex, GL_COMPILE_STATUS, &success );
if ( !success )
{
glGetShaderInfoLog( vertex, 512, NULL, infoLog );
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// Fragment Shader
fragment = glCreateShader( GL_FRAGMENT_SHADER );
glShaderSource( fragment, 1, &fShaderCode, NULL );
glCompileShader( fragment );
// Print compile errors if any
glGetShaderiv( fragment, GL_COMPILE_STATUS, &success );
if ( !success )
{
glGetShaderInfoLog( fragment, 512, NULL, infoLog );
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// Shader Program
this->Program = glCreateProgram( );
glAttachShader( this->Program, vertex );
glAttachShader( this->Program, fragment );
glLinkProgram( this->Program );
// Print linking errors if any
glGetProgramiv( this->Program, GL_LINK_STATUS, &success );
if (!success)
{
glGetProgramInfoLog( this->Program, 512, NULL, infoLog );
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// Delete the shaders as they're linked into our program now and no longer necessery
glDeleteShader( vertex );
glDeleteShader( fragment );
}
// Uses the current shader
void Use( )
{
glUseProgram( this->Program );
}
};
#endif

81
cw 6/src/Skybox.cpp Normal file
View File

@ -0,0 +1,81 @@
#include "SOIL/SOIL.h"
#include "glew.h"
#include "freeglut.h"
#include "glm.hpp"
#include <vector>
#include <iostream>
unsigned int loadCubemap(std::vector<std::string> faces)
{
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
int width, height, nrChannels;
for (unsigned int i = 0; i < faces.size(); i++) //looping over texture targets
{
//loading particular image
unsigned char* data = SOIL_load_image(faces[i].c_str(), &width, &height, 0, SOIL_LOAD_RGB);
if (data)
{
//generating textures
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); /// GL_TEXTURE_CUBE_MAP is linealy incremented
SOIL_free_image_data(data);
std::cout << "Cubemap texture loaded at some point to load at path: " << faces[i] << std::endl; //error
}
else
{
std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl; //error
}
}
//setting texture parameters
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
// This might help with seams on some systems
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
return textureID;
}
/*
unsigned int LoadSkybox(unsigned int skyboxVAO, unsigned int skyboxVBO)
{
unsigned int cubemapTexture = loadCubemap(faces);
glGenVertexArrays(1, &skyboxVAO);
glGenBuffers(1, &skyboxVBO);
glBindVertexArray(skyboxVAO);
glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
return cubemapTexture;
}
*/
void drawSkyBox(glm::mat4 perspective, glm::mat4 view,GLuint programSkyBox, unsigned int skyboxVAO, unsigned int skyboxVBO,unsigned int cubemapTexture)
{
glDepthMask(GL_FALSE);
glUseProgram(programSkyBox);
glUniformMatrix4fv(glGetUniformLocation(programSkyBox, "view"), 1, GL_FALSE, (float*)&view);
glUniformMatrix4fv(glGetUniformLocation(programSkyBox, "projection"), 1, GL_FALSE, (float*)&perspective);
// skybox cube
glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
glBindVertexArray(skyboxVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glUseProgram(0);
glDepthMask(GL_TRUE);
}

11
cw 6/src/Skybox.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include "SOIL/SOIL.h"
#include "glew.h"
#include "freeglut.h"
#include "glm.hpp"
//unsigned int LoadSkybox(unsigned int skyboxVAO, unsigned int skyboxVBO);
unsigned int loadCubemap(std::vector<std::string> faces);
void drawSkyBox(glm::mat4 perspective, glm::mat4 view, GLuint programSkyBox, unsigned int skyboxVAO, unsigned int skyboxVBO, unsigned int cubemapTexture);

View File

@ -7,25 +7,24 @@
#include <vector>
#include <ctime>
#include <cstdlib>
#include "Shader_Loader.h"
#include "Render_Utils.h"
#include "Camera.h"
#include "Texture.h"
#include "Bubble.h"
#include "Skybox.h"
#include "SOIL/stb_image_aug.h"
#include "Model.h"
GLuint programColor;
GLuint programTexture;
//GLuint skyboxTexture;
Core::Shader_Loader shaderLoader;
Core::RenderContext sharkModel;
Core::RenderContext sphereContext;
glm::vec3 cameraPos = glm::vec3(0, 0, 5);
glm::vec3 cameraDir; // Wektor "do przodu" kamery
glm::vec3 cameraSide; // Wektor "w bok" kamery
@ -39,9 +38,10 @@ glm::quat rotationX = glm::quat(1, 0, 0, 0);
glm::quat rotationY = glm::quat(1, 0, 0, 0);
glm::quat rotationZ = glm::quat(0, 0, 0, 0);
std::vector<glm::vec3> planetsCoords;
glm::mat4 trans;
std::vector<Bubble> bubbles;
float mouseX = 0;
float mouseY = 0;
float prevX = 0;
@ -49,6 +49,12 @@ float prevY = 0;
GLuint sharkTexture;
// SKYBOX
unsigned int skyboxVAO, skyboxVBO;
unsigned int cubemapTexture;
GLuint testShader;
GLuint programSkyBox;
float skyboxVertices[] = {
// positions
-1.0f, 1.0f, -1.0f,
@ -94,6 +100,18 @@ float skyboxVertices[] = {
1.0f, -1.0f, 1.0f
};
std::vector<std::string> faces
{
"textures/right.jpg",
"textures/left.jpg",
"textures/top.jpg",
"textures/bottom.jpg",
"textures/front.jpg",
"textures/back.jpg"
};
void keyboard(unsigned char key, int x, int y)
{
@ -110,6 +128,21 @@ void keyboard(unsigned char key, int x, int y)
}
}
// SKYBOX
void loadSkybox()
{
cubemapTexture = loadCubemap(faces);
glGenVertexArrays(1, &skyboxVAO);
glGenBuffers(1, &skyboxVBO);
glBindVertexArray(skyboxVAO);
glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void mouse(int x, int y)
{
mouseX = x - prevX;
@ -183,49 +216,41 @@ void drawBubbles()
}
unsigned int loadCubemap(std::vector<std::string> faces)
// SKYBOX
void setUpUniforms(GLuint program, glm::mat4 modelMatrix)
{
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
glUniform3f(glGetUniformLocation(program, "lightDir"), lightDir.x, lightDir.y, lightDir.z);
glUniform3f(glGetUniformLocation(program, "cameraPos"), cameraPos.x, cameraPos.y, cameraPos.z);
int width, height, nrChannels;
for (unsigned int i = 0; i < faces.size(); i++)
{
unsigned char* data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
);
stbi_image_free(data);
}
else
{
std::cout << "Cubemap tex failed to load at path: " << faces[i] << std::endl;
stbi_image_free(data);
}
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
return textureID;
glm::mat4 transformation = perspectiveMatrix * cameraMatrix * modelMatrix;
glUniformMatrix4fv(glGetUniformLocation(program, "modelViewProjectionMatrix"), 1, GL_FALSE, (float*)&transformation);
glUniformMatrix4fv(glGetUniformLocation(program, "modelMatrix"), 1, GL_FALSE, (float*)&modelMatrix);
}
std::vector<std::string> faces =
void drawObjectFromMesh(glm::mat4 modelMatrix, GLuint shader, int flag)
{
"textures/right.jpg",
"textures/left.jpg",
"textures/top.jpg",
"textures/bottom.jpg",
"textures/front.jpg",
"textures/back.jpg"
};
unsigned int cubemapTexture = loadCubemap(faces);
if (flag == 3)
{
GLuint program = shader;
glm::vec3 color = glm::vec3(1.0f, 0.5f, 0.2f);
glUseProgram(program);
glUniform3f(glGetUniformLocation(program, "objectColor"), color.x, color.y, color.z);
glm::mat4 transformation = perspectiveMatrix * cameraMatrix * modelMatrix * trans;
glUniformMatrix4fv(glGetUniformLocation(program, "transformation"), 1, GL_FALSE, (float*)&transformation);
//glUniform3f(glGetUniformLocation(program, "lightDir"), lightDir[0], lightDir[1], lightDir[2]);
glUniformMatrix4fv(glGetUniformLocation(program, "model"), 1, GL_FALSE, (float*)&modelMatrix);
glUniform3f(glGetUniformLocation(program, "cameraPos"), cameraPos[0], cameraPos[1], cameraPos[2]);
glUseProgram(0);
}
else
{
GLuint program = shader;
glUseProgram(program);
setUpUniforms(program, modelMatrix);//modelMatrix pozycja poczatkowa
glUseProgram(0);
}
}
void renderScene()
@ -234,23 +259,22 @@ void renderScene()
cameraMatrix = createCameraMatrix();
perspectiveMatrix = Core::createPerspectiveMatrix();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glm::mat4 shipInitialTransformation = glm::translate(glm::vec3(0,-1.0f,-1.0f)) * glm::rotate(glm::radians(180.0f), glm::vec3(0,1,0)) * glm::scale(glm::vec3(0.25f));
// SKYBOX
glm::mat4 view = glm::mat4(glm::mat3(cameraMatrix));
drawSkyBox(perspectiveMatrix, view, programSkyBox, skyboxVAO, skyboxVBO, cubemapTexture);
//drawObjectFromMesh(shipModelMatrix, testShader, 1);
glm::mat4 shipInitialTransformation = glm::translate(glm::vec3(0, -1.0f, -1.0f)) * glm::rotate(glm::radians(180.0f), glm::vec3(0, 1, 0)) * glm::scale(glm::vec3(0.25f));
glm::mat4 shipModelMatrix = glm::translate(cameraPos + cameraDir * 0.5f) * glm::mat4_cast(glm::inverse(rotationX * rotationY * rotationZ)) * shipInitialTransformation;
drawObjectTexture(sharkModel, shipModelMatrix, sharkTexture);
drawObjectTexture(sharkModel, glm::mat4() * glm::scale(glm::vec3(0.25f)), sharkTexture);
drawBubbles();
//drawObjectColor(sphereContext, glm::translate(glm::vec3(1.0f, 0.0f, 0.0f)), glm::vec4(0.0f, 0.0f, 1.0f, 0.3f));
//for (auto& coords : planetsCoords) {
// drawObjectColor(sphereContext, glm::translate(coords), coords);
//}
glutSwapBuffers();
}
@ -283,28 +307,29 @@ void initBubbles() {
);
}
void init()
{
srand(time(0));
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_CULL_FACE);
programColor = shaderLoader.CreateProgram("shaders/shader_color.vert", "shaders/shader_color.frag");
programTexture = shaderLoader.CreateProgram("shaders/shader_tex.vert", "shaders/shader_tex.frag");
loadModelToContext("models/orca.obj", sharkModel);
loadModelToContext("models/sphere.obj", sphereContext);
sharkTexture = Core::LoadTexture("textures/Orca_Diffuse.jpg");
initBubbles();
testShader = shaderLoader.CreateProgram("shaders/shader_test.vert", "shaders/shader_test.frag");
programSkyBox = shaderLoader.CreateProgram("shaders/shader_cube.vert", "shaders/shader_cube.frag");
loadSkybox();
programColor = shaderLoader.CreateProgram("shaders/shader_color.vert", "shaders/shader_color.frag");
programTexture = shaderLoader.CreateProgram("shaders/shader_tex.vert", "shaders/shader_tex.frag");
loadModelToContext("models/orca.obj", sharkModel);
loadModelToContext("models/sphere.obj", sphereContext);
sharkTexture = Core::LoadTexture("textures/Orca_Diffuse.jpg");
initBubbles();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 KiB

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 588 KiB

After

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 KiB

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 292 KiB