Sci-fi Engine Thrust Shader

jet engine shader godot 4

Sci fi engine thruster VFX shader for Godot 4. Jet engine exhaust flame/propulsion.

Breakdown

  1. Initially I made a gradient on object, making one end of object transparent and other end opaque.
  2. Then I applied noise texture, thus making the transition a bit rough.
  3. Then I animated the UVs and modified the intensity of colors at different levels.
  4. Finally, I applied a Fresnel effect on edges so they don’t look sharp.

We will start with the simple effect, and will add details to it with time.

Start with Simple Gradient

The first step is to make a gradient spanning across the mesh. One end of the mesh should be transparent (its ALPHA = 0.0) and other end must have ALPHA value 1.0.

This is how it looks:

3d gradient shader godot

Code:

uniform float model_height = 3.0;
uniform float dissolve_start : hint_range(0.0, 1.0) = 0.001;
uniform float dissolve_length : hint_range(0.0, 1.0) = 1.0;
uniform float gradient_bias : hint_range(0.1, 5.0) = 1.0;

varying float vert_height;
varying vec2 v_uv;

void vertex() {
    vert_height = (VERTEX.y + (model_height / 2.0)) / model_height;
    v_uv = UV;
}

void fragment(){
    float gradient_height = vert_height - dissolve_start;
    gradient_height *= 1.0 / dissolve_length;
    gradient_height = clamp(pow(gradient_height, gradient_bias), 0.0, 1.0);
}
  
  ALPHA = mix(1.0, 0.0, gradient_height);
  ALBEDO = vec3(1.0 - gradient_height);
}

The vertex shader calculates vert_height, which is the vertical position of each vertex normalized between 0 and 1. The calculation vert_height = (VERTEX.y + (model_height / 2.0)) / model_height; ensures that the bottom vertex is at 0 and the top vertex is at 1.

In fragment shader, gradient_height is computed by taking the vert_height, subtracting dissolve_start (to begin the effect partway up the model), and then dividing by dissolve_length to scale the effect over the desired portion of the model.

gradient_height is then modified using pow(gradient_height, gradient_bias) to apply a bias to the gradient, which adjusts how sharply or smoothly the dissolve happens. Finally, these values are assigned to visualize the effect.

Adding noise to make it interesting

The above jet engine like effect is sufficient for some games, but I needed something more interesting, so I added the contribution of noise to make it more detailed. It is simple, just add a noise sampler2D and add its value to gradient to distort the gradient.

// ADD THESE UNIFORMS:
uniform float noise_speed = 1.0; // Speed of noise movement
uniform float noise_strength = 0.1; // Strength of the noise effect
uniform float stretch_factor = 0.6; // Factor to stretch the noise
uniform sampler2D noise_texture; // Noise texture


// THEN IN fragment():
// ADD (on top):
    float time = TIME * noise_speed;
    vec2 moving_uv = v_uv + noise_direction * time;
    vec2 stretched_uv = vec2(moving_uv.x, moving_uv.y * stretch_factor);
    float noise_value = texture(noise_texture, stretched_uv).r * noise_strength;


// AND THEN:
// REPLACE:
gradient_height = clamp(pow(gradient_height, gradient_bias), 0.0, 1.0);

// WITH THIS:
gradient_height = clamp(pow(gradient_height, gradient_bias) + noise_value, 0.0, 1.0);

The noise was static, so it was animated from one end to the other by moving the UVs with time. Also, UVs were stretched in one direction by multiplying it with a uniform variable stretch_factor.

This is how it looks so far:

jet engine shader tutorial

Adding Colors

Now we replace the boring gradient color with our custon color that we assign as uniform. For this, create a new uniform variable of vec3 type and call it _color. Next assign it to ALBEDO like this:

ALBEDO = neon(pow(ALPHA, power_factor), _color);

But wait, note that I have used a neon() function instead of directly assigning the color. This is because neon function intensifies the color and makes it look more like flame. Directly assigning will make it look dull.

The neon function is simple:

vec3 neon(float value, vec3 color) {
    float ramp = clamp(value, 0.0, 1.0);
    vec3 output_color = vec3(0.0);
    ramp = ramp * ramp;
    output_color += pow(color, vec3(4.0)) * ramp;
    ramp = ramp * ramp;
    output_color += color * ramp;
    ramp = ramp * ramp;
    output_color += vec3(1.0) * ramp;
    return output_color;
}
rocket flame shader

Making edges smooth

Notice the sharp edges of cone above. We can easily remove them with a simple Fresnel function.

float fresnel(float amount, vec3 normal, vec3 view) {
    return pow((1.0 - clamp(dot(normalize(normal), normalize(view)), 0.0, 1.0)), amount);
}

This is how Fresnel function looks like (the darker regions have lower value (approaching 0.0) and brighter region have values approaching 1.0.

fresnel shader godot 3d

Now if we multiply the fresnel function with the ALPHA, we will get ends of the model as transparent, so edges will not be sharp. So lets calculate fresnel & multiply them:

// AT END OF fragment():
float fresnel_effect = 1.0 - fresnel(fresnel_factor, NORMAL, VIEW);
fresnel_effect = pow(fresnel_effect * fresnel_amplification, fresnel_power);

ALPHA *= fresnel_effect;

I have just amplified the fresnel to make it look right using the pow function with some variables that are uniforms. fresnel_effect, fresnel_amplification and fresnel_power are simple float type uniforms.

jet engine flame shader godot

More

There are tons of controls to make jet thruster look better. You can play with the values to get what is desirable. For example:

engine flame vfx godot
godot jet propulsion shader
sci fi flame sahder

Full Godot Shader Code


shader_type spatial;

render_mode blend_mix, unshaded, cull_back;

group_uniforms Basic_Effects;
uniform float model_height = 3.0;
uniform float dissolve_start : hint_range(0.0, 1.0) = 0.001;
uniform float dissolve_length : hint_range(0.0, 1.0) = 1.0;
uniform float gradient_bias : hint_range(0.1, 5.0) = 1.0;
uniform vec2 noise_direction = vec2(0.0, 1.0); // Direction of noise movement

group_uniforms Noise_Effects;
uniform float noise_speed = 1.0; // Speed of noise movement
uniform float noise_strength = 0.1; // Strength of the noise effect
uniform float stretch_factor = 0.6; // Factor to stretch the noise
uniform sampler2D noise_texture; // Noise texture

varying float vert_height;
varying vec2 v_uv;

group_uniforms Misc_Effects;
uniform vec3 _color : source_color;
uniform float power_factor = 1.0;
uniform float alpha_intensity_factor = 2.0;

group_uniforms Fresnel_Effects;
uniform float fresnel_factor = 1.0;
uniform float fresnel_amplification = 2.0; // Corrected typo
uniform float fresnel_power = 2.0;
uniform bool enable_fresnel = true;

vec3 neon(float value, vec3 color) {
    float ramp = clamp(value, 0.0, 1.0);
    vec3 output_color = vec3(0.0);
    ramp = ramp * ramp;
    output_color += pow(color, vec3(4.0)) * ramp;
    ramp = ramp * ramp;
    output_color += color * ramp;
    ramp = ramp * ramp;
    output_color += vec3(1.0) * ramp;
    return output_color;
}

float fresnel(float amount, vec3 normal, vec3 view) {
    return pow((1.0 - clamp(dot(normalize(normal), normalize(view)), 0.0, 1.0)), amount);
}

void vertex() {
    vert_height = (VERTEX.y + (model_height / 2.0)) / model_height;
    v_uv = UV;
}

void fragment() {
    float time = TIME * noise_speed;
    vec2 moving_uv = v_uv + noise_direction * time;
    vec2 stretched_uv = vec2(moving_uv.x, moving_uv.y * stretch_factor);
    float noise_value = texture(noise_texture, stretched_uv).r * noise_strength;

    float gradient_height = vert_height - dissolve_start;
    gradient_height *= 1.0 / dissolve_length;
    gradient_height = clamp(pow(gradient_height, gradient_bias) + noise_value, 0.0, 1.0);

    ALPHA = mix(1.0, 0.0, gradient_height);
    ALPHA = pow(ALPHA, power_factor);
    ALBEDO = neon(pow(ALPHA, power_factor), _color);

    ALPHA = pow(ALPHA, alpha_intensity_factor);

    if (enable_fresnel) {
        float fresnel_effect = 1.0 - fresnel(fresnel_factor, NORMAL, VIEW);
        fresnel_effect = pow(fresnel_effect * fresnel_amplification, fresnel_power);

        ALPHA *= fresnel_effect;
    }
}

Thank you for reading. Have a great day <3

Leave a Reply

Your email address will not be published. Required fields are marked *