Files
filament/shaders/src/main.vs
fvbj 5d30435620 Extend skinning for >4 bones per vertex (#6772)
* Add skinning and morphing samples to check functionality
* Implement skinning for more than four bones pair vertex

The API allows defining an unlimited number of bone indices and weights of primitives. Data is defined in building process of the renderable manager. Backward compatibility with the original solution.
Skinning of vertices is calculated on GPU, data is transferred to the vertex shader in the texture.
2023-09-21 13:04:12 -07:00

258 lines
11 KiB
GLSL

/*
* This is the main vertex shader of surface materials. It can be invoked with
* USE_OPTIMIZED_DEPTH_VERTEX_SHADER defined, and in this case we are guaranteed that the
* DEPTH variant is active *AND* there is no custom vertex shader (i.e.: materialVertex() is
* empty).
* We can use this to remove all code that doesn't participate in the depth computation.
*/
void main() {
#if defined(FILAMENT_HAS_FEATURE_INSTANCING)
# if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT)
instance_index = gl_InstanceIndex;
# else
// PowerVR drivers don't initialize gl_InstanceID correctly if it's assigned to the varying
// directly and early in the shader. Adding a bit of extra integer math, works around it.
// Using an intermediate variable doesn't work because of spirv-opt.
if (CONFIG_POWER_VR_SHADER_WORKAROUNDS) {
instance_index = (1 + gl_InstanceID) - 1;
} else {
instance_index = gl_InstanceID;
}
# endif
logical_instance_index = instance_index;
#endif
#if defined(VARIANT_HAS_INSTANCED_STEREO)
#if !defined(FILAMENT_HAS_FEATURE_INSTANCING)
#error Instanced stereo not supported at this feature level
#endif
// The lowest bit of the instance index represents the eye.
// This logic must be updated if CONFIG_STEREOSCOPIC_EYES changes
logical_instance_index = instance_index >> 1;
#endif
initObjectUniforms();
// Initialize the inputs to sensible default values, see material_inputs.vs
#if defined(USE_OPTIMIZED_DEPTH_VERTEX_SHADER)
// In USE_OPTIMIZED_DEPTH_VERTEX_SHADER mode, we can even skip this if we're already in
// VERTEX_DOMAIN_DEVICE and we don't have VSM.
#if !defined(VERTEX_DOMAIN_DEVICE) || defined(VARIANT_HAS_VSM)
// Run initMaterialVertex to compute material.worldPosition.
MaterialVertexInputs material;
initMaterialVertex(material);
// materialVertex() is guaranteed to be empty here, but we keep it to workaround some problem
// in NVIDA drivers related to depth invariance.
materialVertex(material);
#endif
#else // defined(USE_OPTIMIZED_DEPTH_VERTEX_SHADER)
MaterialVertexInputs material;
initMaterialVertex(material);
#if defined(HAS_ATTRIBUTE_TANGENTS)
// If the material defines a value for the "normal" property, we need to output
// the full orthonormal basis to apply normal mapping
#if defined(MATERIAL_NEEDS_TBN)
// Extract the normal and tangent in world space from the input quaternion
// We encode the orthonormal basis as a quaternion to save space in the attributes
toTangentFrame(mesh_tangents, material.worldNormal, vertex_worldTangent.xyz);
#if defined(VARIANT_HAS_SKINNING_OR_MORPHING)
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0) {
#if defined(LEGACY_MORPHING)
vec3 normal0, normal1, normal2, normal3;
toTangentFrame(mesh_custom4, normal0);
toTangentFrame(mesh_custom5, normal1);
toTangentFrame(mesh_custom6, normal2);
toTangentFrame(mesh_custom7, normal3);
vec3 baseNormal = material.worldNormal;
material.worldNormal += morphingUniforms.weights[0].xyz * (normal0 - baseNormal);
material.worldNormal += morphingUniforms.weights[1].xyz * (normal1 - baseNormal);
material.worldNormal += morphingUniforms.weights[2].xyz * (normal2 - baseNormal);
material.worldNormal += morphingUniforms.weights[3].xyz * (normal3 - baseNormal);
#else
morphNormal(material.worldNormal);
material.worldNormal = normalize(material.worldNormal);
#endif
}
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_SKINNING_ENABLED_BIT) != 0) {
skinNormalTangent(material.worldNormal, vertex_worldTangent.xyz, mesh_bone_indices, mesh_bone_weights);
}
#endif
// We don't need to normalize here, even if there's a scale in the matrix
// because we ensure the worldFromModelNormalMatrix pre-scales the normal such that
// all its components are < 1.0. This prevents the bitangent to exceed the range of fp16
// in the fragment shader, where we renormalize after interpolation
vertex_worldTangent.xyz = getWorldFromModelNormalMatrix() * vertex_worldTangent.xyz;
vertex_worldTangent.w = mesh_tangents.w;
material.worldNormal = getWorldFromModelNormalMatrix() * material.worldNormal;
#else // MATERIAL_NEEDS_TBN
// Without anisotropy or normal mapping we only need the normal vector
toTangentFrame(mesh_tangents, material.worldNormal);
#if defined(VARIANT_HAS_SKINNING_OR_MORPHING)
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0) {
#if defined(LEGACY_MORPHING)
vec3 normal0, normal1, normal2, normal3;
toTangentFrame(mesh_custom4, normal0);
toTangentFrame(mesh_custom5, normal1);
toTangentFrame(mesh_custom6, normal2);
toTangentFrame(mesh_custom7, normal3);
vec3 baseNormal = material.worldNormal;
material.worldNormal += morphingUniforms.weights[0].xyz * (normal0 - baseNormal);
material.worldNormal += morphingUniforms.weights[1].xyz * (normal1 - baseNormal);
material.worldNormal += morphingUniforms.weights[2].xyz * (normal2 - baseNormal);
material.worldNormal += morphingUniforms.weights[3].xyz * (normal3 - baseNormal);
#else
morphNormal(material.worldNormal);
material.worldNormal = normalize(material.worldNormal);
#endif
}
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_SKINNING_ENABLED_BIT) != 0) {
skinNormal(material.worldNormal, mesh_bone_indices, mesh_bone_weights);
}
#endif
material.worldNormal = getWorldFromModelNormalMatrix() * material.worldNormal;
#endif // MATERIAL_HAS_ANISOTROPY || MATERIAL_HAS_NORMAL || MATERIAL_HAS_CLEAR_COAT_NORMAL
#endif // HAS_ATTRIBUTE_TANGENTS
// Invoke user code
materialVertex(material);
// Handle built-in interpolated attributes
#if defined(HAS_ATTRIBUTE_COLOR)
vertex_color = material.color;
#endif
#if defined(HAS_ATTRIBUTE_UV0)
vertex_uv01.xy = material.uv0;
#endif
#if defined(HAS_ATTRIBUTE_UV1)
vertex_uv01.zw = material.uv1;
#endif
// Handle user-defined interpolated attributes
#if defined(VARIABLE_CUSTOM0)
VARIABLE_CUSTOM_AT0 = material.VARIABLE_CUSTOM0;
#endif
#if defined(VARIABLE_CUSTOM1)
VARIABLE_CUSTOM_AT1 = material.VARIABLE_CUSTOM1;
#endif
#if defined(VARIABLE_CUSTOM2)
VARIABLE_CUSTOM_AT2 = material.VARIABLE_CUSTOM2;
#endif
#if defined(VARIABLE_CUSTOM3)
VARIABLE_CUSTOM_AT3 = material.VARIABLE_CUSTOM3;
#endif
// The world position can be changed by the user in materialVertex()
vertex_worldPosition.xyz = material.worldPosition.xyz;
#ifdef HAS_ATTRIBUTE_TANGENTS
vertex_worldNormal = material.worldNormal;
#endif
#if defined(VARIANT_HAS_SHADOWING) && defined(VARIANT_HAS_DIRECTIONAL_LIGHTING)
vertex_lightSpacePosition = computeLightSpacePosition(
vertex_worldPosition.xyz, vertex_worldNormal,
frameUniforms.lightDirection,
shadowUniforms.shadows[0].normalBias,
shadowUniforms.shadows[0].lightFromWorldMatrix);
#endif
#endif // !defined(USE_OPTIMIZED_DEPTH_VERTEX_SHADER)
vec4 position;
#if defined(VERTEX_DOMAIN_DEVICE)
// The other vertex domains are handled in initMaterialVertex()->computeWorldPosition()
position = getPosition();
#if !defined(USE_OPTIMIZED_DEPTH_VERTEX_SHADER)
#if defined(MATERIAL_HAS_CLIP_SPACE_TRANSFORM)
position = getMaterialClipSpaceTransform(material) * position;
#endif
#endif // !USE_OPTIMIZED_DEPTH_VERTEX_SHADER
#if defined(MATERIAL_HAS_VERTEX_DOMAIN_DEVICE_JITTERED)
// Apply the clip-space transform which is normally part of the projection
position.xy = position.xy * frameUniforms.clipTransform.xy + (position.w * frameUniforms.clipTransform.zw);
#endif
#else
position = getClipFromWorldMatrix() * getWorldPosition(material);
#endif
#if defined(VERTEX_DOMAIN_DEVICE)
// GL convention to inverted DX convention (must happen after clipSpaceTransform)
position.z = position.z * -0.5 + 0.5;
#endif
#if defined(VARIANT_HAS_VSM)
// For VSM, we use the linear light-space Z coordinate as the depth metric, which works for both
// directional and spot lights and can be safely interpolated.
// The value is guaranteed to be between [-znear, -zfar] by construction of viewFromWorldMatrix,
// (see ShadowMap.cpp).
// Use vertex_worldPosition.w which is otherwise not used to store the interpolated
// light-space depth.
highp float z = (getViewFromWorldMatrix() * getWorldPosition(material)).z;
// rescale [near, far] to [0, 1]
highp float depth = -z * frameUniforms.oneOverFarMinusNear - frameUniforms.nearOverFarMinusNear;
// remap depth between -1 and 1
depth = depth * 2.0 - 1.0;
vertex_worldPosition.w = depth;
#endif
// this must happen before we compensate for vulkan below
vertex_position = position;
#if defined(VARIANT_HAS_INSTANCED_STEREO)
// This logic must be updated if CONFIG_STEREOSCOPIC_EYES changes
// We're transforming a vertex whose x coordinate is within the range (-w to w).
// To move it to the correct half of the viewport, we need to modify the x coordinate:
// Eye 0 (left half): (-w to 0)
// Eye 1 (right half): ( 0 to w)
// It's important to do this after computing vertex_position.
int eyeIndex = instance_index % 2;
float eyeShift = float(eyeIndex) * 2.0f - 1.0f; // eye 0: -1.0, eye 1: 1.0
position.x = position.x * 0.5f + (position.w * 0.5 * eyeShift);
// A fragment is clipped when gl_ClipDistance is negative (outside the clip plane). So,
// Eye 0 should have a positive value when x is < 0
// -position.x
// Eye 1 should have a positive value when x is > 0
// position.x
FILAMENT_CLIPDISTANCE[0] = position.x * eyeShift;
#endif
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
position.y = -position.y;
#endif
#if !defined(TARGET_VULKAN_ENVIRONMENT) && !defined(TARGET_METAL_ENVIRONMENT)
// This is not needed in Vulkan or Metal because clipControl is always (1, 0)
// (We don't use a dot() here because it workaround a spirv-opt optimization that in turn
// causes a crash on PowerVR, see #5118)
position.z = position.z * frameUniforms.clipControl.x + position.w * frameUniforms.clipControl.y;
#endif
// some PowerVR drivers crash when gl_Position is written more than once
gl_Position = position;
#if defined(VARIANT_HAS_INSTANCED_STEREO)
// Fragment shaders filter out the stereo variant, so we need to set instance_index here.
instance_index = logical_instance_index;
#endif
}