Commit 4a146e26 authored by Florian Oetke's avatar Florian Oetke
Browse files

dual quaternion skinning

parent 1a923be6
......@@ -11,6 +11,7 @@ frag_shader:shadow_model = shader/bin/shadow_model.frag.spv
vert_shader:model = shader/bin/model.vert.spv
vert_shader:model_animated = shader/bin/model_animated.vert.spv
vert_shader:model_animated_dqs = shader/bin/model_animated_dqs.vert.spv
frag_shader:model = shader/bin/model.frag.spv
frag_shader:model_emissive = shader/bin/model_emissive.frag.spv
frag_shader:model_alphatest = shader/bin/model_alphatest.frag.spv
......
#version 450
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
#include "global_uniforms.glsl"
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
layout(location = 2) in vec2 tex_coords;
layout(location = 3) in ivec4 bone_ids;
layout(location = 4) in vec4 bone_weights;
layout(location = 0) out vec3 out_view_pos;
layout(location = 1) out vec3 out_normal;
layout(location = 2) out vec2 out_tex_coords;
layout(set=2, binding = 0, std140) uniform Bone_uniforms {
mat3x4 offset[64];
} bones;
layout(push_constant) uniform Per_model_uniforms {
mat4 model_to_view;
vec4 light_color;
vec4 options;
} model_uniforms;
out gl_PerVertex {
vec4 gl_Position;
};
vec3 transform_position(vec3 p, mat3x4 dq) {
p *= dq[2].xyz;
return p +
2 * cross(dq[0].xyz, cross(dq[0].xyz, p) + dq[0].w*p) +
2 * (dq[0].w * dq[1].xyz - dq[1].w * dq[0].xyz +
cross(dq[0].xyz, dq[1].xyz));
}
vec3 transform_normal(vec3 n, mat3x4 dq) {
return n + 2.0 * cross(dq[0].xyz, cross(dq[0].xyz, n) + dq[0].w * n);
}
void main() {
float unused_weight = 1.0 - dot(bone_weights, vec4(1.0));
mat3x4 identity_dqs = mat3x4(vec4(1,0,0,0), vec4(0,0,0,0), vec4(1,1,1,1));
mat3x4 bone = bones.offset[bone_ids[0]] * bone_weights[0]
+ bones.offset[bone_ids[1]] * bone_weights[1]
+ bones.offset[bone_ids[2]] * bone_weights[2]
+ bones.offset[bone_ids[3]] * bone_weights[3]
+ identity_dqs * unused_weight;
float dq_len = length(bone[0]);
bone[0] /= dq_len;
bone[1] /= dq_len;
vec3 p = transform_position(position, bone);
vec3 n = transform_normal (normal, bone);
//vec3 p = position;
//vec3 n = normal;
vec4 view_pos = model_uniforms.model_to_view * vec4(p, 1.0);
out_view_pos = view_pos.xyz / view_pos.w;
out_normal = (model_uniforms.model_to_view * vec4(n, 0.0)).xyz;
out_tex_coords = tex_coords;
gl_Position = global_uniforms.proj_mat * view_pos;
}
......@@ -44,7 +44,7 @@
},
"mouse_buttons": {
{"button":1, "clicks":0}: {"type":"continuous", "action":"capture_mouse"},
{"button":1, "clicks":0}: {"type":"continuous", "action":"capture_ptr"},
{"button":3, "clicks":0}: {"type":"once", "action":"create"}
},
"mouse_movement": {"type": "range", "action": "mouse_look"}
......
......@@ -72,18 +72,31 @@ namespace mirrage {
, _window_fullscreen(engine.window().fullscreen() != graphic::Fullscreen::no)
{
_animation_test = _meta_system.entities().emplace("monk");
_animation_test.get<Transform_comp>().process([](auto& transform) {
transform.position = {-8, 0, -0.5f};
_animation_test_dqs = _meta_system.entities().emplace("monk");
_animation_test_dqs.get<Transform_comp>().process([](auto& transform) {
transform.position = {-8, 0, -0.5f - 1.f};
transform.orientation = glm::quatLookAt(glm::vec3{-1, 0, 0}, glm::vec3{0, 1, 0});
});
_animation_test.get<renderer::Animation_comp>().process(
_animation_test_dqs.get<renderer::Animation_comp>().process(
[](auto& anim) { anim.animation("dance"_strid); });
_animation_test_lbs = _meta_system.entities().emplace("monk_lbs");
_animation_test_lbs.get<Transform_comp>().process([](auto& transform) {
transform.position = {-8, 0, -0.5f + 1.f};
transform.orientation = glm::quatLookAt(glm::vec3{-1, 0, 0}, glm::vec3{0, 1, 0});
});
_animation_test_lbs.get<renderer::Animation_comp>().process(
[](auto& anim) { anim.animation("dance"_strid); });
auto rotation_test = _meta_system.entities().emplace("rotation_test");
rotation_test.get<Transform_comp>().process([](auto& transform) {
transform.position = {-4, 0, -0.5f};
transform.position = {-4, 0, -0.5f - 1.f};
});
auto rotation_test_lbs = _meta_system.entities().emplace("rotation_test_lbs");
rotation_test_lbs.get<Transform_comp>().process([](auto& transform) {
transform.position = {-4, 0, -0.5f + 1.f};
});
......@@ -183,7 +196,7 @@ namespace mirrage {
switch(e.id) {
case "quit"_strid: _engine.screens().leave(); break;
case "capture_mouse"_strid:
case "capture_ptr"_strid:
_engine.input().capture_mouse(e.begin);
_mouse_look = e.begin;
_set_preset(0);
......@@ -715,12 +728,16 @@ namespace mirrage {
void Test_screen::_draw_animation_window()
{
auto anim_mb = _animation_test.get<renderer::Animation_comp>();
auto anim_mb = _animation_test_dqs.get<renderer::Animation_comp>();
if(anim_mb.is_nothing())
return;
auto& anim = anim_mb.get_or_throw();
auto anim_lbs_mb = _animation_test_lbs.get<renderer::Animation_comp>();
anim_lbs_mb.process([&](auto& anim_lbs) { anim_lbs.time(anim.time()); });
auto ctx = _gui.ctx();
if(nk_begin_titled(ctx,
"Animation",
......@@ -758,6 +775,8 @@ namespace mirrage {
if(new_idx != curr_idx) {
anim.animation(animations_ids.at(std::size_t(new_idx)));
anim_lbs_mb.process(
[&](auto& anim) { anim.animation(animations_ids.at(std::size_t(new_idx))); });
}
if(auto curr_animation = anim.animation(); curr_animation) {
......@@ -765,8 +784,9 @@ namespace mirrage {
nk_label(ctx, "Time", NK_TEXT_LEFT);
auto new_time = nk_slide_float(ctx, 0.f, anim.time(), duration, 0.01f);
if(std::abs(new_time - anim.time()) > 0.00001f)
if(std::abs(new_time - anim.time()) > 0.00001f) {
anim.time(new_time);
}
nk_label(ctx,
(util::to_string(new_time) + " / " + util::to_string(duration)).c_str(),
......
......@@ -44,7 +44,8 @@ namespace mirrage {
ecs::Entity_facet _camera;
ecs::Entity_facet _sun;
ecs::Entity_facet _animation_test;
ecs::Entity_facet _animation_test_dqs;
ecs::Entity_facet _animation_test_lbs;
float _sun_elevation = 0.92f;
float _sun_azimuth = 1.22f;
......
#pragma once
#include <mirrage/renderer/animation.hpp>
#include <assimp/scene.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
......@@ -36,6 +38,8 @@ namespace mirrage {
std::string default_output_directory = "output";
renderer::Skinning_type skinning_type = renderer::Skinning_type::linear_blend_skinning;
bool print_material_info = false;
bool print_animations = true;
};
......@@ -44,6 +48,7 @@ namespace mirrage {
material_texture_override,
empty_bones_to_keep,
default_output_directory,
skinning_type,
print_material_info,
print_animations);
......
......@@ -77,8 +77,8 @@ namespace mirrage {
if(node != skeleton.bones_by_name.end()) {
auto& bone_data = skeleton.bones[std::size_t(node->second)];
bone_data.assimp_bone = bone;
bone_data.offset =
renderer::to_bone_transform(to_glm(bone->mOffsetMatrix) * inv_transform);
bone_data.offset = renderer::compress_bone_transform(to_glm(bone->mOffsetMatrix)
* inv_transform);
// mark bone and all parents as used
used_bones[std::size_t(bone_data.idx)] = true;
......@@ -138,10 +138,11 @@ namespace mirrage {
out_file.write("MBFF", 4);
constexpr auto version = std::uint16_t(1);
write(out_file, version);
write(out_file, std::uint16_t(0));
write(out_file, std::uint16_t(std::uint16_t(cfg.skinning_type) & 0b11));
write(out_file, std::uint32_t(skeleton.bones.size()));
write(out_file, renderer::to_bone_transform(glm::inverse(to_glm(scene.mRootNode->mTransformation))));
write(out_file,
renderer::compress_bone_transform(glm::inverse(to_glm(scene.mRootNode->mTransformation))));
for(auto& bone : skeleton.bones)
write(out_file, bone.offset);
......
......@@ -23,7 +23,7 @@ namespace mirrage {
int idx = -1;
std::string parent_name;
int parent_idx = -1;
renderer::Bone_transform offset{1};
glm::mat3x4 offset{1};
renderer::Local_bone_transform local_node_transform{};
Bone_data() = default;
......
......@@ -529,6 +529,26 @@ namespace mirrage::graphic {
: vk::SubpassContents::eSecondaryCommandBuffers);
}
namespace {
auto print_stages(const std::unordered_map<Stage_id, std::size_t>& stages)
{
auto os = std::stringstream();
os << '[';
auto first = true;
for(auto&& [key, value] : stages) {
(void) value;
if(first)
first = false;
else
os << ", ";
os << key;
}
os << ']';
return os.str();
}
} // namespace
void Render_pass::set_stage(util::Str_id stage_id)
{
auto& cmb =
......@@ -536,7 +556,9 @@ namespace mirrage::graphic {
auto& stages = _stages.at(_subpass_index);
auto pipeline_idx = stages.find(stage_id);
MIRRAGE_INVARIANT(pipeline_idx != stages.end(), "Unknown render stage '" << stage_id.str() << "'!");
MIRRAGE_INVARIANT(pipeline_idx != stages.end(),
"Unknown render stage '" << stage_id.str() << "'! Expected one of "
<< print_stages(stages));
_bound_pipeline = pipeline_idx->second;
cmb.bindPipeline(vk::PipelineBindPoint::eGraphics, *_pipelines.at(pipeline_idx->second));
......
......@@ -4,6 +4,7 @@
#include <mirrage/utils/str_id.hpp>
#include <glm/glm.hpp>
#include <glm/gtx/dual_quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
#include <unordered_map>
......@@ -14,6 +15,10 @@ namespace mirrage::renderer {
using Bone_id = std::int_fast32_t;
enum class Skinning_type { linear_blend_skinning = 0b00, dual_quaternion_skinning = 0b01 };
sf2_enumDef(Skinning_type, linear_blend_skinning, dual_quaternion_skinning);
/// The local transform of a bone (relativ to its parent)
struct Local_bone_transform {
glm::quat orientation;
glm::vec3 translation;
......@@ -21,34 +26,27 @@ namespace mirrage::renderer {
};
static_assert(sizeof(Local_bone_transform) == 4 * (4 + 3 + 3), "Local_bone_transform contains padding");
using Bone_transform = glm::mat3x4;
extern auto default_bone_transform() -> Bone_transform;
extern auto to_bone_transform(const glm::vec3& translation,
const glm::quat& orientation,
const glm::vec3& scale) -> Bone_transform;
extern auto to_bone_transform(const Local_bone_transform&) -> Bone_transform;
extern auto to_bone_transform(const glm::mat4&) -> Bone_transform;
extern auto from_bone_transform(const Bone_transform&) -> glm::mat4;
extern auto mul(const Bone_transform& lhs, const Bone_transform& rhs) -> Bone_transform;
template <class... BT, typename = std::enable_if<std::conjunction_v<std::is_same<Bone_transform, BT>...>>>
auto mul(const Bone_transform& lhs, const Bone_transform& rhs, const BT&... bts) -> Bone_transform
{
auto r = mul(lhs, rhs);
if constexpr(sizeof...(bts) == 0)
return r;
else
return mul(r, bts...);
}
/// compresses a bone transform into a 3x4 matrix by dropping the last row and transposing it
extern auto compress_bone_transform(const glm::mat4&) -> glm::mat3x4;
// The global transform of each bone, as passed to the vertex shader
union Final_bone_transform {
glm::mat3x4 lbs;
struct Dqs {
glm::dualquat dq;
glm::vec4 scale;
} dqs;
};
static_assert(sizeof(Final_bone_transform) == sizeof(glm::mat3x4), "Final_bone_transform has wrong size");
static_assert(alignof(Final_bone_transform) == alignof(glm::mat3x4),
"Bone_transform has wrong alignment");
/*
* File format:
* | 0 | 1 | 2 | 3 |
* | M | B | F | F |
* | VERSION | RESERVED |
* | VERSION | FLAGS | flags: 2 bits skinning type, rest reserved
* | BONE_COUNT |
*
* | INVERSE ROOT TRANSFORM |
......@@ -74,16 +72,10 @@ namespace mirrage::renderer {
auto bone_count() const noexcept { return Bone_id(_inv_bind_poses.size()); }
auto inv_bind_pose(Bone_id bone) const -> const Bone_transform&
{
return _inv_bind_poses[std::size_t(bone)];
}
auto node_transform(Bone_id bone) const -> const Local_bone_transform&
{
return _node_transforms[std::size_t(bone)];
}
auto inverse_root_transform() const -> const Bone_transform& { return _inv_root_transform; }
auto parent_bone(Bone_id bone) const noexcept -> util::maybe<Bone_id>
{
......@@ -95,16 +87,20 @@ namespace mirrage::renderer {
}
void to_final_transforms(gsl::span<const Local_bone_transform> in,
gsl::span<Bone_transform> out) const;
gsl::span<Final_bone_transform> out) const;
auto skinning_type() const noexcept { return _skinning_type; }
private:
std::vector<Bone_transform> _inv_bind_poses;
using Bone_id_by_name = std::unordered_map<util::Str_id, std::size_t>;
std::vector<Final_bone_transform> _inv_bind_poses;
std::vector<Local_bone_transform> _node_transforms;
std::vector<std::int32_t> _parent_ids;
std::vector<util::Str_id> _names;
Bone_transform _inv_root_transform;
std::unordered_map<util::Str_id, std::size_t> _bones_by_name;
Final_bone_transform _inv_root_transform;
Bone_id_by_name _bones_by_name;
Skinning_type _skinning_type;
};
namespace detail {
......
......@@ -37,17 +37,17 @@ namespace mirrage::renderer {
vk::Sampler,
graphic::Texture_ptr albedo,
graphic::Texture_ptr mat_data,
util::Str_id material_id);
util::Str_id substance_id);
void bind(graphic::Render_pass& pass) const;
auto material_id() const noexcept { return _material_id; }
auto substance_id() const noexcept { return _substance_id; }
private:
graphic::DescriptorSet _descriptor_set;
graphic::Texture_ptr _albedo;
graphic::Texture_ptr _mat_data;
util::Str_id _material_id;
util::Str_id _substance_id;
};
using Material_ptr = asset::Ptr<Material>;
......
......@@ -2,6 +2,7 @@
#include <mirrage/ecs/entity_handle.hpp>
#include <mirrage/utils/maybe.hpp>
#include <mirrage/utils/str_id.hpp>
#include <mirrage/utils/units.hpp>
#include <glm/gtx/quaternion.hpp>
......@@ -40,6 +41,7 @@ namespace mirrage::renderer {
glm::quat orientation{1, 0, 0, 0};
glm::vec3 scale{1.f, 1.f, 1.f};
const Model* model;
util::Str_id substance_id;
std::uint32_t sub_mesh;
std::uint32_t culling_mask;
......@@ -49,6 +51,7 @@ namespace mirrage::renderer {
glm::quat orientation,
glm::vec3 scale,
const Model* model,
util::Str_id substance_id,
std::uint32_t sub_mesh,
std::uint32_t culling_mask)
: entity(entity)
......@@ -56,6 +59,7 @@ namespace mirrage::renderer {
, orientation(orientation)
, scale(scale)
, model(model)
, substance_id(substance_id)
, sub_mesh(sub_mesh)
, culling_mask(culling_mask)
{
......
#include <mirrage/renderer/animation.hpp>
#include <glm/glm.hpp>
#include <glm/gtx/dual_quaternion.hpp>
#include <glm/gtx/matrix_decompose.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/string_cast.hpp>
#include <glm/gtx/transform.hpp>
......@@ -25,62 +27,21 @@ namespace mirrage::renderer {
} // namespace
auto default_bone_transform() -> Bone_transform { return Bone_transform(1); }
auto to_bone_transform(const glm::vec3& translation, const glm::quat& orientation, const glm::vec3& scale)
-> Bone_transform
{
return to_bone_transform(glm::translate(glm::mat4(1), translation) * glm::mat4_cast(orientation)
* glm::scale(glm::mat4(1.f), scale));
}
auto to_bone_transform(const Local_bone_transform& t) -> Bone_transform
{
return to_bone_transform(t.translation, t.orientation, t.scale);
}
auto to_bone_transform(const glm::mat4& m) -> Bone_transform
auto compress_bone_transform(const glm::mat4& m) -> glm::mat3x4
{
MIRRAGE_INVARIANT(
std::abs(m[0][3]) < 0.000001f && std::abs(m[1][3]) < 0.000001f
&& std::abs(m[2][3]) < 0.000001f && std::abs(m[3][3] - 1) < 0.000001f,
"Last row of input into to_bone_transform is not well formed: " << glm::to_string(m));
auto r = Bone_transform();
auto r = glm::mat3x4();
r[0] = glm::vec4(m[0][0], m[1][0], m[2][0], m[3][0]);
r[1] = glm::vec4(m[0][1], m[1][1], m[2][1], m[3][1]);
r[2] = glm::vec4(m[0][2], m[1][2], m[2][2], m[3][2]);
return r;
}
auto from_bone_transform(const Bone_transform& transform) -> glm::mat4
{
auto m = glm::mat4();
m[0] = transform[0];
m[1] = transform[1];
m[2] = transform[2];
m[3] = glm::vec4(0, 0, 0, 1);
return glm::transpose(m);
}
auto mul(const Bone_transform& rhs, const Bone_transform& lhs) -> Bone_transform
{
// Bone_transform is a mat4x4 with the last row cut of and transposed
// since (A^T B^T)^T = BA => switch lhs and rhs and multiply
const auto src_A0 = lhs[0];
const auto src_A1 = lhs[1];
const auto src_A2 = lhs[2];
const auto src_B0 = rhs[0];
const auto src_B1 = rhs[1];
const auto src_B2 = rhs[2];
Bone_transform r;
r[0] = src_A0 * src_B0[0] + src_A1 * src_B0[1] + src_A2 * src_B0[2]
+ glm::vec4(0, 0, 0, 1) * src_B0[3];
r[1] = src_A0 * src_B1[0] + src_A1 * src_B1[1] + src_A2 * src_B1[2]
+ glm::vec4(0, 0, 0, 1) * src_B1[3];
r[2] = src_A0 * src_B2[0] + src_A1 * src_B2[1] + src_A2 * src_B2[2]
+ glm::vec4(0, 0, 0, 1) * src_B2[3];
return r;
}
// SKELETON
Skeleton::Skeleton(asset::istream& file)
{
auto header = std::array<char, 4>();
......@@ -91,12 +52,12 @@ namespace mirrage::renderer {
auto version = read<std::uint16_t>(file);
MIRRAGE_INVARIANT(version == 1, "Unsupported bone file version " << version << ". Expected 1");
// skip reserved
read<std::uint16_t>(file);
auto flags = read<std::uint16_t>(file);
_skinning_type = static_cast<Skinning_type>(flags & 0b11);
auto bone_count = read<std::uint32_t>(file);
_inv_root_transform = read<Bone_transform>(file);
_inv_root_transform = read<Final_bone_transform>(file);
_inv_bind_poses.resize(bone_count);
_node_transforms.resize(bone_count);
......@@ -118,31 +79,112 @@ namespace mirrage::renderer {
"Mirrage bone file '" << file.aid().str() << "' corrupted (footer).");
}
namespace {
auto default_final_transform(Skinning_type st)
{
switch(st) {
case Skinning_type::linear_blend_skinning: {
auto r = Final_bone_transform();
r.lbs = glm::mat3x4(1);
return r;
}
case Skinning_type::dual_quaternion_skinning: {
auto r = Final_bone_transform();
r.dqs.dq = glm::dual_quat_identity<float, glm::defaultp>();
r.dqs.scale = glm::vec4(1, 1, 1, 1);
return r;
}
}
MIRRAGE_FAIL("Unknown Skinning_type: " << int(st));
}
auto to_bone_transform(const Local_bone_transform& t) -> Final_bone_transform
{
auto r = Final_bone_transform();
r.lbs = compress_bone_transform(glm::translate(glm::mat4(1), t.translation)
* glm::mat4_cast(t.orientation)
* glm::scale(glm::mat4(1.f), t.scale));
return r;
}
auto mul(const Final_bone_transform& rhs, const Final_bone_transform& lhs) -> Final_bone_transform
{
// Bone_transform is a mat4x4 with the last row cut of and transposed
// since (A^T B^T)^T = BA => switch lhs and rhs and multiply
const auto src_A0 = lhs.lbs[0];
const auto src_A1 = lhs.lbs[1];
const auto src_A2 = lhs.lbs[2];
const auto src_B0 = rhs.lbs[0];
const auto src_B1 = rhs.lbs[1];
const auto src_B2 = rhs.lbs[2];
Final_bone_transform r;
r.lbs[0] = src_A0 * src_B0[0] + src_A1 * src_B0[1] + src_A2 * src_B0[2]
+ glm::vec4(0, 0, 0, 1) * src_B0[3];