Skeletons are now working.
This commit is contained in:
parent
e3b76fd040
commit
b08f13d558
6 changed files with 376 additions and 13 deletions
|
@ -626,7 +626,12 @@ void RasterizerSceneForwardRD::_fill_instances(RenderList::Element **p_elements,
|
|||
}
|
||||
|
||||
id.flags |= (stride << INSTANCE_DATA_FLAGS_MULTIMESH_STRIDE_SHIFT);
|
||||
} else if (e->instance->base_type == VS::INSTANCE_MESH) {
|
||||
if (e->instance->skeleton.is_valid()) {
|
||||
id.flags |= INSTANCE_DATA_FLAG_SKELETON;
|
||||
}
|
||||
}
|
||||
|
||||
//forward
|
||||
|
||||
uint32_t reflection_count = 0;
|
||||
|
@ -746,6 +751,9 @@ void RasterizerSceneForwardRD::_render_list(RenderingDevice::DrawListID p_draw_l
|
|||
switch (e->instance->base_type) {
|
||||
case VS::INSTANCE_MESH: {
|
||||
primitive = storage->mesh_surface_get_primitive(e->instance->base, e->surface_index);
|
||||
if (e->instance->skeleton.is_valid()) {
|
||||
xforms_uniform_set = storage->skeleton_get_3d_uniform_set(e->instance->skeleton, default_shader_rd, 1);
|
||||
}
|
||||
} break;
|
||||
case VS::INSTANCE_MULTIMESH: {
|
||||
RID mesh = storage->multimesh_get_mesh(e->instance->base);
|
||||
|
|
|
@ -254,6 +254,7 @@ class RasterizerSceneForwardRD : public RasterizerSceneRD {
|
|||
INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA = 1 << 15,
|
||||
INSTANCE_DATA_FLAGS_MULTIMESH_STRIDE_SHIFT = 16,
|
||||
INSTANCE_DATA_FLAGS_MULTIMESH_STRIDE_MASK = 0x7,
|
||||
INSTANCE_DATA_FLAG_SKELETON = 1 << 19,
|
||||
};
|
||||
|
||||
struct InstanceData {
|
||||
|
|
|
@ -2010,11 +2010,104 @@ AABB RasterizerStorageRD::mesh_get_aabb(RID p_mesh, RID p_skeleton) {
|
|||
return mesh->custom_aabb;
|
||||
}
|
||||
|
||||
if (!p_skeleton.is_valid()) {
|
||||
Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
|
||||
|
||||
if (!skeleton || skeleton->size == 0) {
|
||||
return mesh->aabb;
|
||||
}
|
||||
|
||||
return mesh->aabb;
|
||||
AABB aabb;
|
||||
|
||||
for (int i = 0; i < mesh->surface_count; i++) {
|
||||
|
||||
AABB laabb;
|
||||
if ((mesh->surfaces[i]->format & VS::ARRAY_FORMAT_BONES) && mesh->surfaces[i]->bone_aabbs.size()) {
|
||||
|
||||
int bs = mesh->surfaces[i]->bone_aabbs.size();
|
||||
const AABB *skbones = mesh->surfaces[i]->bone_aabbs.ptr();
|
||||
|
||||
int sbs = skeleton->size;
|
||||
ERR_CONTINUE(bs > sbs);
|
||||
const float *baseptr = skeleton->data.ptr();
|
||||
|
||||
bool first = true;
|
||||
|
||||
if (skeleton->use_2d) {
|
||||
for (int j = 0; j < bs; j++) {
|
||||
|
||||
if (skbones[0].size == Vector3())
|
||||
continue; //bone is unused
|
||||
|
||||
const float *dataptr = baseptr + j * 8;
|
||||
|
||||
Transform mtx;
|
||||
|
||||
mtx.basis.elements[0].x = dataptr[0];
|
||||
mtx.basis.elements[1].x = dataptr[1];
|
||||
mtx.origin.x = dataptr[3];
|
||||
|
||||
mtx.basis.elements[0].y = dataptr[4];
|
||||
mtx.basis.elements[1].y = dataptr[5];
|
||||
mtx.origin.y = dataptr[7];
|
||||
|
||||
AABB baabb = mtx.xform(skbones[j]);
|
||||
|
||||
if (first) {
|
||||
laabb = baabb;
|
||||
first = false;
|
||||
} else {
|
||||
laabb.merge_with(baabb);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < bs; j++) {
|
||||
|
||||
if (skbones[0].size == Vector3())
|
||||
continue; //bone is unused
|
||||
|
||||
const float *dataptr = baseptr + j * 12;
|
||||
|
||||
Transform mtx;
|
||||
|
||||
mtx.basis.elements[0][0] = dataptr[0];
|
||||
mtx.basis.elements[0][1] = dataptr[1];
|
||||
mtx.basis.elements[0][2] = dataptr[2];
|
||||
mtx.origin.x = dataptr[3];
|
||||
mtx.basis.elements[1][0] = dataptr[4];
|
||||
mtx.basis.elements[1][1] = dataptr[5];
|
||||
mtx.basis.elements[1][2] = dataptr[6];
|
||||
mtx.origin.y = dataptr[7];
|
||||
mtx.basis.elements[2][0] = dataptr[8];
|
||||
mtx.basis.elements[2][1] = dataptr[9];
|
||||
mtx.basis.elements[2][2] = dataptr[10];
|
||||
mtx.origin.z = dataptr[11];
|
||||
|
||||
AABB baabb = mtx.xform(skbones[j]);
|
||||
if (first) {
|
||||
laabb = baabb;
|
||||
first = false;
|
||||
} else {
|
||||
laabb.merge_with(baabb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (laabb.size == Vector3()) {
|
||||
laabb = mesh->surfaces[i]->aabb;
|
||||
}
|
||||
} else {
|
||||
|
||||
laabb = mesh->surfaces[i]->aabb;
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
aabb = laabb;
|
||||
} else {
|
||||
aabb.merge_with(laabb);
|
||||
}
|
||||
}
|
||||
|
||||
return aabb;
|
||||
}
|
||||
|
||||
void RasterizerStorageRD::mesh_clear(RID p_mesh) {
|
||||
|
@ -2783,6 +2876,186 @@ void RasterizerStorageRD::_update_dirty_multimeshes() {
|
|||
multimesh_dirty_list = nullptr;
|
||||
}
|
||||
|
||||
/* SKELETON */
|
||||
|
||||
/* SKELETON API */
|
||||
|
||||
RID RasterizerStorageRD::skeleton_create() {
|
||||
|
||||
return skeleton_owner.make_rid(Skeleton());
|
||||
}
|
||||
|
||||
void RasterizerStorageRD::_skeleton_make_dirty(Skeleton *skeleton) {
|
||||
|
||||
if (!skeleton->dirty) {
|
||||
skeleton->dirty = true;
|
||||
skeleton->dirty_list = skeleton_dirty_list;
|
||||
skeleton_dirty_list = skeleton;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerStorageRD::skeleton_allocate(RID p_skeleton, int p_bones, bool p_2d_skeleton) {
|
||||
|
||||
Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
|
||||
ERR_FAIL_COND(!skeleton);
|
||||
ERR_FAIL_COND(p_bones < 0);
|
||||
|
||||
if (skeleton->size == p_bones && skeleton->use_2d == p_2d_skeleton)
|
||||
return;
|
||||
|
||||
skeleton->size = p_bones;
|
||||
skeleton->use_2d = p_2d_skeleton;
|
||||
skeleton->uniform_set_3d = RID();
|
||||
|
||||
if (skeleton->buffer.is_valid()) {
|
||||
RD::get_singleton()->free(skeleton->buffer);
|
||||
skeleton->buffer = RID();
|
||||
skeleton->data.resize(0);
|
||||
}
|
||||
|
||||
if (skeleton->size) {
|
||||
|
||||
skeleton->data.resize(skeleton->size * (skeleton->use_2d ? 8 : 12));
|
||||
skeleton->buffer = RD::get_singleton()->storage_buffer_create(skeleton->data.size() * sizeof(float));
|
||||
zeromem(skeleton->data.ptrw(), skeleton->data.size() * sizeof(float));
|
||||
|
||||
_skeleton_make_dirty(skeleton);
|
||||
}
|
||||
}
|
||||
int RasterizerStorageRD::skeleton_get_bone_count(RID p_skeleton) const {
|
||||
|
||||
Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
|
||||
ERR_FAIL_COND_V(!skeleton, 0);
|
||||
|
||||
return skeleton->size;
|
||||
}
|
||||
|
||||
void RasterizerStorageRD::skeleton_bone_set_transform(RID p_skeleton, int p_bone, const Transform &p_transform) {
|
||||
|
||||
Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
|
||||
|
||||
ERR_FAIL_COND(!skeleton);
|
||||
ERR_FAIL_INDEX(p_bone, skeleton->size);
|
||||
ERR_FAIL_COND(skeleton->use_2d);
|
||||
|
||||
float *dataptr = skeleton->data.ptrw() + p_bone * 12;
|
||||
|
||||
dataptr[0] = p_transform.basis.elements[0][0];
|
||||
dataptr[1] = p_transform.basis.elements[0][1];
|
||||
dataptr[2] = p_transform.basis.elements[0][2];
|
||||
dataptr[3] = p_transform.origin.x;
|
||||
dataptr[4] = p_transform.basis.elements[1][0];
|
||||
dataptr[5] = p_transform.basis.elements[1][1];
|
||||
dataptr[6] = p_transform.basis.elements[1][2];
|
||||
dataptr[7] = p_transform.origin.y;
|
||||
dataptr[8] = p_transform.basis.elements[2][0];
|
||||
dataptr[9] = p_transform.basis.elements[2][1];
|
||||
dataptr[10] = p_transform.basis.elements[2][2];
|
||||
dataptr[11] = p_transform.origin.z;
|
||||
|
||||
_skeleton_make_dirty(skeleton);
|
||||
}
|
||||
|
||||
Transform RasterizerStorageRD::skeleton_bone_get_transform(RID p_skeleton, int p_bone) const {
|
||||
|
||||
Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
|
||||
|
||||
ERR_FAIL_COND_V(!skeleton, Transform());
|
||||
ERR_FAIL_INDEX_V(p_bone, skeleton->size, Transform());
|
||||
ERR_FAIL_COND_V(skeleton->use_2d, Transform());
|
||||
|
||||
const float *dataptr = skeleton->data.ptr() + p_bone * 12;
|
||||
|
||||
Transform t;
|
||||
|
||||
t.basis.elements[0][0] = dataptr[0];
|
||||
t.basis.elements[0][1] = dataptr[1];
|
||||
t.basis.elements[0][2] = dataptr[2];
|
||||
t.origin.x = dataptr[3];
|
||||
t.basis.elements[1][0] = dataptr[4];
|
||||
t.basis.elements[1][1] = dataptr[5];
|
||||
t.basis.elements[1][2] = dataptr[6];
|
||||
t.origin.y = dataptr[7];
|
||||
t.basis.elements[2][0] = dataptr[8];
|
||||
t.basis.elements[2][1] = dataptr[9];
|
||||
t.basis.elements[2][2] = dataptr[10];
|
||||
t.origin.z = dataptr[11];
|
||||
|
||||
return t;
|
||||
}
|
||||
void RasterizerStorageRD::skeleton_bone_set_transform_2d(RID p_skeleton, int p_bone, const Transform2D &p_transform) {
|
||||
|
||||
Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
|
||||
|
||||
ERR_FAIL_COND(!skeleton);
|
||||
ERR_FAIL_INDEX(p_bone, skeleton->size);
|
||||
ERR_FAIL_COND(!skeleton->use_2d);
|
||||
|
||||
float *dataptr = skeleton->data.ptrw() + p_bone * 8;
|
||||
|
||||
dataptr[0] = p_transform.elements[0][0];
|
||||
dataptr[1] = p_transform.elements[1][0];
|
||||
dataptr[2] = 0;
|
||||
dataptr[3] = p_transform.elements[2][0];
|
||||
dataptr[4] = p_transform.elements[0][1];
|
||||
dataptr[5] = p_transform.elements[1][1];
|
||||
dataptr[6] = 0;
|
||||
dataptr[7] = p_transform.elements[2][1];
|
||||
|
||||
_skeleton_make_dirty(skeleton);
|
||||
}
|
||||
Transform2D RasterizerStorageRD::skeleton_bone_get_transform_2d(RID p_skeleton, int p_bone) const {
|
||||
|
||||
Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
|
||||
|
||||
ERR_FAIL_COND_V(!skeleton, Transform2D());
|
||||
ERR_FAIL_INDEX_V(p_bone, skeleton->size, Transform2D());
|
||||
ERR_FAIL_COND_V(!skeleton->use_2d, Transform2D());
|
||||
|
||||
const float *dataptr = skeleton->data.ptr() + p_bone * 8;
|
||||
|
||||
Transform2D t;
|
||||
t.elements[0][0] = dataptr[0];
|
||||
t.elements[1][0] = dataptr[1];
|
||||
t.elements[2][0] = dataptr[3];
|
||||
t.elements[0][1] = dataptr[4];
|
||||
t.elements[1][1] = dataptr[5];
|
||||
t.elements[2][1] = dataptr[7];
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
void RasterizerStorageRD::skeleton_set_base_transform_2d(RID p_skeleton, const Transform2D &p_base_transform) {
|
||||
|
||||
Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
|
||||
|
||||
ERR_FAIL_COND(!skeleton->use_2d);
|
||||
|
||||
skeleton->base_transform_2d = p_base_transform;
|
||||
}
|
||||
|
||||
void RasterizerStorageRD::_update_dirty_skeletons() {
|
||||
|
||||
while (skeleton_dirty_list) {
|
||||
|
||||
Skeleton *skeleton = skeleton_dirty_list;
|
||||
|
||||
if (skeleton->size) {
|
||||
|
||||
RD::get_singleton()->buffer_update(skeleton->buffer, 0, skeleton->data.size() * sizeof(float), skeleton->data.ptr(), false);
|
||||
}
|
||||
|
||||
skeleton_dirty_list = skeleton->dirty_list;
|
||||
|
||||
skeleton->instance_dependency.instance_notify_changed(true, false);
|
||||
|
||||
skeleton->dirty = false;
|
||||
skeleton->dirty_list = nullptr;
|
||||
}
|
||||
|
||||
skeleton_dirty_list = nullptr;
|
||||
}
|
||||
|
||||
/* LIGHT */
|
||||
|
||||
RID RasterizerStorageRD::light_create(VS::LightType p_type) {
|
||||
|
@ -3601,6 +3874,14 @@ void RasterizerStorageRD::base_update_dependency(RID p_base, RasterizerScene::In
|
|||
}
|
||||
}
|
||||
|
||||
void RasterizerStorageRD::skeleton_update_dependency(RID p_skeleton, RasterizerScene::InstanceBase *p_instance) {
|
||||
|
||||
Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
|
||||
ERR_FAIL_COND(!skeleton);
|
||||
|
||||
p_instance->update_dependency(&skeleton->instance_dependency);
|
||||
}
|
||||
|
||||
VS::InstanceType RasterizerStorageRD::get_base_type(RID p_rid) const {
|
||||
|
||||
if (mesh_owner.owns(p_rid)) {
|
||||
|
@ -3621,6 +3902,7 @@ VS::InstanceType RasterizerStorageRD::get_base_type(RID p_rid) const {
|
|||
void RasterizerStorageRD::update_dirty_resources() {
|
||||
_update_queued_materials();
|
||||
_update_dirty_multimeshes();
|
||||
_update_dirty_skeletons();
|
||||
}
|
||||
|
||||
bool RasterizerStorageRD::has_os_feature(const String &p_feature) const {
|
||||
|
@ -3705,6 +3987,12 @@ bool RasterizerStorageRD::free(RID p_rid) {
|
|||
MultiMesh *multimesh = multimesh_owner.getornull(p_rid);
|
||||
multimesh->instance_dependency.instance_notify_deleted(p_rid);
|
||||
multimesh_owner.free(p_rid);
|
||||
} else if (skeleton_owner.owns(p_rid)) {
|
||||
_update_dirty_skeletons();
|
||||
skeleton_allocate(p_rid, 0);
|
||||
Skeleton *skeleton = skeleton_owner.getornull(p_rid);
|
||||
skeleton->instance_dependency.instance_notify_deleted(p_rid);
|
||||
skeleton_owner.free(p_rid);
|
||||
} else if (reflection_probe_owner.owns(p_rid)) {
|
||||
ReflectionProbe *reflection_probe = reflection_probe_owner.getornull(p_rid);
|
||||
reflection_probe->instance_dependency.instance_notify_deleted(p_rid);
|
||||
|
|
|
@ -327,6 +327,32 @@ private:
|
|||
_FORCE_INLINE_ void _multimesh_mark_all_dirty(MultiMesh *multimesh, bool p_data, bool p_aabb);
|
||||
_FORCE_INLINE_ void _multimesh_re_create_aabb(MultiMesh *multimesh, const float *p_data, int p_instances);
|
||||
void _update_dirty_multimeshes();
|
||||
|
||||
/* Skeleton */
|
||||
|
||||
struct Skeleton {
|
||||
bool use_2d = false;
|
||||
int size = 0;
|
||||
Vector<float> data;
|
||||
RID buffer;
|
||||
|
||||
bool dirty = false;
|
||||
Skeleton *dirty_list = nullptr;
|
||||
Transform2D base_transform_2d;
|
||||
|
||||
RID uniform_set_3d;
|
||||
|
||||
RasterizerScene::InstanceDependency instance_dependency;
|
||||
};
|
||||
|
||||
mutable RID_Owner<Skeleton> skeleton_owner;
|
||||
|
||||
_FORCE_INLINE_ void _skeleton_make_dirty(Skeleton *skeleton);
|
||||
|
||||
Skeleton *skeleton_dirty_list = nullptr;
|
||||
|
||||
void _update_dirty_skeletons();
|
||||
|
||||
/* LIGHT */
|
||||
|
||||
struct Light {
|
||||
|
@ -740,16 +766,33 @@ public:
|
|||
|
||||
/* SKELETON API */
|
||||
|
||||
RID skeleton_create() { return RID(); }
|
||||
void skeleton_allocate(RID p_skeleton, int p_bones, bool p_2d_skeleton = false) {}
|
||||
void skeleton_set_base_transform_2d(RID p_skeleton, const Transform2D &p_base_transform) {}
|
||||
void skeleton_set_world_transform(RID p_skeleton, bool p_enable, const Transform &p_world_transform) {}
|
||||
int skeleton_get_bone_count(RID p_skeleton) const { return 0; }
|
||||
void skeleton_bone_set_transform(RID p_skeleton, int p_bone, const Transform &p_transform) {}
|
||||
Transform skeleton_bone_get_transform(RID p_skeleton, int p_bone) const { return Transform(); }
|
||||
void skeleton_bone_set_transform_2d(RID p_skeleton, int p_bone, const Transform2D &p_transform) {}
|
||||
Transform2D skeleton_bone_get_transform_2d(RID p_skeleton, int p_bone) const { return Transform2D(); }
|
||||
RID skeleton_create();
|
||||
void skeleton_allocate(RID p_skeleton, int p_bones, bool p_2d_skeleton = false);
|
||||
void skeleton_set_base_transform_2d(RID p_skeleton, const Transform2D &p_base_transform);
|
||||
void skeleton_set_world_transform(RID p_skeleton, bool p_enable, const Transform &p_world_transform);
|
||||
int skeleton_get_bone_count(RID p_skeleton) const;
|
||||
void skeleton_bone_set_transform(RID p_skeleton, int p_bone, const Transform &p_transform);
|
||||
Transform skeleton_bone_get_transform(RID p_skeleton, int p_bone) const;
|
||||
void skeleton_bone_set_transform_2d(RID p_skeleton, int p_bone, const Transform2D &p_transform);
|
||||
Transform2D skeleton_bone_get_transform_2d(RID p_skeleton, int p_bone) const;
|
||||
|
||||
_FORCE_INLINE_ RID skeleton_get_3d_uniform_set(RID p_skeleton, RID p_shader, uint32_t p_set) const {
|
||||
Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
|
||||
if (skeleton->use_2d) {
|
||||
return RID();
|
||||
}
|
||||
if (!skeleton->uniform_set_3d.is_valid()) {
|
||||
Vector<RD::Uniform> uniforms;
|
||||
RD::Uniform u;
|
||||
u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
|
||||
u.binding = 0;
|
||||
u.ids.push_back(skeleton->buffer);
|
||||
uniforms.push_back(u);
|
||||
skeleton->uniform_set_3d = RD::get_singleton()->uniform_set_create(uniforms, p_shader, p_set);
|
||||
}
|
||||
|
||||
return skeleton->uniform_set_3d;
|
||||
}
|
||||
/* Light API */
|
||||
|
||||
RID light_create(VS::LightType p_type);
|
||||
|
@ -873,7 +916,7 @@ public:
|
|||
float reflection_probe_get_interior_ambient_probe_contribution(RID p_probe) const;
|
||||
|
||||
void base_update_dependency(RID p_base, RasterizerScene::InstanceBase *p_instance);
|
||||
void skeleton_update_dependency(RID p_skeleton, RasterizerScene::InstanceBase *p_instance) {}
|
||||
void skeleton_update_dependency(RID p_skeleton, RasterizerScene::InstanceBase *p_instance);
|
||||
|
||||
/* GI PROBE API */
|
||||
|
||||
|
|
|
@ -136,13 +136,35 @@ void main() {
|
|||
#if defined(TANGENT_USED) || defined(NORMALMAP_USED) || defined(LIGHT_ANISOTROPY_USED)
|
||||
vec3 tangent = tangent_attrib.xyz;
|
||||
float binormalf = tangent_attrib.a;
|
||||
vec3 binormal = normalize(cross(normal, tangent) * binormalf);
|
||||
#endif
|
||||
|
||||
|
||||
if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_SKELETON)) {
|
||||
//multimesh, instances are for it
|
||||
|
||||
uvec2 bones_01 = uvec2(bone_attrib.x&0xFFFF,bone_attrib.x>>16) * 3;
|
||||
uvec2 bones_23 = uvec2(bone_attrib.y&0xFFFF,bone_attrib.y>>16) * 3;
|
||||
vec2 weights_01 = unpackUnorm2x16(bone_attrib.z);
|
||||
vec2 weights_23 = unpackUnorm2x16(bone_attrib.w);
|
||||
|
||||
mat4 m = mat4(transforms.data[bones_01.x],transforms.data[bones_01.x+1],transforms.data[bones_01.x+2],vec4(0.0,0.0,0.0,1.0)) * weights_01.x;
|
||||
m += mat4(transforms.data[bones_01.y],transforms.data[bones_01.y+1],transforms.data[bones_01.y+2],vec4(0.0,0.0,0.0,1.0)) * weights_01.y;
|
||||
m += mat4(transforms.data[bones_23.x],transforms.data[bones_23.x+1],transforms.data[bones_23.x+2],vec4(0.0,0.0,0.0,1.0)) * weights_23.x;
|
||||
m += mat4(transforms.data[bones_23.y],transforms.data[bones_23.y+1],transforms.data[bones_23.y+2],vec4(0.0,0.0,0.0,1.0)) * weights_23.y;
|
||||
|
||||
//reverse order because its transposed
|
||||
vertex = (vec4(vertex,1.0) * m).xyz;
|
||||
normal = (vec4(normal,0.0) * m).xyz;
|
||||
|
||||
#if defined(TANGENT_USED) || defined(NORMALMAP_USED) || defined(LIGHT_ANISOTROPY_USED)
|
||||
|
||||
vec3 binormal = normalize(cross(normal, tangent) * binormalf);
|
||||
tangent = (vec4(tangent,0.0) * m).xyz;
|
||||
binormal = (vec4(binormal,0.0) * m).xyz;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#if defined(UV_USED)
|
||||
uv_interp = uv_attrib;
|
||||
#endif
|
||||
|
|
|
@ -131,6 +131,7 @@ layout(set=0,binding=8,std140) uniform SceneData {
|
|||
//3 bits of stride
|
||||
#define INSTANCE_FLAGS_MULTIMESH_STRIDE_MASK 0x7
|
||||
|
||||
#define INSTANCE_FLAGS_SKELETON (1 << 19)
|
||||
|
||||
|
||||
struct InstanceData {
|
||||
|
|
Loading…
Reference in a new issue