Add skybox and crude IBL approximation
20
TODO.txt
@ -3,15 +3,13 @@
|
||||
vulkan/device.cpp: report what is missing when a device fails to be found.
|
||||
Only select physical device 0 to simplify code.
|
||||
|
||||
Supported different event 'kinds' (postPhysics, preRender, postRender etc)
|
||||
Add support for multiple lights.
|
||||
|
||||
TODO now: the collision system doesn't use the "isTrigger" bool properly.
|
||||
Add support for simple shadow mapping.
|
||||
+ cascaded shadow mapping
|
||||
|
||||
Add support for shadows and other complex lighting. Also add post-processing.
|
||||
|
||||
Add AABB colliders, sphere colliders, and mesh colliders.
|
||||
|
||||
Support animations and skinned meshes.
|
||||
Support animations.
|
||||
+ skinned meshes / morph targets
|
||||
|
||||
At some point, add game controller support. Make sure it works well with the
|
||||
InputManager class.
|
||||
@ -38,3 +36,11 @@ Place all instances of a particular component in contiguous memory: I.e., a
|
||||
scene holds many std::vectors, one for each type of component. These vectors are
|
||||
looped through every frame. This should optimise things by improving the memory
|
||||
layout of the program, significantly reducing cache misses.
|
||||
|
||||
Implemented glTF file loader
|
||||
|
||||
Added a PBR shader with albedo, normal, metallic, roughness, and AO textures
|
||||
|
||||
Added the BVH AABB tree made in Summer to start a much better Collision system.
|
||||
|
||||
The CameraControllerSystem now uses raycasting to enable FPS-style player movement.
|
@ -31,7 +31,8 @@ enum class MSAALevel {
|
||||
};
|
||||
|
||||
struct GraphicsSettings {
|
||||
GraphicsSettings() {
|
||||
GraphicsSettings()
|
||||
{
|
||||
// sane defaults
|
||||
enable_validation = true;
|
||||
vsync = true;
|
||||
@ -74,12 +75,7 @@ enum class Primitive {
|
||||
kTriangleStrip,
|
||||
};
|
||||
|
||||
enum class CullMode {
|
||||
kCullNone,
|
||||
kCullFront,
|
||||
kCullBack,
|
||||
kCullFrontAndBack
|
||||
};
|
||||
enum class CullMode { kCullNone, kCullFront, kCullBack, kCullFrontAndBack };
|
||||
|
||||
enum class VertexAttribFormat { kFloat2, kFloat3, kFloat4 };
|
||||
|
||||
@ -88,6 +84,12 @@ enum class Filter : int {
|
||||
kNearest,
|
||||
};
|
||||
|
||||
enum class WrapMode : int {
|
||||
kRepeat,
|
||||
kMirroredRepeat,
|
||||
kClampToEdge,
|
||||
};
|
||||
|
||||
enum class DescriptorType {
|
||||
kUniformBuffer,
|
||||
kCombinedImageSampler,
|
||||
@ -102,9 +104,7 @@ typedef std::underlying_type<Bits>::type Flags;
|
||||
} // namespace ShaderStageFlags
|
||||
|
||||
struct VertexAttribDescription {
|
||||
VertexAttribDescription(uint32_t location, VertexAttribFormat format,
|
||||
uint32_t offset)
|
||||
: location(location), format(format), offset(offset) {}
|
||||
VertexAttribDescription(uint32_t location, VertexAttribFormat format, uint32_t offset) : location(location), format(format), offset(offset) {}
|
||||
uint32_t location; // the index to use in the shader
|
||||
VertexAttribFormat format;
|
||||
uint32_t offset;
|
||||
@ -135,6 +135,9 @@ struct SamplerInfo {
|
||||
Filter minify = gfx::Filter::kLinear;
|
||||
Filter magnify = gfx::Filter::kLinear;
|
||||
Filter mipmap = gfx::Filter::kLinear;
|
||||
WrapMode wrap_u = gfx::WrapMode::kRepeat;
|
||||
WrapMode wrap_v = gfx::WrapMode::kRepeat;
|
||||
WrapMode wrap_w = gfx::WrapMode::kRepeat; // only useful for cubemaps AFAIK
|
||||
bool anisotropic_filtering = true; // this can be force disabled by a global setting
|
||||
|
||||
bool operator==(const SamplerInfo&) const = default;
|
||||
@ -146,16 +149,20 @@ struct SamplerInfo {
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<engine::gfx::SamplerInfo> {
|
||||
std::size_t operator()(const engine::gfx::SamplerInfo& k) const {
|
||||
std::size_t operator()(const engine::gfx::SamplerInfo& k) const
|
||||
{
|
||||
using std::hash;
|
||||
|
||||
size_t h1 = hash<int>()(static_cast<int>(k.minify));
|
||||
size_t h2 = hash<int>()(static_cast<int>(k.magnify));
|
||||
size_t h3 = hash<int>()(static_cast<int>(k.mipmap));
|
||||
size_t h4 = hash<bool>()(k.anisotropic_filtering);
|
||||
size_t h4 = hash<int>()(static_cast<int>(k.wrap_u));
|
||||
size_t h5 = hash<int>()(static_cast<int>(k.wrap_v));
|
||||
size_t h6 = hash<int>()(static_cast<int>(k.wrap_w));
|
||||
size_t h7 = hash<bool>()(k.anisotropic_filtering);
|
||||
|
||||
return ((h1 & 0xFF) << 24) | ((h2 & 0xFF) << 16) | ((h3 & 0xFF) << 8) |
|
||||
((h4 & 0xFF) << 0);
|
||||
return ((h1 & 0xFF) << 48) | ((h2 & 0xFF) << 40) | ((h3 & 0xFF) << 32) | ((h4 & 0xFF) << 24) | ((h5 & 0xFF) << 16) | ((h6 & 0xFF) << 8) |
|
||||
((h7 & 0xFF) << 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -104,6 +104,8 @@ class GFXDevice {
|
||||
|
||||
gfx::Image* CreateImage(uint32_t w, uint32_t h, gfx::ImageFormat input_format, const void* image_data);
|
||||
|
||||
gfx::Image* CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageFormat input_format, const std::array<const void*, 6>& image_data);
|
||||
|
||||
void DestroyImage(const gfx::Image* image);
|
||||
|
||||
const gfx::Sampler* CreateSampler(const gfx::SamplerInfo& info);
|
||||
|
@ -58,7 +58,7 @@ class Renderer : private ApplicationComponent {
|
||||
|
||||
struct CameraSettings {
|
||||
float vertical_fov_radians = glm::radians(70.0f);
|
||||
float clip_near = 0.5f;
|
||||
float clip_near = 0.1f;
|
||||
float clip_far = 1000.0f;
|
||||
} camera_settings_;
|
||||
|
||||
@ -101,6 +101,11 @@ class Renderer : private ApplicationComponent {
|
||||
// shader will take 2 clip space xyzw coords as push constants to define the line
|
||||
} debug_rendering_things_{};
|
||||
|
||||
gfx::Image* skybox_cubemap = nullptr;
|
||||
const gfx::Sampler* skybox_sampler = nullptr;
|
||||
const gfx::Pipeline* skybox_pipeline = nullptr;
|
||||
const gfx::Buffer* skybox_buffer = nullptr;
|
||||
|
||||
void DrawRenderList(gfx::DrawBuffer* draw_buffer, const RenderList& render_list);
|
||||
};
|
||||
|
||||
|
@ -19,8 +19,7 @@ class Material {
|
||||
|
||||
void SetAlbedoTexture(std::shared_ptr<Texture> texture);
|
||||
void SetNormalTexture(std::shared_ptr<Texture> texture);
|
||||
void SetOcclusionTexture(std::shared_ptr<Texture> texture);
|
||||
void SetMetallicRoughnessTexture(std::shared_ptr<Texture> texture);
|
||||
void SetOcclusionRoughnessMetallicTexture(std::shared_ptr<Texture> texture);
|
||||
|
||||
const gfx::DescriptorSet* GetDescriptorSet() { return material_set_; }
|
||||
Shader* GetShader() { return shader_.get(); }
|
||||
@ -29,8 +28,7 @@ class Material {
|
||||
const std::shared_ptr<Shader> shader_;
|
||||
std::shared_ptr<Texture> texture_albedo_;
|
||||
std::shared_ptr<Texture> texture_normal_;
|
||||
std::shared_ptr<Texture> texture_occlusion_;
|
||||
std::shared_ptr<Texture> texture_metallic_roughness_;
|
||||
std::shared_ptr<Texture> texture_occlusion_roughness_metallic_;
|
||||
|
||||
const gfx::DescriptorSet* material_set_ = nullptr;
|
||||
|
||||
|
@ -3,15 +3,19 @@
|
||||
#define PI 3.1415926535897932384626433832795
|
||||
#define PI_INV 0.31830988618379067153776752674503
|
||||
|
||||
layout(set = 0, binding = 1) uniform samplerCube globalSetSkybox;
|
||||
|
||||
layout(set = 2, binding = 0) uniform sampler2D materialSetAlbedoSampler;
|
||||
layout(set = 2, binding = 1) uniform sampler2D materialSetNormalSampler;
|
||||
layout(set = 2, binding = 2) uniform sampler2D materialSetOcclusionSampler;
|
||||
layout(set = 2, binding = 3) uniform sampler2D materialSetMetallicRoughnessSampler;
|
||||
layout(set = 2, binding = 2) uniform sampler2D materialSetOcclusionRoughnessMetallic;
|
||||
|
||||
layout(location = 0) in vec2 fragUV;
|
||||
layout(location = 1) in vec3 fragPosTangentSpace;
|
||||
layout(location = 2) in vec3 fragViewPosTangentSpace;
|
||||
layout(location = 3) in vec3 fragLightPosTangentSpace;
|
||||
layout(location = 4) in vec3 fragNormWorldSpace;
|
||||
layout(location = 5) in vec3 fragViewPosWorldSpace;
|
||||
layout(location = 6) in vec3 fragPosWorldSpace;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
@ -28,21 +32,24 @@ float GGXDist(float alpha_2, float N_dot_H) {
|
||||
|
||||
void main() {
|
||||
|
||||
const vec3 metallic_roughness = vec3(texture(materialSetMetallicRoughnessSampler, fragUV));
|
||||
const float metallic = metallic_roughness.b;
|
||||
const float roughness = metallic_roughness.g; // roughness of zero is completely black?
|
||||
const vec3 occlusion_roughness_metallic = vec3(texture(materialSetOcclusionRoughnessMetallic, fragUV));
|
||||
const float ao = occlusion_roughness_metallic.r;
|
||||
const float roughness = occlusion_roughness_metallic.g;
|
||||
const float metallic = occlusion_roughness_metallic.b;
|
||||
|
||||
const vec3 I = normalize(fragPosWorldSpace - fragViewPosWorldSpace);
|
||||
const vec3 R = reflect(I, fragNormWorldSpace);
|
||||
|
||||
const float roughness_2 = roughness * roughness;
|
||||
|
||||
const vec3 light_colour = vec3(1.0, 1.0, 1.0) * 2.4;
|
||||
const vec3 emission = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
const float ao = texture(materialSetOcclusionSampler, fragUV).r;
|
||||
|
||||
const vec3 albedo = vec3(texture(materialSetAlbedoSampler, fragUV));
|
||||
const vec3 N = GetNormal();
|
||||
|
||||
const vec3 V = normalize(fragViewPosTangentSpace - fragPosTangentSpace);
|
||||
const vec3 L = normalize(fragLightPosTangentSpace - fragPosTangentSpace);
|
||||
const vec3 L = normalize(fragLightPosTangentSpace);
|
||||
//const vec3 L = normalize(vec3(5.0, 0.0, 3.0));
|
||||
const vec3 H = normalize(V + L);
|
||||
|
||||
@ -69,7 +76,10 @@ void main() {
|
||||
|
||||
const vec3 brdf = mix(dielectric_brdf, metal_brdf, metallic);
|
||||
|
||||
const vec3 lighting = brdf * light_colour * L_dot_N;
|
||||
vec3 lighting = brdf * light_colour * L_dot_N;
|
||||
|
||||
vec3 ambient_light = vec3(0.09082, 0.13281, 0.18164);
|
||||
lighting += mix(ambient_light, texture(globalSetSkybox, R).rgb, metallic) * ao * diffuse_brdf; // this is NOT physically-based, it just looks cool
|
||||
|
||||
outColor = vec4(min(emission + lighting, 1.0), 1.0);
|
||||
}
|
||||
|
@ -21,6 +21,9 @@ layout(location = 0) out vec2 fragUV;
|
||||
layout(location = 1) out vec3 fragPosTangentSpace;
|
||||
layout(location = 2) out vec3 fragViewPosTangentSpace;
|
||||
layout(location = 3) out vec3 fragLightPosTangentSpace;
|
||||
layout(location = 4) out vec3 fragNormWorldSpace;
|
||||
layout(location = 5) out vec3 fragViewPosWorldSpace;
|
||||
layout(location = 6) out vec3 fragPosWorldSpace;
|
||||
|
||||
void main() {
|
||||
vec4 worldPosition = constants.model * vec4(inPosition, 1.0);
|
||||
@ -34,7 +37,11 @@ void main() {
|
||||
fragUV = inUV;
|
||||
fragPosTangentSpace = worldToTangentSpace * vec3(worldPosition);
|
||||
fragViewPosTangentSpace = worldToTangentSpace * vec3(inverse(frameSetUniformBuffer.view) * vec4(0.0, 0.0, 0.0, 1.0));
|
||||
fragLightPosTangentSpace = worldToTangentSpace * vec3(10000.0, 0000.0, 20000.0);
|
||||
fragLightPosTangentSpace = worldToTangentSpace * vec3(-0.4278,0.7923,0.43502);
|
||||
|
||||
fragNormWorldSpace = N;
|
||||
fragViewPosWorldSpace = vec3(inverse(frameSetUniformBuffer.view) * vec4(0.0, 0.0, 0.0, 1.0));
|
||||
fragPosWorldSpace = worldPosition.xyz;
|
||||
|
||||
gl_Position.y *= -1.0;
|
||||
}
|
||||
|
@ -1,14 +1,11 @@
|
||||
#version 450
|
||||
|
||||
layout(set = 2, binding = 0) uniform sampler2D materialSetAlbedoSampler;
|
||||
layout(set = 2, binding = 1) uniform sampler2D materialSetNormalSampler;
|
||||
layout(set = 2, binding = 2) uniform sampler2D materialSetOcclusionSampler;
|
||||
layout(set = 2, binding = 3) uniform sampler2D materialSetMetallicRoughnessSampler;
|
||||
layout(set = 0, binding = 1) uniform samplerCube cubeSampler;
|
||||
|
||||
layout(location = 0) in vec2 fragUV;
|
||||
layout(location = 0) in vec3 fragPosition;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = texture(materialSetAlbedoSampler, fragUV);
|
||||
outColor = texture(cubeSampler, fragPosition);
|
||||
}
|
@ -8,20 +8,13 @@ layout(set = 1, binding = 0) uniform FrameSetUniformBuffer {
|
||||
mat4 view;
|
||||
} frameSetUniformBuffer;
|
||||
|
||||
layout( push_constant ) uniform Constants {
|
||||
mat4 model;
|
||||
} constants;
|
||||
|
||||
layout(location = 0) in vec3 inPosition;
|
||||
layout(location = 1) in vec3 inNorm;
|
||||
layout(location = 2) in vec4 inTangent;
|
||||
layout(location = 3) in vec2 inUV;
|
||||
|
||||
layout(location = 0) out vec2 fragUV;
|
||||
layout(location = 0) out vec3 fragPosition;
|
||||
|
||||
void main() {
|
||||
vec3 position = mat3(frameSetUniformBuffer.view) * vec3(constants.model * vec4(inPosition, 1.0));
|
||||
gl_Position = (globalSetUniformBuffer.proj * vec4(position, 0.0)).xyzz;
|
||||
fragUV = inUV;
|
||||
fragPosition = inPosition;
|
||||
gl_Position = (globalSetUniformBuffer.proj * vec4(mat3(frameSetUniformBuffer.view) * inPosition, 0.0)).xyzz;
|
||||
|
||||
gl_Position.y *= -1.0;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 140 B |
BIN
res/engine/textures/skybox0.jpg
Normal file
After Width: | Height: | Size: 266 KiB |
BIN
res/engine/textures/skybox1.jpg
Normal file
After Width: | Height: | Size: 296 KiB |
BIN
res/engine/textures/skybox2.jpg
Normal file
After Width: | Height: | Size: 364 KiB |
BIN
res/engine/textures/skybox3.jpg
Normal file
After Width: | Height: | Size: 228 KiB |
BIN
res/engine/textures/skybox4.jpg
Normal file
After Width: | Height: | Size: 164 KiB |
BIN
res/engine/textures/skybox5.jpg
Normal file
After Width: | Height: | Size: 154 KiB |
Before Width: | Height: | Size: 195 B |
@ -120,21 +120,6 @@ Application::Application(const char* appName, const char* appVersion, gfx::Graph
|
||||
std::make_unique<Shader>(renderer(), GetResourcePath("engine/shaders/fancy.vert"), GetResourcePath("engine/shaders/fancy.frag"), shaderSettings);
|
||||
GetResourceManager<Shader>()->AddPersistent("builtin.fancy", std::move(fancyShader));
|
||||
}
|
||||
{
|
||||
Shader::VertexParams vertParams{};
|
||||
vertParams.has_normal = true;
|
||||
vertParams.has_tangent = true;
|
||||
vertParams.has_uv0 = true;
|
||||
Shader::ShaderSettings shaderSettings{};
|
||||
shaderSettings.vertexParams = vertParams;
|
||||
shaderSettings.alpha_blending = false;
|
||||
shaderSettings.cull_backface = true;
|
||||
shaderSettings.write_z = false;
|
||||
shaderSettings.render_order = 1;
|
||||
auto skyboxShader =
|
||||
std::make_unique<Shader>(renderer(), GetResourcePath("engine/shaders/skybox.vert"), GetResourcePath("engine/shaders/skybox.frag"), shaderSettings);
|
||||
GetResourceManager<Shader>()->AddPersistent("builtin.skybox", std::move(skyboxShader));
|
||||
}
|
||||
|
||||
/* default textures */
|
||||
{
|
||||
@ -208,7 +193,8 @@ void Application::GameLoop()
|
||||
|
||||
struct DebugMenuState {
|
||||
bool menu_active = false;
|
||||
bool show_aabbs = false;
|
||||
bool show_entity_boxes = false;
|
||||
bool show_bounding_volumes = false;
|
||||
bool show_info_window = false;
|
||||
} debug_menu_state;
|
||||
|
||||
@ -246,7 +232,8 @@ void Application::GameLoop()
|
||||
if (ImGui::Begin("debugMenu", 0)) {
|
||||
ImGui::Text("Test!");
|
||||
ImGui::Text("FPS: %.3f", std::roundf(avg_fps));
|
||||
ImGui::Checkbox("Show AABBs?", &debug_menu_state.show_aabbs);
|
||||
ImGui::Checkbox("Show entity hitboxes?", &debug_menu_state.show_entity_boxes);
|
||||
ImGui::Checkbox("Show bounding volumes?", &debug_menu_state.show_bounding_volumes);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
@ -288,7 +275,7 @@ void Application::GameLoop()
|
||||
const RenderList* dynamic_list = nullptr;
|
||||
glm::mat4 camera_transform{1.0f};
|
||||
if (scene) {
|
||||
if (debug_menu_state.show_aabbs) {
|
||||
if (debug_menu_state.show_entity_boxes) {
|
||||
if (CollisionSystem* colsys = scene->GetSystem<CollisionSystem>()) {
|
||||
for (const auto& node : colsys->bvh_) {
|
||||
if (node.type1 == CollisionSystem::BiTreeNode::Type::Entity) {
|
||||
@ -354,6 +341,72 @@ void Application::GameLoop()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (debug_menu_state.show_bounding_volumes) {
|
||||
if (CollisionSystem* colsys = scene->GetSystem<CollisionSystem>()) {
|
||||
for (const auto& node : colsys->bvh_) {
|
||||
if (node.type1 == CollisionSystem::BiTreeNode::Type::BoundingVolume) {
|
||||
const glm::vec3 col =
|
||||
(node.type1 == CollisionSystem::BiTreeNode::Type::BoundingVolume) ? glm::vec3{ 1.0f, 0.0f, 0.0f } : glm::vec3{ 0.0f, 1.0f, 0.0f };
|
||||
Line line1{ glm::vec3{node.box1.min.x, node.box1.min.y, node.box1.min.z}, glm::vec3{node.box1.max.x, node.box1.min.y, node.box1.min.z}, col };
|
||||
debug_lines.push_back(line1);
|
||||
Line line2{ glm::vec3{node.box1.min.x, node.box1.min.y, node.box1.min.z}, glm::vec3{node.box1.min.x, node.box1.max.y, node.box1.min.z}, col };
|
||||
debug_lines.push_back(line2);
|
||||
Line line3{ glm::vec3{node.box1.max.x, node.box1.max.y, node.box1.min.z}, glm::vec3{node.box1.max.x, node.box1.min.y, node.box1.min.z}, col };
|
||||
debug_lines.push_back(line3);
|
||||
Line line4{ glm::vec3{node.box1.max.x, node.box1.max.y, node.box1.min.z}, glm::vec3{node.box1.min.x, node.box1.max.y, node.box1.min.z}, col };
|
||||
debug_lines.push_back(line4);
|
||||
|
||||
Line line5{ glm::vec3{node.box1.min.x, node.box1.min.y, node.box1.min.z}, glm::vec3{node.box1.min.x, node.box1.min.y, node.box1.max.z}, col };
|
||||
debug_lines.push_back(line5);
|
||||
Line line6{ glm::vec3{node.box1.min.x, node.box1.max.y, node.box1.min.z}, glm::vec3{node.box1.min.x, node.box1.max.y, node.box1.max.z}, col };
|
||||
debug_lines.push_back(line6);
|
||||
Line line7{ glm::vec3{node.box1.max.x, node.box1.min.y, node.box1.min.z}, glm::vec3{node.box1.max.x, node.box1.min.y, node.box1.max.z}, col };
|
||||
debug_lines.push_back(line7);
|
||||
Line line8{ glm::vec3{node.box1.max.x, node.box1.max.y, node.box1.min.z}, glm::vec3{node.box1.max.x, node.box1.max.y, node.box1.max.z}, col };
|
||||
debug_lines.push_back(line8);
|
||||
|
||||
Line line9{ glm::vec3{node.box1.min.x, node.box1.min.y, node.box1.max.z}, glm::vec3{node.box1.max.x, node.box1.min.y, node.box1.max.z}, col };
|
||||
debug_lines.push_back(line9);
|
||||
Line line10{ glm::vec3{node.box1.min.x, node.box1.min.y, node.box1.max.z}, glm::vec3{node.box1.min.x, node.box1.max.y, node.box1.max.z}, col };
|
||||
debug_lines.push_back(line10);
|
||||
Line line11{ glm::vec3{node.box1.max.x, node.box1.max.y, node.box1.max.z}, glm::vec3{node.box1.max.x, node.box1.min.y, node.box1.max.z}, col };
|
||||
debug_lines.push_back(line11);
|
||||
Line line12{ glm::vec3{node.box1.max.x, node.box1.max.y, node.box1.max.z}, glm::vec3{node.box1.min.x, node.box1.max.y, node.box1.max.z}, col };
|
||||
debug_lines.push_back(line12);
|
||||
}
|
||||
if (node.type2 == CollisionSystem::BiTreeNode::Type::BoundingVolume) {
|
||||
const glm::vec3 col =
|
||||
(node.type2 == CollisionSystem::BiTreeNode::Type::BoundingVolume) ? glm::vec3{ 1.0f, 0.0f, 0.0f } : glm::vec3{ 0.0f, 1.0f, 0.0f };
|
||||
Line line1{ glm::vec3{node.box2.min.x, node.box2.min.y, node.box2.min.z}, glm::vec3{node.box2.max.x, node.box2.min.y, node.box2.min.z}, col };
|
||||
debug_lines.push_back(line1);
|
||||
Line line2{ glm::vec3{node.box2.min.x, node.box2.min.y, node.box2.min.z}, glm::vec3{node.box2.min.x, node.box2.max.y, node.box2.min.z}, col };
|
||||
debug_lines.push_back(line2);
|
||||
Line line3{ glm::vec3{node.box2.max.x, node.box2.max.y, node.box2.min.z}, glm::vec3{node.box2.max.x, node.box2.min.y, node.box2.min.z}, col };
|
||||
debug_lines.push_back(line3);
|
||||
Line line4{ glm::vec3{node.box2.max.x, node.box2.max.y, node.box2.min.z}, glm::vec3{node.box2.min.x, node.box2.max.y, node.box2.min.z}, col };
|
||||
debug_lines.push_back(line4);
|
||||
|
||||
Line line5{ glm::vec3{node.box2.min.x, node.box2.min.y, node.box2.min.z}, glm::vec3{node.box2.min.x, node.box2.min.y, node.box2.max.z}, col };
|
||||
debug_lines.push_back(line5);
|
||||
Line line6{ glm::vec3{node.box2.min.x, node.box2.max.y, node.box2.min.z}, glm::vec3{node.box2.min.x, node.box2.max.y, node.box2.max.z}, col };
|
||||
debug_lines.push_back(line6);
|
||||
Line line7{ glm::vec3{node.box2.max.x, node.box2.min.y, node.box2.min.z}, glm::vec3{node.box2.max.x, node.box2.min.y, node.box2.max.z}, col };
|
||||
debug_lines.push_back(line7);
|
||||
Line line8{ glm::vec3{node.box2.max.x, node.box2.max.y, node.box2.min.z}, glm::vec3{node.box2.max.x, node.box2.max.y, node.box2.max.z}, col };
|
||||
debug_lines.push_back(line8);
|
||||
|
||||
Line line9{ glm::vec3{node.box2.min.x, node.box2.min.y, node.box2.max.z}, glm::vec3{node.box2.max.x, node.box2.min.y, node.box2.max.z}, col };
|
||||
debug_lines.push_back(line9);
|
||||
Line line10{ glm::vec3{node.box2.min.x, node.box2.min.y, node.box2.max.z}, glm::vec3{node.box2.min.x, node.box2.max.y, node.box2.max.z}, col };
|
||||
debug_lines.push_back(line10);
|
||||
Line line11{ glm::vec3{node.box2.max.x, node.box2.max.y, node.box2.max.z}, glm::vec3{node.box2.max.x, node.box2.min.y, node.box2.max.z}, col };
|
||||
debug_lines.push_back(line11);
|
||||
Line line12{ glm::vec3{node.box2.max.x, node.box2.max.y, node.box2.max.z}, glm::vec3{node.box2.min.x, node.box2.max.y, node.box2.max.z}, col };
|
||||
debug_lines.push_back(line12);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
camera_transform = scene->GetComponent<TransformComponent>(scene->GetEntity("camera"))->world_matrix;
|
||||
auto mesh_render_system = scene->GetSystem<MeshRenderSystem>();
|
||||
static_list = mesh_render_system->GetStaticRenderList();
|
||||
|
@ -173,6 +173,19 @@ static VkBufferUsageFlagBits getBufferUsageFlag(gfx::BufferType type)
|
||||
throw std::runtime_error("Unknown filter");
|
||||
}
|
||||
|
||||
[[maybe_unused]] static VkSamplerAddressMode getSamplerAddressMode(gfx::WrapMode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case gfx::WrapMode::kRepeat:
|
||||
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
case gfx::WrapMode::kMirroredRepeat:
|
||||
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
|
||||
case gfx::WrapMode::kClampToEdge:
|
||||
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
}
|
||||
throw std::runtime_error("Unknown wrap mode");
|
||||
}
|
||||
|
||||
[[maybe_unused]] static VkSamplerMipmapMode getSamplerMipmapMode(gfx::Filter filter)
|
||||
{
|
||||
switch (filter) {
|
||||
@ -1377,7 +1390,7 @@ void GFXDevice::DestroyBuffer(const gfx::Buffer* buffer)
|
||||
delete buffer;
|
||||
}
|
||||
|
||||
// imageData must have pixel format R8G8B8A8_SRGB
|
||||
// imageData must have pixel format R8G8B8A8
|
||||
gfx::Image* GFXDevice::CreateImage(uint32_t w, uint32_t h, gfx::ImageFormat input_format, const void* imageData)
|
||||
{
|
||||
assert(imageData != nullptr);
|
||||
@ -1624,6 +1637,263 @@ gfx::Image* GFXDevice::CreateImage(uint32_t w, uint32_t h, gfx::ImageFormat inpu
|
||||
return out;
|
||||
}
|
||||
|
||||
gfx::Image* GFXDevice::CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageFormat input_format, const std::array<const void*, 6>& image_data)
|
||||
{
|
||||
assert(image_data[0] != nullptr);
|
||||
assert(image_data[1] != nullptr);
|
||||
assert(image_data[2] != nullptr);
|
||||
assert(image_data[3] != nullptr);
|
||||
assert(image_data[4] != nullptr);
|
||||
assert(image_data[5] != nullptr);
|
||||
|
||||
if (pimpl->FRAMECOUNT != 0) abort(); // TODO. This is annoying
|
||||
|
||||
gfx::Image* out = new gfx::Image{};
|
||||
|
||||
uint32_t mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(w, h)))) + 1;
|
||||
VkFormat imageFormat = converters::getImageFormat(input_format);
|
||||
|
||||
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
||||
VmaAllocation stagingAllocation = VK_NULL_HANDLE;
|
||||
const VkDeviceSize stagingBufferSizePerSide = (VkDeviceSize)w * (VkDeviceSize)h * 4;
|
||||
|
||||
/* create staging buffer */
|
||||
{
|
||||
VkBufferCreateInfo stagingBufferInfo{};
|
||||
stagingBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
stagingBufferInfo.size = stagingBufferSizePerSide * 6;
|
||||
stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
stagingBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
stagingBufferInfo.flags = 0;
|
||||
|
||||
VmaAllocationCreateInfo stagingAllocInfo{};
|
||||
stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO;
|
||||
stagingAllocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||
stagingAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
|
||||
VKCHECK(vmaCreateBuffer(pimpl->allocator, &stagingBufferInfo, &stagingAllocInfo, &stagingBuffer, &stagingAllocation, nullptr));
|
||||
|
||||
void* dataDest;
|
||||
VKCHECK(vmaMapMemory(pimpl->allocator, stagingAllocation, &dataDest));
|
||||
for (size_t i = 0; i < 6; ++i) {
|
||||
memcpy(reinterpret_cast<std::byte*>(dataDest) + stagingBufferSizePerSide * i, image_data[i], stagingBufferSizePerSide);
|
||||
}
|
||||
vmaUnmapMemory(pimpl->allocator, stagingAllocation);
|
||||
}
|
||||
|
||||
VkImageCreateInfo imageInfo{};
|
||||
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imageInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.format = imageFormat;
|
||||
imageInfo.extent.width = w;
|
||||
imageInfo.extent.height = h;
|
||||
imageInfo.extent.depth = 1;
|
||||
imageInfo.mipLevels = mipLevels;
|
||||
imageInfo.arrayLayers = 6;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
VmaAllocationCreateInfo allocCreateInfo{};
|
||||
allocCreateInfo.flags = 0;
|
||||
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||
allocCreateInfo.priority = 0.5f;
|
||||
|
||||
VKCHECK(vmaCreateImage(pimpl->allocator, &imageInfo, &allocCreateInfo, &out->image, &out->allocation, nullptr));
|
||||
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = out->image;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
viewInfo.format = imageFormat;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = mipLevels;
|
||||
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||||
viewInfo.subresourceRange.layerCount = 6;
|
||||
|
||||
VKCHECK(vkCreateImageView(pimpl->device.device, &viewInfo, nullptr, &out->view));
|
||||
|
||||
/* begin command buffer */
|
||||
VkCommandBufferAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
allocInfo.commandPool = pimpl->graphicsCommandPool;
|
||||
allocInfo.commandBufferCount = 1;
|
||||
|
||||
VkCommandBuffer commandBuffer;
|
||||
VKCHECK(vkAllocateCommandBuffers(pimpl->device.device, &allocInfo, &commandBuffer));
|
||||
|
||||
{ // record the command buffer
|
||||
VkCommandBufferBeginInfo beginInfo{};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
VKCHECK(vkBeginCommandBuffer(commandBuffer, &beginInfo));
|
||||
|
||||
// barrier: (all mip levels): UNDEFINED -> TRANSFER_DST_OPTIMAL
|
||||
// Used for copying staging buffer AND blitting mipmaps
|
||||
// Must happen before vkCmdCopyBufferToImage performs a TRANSFER_WRITE in
|
||||
// the COPY stage.
|
||||
VkImageMemoryBarrier2 beforeCopyBarrier{};
|
||||
beforeCopyBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
||||
beforeCopyBarrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE;
|
||||
beforeCopyBarrier.srcAccessMask = VK_ACCESS_2_NONE;
|
||||
beforeCopyBarrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
|
||||
beforeCopyBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
|
||||
beforeCopyBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
beforeCopyBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
beforeCopyBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily;
|
||||
beforeCopyBarrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily;
|
||||
beforeCopyBarrier.image = out->image;
|
||||
beforeCopyBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
beforeCopyBarrier.subresourceRange.baseMipLevel = 0;
|
||||
beforeCopyBarrier.subresourceRange.levelCount = mipLevels;
|
||||
beforeCopyBarrier.subresourceRange.baseArrayLayer = 0;
|
||||
beforeCopyBarrier.subresourceRange.layerCount = 6;
|
||||
VkDependencyInfo beforeCopyDependency{};
|
||||
beforeCopyDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
beforeCopyDependency.imageMemoryBarrierCount = 1;
|
||||
beforeCopyDependency.pImageMemoryBarriers = &beforeCopyBarrier;
|
||||
vkCmdPipelineBarrier2(commandBuffer, &beforeCopyDependency);
|
||||
|
||||
for (int face = 0; face < 6; ++face) {
|
||||
// copy staging buffer to mipLevel 0 (full res image)
|
||||
VkBufferImageCopy region{};
|
||||
region.bufferOffset = stagingBufferSizePerSide * face;
|
||||
region.bufferRowLength = 0;
|
||||
region.bufferImageHeight = 0;
|
||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.imageSubresource.mipLevel = 0;
|
||||
region.imageSubresource.baseArrayLayer = face;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageOffset.x = 0;
|
||||
region.imageOffset.y = 0;
|
||||
region.imageOffset.z = 0;
|
||||
region.imageExtent = imageInfo.extent;
|
||||
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, out->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||
|
||||
int32_t mipWidth = w;
|
||||
int32_t mipHeight = h;
|
||||
for (uint32_t i = 1; i < mipLevels; i++) {
|
||||
// barrier: (i - 1) TRANSFER_DST_OPTIMAL -> TRANSFER_SRC_OPTIMAL
|
||||
// Must happen after TRANSFER_WRITE in the COPY stage and BLIT stage.
|
||||
VkImageMemoryBarrier2 beforeBlitBarrier{};
|
||||
beforeBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
||||
beforeBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT;
|
||||
beforeBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
|
||||
beforeBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT;
|
||||
beforeBlitBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
|
||||
beforeBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
beforeBlitBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
beforeBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily;
|
||||
beforeBlitBarrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily;
|
||||
beforeBlitBarrier.image = out->image;
|
||||
beforeBlitBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
beforeBlitBarrier.subresourceRange.baseMipLevel = (i - 1);
|
||||
beforeBlitBarrier.subresourceRange.levelCount = 1;
|
||||
beforeBlitBarrier.subresourceRange.baseArrayLayer = face;
|
||||
beforeBlitBarrier.subresourceRange.layerCount = 1;
|
||||
VkDependencyInfo beforeBlitDependency{};
|
||||
beforeBlitDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
beforeBlitDependency.imageMemoryBarrierCount = 1;
|
||||
beforeBlitDependency.pImageMemoryBarriers = &beforeBlitBarrier;
|
||||
vkCmdPipelineBarrier2(commandBuffer, &beforeBlitDependency);
|
||||
|
||||
VkImageBlit blit{};
|
||||
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
blit.srcSubresource.mipLevel = i - 1;
|
||||
blit.srcSubresource.baseArrayLayer = face;
|
||||
blit.srcSubresource.layerCount = 1;
|
||||
blit.srcOffsets[0] = { 0, 0, 0 };
|
||||
blit.srcOffsets[1] = { mipWidth, mipHeight, 1 };
|
||||
blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
blit.dstSubresource.mipLevel = i;
|
||||
blit.dstSubresource.baseArrayLayer = face;
|
||||
blit.dstSubresource.layerCount = 1;
|
||||
blit.dstOffsets[0] = { 0, 0, 0 };
|
||||
blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 };
|
||||
vkCmdBlitImage(commandBuffer, out->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, out->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit,
|
||||
VK_FILTER_LINEAR);
|
||||
|
||||
// barrier: (i - 1) TRANSFER_SRC_OPTIMAL -> SHADER_READ_ONLY_OPTIMALs
|
||||
// Must happen after usage in the BLIT stage.
|
||||
// Must happen before SHADER_SAMPLED_READ in the FRAGMENT_SHADER stage
|
||||
VkImageMemoryBarrier2 afterBlitBarrier{};
|
||||
afterBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
||||
afterBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT;
|
||||
afterBlitBarrier.srcAccessMask = 0;
|
||||
afterBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
|
||||
afterBlitBarrier.dstAccessMask = VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT;
|
||||
afterBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
afterBlitBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
afterBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily;
|
||||
afterBlitBarrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily;
|
||||
afterBlitBarrier.image = out->image;
|
||||
afterBlitBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
afterBlitBarrier.subresourceRange.baseMipLevel = (i - 1);
|
||||
afterBlitBarrier.subresourceRange.levelCount = 1;
|
||||
afterBlitBarrier.subresourceRange.baseArrayLayer = face;
|
||||
afterBlitBarrier.subresourceRange.layerCount = 1;
|
||||
VkDependencyInfo afterBlitDependency{};
|
||||
afterBlitDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
afterBlitDependency.imageMemoryBarrierCount = 1;
|
||||
afterBlitDependency.pImageMemoryBarriers = &afterBlitBarrier;
|
||||
vkCmdPipelineBarrier2(commandBuffer, &afterBlitDependency);
|
||||
|
||||
if (mipWidth > 1) mipWidth /= 2;
|
||||
if (mipHeight > 2) mipHeight /= 2;
|
||||
}
|
||||
|
||||
// Final mipLevel is never transitioned from TRANSFER_DST_OPTIMAL
|
||||
// barrier: (mipLevels - 1) TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL
|
||||
VkImageMemoryBarrier2 finalBlitBarrier{};
|
||||
finalBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
||||
finalBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT | VK_PIPELINE_STAGE_2_COPY_BIT;
|
||||
finalBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
|
||||
finalBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
|
||||
finalBlitBarrier.dstAccessMask = VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT;
|
||||
finalBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
finalBlitBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
finalBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily;
|
||||
finalBlitBarrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily;
|
||||
finalBlitBarrier.image = out->image;
|
||||
finalBlitBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
finalBlitBarrier.subresourceRange.baseMipLevel = (mipLevels - 1);
|
||||
finalBlitBarrier.subresourceRange.levelCount = 1;
|
||||
finalBlitBarrier.subresourceRange.baseArrayLayer = face;
|
||||
finalBlitBarrier.subresourceRange.layerCount = 1;
|
||||
VkDependencyInfo afterBlitDependency{};
|
||||
afterBlitDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
afterBlitDependency.imageMemoryBarrierCount = 1;
|
||||
afterBlitDependency.pImageMemoryBarriers = &finalBlitBarrier;
|
||||
vkCmdPipelineBarrier2(commandBuffer, &afterBlitDependency);
|
||||
|
||||
}
|
||||
|
||||
VKCHECK(vkEndCommandBuffer(commandBuffer));
|
||||
}
|
||||
|
||||
// submit
|
||||
VkSubmitInfo submitInfo{};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &commandBuffer;
|
||||
|
||||
VKCHECK(vkQueueSubmit(pimpl->device.queues.drawQueues[0], 1, &submitInfo, VK_NULL_HANDLE));
|
||||
|
||||
VKCHECK(vkQueueWaitIdle(pimpl->device.queues.drawQueues[0]));
|
||||
|
||||
vkFreeCommandBuffers(pimpl->device.device, pimpl->graphicsCommandPool, 1, &commandBuffer);
|
||||
|
||||
vmaDestroyBuffer(pimpl->allocator, stagingBuffer, stagingAllocation);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
void GFXDevice::DestroyImage(const gfx::Image* image)
|
||||
{
|
||||
assert(image != nullptr);
|
||||
@ -1641,9 +1911,9 @@ const gfx::Sampler* GFXDevice::CreateSampler(const gfx::SamplerInfo& info)
|
||||
samplerInfo.magFilter = converters::getFilter(info.magnify);
|
||||
samplerInfo.minFilter = converters::getFilter(info.minify);
|
||||
samplerInfo.mipmapMode = converters::getSamplerMipmapMode(info.mipmap);
|
||||
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
samplerInfo.addressModeU = converters::getSamplerAddressMode(info.wrap_u);
|
||||
samplerInfo.addressModeV = converters::getSamplerAddressMode(info.wrap_v);
|
||||
samplerInfo.addressModeW = converters::getSamplerAddressMode(info.wrap_w);
|
||||
samplerInfo.mipLodBias = 0.0f;
|
||||
samplerInfo.anisotropyEnable = (info.anisotropic_filtering && pimpl->graphicsSettings.enable_anisotropy) ? VK_TRUE : VK_FALSE;
|
||||
samplerInfo.maxAnisotropy = pimpl->device.properties.limits.maxSamplerAnisotropy;
|
||||
|
131
src/renderer.cpp
@ -1,6 +1,9 @@
|
||||
#include "renderer.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "application_component.h"
|
||||
#include "util/files.h"
|
||||
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/trigonometric.hpp>
|
||||
@ -20,12 +23,16 @@ Renderer::Renderer(Application& app, gfx::GraphicsSettings settings) : Applicati
|
||||
auto& binding0 = globalSetBindings.emplace_back();
|
||||
binding0.descriptor_type = gfx::DescriptorType::kUniformBuffer;
|
||||
binding0.stage_flags = gfx::ShaderStageFlags::kVertex;
|
||||
auto& binding1 = globalSetBindings.emplace_back();
|
||||
binding1.descriptor_type = gfx::DescriptorType::kCombinedImageSampler;
|
||||
binding1.stage_flags = gfx::ShaderStageFlags::kFragment;
|
||||
}
|
||||
global_uniform.layout = device_->CreateDescriptorSetLayout(globalSetBindings);
|
||||
global_uniform.set = device_->AllocateDescriptorSet(global_uniform.layout);
|
||||
global_uniform.uniform_buffer_data.data = glm::mat4{1.0f};
|
||||
global_uniform.uniform_buffer = device_->CreateUniformBuffer(sizeof(global_uniform.uniform_buffer_data), &global_uniform.uniform_buffer_data);
|
||||
device_->UpdateDescriptorUniformBuffer(global_uniform.set, 0, global_uniform.uniform_buffer, 0, sizeof(global_uniform.uniform_buffer_data));
|
||||
// binding1 is updated towards the end of the constructor once the skybox texture is loaded
|
||||
|
||||
std::vector<gfx::DescriptorSetLayoutBinding> frameSetBindings;
|
||||
{
|
||||
@ -55,6 +62,7 @@ Renderer::Renderer(Application& app, gfx::GraphicsSettings settings) : Applicati
|
||||
debug_vertex_format.stride = 0;
|
||||
// debug_vertex_format.vertex_attrib_descriptions = empty
|
||||
|
||||
{
|
||||
gfx::PipelineInfo debug_pipeline_info{};
|
||||
debug_pipeline_info.vert_shader_path = GetResourcePath("engine/shaders/debug.vert");
|
||||
debug_pipeline_info.frag_shader_path = GetResourcePath("engine/shaders/debug.frag");
|
||||
@ -66,10 +74,122 @@ Renderer::Renderer(Application& app, gfx::GraphicsSettings settings) : Applicati
|
||||
debug_pipeline_info.line_primitives = true;
|
||||
|
||||
debug_rendering_things_.pipeline = device_->CreatePipeline(debug_pipeline_info);
|
||||
}
|
||||
|
||||
// create the skybox cubemap
|
||||
{
|
||||
constexpr uint32_t cubemap_w = 2048;
|
||||
constexpr uint32_t cubemap_h = 2048;
|
||||
int w{}, h{};
|
||||
std::array<std::unique_ptr<std::vector<uint8_t>>, 6> face_unq_ptrs{};
|
||||
std::array<const void*, 6> face_unsafe_ptrs{};
|
||||
|
||||
for (int face = 0; face < 6; ++face) {
|
||||
std::string path = std::string("engine/textures/skybox") + std::to_string(face) + std::string(".jpg");
|
||||
face_unq_ptrs[face] = util::ReadImageFile(GetResourcePath(path), w, h);
|
||||
if (cubemap_w != w || cubemap_h != h) throw std::runtime_error("Skybox textures must be 512x512!");
|
||||
face_unsafe_ptrs[face] = face_unq_ptrs[face]->data();
|
||||
}
|
||||
|
||||
|
||||
skybox_cubemap = device_->CreateImageCubemap(cubemap_w, cubemap_h, gfx::ImageFormat::kSRGB, face_unsafe_ptrs);
|
||||
gfx::SamplerInfo sampler_info{};
|
||||
sampler_info.magnify = gfx::Filter::kLinear;
|
||||
sampler_info.minify = gfx::Filter::kLinear;
|
||||
sampler_info.mipmap = gfx::Filter::kLinear;
|
||||
sampler_info.wrap_u = gfx::WrapMode::kClampToEdge;
|
||||
sampler_info.wrap_v = gfx::WrapMode::kClampToEdge;
|
||||
sampler_info.wrap_w = gfx::WrapMode::kClampToEdge;
|
||||
sampler_info.anisotropic_filtering = true;
|
||||
skybox_sampler = device_->CreateSampler(sampler_info);
|
||||
}
|
||||
|
||||
// create skybox shader
|
||||
{
|
||||
gfx::VertexFormat vertex_format{};
|
||||
vertex_format.attribute_descriptions.emplace_back(0, gfx::VertexAttribFormat::kFloat3, 0);
|
||||
vertex_format.stride = 3 * sizeof(float);
|
||||
|
||||
gfx::PipelineInfo pipeline_info{};
|
||||
pipeline_info.vert_shader_path = GetResourcePath("engine/shaders/skybox.vert");
|
||||
pipeline_info.frag_shader_path = GetResourcePath("engine/shaders/skybox.frag");
|
||||
pipeline_info.vertex_format = vertex_format;
|
||||
pipeline_info.alpha_blending = false;
|
||||
pipeline_info.backface_culling = true;
|
||||
pipeline_info.write_z = false;
|
||||
pipeline_info.line_primitives = false;
|
||||
pipeline_info.descriptor_set_layouts.push_back(GetGlobalSetLayout());
|
||||
pipeline_info.descriptor_set_layouts.push_back(GetFrameSetLayout());
|
||||
|
||||
skybox_pipeline = device_->CreatePipeline(pipeline_info);
|
||||
|
||||
device_->UpdateDescriptorCombinedImageSampler(global_uniform.set, 1, skybox_cubemap, skybox_sampler);
|
||||
}
|
||||
|
||||
// create skybox vertex buffer
|
||||
{
|
||||
std::vector<glm::vec3> v{};
|
||||
v.push_back({0.0f, 0.0f, 2.0f});
|
||||
v.push_back({0.0f, 0.0f, 0.0f});
|
||||
v.push_back({2.0f, 0.0f, 0.0f});
|
||||
v.push_back({2.0f, 0.0f, 0.0f});
|
||||
v.push_back({2.0f, 0.0f, 2.0f});
|
||||
v.push_back({0.0f, 0.0f, 2.0f});
|
||||
// back
|
||||
v.push_back({2.0f, 2.0f, 2.0f });
|
||||
v.push_back({ 2.0f, 2.0f, 0.0f});
|
||||
v.push_back({0.0f, 2.0f, 0.0f});
|
||||
v.push_back({0.0f, 2.0f, 0.0f});
|
||||
v.push_back({0.0f, 2.0f, 2.0f });
|
||||
v.push_back({ 2.0f, 2.0f, 2.0f });
|
||||
// left
|
||||
v.push_back({0.0f, 2.0f, 2.0f });
|
||||
v.push_back({0.0f, 2.0f, 0.0f});
|
||||
v.push_back({0.0f, 0.0f, 0.0f});
|
||||
v.push_back({0.0f, 0.0f, 0.0f});
|
||||
v.push_back({0.0f, 0.0f, 2.0f });
|
||||
v.push_back({0.0f, 2.0f, 2.0f });
|
||||
// right
|
||||
v.push_back({ 2.0f, 0.0f, 2.0f });
|
||||
v.push_back({ 2.0f, 0.0f, 0.0f});
|
||||
v.push_back({ 2.0f, 2.0f, 0.0f});
|
||||
v.push_back({ 2.0f, 2.0f, 0.0f});
|
||||
v.push_back({ 2.0f, 2.0f, 2.0f });
|
||||
v.push_back({ 2.0f, 0.0f, 2.0f });
|
||||
// top
|
||||
v.push_back({0.0f, 2.0f, 2.0f });
|
||||
v.push_back({0.0f, 0.0f, 2.0f });
|
||||
v.push_back({ 2.0f, 0.0f, 2.0f });
|
||||
v.push_back({ 2.0f, 0.0f, 2.0f });
|
||||
v.push_back({ 2.0f, 2.0f, 2.0f });
|
||||
v.push_back({0.0f, 2.0f, 2.0f });
|
||||
// bottom
|
||||
v.push_back({ 2.0f, 2.0f, 0.0f});
|
||||
v.push_back({ 2.0f, 0.0f, 0.0f});
|
||||
v.push_back({0.0f, 0.0f, 0.0f});
|
||||
v.push_back({0.0f, 0.0f, 0.0f});
|
||||
v.push_back({0.0f, 2.0f, 0.0f});
|
||||
v.push_back({ 2.0f, 2.0f, 0.0f});
|
||||
for (glm::vec3& pos : v) {
|
||||
pos.x -= 1.0f;
|
||||
pos.y -= 1.0f;
|
||||
pos.z -= 1.0f;
|
||||
}
|
||||
for (size_t i = 0; i < v.size(); i += 3) {
|
||||
std::swap(v[i], v[i + 2]);
|
||||
}
|
||||
|
||||
skybox_buffer = device_->CreateBuffer(gfx::BufferType::kVertex, v.size() * sizeof(glm::vec3), v.data());
|
||||
}
|
||||
};
|
||||
|
||||
Renderer::~Renderer()
|
||||
{
|
||||
device_->DestroyBuffer(skybox_buffer);
|
||||
device_->DestroyPipeline(skybox_pipeline);
|
||||
device_->DestroySampler(skybox_sampler);
|
||||
device_->DestroyImage(skybox_cubemap);
|
||||
|
||||
device_->DestroyPipeline(debug_rendering_things_.pipeline);
|
||||
|
||||
for (const auto& [info, sampler] : samplers) {
|
||||
@ -127,6 +247,15 @@ void Renderer::Render(const RenderList* static_list, const RenderList* dynamic_l
|
||||
glm::vec3 color;
|
||||
};
|
||||
|
||||
// draw skybox
|
||||
{
|
||||
device_->CmdBindPipeline(draw_buffer, skybox_pipeline);
|
||||
device_->CmdBindDescriptorSet(draw_buffer, skybox_pipeline, global_uniform.set, 0);
|
||||
device_->CmdBindDescriptorSet(draw_buffer, skybox_pipeline, frame_uniform.set, 1);
|
||||
device_->CmdBindVertexBuffer(draw_buffer, 0, skybox_buffer);
|
||||
device_->CmdDraw(draw_buffer, 36, 1, 0, 0);
|
||||
}
|
||||
|
||||
// draw debug shit here
|
||||
device_->CmdBindPipeline(draw_buffer, debug_rendering_things_.pipeline);
|
||||
DebugPush push{};
|
||||
@ -139,7 +268,7 @@ void Renderer::Render(const RenderList* static_list, const RenderList* dynamic_l
|
||||
}
|
||||
|
||||
// also make a lil crosshair
|
||||
push.color = glm::vec3{ 1.0f, 1.0f, 1.0f };
|
||||
push.color = glm::vec3{1.0f, 1.0f, 1.0f};
|
||||
push.pos1 = glm::vec4(-0.05f, 0.0f, 0.0f, 1.0f);
|
||||
push.pos2 = glm::vec4(0.05f, 0.0f, 0.0f, 1.0f);
|
||||
device_->CmdPushConstants(draw_buffer, debug_rendering_things_.pipeline, 0, sizeof(DebugPush), &push);
|
||||
|
@ -23,16 +23,10 @@ void Material::SetNormalTexture(std::shared_ptr<Texture> texture)
|
||||
texture_normal_ = texture;
|
||||
}
|
||||
|
||||
void Material::SetOcclusionTexture(std::shared_ptr<Texture> texture)
|
||||
void Material::SetOcclusionRoughnessMetallicTexture(std::shared_ptr<Texture> texture)
|
||||
{
|
||||
renderer_->GetDevice()->UpdateDescriptorCombinedImageSampler(material_set_, 2, texture->GetImage(), texture->GetSampler());
|
||||
texture_occlusion_ = texture;
|
||||
}
|
||||
|
||||
void Material::SetMetallicRoughnessTexture(std::shared_ptr<Texture> texture)
|
||||
{
|
||||
renderer_->GetDevice()->UpdateDescriptorCombinedImageSampler(material_set_, 3, texture->GetImage(), texture->GetSampler());
|
||||
texture_metallic_roughness_ = texture;
|
||||
texture_occlusion_roughness_metallic_ = texture;
|
||||
}
|
||||
|
||||
} // namespace engine
|
||||
|
@ -109,6 +109,14 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic)
|
||||
throw std::runtime_error("Failed to load glTF file!");
|
||||
}
|
||||
|
||||
// check for required extensions
|
||||
if (model.extensionsRequired.empty() == false) { // this loader doesn't support any extensions
|
||||
for (const auto& ext : model.extensionsRequired) {
|
||||
LOG_ERROR("Unsupported required extension: {}", ext);
|
||||
}
|
||||
throw std::runtime_error("One or more required extensions are unsupported. File: " + path);
|
||||
}
|
||||
|
||||
// test model loading
|
||||
|
||||
if (model.scenes.size() < 1) {
|
||||
@ -142,7 +150,10 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic)
|
||||
// use missing texture image by default
|
||||
textures.emplace_back(scene.app()->GetResource<Texture>("builtin.white"));
|
||||
|
||||
if (texture.source == -1) continue;
|
||||
if (texture.source == -1) {
|
||||
LOG_ERROR("A gltf file specifies a texture with no source.");
|
||||
continue;
|
||||
}
|
||||
|
||||
gfx::SamplerInfo samplerInfo{};
|
||||
// default to trilinear filtering even if mipmaps are not specified
|
||||
@ -193,6 +204,9 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic)
|
||||
textures.back() = std::make_shared<Texture>(scene.app()->renderer(), image.image.data(), image.width, image.height, samplerInfo,
|
||||
tex_index_is_base_color[texture_idx]);
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error("Texture found in gltf file is unsupported! Make sure texture is 8 bpp. File: " + path);
|
||||
}
|
||||
|
||||
++texture_idx;
|
||||
}
|
||||
@ -256,20 +270,20 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic)
|
||||
materials.back()->SetAlbedoTexture(colour_textures.at(c));
|
||||
}
|
||||
|
||||
// metallic roughness
|
||||
materials.back()->SetMetallicRoughnessTexture(scene.app()->GetResource<Texture>("builtin.white")); // default metal = 1.0, rough = 1.0
|
||||
// occlusion roughness metallic
|
||||
materials.back()->SetOcclusionRoughnessMetallicTexture(scene.app()->GetResource<Texture>("builtin.white")); // default ao = 1.0, rough = 1.0, metal = 1.0
|
||||
if (material.pbrMetallicRoughness.metallicRoughnessTexture.index != -1) {
|
||||
if (material.pbrMetallicRoughness.metallicRoughnessTexture.texCoord == 0) {
|
||||
LOG_INFO("Setting metallic roughness texture!");
|
||||
materials.back()->SetMetallicRoughnessTexture(textures.at(material.pbrMetallicRoughness.metallicRoughnessTexture.index));
|
||||
LOG_INFO("Setting occlusion roughness metallic texture!");
|
||||
materials.back()->SetOcclusionRoughnessMetallicTexture(textures.at(material.pbrMetallicRoughness.metallicRoughnessTexture.index));
|
||||
}
|
||||
else {
|
||||
LOG_WARN("Material {} metallic roughness texture specifies a UV channel other than zero which is unsupported.", material.name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG_INFO("Creating a metallic-roughness texture...");
|
||||
const std::vector<double> mr_values{1.0f, material.pbrMetallicRoughness.roughnessFactor, material.pbrMetallicRoughness.metallicFactor, 1.0f};
|
||||
LOG_INFO("Creating occlusion roughness metallic texture...");
|
||||
const std::vector<double> mr_values{1.0f /* no AO */, material.pbrMetallicRoughness.roughnessFactor, material.pbrMetallicRoughness.metallicFactor, 1.0f};
|
||||
Color mr(mr_values);
|
||||
if (metal_rough_textures.contains(mr) == false) {
|
||||
const uint8_t pixel[4] = {mr.r, mr.g, mr.b, mr.a};
|
||||
@ -280,15 +294,16 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic)
|
||||
samplerInfo.anisotropic_filtering = false;
|
||||
metal_rough_textures.emplace(std::make_pair(mr, std::make_shared<Texture>(scene.app()->renderer(), pixel, 1, 1, samplerInfo, false)));
|
||||
}
|
||||
materials.back()->SetMetallicRoughnessTexture(metal_rough_textures.at(mr));
|
||||
materials.back()->SetOcclusionRoughnessMetallicTexture(metal_rough_textures.at(mr));
|
||||
}
|
||||
|
||||
// occlusion texture
|
||||
materials.back()->SetOcclusionTexture(scene.app()->GetResource<Texture>("builtin.white")); // R=255 means no AO so white will work
|
||||
// if occlusion texture was same as metallic-roughness texture, this will already have been applied
|
||||
if (material.occlusionTexture.index != -1) {
|
||||
if (material.occlusionTexture.texCoord == 0) {
|
||||
LOG_INFO("Setting occlusion texture!");
|
||||
materials.back()->SetOcclusionTexture(textures.at(material.occlusionTexture.index));
|
||||
if (material.occlusionTexture.index != material.pbrMetallicRoughness.metallicRoughnessTexture.index) {
|
||||
throw std::runtime_error(std::string("Material ") + material.name + std::string(" has an ambient occlusion texture different to the metal-rough texture."));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG_WARN("Material {} occlusion texture specifies a UV channel other than zero which is unsupported.", material.name);
|
||||
|
@ -11,18 +11,18 @@
|
||||
|
||||
namespace engine {
|
||||
|
||||
static bool checkQueueFamilySupportsPresent(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t familyIndex)
|
||||
{
|
||||
static bool checkQueueFamilySupportsPresent(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t familyIndex)
|
||||
{
|
||||
VkBool32 supportsPresent;
|
||||
VkResult res;
|
||||
res = vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, static_cast<uint32_t>(familyIndex), surface, &supportsPresent);
|
||||
if (res != VK_SUCCESS) throw std::runtime_error("Failed to check for queue family present support!");
|
||||
return supportsPresent;
|
||||
}
|
||||
}
|
||||
|
||||
/* chooses a device, creates it, gets its function pointers, and retrieves queues */
|
||||
Device createDevice(VkInstance instance, const DeviceRequirements& requirements, VkSurfaceKHR surface)
|
||||
{
|
||||
/* chooses a device, creates it, gets its function pointers, and retrieves queues */
|
||||
Device createDevice(VkInstance instance, const DeviceRequirements& requirements, VkSurfaceKHR surface)
|
||||
{
|
||||
Device d{};
|
||||
|
||||
// enumerate physical devices
|
||||
@ -210,10 +210,12 @@ namespace engine {
|
||||
if (memoryPriorityRequired) {
|
||||
if (memoryPriorityFeatures.memoryPriority == VK_FALSE) {
|
||||
throw std::runtime_error("Required device feature 'memoryPriority' not found, but extension was");
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
d.memoryPriorityFeature = true;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// see if memoryPriority was optionally requested */
|
||||
for (const char* optExt : requirements.optionalExtensions) {
|
||||
if (strcmp(optExt, VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) == 0) {
|
||||
@ -221,17 +223,18 @@ namespace engine {
|
||||
if (strcmp(extAvail.extensionName, VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) == 0) {
|
||||
if (memoryPriorityFeatures.memoryPriority == VK_TRUE) {
|
||||
d.memoryPriorityFeature = true;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error("Optional device extension 'VK_EXT_memory_priority' found, but feature wasn't");
|
||||
}
|
||||
break; // |
|
||||
} // |
|
||||
} // V
|
||||
// <--------------------
|
||||
// <---------------
|
||||
break; // |
|
||||
} // |
|
||||
} // V
|
||||
// <--------------------
|
||||
// <---------------
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,7 +258,7 @@ namespace engine {
|
||||
d.physicalDevice = physDev;
|
||||
d.properties = devProps;
|
||||
d.features = requirements.requiredFeatures; // to be safe, only advertise requested features
|
||||
//deviceInfo->features = devFeatures;
|
||||
// deviceInfo->features = devFeatures;
|
||||
|
||||
break;
|
||||
}
|
||||
@ -304,9 +307,7 @@ namespace engine {
|
||||
// prefer a dedicated transfer queue family
|
||||
for (size_t i = 0; i < queueFamilies.size(); i++) {
|
||||
VkQueueFamilyProperties p = queueFamilies[i];
|
||||
if (((p.queueFlags & VK_QUEUE_TRANSFER_BIT) != 0) &&
|
||||
((p.queueFlags & VK_QUEUE_COMPUTE_BIT) == 0) &&
|
||||
((p.queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0)) {
|
||||
if (((p.queueFlags & VK_QUEUE_TRANSFER_BIT) != 0) && ((p.queueFlags & VK_QUEUE_COMPUTE_BIT) == 0) && ((p.queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0)) {
|
||||
transferFamily = static_cast<uint32_t>(i);
|
||||
break;
|
||||
}
|
||||
@ -323,24 +324,20 @@ namespace engine {
|
||||
std::fill(transferQueuePriorities.begin(), transferQueuePriorities.end(), 1.0f);
|
||||
|
||||
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos{};
|
||||
queueCreateInfos.push_back(VkDeviceQueueCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
queueCreateInfos.push_back(VkDeviceQueueCreateInfo{.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.queueFamilyIndex = graphicsFamily,
|
||||
.queueCount = queueFamilies[graphicsFamily].queueCount,
|
||||
.pQueuePriorities = graphicsQueuePriorities.data()
|
||||
});
|
||||
.pQueuePriorities = graphicsQueuePriorities.data()});
|
||||
|
||||
if (transferFamily != graphicsFamily) {
|
||||
queueCreateInfos.push_back(VkDeviceQueueCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
queueCreateInfos.push_back(VkDeviceQueueCreateInfo{.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.queueFamilyIndex = transferFamily,
|
||||
.queueCount = queueFamilies[transferFamily].queueCount,
|
||||
.pQueuePriorities = transferQueuePriorities.data()
|
||||
});
|
||||
.pQueuePriorities = transferQueuePriorities.data()});
|
||||
}
|
||||
|
||||
/* set enabled features */
|
||||
@ -359,8 +356,8 @@ namespace engine {
|
||||
/* get list of extensions to enable */
|
||||
std::vector<const char*> extensionsToEnable{};
|
||||
for (const VkExtensionProperties& availableExt : availableExtensions) {
|
||||
if ( std::find(requirements.optionalExtensions.begin(), requirements.optionalExtensions.end(),
|
||||
std::string(availableExt.extensionName)) != requirements.optionalExtensions.end()) {
|
||||
if (std::find(requirements.optionalExtensions.begin(), requirements.optionalExtensions.end(), std::string(availableExt.extensionName)) !=
|
||||
requirements.optionalExtensions.end()) {
|
||||
extensionsToEnable.push_back(availableExt.extensionName);
|
||||
}
|
||||
}
|
||||
@ -401,7 +398,8 @@ namespace engine {
|
||||
for (uint32_t i = 0; i < d.queues.drawQueues.size(); i++) {
|
||||
vkGetDeviceQueue(d.device, graphicsFamily, i + 1, &d.queues.drawQueues[i]);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
d.queues.drawQueues.resize(1);
|
||||
d.queues.drawQueues[0] = d.queues.presentQueue;
|
||||
}
|
||||
@ -409,7 +407,8 @@ namespace engine {
|
||||
for (uint32_t i = 0; i < d.queues.transferQueues.size(); i++) {
|
||||
vkGetDeviceQueue(d.device, transferFamily, i, &d.queues.transferQueues[i]);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// same graphics family for graphics/present and transfer
|
||||
uint32_t queueCount = queueFamilies[graphicsFamily].queueCount;
|
||||
vkGetDeviceQueue(d.device, graphicsFamily, 0, &d.queues.presentQueue);
|
||||
@ -422,14 +421,16 @@ namespace engine {
|
||||
for (uint32_t i = 0; i < queueCount - 2; i++) {
|
||||
vkGetDeviceQueue(d.device, graphicsFamily, i + 2, &d.queues.drawQueues[i]);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// 2 queues available
|
||||
// present and drawing share a queue
|
||||
// transfer gets its own queue
|
||||
d.queues.drawQueues.resize(1);
|
||||
d.queues.drawQueues[0] = d.queues.presentQueue;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// only 1 queue available :(
|
||||
d.queues.transferQueues.resize(1);
|
||||
d.queues.transferQueues[0] = d.queues.presentQueue;
|
||||
@ -442,12 +443,8 @@ namespace engine {
|
||||
d.queues.transferQueueFamily = transferFamily;
|
||||
|
||||
return d;
|
||||
|
||||
}
|
||||
|
||||
void destroyDevice(Device device)
|
||||
{
|
||||
vkDestroyDevice(device.device, nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void destroyDevice(Device device) { vkDestroyDevice(device.device, nullptr); }
|
||||
|
||||
} // namespace engine
|
||||
|
BIN
test/res/models/cube.glb
Normal file
BIN
test/res/models/floor.glb
Normal file
@ -37,6 +37,10 @@ void CameraControllerSystem::OnUpdate(float ts)
|
||||
float dx = scene_->app()->input_manager()->GetAxis("movex") * CameraControllerComponent::kSpeedStrafe;
|
||||
float dy = scene_->app()->input_manager()->GetAxis("movey") * CameraControllerComponent::kSpeedForwardBack;
|
||||
|
||||
if (scene_->app()->input_manager()->GetButton("sprint")) {
|
||||
dy *= CameraControllerComponent::kSprintMultiplier;
|
||||
}
|
||||
|
||||
// calculate new pitch and yaw
|
||||
|
||||
float d_pitch = scene_->app()->input_manager()->GetAxis("looky") * -1.0f * CameraControllerComponent::kCameraSensitivity;
|
||||
@ -66,25 +70,41 @@ void CameraControllerSystem::OnUpdate(float ts)
|
||||
|
||||
// check horizontal collisions first as otherwise the player may be teleported above a wall instead of colliding against it
|
||||
if (c->vel.x != 0.0f || c->vel.y != 0.0f) { // just in case, to avoid a ray with direction = (0,0,0)
|
||||
engine::Ray horiz_ray{};
|
||||
horiz_ray.origin = t->position; // set origin to 'MaxStairHeight' units above player's feet
|
||||
horiz_ray.origin.z += CameraControllerComponent::kMaxStairHeight - CameraControllerComponent::kPlayerHeight;
|
||||
horiz_ray.direction.x = c->vel.x;
|
||||
horiz_ray.direction.y = c->vel.y; // this is normalized by GetRayCast()
|
||||
horiz_ray.direction.z = 0.0f;
|
||||
const engine::Raycast horiz_raycast = scene_->GetSystem<engine::CollisionSystem>()->GetRaycast(horiz_ray);
|
||||
if (horiz_raycast.hit) {
|
||||
const glm::vec2 norm_xy = glm::normalize(glm::vec2{horiz_raycast.normal.x, horiz_raycast.normal.y}) * -1.0f; // make it point towards object
|
||||
|
||||
std::array<engine::Raycast, CameraControllerComponent::kNumHorizontalRays> raycasts;
|
||||
engine::Raycast* chosen_cast = nullptr; // nullptr means no hit at all
|
||||
|
||||
float smallest_distance = std::numeric_limits<float>::infinity();
|
||||
for (size_t i = 0; i < raycasts.size(); ++i) {
|
||||
|
||||
const float lerp_value = static_cast<float>(i) / (static_cast<float>(CameraControllerComponent::kNumHorizontalRays) - 1);
|
||||
|
||||
engine::Ray ray{};
|
||||
ray.origin = t->position;
|
||||
ray.origin.z -= (CameraControllerComponent::kPlayerHeight - CameraControllerComponent::kMaxStairHeight) * lerp_value;
|
||||
ray.direction.x = c->vel.x;
|
||||
ray.direction.y = c->vel.y; // this is normalized by GetRayCast()
|
||||
ray.direction.z = 0.0f;
|
||||
raycasts[i] = scene_->GetSystem<engine::CollisionSystem>()->GetRaycast(ray);
|
||||
|
||||
if (raycasts[i].hit && raycasts[i].distance < smallest_distance) {
|
||||
smallest_distance = raycasts[i].distance;
|
||||
chosen_cast = &raycasts[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (chosen_cast != nullptr) {
|
||||
const glm::vec2 norm_xy = glm::normalize(glm::vec2{chosen_cast->normal.x, chosen_cast->normal.y}) * -1.0f; // make it point towards object
|
||||
const glm::vec2 vel_xy = glm::vec2{ c->vel.x, c->vel.y };
|
||||
// find the extent of the player's velocity in the direction of the wall's normal vector
|
||||
const glm::vec2 partial_vel = norm_xy * glm::dot(norm_xy, vel_xy);
|
||||
const glm::vec2 partial_dX = partial_vel * dt;
|
||||
if (glm::length(partial_dX) > horiz_raycast.distance - CameraControllerComponent::kPlayerCollisionRadius) {
|
||||
if (glm::length(partial_dX) > chosen_cast->distance - CameraControllerComponent::kPlayerCollisionRadius) {
|
||||
// player will collide with wall
|
||||
// push player out of collision zone
|
||||
const glm::vec2 push_vector = glm::normalize(vel_xy) * fmaxf(CameraControllerComponent::kPlayerCollisionRadius, horiz_raycast.distance);
|
||||
t->position.x = horiz_raycast.location.x - push_vector.x;
|
||||
t->position.y = horiz_raycast.location.y - push_vector.y;
|
||||
const glm::vec2 push_vector = glm::normalize(vel_xy) * fmaxf(CameraControllerComponent::kPlayerCollisionRadius, chosen_cast->distance);
|
||||
t->position.x = chosen_cast->location.x - push_vector.x;
|
||||
t->position.y = chosen_cast->location.y - push_vector.y;
|
||||
c->vel.x -= partial_vel.x;
|
||||
c->vel.y -= partial_vel.y;
|
||||
}
|
||||
@ -103,7 +123,6 @@ void CameraControllerSystem::OnUpdate(float ts)
|
||||
const float mag_dz = fabsf(c->vel.z * dt);
|
||||
// check if the player will be less than 'height' units above the collided ground
|
||||
if (mag_dz > fall_raycast.distance - CameraControllerComponent::kMaxStairHeight) {
|
||||
LOG_INFO("HIT");
|
||||
// push player up to ground level and set as grounded
|
||||
t->position.z = fall_raycast.location.z + CameraControllerComponent::kPlayerHeight;
|
||||
c->vel.z = 0.0f;
|
||||
@ -119,19 +138,25 @@ void CameraControllerSystem::OnUpdate(float ts)
|
||||
}
|
||||
else if (c->vel.z > 0.0f) {
|
||||
c->grounded = false; // they are jumping
|
||||
// check if intersection with ceiling
|
||||
engine::Ray jump_ray{};
|
||||
jump_ray.origin = t->position;
|
||||
jump_ray.direction = { 0.0f, 0.0f, 1.0f };
|
||||
const engine::Raycast jump_raycast = scene_->GetSystem<engine::CollisionSystem>()->GetRaycast(jump_ray);
|
||||
if (jump_raycast.hit) {
|
||||
// find how far the player will move (upwards) if velocity is applied without collision
|
||||
const float mag_dz = fabsf(c->vel.z * dt);
|
||||
// check if the player will be higher than the collided ground
|
||||
if (mag_dz > jump_raycast.distance - CameraControllerComponent::kPlayerCollisionRadius) {
|
||||
// push player below ceiling
|
||||
t->position.z = jump_raycast.location.z - CameraControllerComponent::kPlayerCollisionRadius;
|
||||
c->vel.z = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (c->was_grounded != c->grounded) {
|
||||
LOG_INFO("GROUNDED? {}", c->grounded);
|
||||
c->was_grounded = c->grounded;
|
||||
}
|
||||
|
||||
t->position += c->vel * dt;
|
||||
|
||||
if (glm::length(t->position) > CameraControllerComponent::kMaxDistanceFromOrigin) {
|
||||
t->position = {0.0f, 0.0f, 100.0f};
|
||||
}
|
||||
|
||||
/* ROTATION STUFF */
|
||||
|
||||
// pitch quaternion
|
||||
@ -155,15 +180,8 @@ void CameraControllerSystem::OnUpdate(float ts)
|
||||
|
||||
/* user interface inputs */
|
||||
|
||||
if (scene_->app()->window()->GetKeyPress(engine::inputs::Key::K_P)) {
|
||||
std::string pos_string{"x: " + std::to_string(t->world_matrix[3][0]) + " y: " + std::to_string(t->world_matrix[3][1]) +
|
||||
" z: " + std::to_string(t->world_matrix[3][2])};
|
||||
LOG_INFO("position {}", pos_string);
|
||||
LOG_INFO("rotation w: {} x: {} y: {} z: {}", t->rotation.w, t->rotation.x, t->rotation.y, t->rotation.z);
|
||||
}
|
||||
|
||||
if (scene_->app()->window()->GetKeyPress(engine::inputs::Key::K_R)) {
|
||||
t->position = {0.0f, 0.0f, 10.0f};
|
||||
if (scene_->app()->window()->GetKeyPress(engine::inputs::Key::K_R) || glm::length(t->position) > CameraControllerComponent::kMaxDistanceFromOrigin) {
|
||||
t->position = {0.0f, 0.0f, 100.0f};
|
||||
c->vel = {0.0f, 0.0f, 0.0f};
|
||||
c->pitch = glm::half_pi<float>();
|
||||
c->yaw = 0.0f;
|
||||
@ -181,9 +199,7 @@ void CameraControllerSystem::OnUpdate(float ts)
|
||||
scene_->app()->scene_manager()->SetActiveScene(next_scene_);
|
||||
}
|
||||
|
||||
static std::vector<engine::Line> perm_lines{};
|
||||
|
||||
if (scene_->app()->window()->GetButton(engine::inputs::MouseButton::M_LEFT)) {
|
||||
if (scene_->app()->window()->GetButtonPress(engine::inputs::MouseButton::M_LEFT)) {
|
||||
engine::Ray ray{};
|
||||
ray.origin = t->position;
|
||||
ray.direction = glm::vec3(glm::mat4_cast(t->rotation) * glm::vec4{0.0f, 0.0f, -1.0f, 1.0f});
|
||||
@ -195,24 +211,9 @@ void CameraControllerSystem::OnUpdate(float ts)
|
||||
LOG_INFO("Normal: {} {} {}", cast.normal.x, cast.normal.y, cast.normal.z);
|
||||
LOG_INFO("Ray direction: {} {} {}", ray.direction.x, ray.direction.y, ray.direction.z);
|
||||
LOG_INFO("Hit Entity: {}", scene_->GetComponent<engine::TransformComponent>(cast.hit_entity)->tag);
|
||||
perm_lines.emplace_back(ray.origin, cast.location, glm::vec3{0.0f, 0.0f, 1.0f});
|
||||
}
|
||||
}
|
||||
if (scene_->app()->window()->GetButtonPress(engine::inputs::MouseButton::M_RIGHT)) {
|
||||
engine::Ray horiz_ray{};
|
||||
horiz_ray.origin = t->position; // set origin to 'MaxStairHeight' units above player's feet
|
||||
horiz_ray.origin.z += CameraControllerComponent::kMaxStairHeight - CameraControllerComponent::kPlayerHeight;
|
||||
horiz_ray.direction.x = c->vel.x;
|
||||
horiz_ray.direction.y = c->vel.y; // this is normalized by GetRayCast()
|
||||
horiz_ray.direction.z = 0.0f;
|
||||
const engine::Raycast cast = scene_->GetSystem<engine::CollisionSystem>()->GetRaycast(horiz_ray);
|
||||
if (cast.hit) {
|
||||
LOG_INFO("Distance: {} m", cast.distance);
|
||||
LOG_INFO("Location: {} {} {}", cast.location.x, cast.location.y, cast.location.z);
|
||||
LOG_INFO("Normal: {} {} {}", cast.normal.x, cast.normal.y, cast.normal.z);
|
||||
perm_lines.emplace_back(horiz_ray.origin, cast.location, glm::vec3{ 0.0f, 0.0f, 1.0f });
|
||||
c->perm_lines.emplace_back(ray.origin, cast.location, glm::vec3{0.0f, 0.0f, 1.0f});
|
||||
}
|
||||
}
|
||||
|
||||
scene_->app()->debug_lines.insert(scene_->app()->debug_lines.end(), perm_lines.begin(), perm_lines.end());
|
||||
scene_->app()->debug_lines.insert(scene_->app()->debug_lines.end(), c->perm_lines.begin(), c->perm_lines.end());
|
||||
}
|
||||
|
@ -4,28 +4,38 @@
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include "components/transform.h"
|
||||
#include "application.h"
|
||||
#include "ecs.h"
|
||||
|
||||
struct CameraControllerComponent {
|
||||
static constexpr float kSpeedForwardBack = 4.0f;
|
||||
static constexpr float kSpeedStrafe = 4.0f;
|
||||
// looking
|
||||
static constexpr float kCameraSensitivity = 0.007f;
|
||||
static constexpr float kMaxDistanceFromOrigin = 10000.0f;
|
||||
static constexpr float kGravAccel = -9.81f;
|
||||
static constexpr float kPlayerHeight = 71.0f * 25.4f / 1000.0f;
|
||||
static constexpr float kMaxStairHeight = 0.2f;
|
||||
static constexpr float kPlayerCollisionRadius = 0.2f;
|
||||
static constexpr float kMaxPitch = glm::pi<float>();
|
||||
static constexpr float kMinPitch = 0.0f;
|
||||
|
||||
// moving
|
||||
static constexpr float kSpeedForwardBack = 4.0f;
|
||||
static constexpr float kSpeedStrafe = 4.0f;
|
||||
static constexpr float kSprintMultiplier = 2.0f;
|
||||
|
||||
// collision
|
||||
static constexpr float kPlayerHeight = 2.0f; // 71.0f * 25.4f / 1000.0f;
|
||||
static constexpr float kPlayerCollisionRadius = 0.2f; // this should be greater than z_near
|
||||
static constexpr float kMaxStairHeight = 0.2f;
|
||||
static constexpr size_t kNumHorizontalRays = 20;
|
||||
|
||||
static constexpr float kGravAccel = -9.81f;
|
||||
static constexpr float kMaxDistanceFromOrigin = 1000.0f;
|
||||
|
||||
float yaw = 0.0f;
|
||||
float pitch = glm::half_pi<float>();
|
||||
glm::vec3 vel{ 0.0f, 0.0f, 0.0f };
|
||||
glm::vec3 vel{0.0f, 0.0f, 0.0f};
|
||||
bool grounded = false;
|
||||
bool was_grounded = false;
|
||||
|
||||
std::vector<engine::Line> perm_lines{}; // raycasting lines
|
||||
};
|
||||
|
||||
class CameraControllerSystem
|
||||
: public engine::System {
|
||||
class CameraControllerSystem : public engine::System {
|
||||
public:
|
||||
CameraControllerSystem(engine::Scene* scene);
|
||||
|
||||
|
@ -81,32 +81,15 @@ void PlayGame(GameSettings settings)
|
||||
* matrix */
|
||||
|
||||
auto camera_transform = main_scene->GetComponent<engine::TransformComponent>(camera);
|
||||
camera_transform->position = {-5.0f, -10.0f, 4.0f};
|
||||
camera_transform->position = {0.0f, 0.0f, 100.0f};
|
||||
|
||||
main_scene->RegisterComponent<CameraControllerComponent>();
|
||||
main_scene->RegisterSystem<CameraControllerSystem>();
|
||||
main_scene->AddComponent<CameraControllerComponent>(camera);
|
||||
|
||||
/* floor */
|
||||
engine::Entity floor = main_scene->CreateEntity("floor", 0, glm::vec3{-50.0f, -50.0f, 0.0f});
|
||||
auto floor_renderable = main_scene->AddComponent<engine::MeshRenderableComponent>(floor);
|
||||
floor_renderable->mesh = GenCuboidMesh(app.renderer()->GetDevice(), 100.0f, 100.0f, 0.1f, 50.0f);
|
||||
floor_renderable->material = std::make_unique<engine::Material>(app.renderer(), app.GetResource<engine::Shader>("builtin.fancy"));
|
||||
std::shared_ptr<engine::Texture> floor_albedo =
|
||||
engine::LoadTextureFromFile(app.GetResourcePath("textures/bricks-mortar-albedo.png"), engine::gfx::SamplerInfo{}, app.renderer());
|
||||
std::shared_ptr<engine::Texture> floor_normal =
|
||||
engine::LoadTextureFromFile(app.GetResourcePath("textures/bricks-mortar-normal.png"), engine::gfx::SamplerInfo{}, app.renderer(), false);
|
||||
std::shared_ptr<engine::Texture> floor_mr =
|
||||
engine::LoadTextureFromFile(app.GetResourcePath("textures/bricks-mortar-roughness.png"), engine::gfx::SamplerInfo{}, app.renderer(), false);
|
||||
floor_renderable->material->SetAlbedoTexture(floor_albedo);
|
||||
floor_renderable->material->SetNormalTexture(floor_normal);
|
||||
floor_renderable->material->SetMetallicRoughnessTexture(floor_mr);
|
||||
floor_renderable->material->SetOcclusionTexture(app.GetResource<engine::Texture>("builtin.white"));
|
||||
floor_renderable->visible = true ;
|
||||
auto floor_col = main_scene->AddComponent<engine::ColliderComponent>(floor);
|
||||
floor_col->aabb.min = glm::vec3{0.0f, 0.0f, 0.0f};
|
||||
floor_col->aabb.max = glm::vec3{100.0f, 100.0f, 0.1f };
|
||||
|
||||
engine::Entity floor = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/floor.glb"));
|
||||
main_scene->GetComponent<engine::MeshRenderableComponent>(main_scene->GetEntity("Cube", floor))->visible = false;
|
||||
|
||||
engine::Entity monke = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/monke.glb"));
|
||||
main_scene->GetComponent<engine::TransformComponent>(monke)->position.y += 10.0f;
|
||||
@ -116,23 +99,13 @@ void PlayGame(GameSettings settings)
|
||||
//main_scene->GetComponent<engine::TransformComponent>(bottle)->position.x += 25.0f;
|
||||
//main_scene->GetComponent<engine::TransformComponent>(bottle)->position.z += 5.0f;
|
||||
|
||||
/* skybox */
|
||||
engine::Entity skybox = main_scene->CreateEntity("skybox");
|
||||
auto skybox_transform = main_scene->GetComponent<engine::TransformComponent>(skybox);
|
||||
skybox_transform->is_static = true;
|
||||
skybox_transform->position = { -5.0f, -5.0f, -5.0f };
|
||||
auto skybox_renderable = main_scene->AddComponent<engine::MeshRenderableComponent>(skybox);
|
||||
skybox_renderable->mesh = GenCuboidMesh(app.renderer()->GetDevice(), 10.0f, 10.0f, 10.0f, 1.0f, true);
|
||||
skybox_renderable->material = std::make_unique<engine::Material>(app.renderer(), app.GetResource<engine::Shader>("builtin.skybox"));
|
||||
skybox_renderable->material->SetAlbedoTexture(app.GetResource<engine::Texture>("builtin.black"));
|
||||
|
||||
engine::Entity helmet = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/DamagedHelmet.glb"));
|
||||
main_scene->GetPosition(helmet) += glm::vec3{5.0f, 5.0f, 1.0f};
|
||||
main_scene->GetScale(helmet) *= 3.0f;
|
||||
|
||||
engine::Entity toycar = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/ToyCar.glb"));
|
||||
main_scene->GetScale(toycar) *= 100.0f;
|
||||
main_scene->GetPosition(toycar).z += 1.8f;
|
||||
main_scene->GetScale(toycar) *= 150.0f;
|
||||
main_scene->GetPosition(toycar).z -= 0.07f;
|
||||
|
||||
engine::Entity stairs = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/stairs.glb"));
|
||||
main_scene->GetPosition(stairs) += glm::vec3{-8.0f, -5.0f, 0.1f};
|
||||
|