// Shader stolen here : https://godotshaders.com/shader/vertex-animation-texture-uses-openvat-output/ shader_type spatial; render_mode cull_disabled; // Prevents GPU hardware culling so shader can handle it /* For use with models exported from Blender using the OpenVAT plugin. Expects: Vertex Normals - Packed (Math gets too complex to include a toggle. Making a separate shader recommended if normals are not needed.) Use Single Row - On Export Model - On Model Format - glTF Binary. (Unsure why the otherse do not work) Ensure .exr file is set to Lossless compression and MipMaps - Off. */ // --- Uniforms --- group_uniforms openVAT_inputs; /** OpenVAT texture exported from Blender plugin. */ uniform sampler2D vertex_animation_texture : repeat_disable; /** Min values from the .json file included with OpenVAT exports. */ uniform vec3 min_bounds; /** Max values from the .json file included with OpenVAT exports. */ uniform vec3 max_bounds; /** Plays animation automatically. Turn off to control manually via keyframes. */ uniform bool autoplay = true; /** Autoplay speed in frames per second. Defaults to 30. [b]Blender default is 24. May require adjustment either in Blender or Here to match speed.[b] */ uniform float autoplay_speed = 30.0; /** Frame to display if animating manually. */ uniform float frame = 1.0; /** Enable this if the asset does not have 2 UV sets. */ uniform bool use_uv_1 = false; /** Cull Mode, editable here for flexibility. 0: Backface Cull 1: Frontface Cull 2: Cull disabled (double sided rendering)*/ uniform int cull_mode : hint_enum("Back:0", "Front:1", "Disabled:2") = 0; group_uniforms; group_uniforms Albedo_map; uniform sampler2D albedo_map : source_color, hint_default_white; uniform vec4 albedo_tint : source_color = vec4(1.0); group_uniforms; group_uniforms ORM_inputs; /** [b]ORM texture.[/b] Standardized detail maps for PBR materials. Ignore, remap, or remove as needed. [b]O[/b]cclusion (Ambient) [b]R[/b]oughness [b]M[/b]etallic */ uniform sampler2D orm_map : hint_default_white; /** Select channel used by Occlusion map. R G [b]or[/b] B, combining will have strange results */ uniform vec4 occlusion_channel : source_color = vec4(1.0, 0.0, 0.0, 1.0); /** Strength of Occlusion*/ uniform float occlusion : hint_range(0.0, 1.0) = 1.0; /** Select channel used by Roughness map. R G [b]or[/b] B, combining will have strange results */ uniform vec4 roughness_channel : source_color = vec4(0.0, 1.0, 0.0, 1.0); /** Strength of Roughness*/ uniform float roughness : hint_range(0.0, 1.0) = 1.0; /** Select channel used by Metallic map. R G [b]or[/b] B, combining will have strange results */ uniform vec4 metallic_channel : source_color = vec4(0.0, 0.0, 1.0, 1.0); /** Strength of Metallic*/ uniform float metallic : hint_range(0.0, 1.0) = 1.0; /** Strength of Specular (No Map Included)*/ uniform float specular : hint_range(0.0, 1.0) = 0.5; group_uniforms; group_uniforms Normal_map; uniform sampler2D normal_map : hint_roughness_normal; uniform float normal_map_scale : hint_range(-10.0, 10.0) = 1.0; /** Passed data from INSTANCE_CUSTOM_VARIABLE : x : Unique Id y : Damage effect z : Size **/ varying flat float unique_id; varying flat float damage_effect; varying flat float size; float remapFloat(float value, float input_min, float input_max, float output_min, float output_max){ // Code to replicate the remap feature from Visual Shader. float _input_range = input_max - input_min; float _output_range = output_max - output_min; return output_min + _output_range * ((value - input_min) / _input_range); } void vertex() { unique_id = INSTANCE_CUSTOM.x; damage_effect = INSTANCE_CUSTOM.y; size = INSTANCE_CUSTOM.z; // Animation Timing // ------ // Divided by 2. since bottom half of texture is the normal data. // Second argument (0) is MipMap level. These VATs use no mipmaps so always 0. int total_frames = textureSize(vertex_animation_texture, 0).y / 2; // Checks if Autoplaying is checked float play_frame = autoplay ? (TIME * autoplay_speed / size) : frame; // Loops the current frame counter so it stays between 0 and total_frames - 1 float current_frame = mod(play_frame + unique_id, float(total_frames)); // Compute UV Coordinates // ------ // samples pixel from the center, not the edge. Avoid unwanted bleeding into normal data pixels at the end of the loop. float center_pixel = current_frame - 0.5; float vat_y = center_pixel / float(total_frames); vat_y = clamp(vat_y * 0.5, 0.0, 0.5); vec2 vat_uv = vec2((use_uv_1 ? UV.x : UV2.x), vat_y); // Vertex Position Displacement // ------ vec4 vat_pos_raw = texture(vertex_animation_texture, vat_uv); // Must go through some remapping to convert Blender to Godot coordinates vec3 offset_values = vec3( remapFloat(vat_pos_raw.r, 0.0, 1.0, min_bounds.x, max_bounds.x), remapFloat(vat_pos_raw.b, 0.0, 1.0, min_bounds.z, max_bounds.z), remapFloat(vat_pos_raw.g, 1.0, 0.0, min_bounds.y, max_bounds.y) ); VERTEX += offset_values; // Normals. 0.5 added to UV.y to shift it down by 1/2 the map - to the normals section. vec4 vat_norm_raw = texture(vertex_animation_texture, vat_uv + vec2(0.0, 0.5)); vec3 decoded_normals = vec3( remapFloat(vat_norm_raw.r, 0.0, 1.0, -1.0, 1.0), remapFloat(vat_norm_raw.b, 0.0, 1.0, -1.0, 1.0), remapFloat(vat_norm_raw.g, 1.0, 0.0, -1.0, 1.0) ); NORMAL = normalize(decoded_normals); // Calculate Tangent and Binormal from computed Normal TANGENT = normalize(vec3(abs(NORMAL.y) + abs(NORMAL.z), 0.0, -abs(NORMAL.x))); BINORMAL = normalize(vec3(0.0, abs(NORMAL.x) + abs(NORMAL.z), -abs(NORMAL.y))); } void fragment() { // Cull Mode if (cull_mode == 0 && !FRONT_FACING) { discard; // Cull back faces } else if (cull_mode == 1 && FRONT_FACING) { discard; // Cull front faces } // Albedo ALBEDO = texture(albedo_map, UV).rgb * albedo_tint.rgb; EMISSION = vec3(1.,1.,1.) * damage_effect; // ORM maps (Occlusion, Roughness, Metallic) Packing vec4 orm_tex = texture(orm_map, UV); AO = occlusion * dot(orm_tex.rgb, occlusion_channel.rgb); ROUGHNESS = roughness * dot(orm_tex.rgb, roughness_channel.rgb); METALLIC = metallic * dot(orm_tex.rgb, metallic_channel.rgb); // Specular SPECULAR = specular; // Normal Map NORMAL_MAP = texture(normal_map, UV).rgb; NORMAL_MAP_DEPTH = normal_map_scale; }