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)
|
shaders/post.frag)
|
||||||
|
|
||||||
add_data(Graph
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
|
@ -11,6 +12,9 @@ struct Vertex {
|
||||||
|
|
||||||
class Mesh {
|
class Mesh {
|
||||||
public:
|
public:
|
||||||
|
std::string name;
|
||||||
|
glm::vec3 position;
|
||||||
|
|
||||||
std::vector<Vertex> vertices;
|
std::vector<Vertex> vertices;
|
||||||
std::vector<uint32_t> indices;
|
std::vector<uint32_t> indices;
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ public:
|
||||||
RenderTarget* createSurfaceRenderTarget(VkSurfaceKHR surface, RenderTarget* oldTarget = nullptr);
|
RenderTarget* createSurfaceRenderTarget(VkSurfaceKHR surface, RenderTarget* oldTarget = nullptr);
|
||||||
void destroyRenderTarget(RenderTarget* target);
|
void destroyRenderTarget(RenderTarget* target);
|
||||||
|
|
||||||
void takeScreenshot(RenderTarget* target);
|
void takeScreenshot(const char* path, RenderTarget* target);
|
||||||
|
|
||||||
VkShaderModule createShader(const char* path);
|
VkShaderModule createShader(const char* path);
|
||||||
|
|
||||||
|
|
210
src/main.cpp
210
src/main.cpp
|
@ -14,8 +14,11 @@
|
||||||
#include "mesh.h"
|
#include "mesh.h"
|
||||||
#include "light.h"
|
#include "light.h"
|
||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
|
#include "cinematic.h"
|
||||||
|
#include "json.hpp"
|
||||||
|
|
||||||
SDL_Window* window = nullptr;
|
SDL_Window* window = nullptr;
|
||||||
|
Renderer* renderer = nullptr;
|
||||||
|
|
||||||
std::vector<const char*> platform::getRequiredExtensions() {
|
std::vector<const char*> platform::getRequiredExtensions() {
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
|
@ -94,30 +97,9 @@ void writeConfig() {
|
||||||
file << "fullscreen=" << windowFullscreen << "\n";
|
file << "fullscreen=" << windowFullscreen << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
Mesh* loadMesh(const char* path) {
|
||||||
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);
|
|
||||||
|
|
||||||
Assimp::Importer importer;
|
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];
|
aiMesh* m = scene->mMeshes[0];
|
||||||
Mesh* mesh = new Mesh();
|
Mesh* mesh = new Mesh();
|
||||||
|
@ -138,8 +120,107 @@ int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
renderer->fillMeshBuffers(mesh);
|
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 world;
|
||||||
world.meshes.push_back(mesh);
|
|
||||||
|
|
||||||
auto light = new Light();
|
auto light = new Light();
|
||||||
light->position.y = 5;
|
light->position.y = 5;
|
||||||
|
@ -147,9 +228,16 @@ int main(int argc, char* argv[]) {
|
||||||
world.lights.push_back(light);
|
world.lights.push_back(light);
|
||||||
|
|
||||||
Camera camera;
|
Camera camera;
|
||||||
camera.position.y = 1;
|
|
||||||
camera.position.z = 3;
|
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;
|
bool running = true;
|
||||||
while(running) {
|
while(running) {
|
||||||
SDL_Event event = {};
|
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) {
|
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);
|
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;
|
delete light;
|
||||||
|
|
||||||
renderer->destroyMeshBuffers(mesh);
|
|
||||||
|
|
||||||
delete mesh;
|
|
||||||
|
|
||||||
renderer->destroyRenderTarget(target);
|
renderer->destroyRenderTarget(target);
|
||||||
|
|
||||||
vkDestroySurfaceKHR(renderer->getInstance(), surface, nullptr);
|
vkDestroySurfaceKHR(renderer->getInstance(), surface, nullptr);
|
||||||
|
|
|
@ -404,7 +404,7 @@ void Renderer::destroyRenderTarget(RenderTarget* target) {
|
||||||
delete target;
|
delete target;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::takeScreenshot(RenderTarget* target) {
|
void Renderer::takeScreenshot(const char* path, RenderTarget* target) {
|
||||||
VkImageCreateInfo imageCreateInfo = {};
|
VkImageCreateInfo imageCreateInfo = {};
|
||||||
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||||
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
|
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||||
|
@ -541,7 +541,7 @@ void Renderer::takeScreenshot(RenderTarget* target) {
|
||||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||||
imageMemoryBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
imageMemoryBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||||
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
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.image = srcImage;
|
||||||
imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
imageMemoryBarrier.subresourceRange.layerCount = 1;
|
imageMemoryBarrier.subresourceRange.layerCount = 1;
|
||||||
|
@ -587,7 +587,7 @@ void Renderer::takeScreenshot(RenderTarget* target) {
|
||||||
vkMapMemory(device_, imageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&data);
|
vkMapMemory(device_, imageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&data);
|
||||||
data += subResourceLayout.offset;
|
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";
|
file << "P6\n" << target->extent.width << "\n" << target->extent.height << "\n" << 255 << "\n";
|
||||||
|
|
||||||
for(uint32_t y = 0; y < target->extent.height; y++) {
|
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;
|
glm::mat4 mvp;
|
||||||
mvp = glm::perspective(glm::radians(75.0f), (float)target->extent.width / target->extent.height, 0.1f, 100.0f);
|
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::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);
|
vkCmdPushConstants(commandBuffer, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &mvp);
|
||||||
|
|
||||||
|
|
Reference in a new issue