Add cinematic engine
This commit is contained in:
parent
918520cc9d
commit
491a9b67c7
9 changed files with 19248 additions and 36 deletions
|
@ -31,4 +31,5 @@ add_shaders(Graph
|
|||
shaders/post.frag)
|
||||
|
||||
add_data(Graph
|
||||
data/suzanne.obj)
|
||||
data/suzanne.obj
|
||||
data/test.cim)
|
||||
|
|
96
data/test.cim
Normal file
96
data/test.cim
Normal file
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"shots": [
|
||||
{
|
||||
"start": 0,
|
||||
"end": 5,
|
||||
"meshes": [
|
||||
{
|
||||
"name": "suzanne",
|
||||
"path": "data/suzanne.obj"
|
||||
},
|
||||
{
|
||||
"name": "suzanne2",
|
||||
"path": "data/suzanne.obj"
|
||||
}
|
||||
],
|
||||
"animations": [
|
||||
{
|
||||
"target": "suzanne",
|
||||
"property": "position",
|
||||
"keyframes": [
|
||||
{
|
||||
"time": 0,
|
||||
"value": "0,0,0"
|
||||
},
|
||||
{
|
||||
"time": 1,
|
||||
"value": "0,2,-3.5"
|
||||
},
|
||||
{
|
||||
"time": 3,
|
||||
"value": "0,0.5,-4"
|
||||
},
|
||||
{
|
||||
"time": 5,
|
||||
"value": "0,0,-6"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "suzanne2",
|
||||
"property": "position",
|
||||
"keyframes": [
|
||||
{
|
||||
"time": 0,
|
||||
"value": "5,0,-2"
|
||||
},
|
||||
{
|
||||
"time": 2,
|
||||
"value": "-5,0,-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "camera",
|
||||
"property": "position",
|
||||
"keyframes": [
|
||||
{
|
||||
"time": 0,
|
||||
"value": "1,3,1"
|
||||
},
|
||||
{
|
||||
"time": 3,
|
||||
"value": "4,4,4"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"start": 5,
|
||||
"end": 10,
|
||||
"meshes": [
|
||||
{
|
||||
"name": "suzanne",
|
||||
"path": "data/suzanne.obj"
|
||||
}
|
||||
],
|
||||
"animations": [
|
||||
{
|
||||
"target": "camera",
|
||||
"property": "position",
|
||||
"keyframes": [
|
||||
{
|
||||
"time": 5,
|
||||
"value": "-5,0,2"
|
||||
},
|
||||
{
|
||||
"time": 10,
|
||||
"value": "5,0,2"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
34
include/cinematic.h
Normal file
34
include/cinematic.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class Mesh;
|
||||
|
||||
struct Keyframe {
|
||||
int time = 0;
|
||||
glm::vec3 value = glm::vec3(0);
|
||||
};
|
||||
|
||||
enum class AnimationProperty {
|
||||
Position
|
||||
};
|
||||
|
||||
struct Animation {
|
||||
Mesh* target = nullptr;
|
||||
AnimationProperty property = AnimationProperty::Position;
|
||||
|
||||
std::vector<Keyframe> keyframes;
|
||||
};
|
||||
|
||||
struct Shot {
|
||||
int start = 0, end = 0;
|
||||
|
||||
std::vector<Mesh*> meshes;
|
||||
std::vector<Animation*> animations;
|
||||
};
|
||||
|
||||
class Cinematic {
|
||||
public:
|
||||
std::vector<Shot*> shots;
|
||||
};
|
18928
include/json.hpp
Normal file
18928
include/json.hpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <glm/glm.hpp>
|
||||
#include <vulkan/vulkan.h>
|
||||
|
@ -11,6 +12,9 @@ struct Vertex {
|
|||
|
||||
class Mesh {
|
||||
public:
|
||||
std::string name;
|
||||
glm::vec3 position;
|
||||
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<uint32_t> indices;
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ public:
|
|||
RenderTarget* createSurfaceRenderTarget(VkSurfaceKHR surface, RenderTarget* oldTarget = nullptr);
|
||||
void destroyRenderTarget(RenderTarget* target);
|
||||
|
||||
void takeScreenshot(RenderTarget* target);
|
||||
void takeScreenshot(const char* path, RenderTarget* target);
|
||||
|
||||
VkShaderModule createShader(const char* path);
|
||||
|
||||
|
|
210
src/main.cpp
210
src/main.cpp
|
@ -14,8 +14,11 @@
|
|||
#include "mesh.h"
|
||||
#include "light.h"
|
||||
#include "camera.h"
|
||||
#include "cinematic.h"
|
||||
#include "json.hpp"
|
||||
|
||||
SDL_Window* window = nullptr;
|
||||
Renderer* renderer = nullptr;
|
||||
|
||||
std::vector<const char*> platform::getRequiredExtensions() {
|
||||
uint32_t count = 0;
|
||||
|
@ -94,30 +97,9 @@ void writeConfig() {
|
|||
file << "fullscreen=" << windowFullscreen << "\n";
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
readConfig();
|
||||
|
||||
window = SDL_CreateWindow("Graph",
|
||||
windowX,
|
||||
windowY,
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
SDL_WINDOW_VULKAN |
|
||||
SDL_WINDOW_RESIZABLE);
|
||||
if(window == nullptr)
|
||||
return -1;
|
||||
|
||||
SDL_SetWindowFullscreen(window, windowFullscreen == 1 ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||
|
||||
auto renderer = new Renderer();
|
||||
|
||||
VkSurfaceKHR surface = nullptr;
|
||||
SDL_Vulkan_CreateSurface(window, renderer->getInstance(), &surface);
|
||||
|
||||
RenderTarget* target = renderer->createSurfaceRenderTarget(surface);
|
||||
|
||||
Mesh* loadMesh(const char* path) {
|
||||
Assimp::Importer importer;
|
||||
const aiScene* scene = importer.ReadFile("data/suzanne.obj", aiProcess_Triangulate);
|
||||
const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate);
|
||||
|
||||
aiMesh* m = scene->mMeshes[0];
|
||||
Mesh* mesh = new Mesh();
|
||||
|
@ -138,8 +120,107 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
renderer->fillMeshBuffers(mesh);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
std::vector<std::string> tokenize(const std::string& str) {
|
||||
size_t lastPos = str.find_first_not_of(',', 0);
|
||||
size_t pos = str.find_first_of(',', lastPos);
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
while(pos != std::string::npos || lastPos != std::string::npos) {
|
||||
tokens.push_back(str.substr(lastPos, pos - lastPos));
|
||||
|
||||
lastPos = str.find_first_not_of(',', pos);
|
||||
pos = str.find_first_of(',', lastPos);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
Cinematic* loadCinematic(const char* path) {
|
||||
std::ifstream file(path);
|
||||
if(!file)
|
||||
return nullptr;
|
||||
|
||||
nlohmann::json json;
|
||||
file >> json;
|
||||
|
||||
Cinematic* cinematic = new Cinematic();
|
||||
for(auto shotObject : json["shots"]) {
|
||||
Shot* shot = new Shot();
|
||||
shot->start = shotObject["start"];
|
||||
shot->end = shotObject["end"];
|
||||
|
||||
for(auto meshObject : shotObject["meshes"]) {
|
||||
Mesh* mesh = loadMesh(meshObject["path"].get<std::string>().c_str());
|
||||
mesh->name = meshObject["name"];
|
||||
|
||||
shot->meshes.push_back(mesh);
|
||||
}
|
||||
|
||||
for(auto animationObject : shotObject["animations"]) {
|
||||
Animation* animation = new Animation();
|
||||
for(auto mesh : shot->meshes) {
|
||||
if(mesh->name == animationObject["target"])
|
||||
animation->target = mesh;
|
||||
}
|
||||
|
||||
const auto property = animationObject["property"];
|
||||
if(property == "position")
|
||||
animation->property = AnimationProperty::Position;
|
||||
|
||||
for(auto keyframeObject : animationObject["keyframes"]) {
|
||||
Keyframe keyframe;
|
||||
keyframe.time = keyframeObject["time"];
|
||||
|
||||
auto tokens = tokenize(keyframeObject["value"]);
|
||||
|
||||
keyframe.value[0] = atof(tokens[0].c_str());
|
||||
keyframe.value[1] = atof(tokens[1].c_str());
|
||||
keyframe.value[2] = atof(tokens[2].c_str());
|
||||
|
||||
animation->keyframes.push_back(keyframe);
|
||||
}
|
||||
|
||||
shot->animations.push_back(animation);
|
||||
}
|
||||
|
||||
cinematic->shots.push_back(shot);
|
||||
}
|
||||
|
||||
return cinematic;
|
||||
}
|
||||
|
||||
Cinematic* cinematic = nullptr;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
readConfig();
|
||||
|
||||
bool cinematicMode = false;
|
||||
if(argc > 2 && strcmp(argv[1], "--cinematic") == 0)
|
||||
cinematicMode = true;
|
||||
|
||||
window = SDL_CreateWindow("Graph",
|
||||
windowX,
|
||||
windowY,
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
SDL_WINDOW_VULKAN |
|
||||
SDL_WINDOW_RESIZABLE);
|
||||
if(window == nullptr)
|
||||
return -1;
|
||||
|
||||
SDL_SetWindowFullscreen(window, windowFullscreen == 1 ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||
|
||||
renderer = new Renderer();
|
||||
|
||||
VkSurfaceKHR surface = nullptr;
|
||||
SDL_Vulkan_CreateSurface(window, renderer->getInstance(), &surface);
|
||||
|
||||
RenderTarget* target = renderer->createSurfaceRenderTarget(surface);
|
||||
|
||||
World world;
|
||||
world.meshes.push_back(mesh);
|
||||
|
||||
auto light = new Light();
|
||||
light->position.y = 5;
|
||||
|
@ -147,9 +228,16 @@ int main(int argc, char* argv[]) {
|
|||
world.lights.push_back(light);
|
||||
|
||||
Camera camera;
|
||||
camera.position.y = 1;
|
||||
camera.position.z = 3;
|
||||
|
||||
if(cinematicMode)
|
||||
cinematic = loadCinematic(argv[2]);
|
||||
else
|
||||
world.meshes.push_back(loadMesh("data/suzanne.obj"));
|
||||
|
||||
float currentTime = 0.0f, lastTime = 0.0f;
|
||||
Shot* currentShot = nullptr;
|
||||
|
||||
bool running = true;
|
||||
while(running) {
|
||||
SDL_Event event = {};
|
||||
|
@ -175,21 +263,81 @@ int main(int argc, char* argv[]) {
|
|||
}
|
||||
|
||||
if(event.type == SDL_KEYDOWN && event.key.keysym.scancode == SDL_SCANCODE_F12) {
|
||||
renderer->takeScreenshot(target);
|
||||
renderer->takeScreenshot("screenshot.ppm", target);
|
||||
}
|
||||
}
|
||||
|
||||
camera.position.x = sin(platform::getTime() / 500.0f);
|
||||
if(cinematicMode) {
|
||||
float endTime = 0.0f;
|
||||
for(auto shot : cinematic->shots) {
|
||||
if(shot->end > endTime)
|
||||
endTime = shot->end;
|
||||
|
||||
if(currentTime >= shot->start && currentTime < shot->end && currentShot != shot) {
|
||||
if(currentShot != nullptr) {
|
||||
world.meshes.clear();
|
||||
}
|
||||
|
||||
for(auto mesh : shot->meshes)
|
||||
world.meshes.push_back(mesh);
|
||||
|
||||
currentShot = shot;
|
||||
}
|
||||
}
|
||||
|
||||
// we have reached the end of the cinematic
|
||||
if(currentTime >= endTime) {
|
||||
currentShot = nullptr;
|
||||
|
||||
world.meshes.clear();
|
||||
}
|
||||
|
||||
if(currentShot != nullptr) {
|
||||
for(auto animation : currentShot->animations) {
|
||||
unsigned int frameIndex = 0;
|
||||
for(size_t i = 0; i < animation->keyframes.size(); i++) {
|
||||
if(currentTime < animation->keyframes[i + 1].time) {
|
||||
frameIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto currentFrame = animation->keyframes[frameIndex];
|
||||
const auto nextFrame = animation->keyframes[(frameIndex + 1) % animation->keyframes.size()];
|
||||
|
||||
const float delta = (currentTime - currentFrame.time) / (nextFrame.time - currentFrame.time);
|
||||
|
||||
glm::vec3 pos = currentFrame.value + delta * (nextFrame.value - currentFrame.value);
|
||||
|
||||
if(animation->target != nullptr)
|
||||
animation->target->position = pos;
|
||||
else
|
||||
camera.position = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderer->render(world, camera, target);
|
||||
|
||||
if(cinematicMode) {
|
||||
currentTime += ((SDL_GetTicks() / 1000.0f) - lastTime);
|
||||
lastTime = currentTime;
|
||||
|
||||
static int frameNum = 0;
|
||||
std::string screenshotName = "frame" + std::to_string(frameNum) + ".ppm";
|
||||
frameNum++;
|
||||
|
||||
renderer->takeScreenshot(screenshotName.c_str(), target);
|
||||
}
|
||||
}
|
||||
|
||||
if(cinematic == nullptr) {
|
||||
renderer->destroyMeshBuffers(world.meshes[0]);
|
||||
delete world.meshes[0];
|
||||
}
|
||||
|
||||
delete light;
|
||||
|
||||
renderer->destroyMeshBuffers(mesh);
|
||||
|
||||
delete mesh;
|
||||
|
||||
renderer->destroyRenderTarget(target);
|
||||
|
||||
vkDestroySurfaceKHR(renderer->getInstance(), surface, nullptr);
|
||||
|
|
|
@ -404,7 +404,7 @@ void Renderer::destroyRenderTarget(RenderTarget* target) {
|
|||
delete target;
|
||||
}
|
||||
|
||||
void Renderer::takeScreenshot(RenderTarget* target) {
|
||||
void Renderer::takeScreenshot(const char* path, RenderTarget* target) {
|
||||
VkImageCreateInfo imageCreateInfo = {};
|
||||
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
|
@ -541,7 +541,7 @@ void Renderer::takeScreenshot(RenderTarget* target) {
|
|||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
imageMemoryBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
imageMemoryBarrier.image = srcImage;
|
||||
imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
imageMemoryBarrier.subresourceRange.layerCount = 1;
|
||||
|
@ -587,7 +587,7 @@ void Renderer::takeScreenshot(RenderTarget* target) {
|
|||
vkMapMemory(device_, imageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&data);
|
||||
data += subResourceLayout.offset;
|
||||
|
||||
std::ofstream file("screenshot.ppm", std::ios::out | std::ios::binary);
|
||||
std::ofstream file(path, std::ios::out | std::ios::binary);
|
||||
file << "P6\n" << target->extent.width << "\n" << target->extent.height << "\n" << 255 << "\n";
|
||||
|
||||
for(uint32_t y = 0; y < target->extent.height; y++) {
|
||||
|
|
|
@ -67,6 +67,7 @@ void WorldPass::render(VkCommandBuffer commandBuffer, World& world, Camera& came
|
|||
glm::mat4 mvp;
|
||||
mvp = glm::perspective(glm::radians(75.0f), (float)target->extent.width / target->extent.height, 0.1f, 100.0f);
|
||||
mvp *= glm::lookAt(camera.position, camera.target, glm::vec3(0, -1, 0));
|
||||
mvp = glm::translate(mvp, mesh->position);
|
||||
|
||||
vkCmdPushConstants(commandBuffer, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &mvp);
|
||||
|
||||
|
|
Reference in a new issue