Godot Version
4.7.2-stable
Question
I have written a custom particle shader to help my friends get into godot a bit easier as they’re coming from a different 3D software, and for some reason when my particles initialize there is 1 frame of initialization lag that a translated ParticleProcessMaterial shader does not have. I have looked through the code of the converted shader and have not found the issue causing this. My shader code is as follows and the linked video demonstrates the init flash I’m talking about (It is hard to describe in text and I cannot attach videos yet).
shader_type particles;
#define SEED USERDATA6.x
#define AGE USERDATA6.y
#define SPAWN_TIME USERDATA6.z
#define CALC_ROT USERDATA1.x
#define CALC_ROT_SPEED USERDATA1.y
#define RAND_SPEED USERDATA1.z
#define RAND_ROT_X USERDATA1.w
#define RAND_ROT_Z USERDATA2.x
#define FIRST_FRAME USERDATA2.y
const float REX_ORE_SIZE = 6.0;
const mat4 default_mat4 = mat4(vec4(1.0, 0.0, 0.0, 0.0),
vec4(0.0, 1.0, 0.0, 0.0),
vec4(0.0, 0.0, 1.0, 0.0),
vec4(0.0, 0.0, 0.0, 1.0));
uniform sampler2D color_ramp;
uniform sampler2D scale_x_ramp;
uniform sampler2D scale_y_ramp;
uniform uint direction;
uniform float rotation_min;
uniform float rotation_max;
uniform float rot_speed_min;
uniform float rot_speed_max;
uniform float speed_min;
uniform float speed_max;
uniform float spread_angle_x;
uniform float spread_angle_z;
uniform vec3 acceleration;
// Following code taken from https://stackoverflow.com/a/17479300
// A single iteration of Bob Jenkins' One-At-A-Time hashing algorithm.
uint hash( uint x ) {
x += ( x << 10u );
x ^= ( x >> 6u );
x += ( x << 3u );
x ^= ( x >> 11u );
x += ( x << 15u );
return x;
}
// Compound versions of the hashing algorithm I whipped together.
uint hash( uvec2 v ) { return hash( v.x ^ hash(v.y) ); }
uint hash( uvec3 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ); }
uint hash( uvec4 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ^ hash(v.w) ); }
// Construct a float with half-open range [0:1] using low 23 bits.
// All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0.
float floatConstruct( uint m ) {
const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask
const uint ieeeOne = 0x3F800000u; // 1.0 in IEEE binary32
m &= ieeeMantissa; // Keep only mantissa bits (fractional part)
m |= ieeeOne; // Add fractional part to 1.0
float f = uintBitsToFloat( m ); // Range [1:2]
return f - 1.0; // Range [0:1]
}
// Pseudo-random value in half-open range [0:1].
float random( float x ) { return floatConstruct(hash(floatBitsToUint(x))); }
float random( vec2 v ) { return floatConstruct(hash(floatBitsToUint(v))); }
float random( vec3 v ) { return floatConstruct(hash(floatBitsToUint(v))); }
float random( vec4 v ) { return floatConstruct(hash(floatBitsToUint(v))); }
float rand_range(inout float seed, float min_value, float max_value) {
seed = random(seed);
return min_value + seed * (max_value - min_value);
}
// Function taken from https://docs.godotengine.org/en/3.2/tutorials/3d/vertex_animation/controlling_thousands_of_fish.html
float rand_from_seed(in uint seed) {
int k;
int s = int(seed);
if (s == 0)
s = 305420679;
k = s / 127773;
s = 16807 * (s - k * 127773) - 2836 * k;
if (s < 0)
s += 2147483647;
seed = uint(s);
return float(seed % uint(65536)) / 65535.0;
}
void scale(inout mat4 transform, vec3 s, vec4 origin) {
transform[3] -= origin;
mat4 t = mat4(vec4(s.x, 0.0, 0.0, 0.0),
vec4(0.0, s.y, 0.0, 0.0),
vec4(0.0, 0.0, s.z, 0.0),
vec4(0.0, 0.0, 0.0, 1.0));
transform = t * transform;
transform[3] += origin;
}
void scale(inout mat4 transform, vec3 s) {
scale(transform, s, transform[3]);
}
void rotate(inout mat4 transform, vec3 rotations, vec4 origin) {
float rot_x = rotations.x * PI / 180.;
float rot_y = rotations.y * PI / 180.;
float rot_z = rotations.z * PI / 180.;
transform[3] -= origin;
mat4 t = default_mat4;
t[0].xyz = vec3(cos(rot_z) * cos(rot_y), cos(rot_z) * sin(rot_y) * sin(rot_x) - sin(rot_z) * cos(rot_x), cos(rot_z) * sin(rot_y) * cos(rot_x) + sin(rot_z) * sin(rot_x));
t[1].xyz = vec3(sin(rot_z) * cos(rot_y), sin(rot_z) * sin(rot_y) * sin(rot_x) + cos(rot_z) * cos(rot_x), sin(rot_z) * sin(rot_y) * cos(rot_x) - cos(rot_z) * sin(rot_x));
t[2].xyz = vec3(-sin(rot_y), cos(rot_y) * sin(rot_x), cos(rot_y) * cos(rot_x));
transform = t * transform;
transform[3] += origin;
}
void rotate(inout mat4 transform, vec3 rotations) {
rotate(transform, rotations, transform[3]);
}
void rotate(inout vec4 vec, vec3 rotations) {
mat4 t = default_mat4;
rotate(t, rotations, vec4(0.0, 0.0, 0.0, 0.0));
vec = t * vec;
}
mat4 init_pos(float lifetime, float calc_rot, float calc_rot_speed) {
mat4 transform = default_mat4;
float init_rot = calc_rot - lifetime * calc_rot_speed;
rotate(transform, vec3(0.0, 0.0, init_rot));
scale(transform, vec3(texture(scale_x_ramp, vec2(0.0)).r, texture(scale_y_ramp, vec2(0.0)).r, 1.0));
return transform;
}
void start() {
if (RESTART_ROT_SCALE) {
TRANSFORM[0].xyz = vec3(0.0, 0.0, 0.0);
TRANSFORM[1].xyz = vec3(0.0, 0.0, 0.0);
TRANSFORM[2].xyz = vec3(0.0, 0.0, 0.0);
}
if (RESTART_COLOR) {
COLOR = vec4(0.0, 0.0, 0.0, 0.0);
}
if (RESTART_VELOCITY) {
VELOCITY = vec3(0.0);
}
if (RESTART_POSITION) {
TRANSFORM[0].xyz = vec3(0.0, 0.0, 0.0);
TRANSFORM[1].xyz = vec3(0.0, 0.0, 0.0);
TRANSFORM[2].xyz = vec3(0.0, 0.0, 0.0);
}
SEED = rand_from_seed(hash(uint(INDEX) + uint(TIME) + RANDOM_SEED)); // seed
AGE = 0.0; // age
SPAWN_TIME = TIME; // spawn_time
CALC_ROT = rand_range(SEED, rotation_min, rotation_max); // calc_rot
CALC_ROT_SPEED = rand_range(SEED, rot_speed_min, rot_speed_max); // calc_rot_speed
RAND_SPEED = rand_range(SEED, speed_min, speed_max); // rand_speed
RAND_ROT_X = rand_range(SEED, -spread_angle_x, spread_angle_x); // rand_rot_x
RAND_ROT_Z = rand_range(SEED, -spread_angle_z, spread_angle_z); // rand_rot_z
FIRST_FRAME = 1.0;
}
void process() {
AGE = clamp((TIME - SPAWN_TIME) / LIFETIME, 0.0, 0.99);
mat4 ORIGINAL = init_pos(LIFETIME, CALC_ROT, CALC_ROT_SPEED);
if (FIRST_FRAME > 0.5) {
FIRST_FRAME = 0.0;
TRANSFORM = ORIGINAL;
vec3 direction_vec;
switch (direction) {
case 0u:
direction_vec = vec3(0.0, 0.0, 1.0);
break;
case 1u:
direction_vec = vec3(0.0, -1.0, 0.0);
break;
case 2u:
direction_vec = vec3(0.0, 0.0, -1.0);
break;
case 3u:
direction_vec = vec3(-1.0, 0.0, 0.0);
break;
case 4u:
direction_vec = vec3(1.0, 0.0, 0.0);
break;
case 5u:
direction_vec = vec3(0.0, 1.0, 0.0);
break;
}
vec4 dv = vec4(RAND_SPEED * 1.0 / REX_ORE_SIZE * direction_vec, 1.0);
rotate(dv, vec3(RAND_ROT_X, 0.0, RAND_ROT_Z));
VELOCITY = dv.xyz;
COLOR = texture(color_ramp, vec2(0.0));
}
vec4 location = TRANSFORM[3];
scale(ORIGINAL, vec3(texture(scale_x_ramp, vec2(AGE, 0.0)).r, texture(scale_y_ramp, vec2(AGE, 0.0)).r, 1.0));
rotate(ORIGINAL, vec3(0.0, 0.0, CALC_ROT_SPEED * AGE * LIFETIME));
TRANSFORM = ORIGINAL;
TRANSFORM[3] = location;
VELOCITY += acceleration * DELTA;
COLOR = texture(color_ramp, vec2(AGE, 0.0));
}
Video: https://youtu.be/hJP3gaSxMLw