Smoke Shader

A (Billboard) Smoke Shader.

Shader code
/*
	煙シェーダー by あるる(きのもと 結衣) @arlez80
	Smoke Shader by Yui Kinomoto

	MIT License
*/

shader_type spatial;
render_mode depth_draw_opaque;

// 煙の細かさ
uniform float scale = 1.0;
// 煙移動速度
uniform vec3 tex_speed = vec3( 0.0, -2.2, 0.5 );
// 煙もこもこ感
uniform vec2 mokomoko = vec2( 0.1, 0.1 );

// 煙の量
uniform float smoke_volume : hint_range( 0.0, 2.0 ) = 0.8;
// 穴開き具合
uniform float smoke_aperture : hint_range( 0.0, 3.0 ) = 0.28;
// 色
uniform vec4 smoke_color : hint_color = vec4( 0.185, 0.185, 0.185, 1.0 );

varying vec4 world_vertex;

float random( vec3 pos )
{ 
	return fract(sin(dot(pos, vec3(12.9898,78.233,-3.532532))) * 43758.5453);
}

float value_noise( vec3 pos )
{
	vec3 p = floor( pos );
	vec3 f = fract( pos );

	float v000 = random( p/*+ vec3( 0.0, 0.0, 0.0 )*/ );
	float v100 = random( p + vec3( 1.0, 0.0, 0.0 ) );
	float v010 = random( p + vec3( 0.0, 1.0, 0.0 ) );
	float v110 = random( p + vec3( 1.0, 1.0, 0.0 ) );
	float v001 = random( p + vec3( 0.0, 0.0, 1.0 ) );
	float v101 = random( p + vec3( 1.0, 0.0, 1.0 ) );
	float v011 = random( p + vec3( 0.0, 1.0, 1.0 ) );
	float v111 = random( p + vec3( 1.0, 1.0, 1.0 ) );

	vec3 u = f * f * ( 3.0 - 2.0 * f );

	return mix(
		mix(
			mix( v000, v100, u.x )
		,	mix( v010, v110, u.x )
		,	u.y
		)
	,	mix(
			mix( v001, v101, u.x )
		,	mix( v011, v111, u.x )
		,	u.y
		)
	,	u.z
	);
}

float noise_tex( vec3 p )
{
	return (
		value_noise( p * 0.984864 ) * 0.5
	+	value_noise( p * 2.543 ) * 0.25
	+	value_noise( p * 9.543543 ) * 0.125
	+	value_noise( p * 21.65436 ) * 0.0625
	+	value_noise( p * 42.0 ) * 0.03125
	+	value_noise( p * 87.135148 ) * 0.015625
	+	value_noise( p * 340.66534654 ) * 0.0078125
	);
}

void vertex( )
{
	world_vertex = WORLD_MATRIX * vec4( VERTEX * scale, 1.0 );
}

void fragment( )
{
	float p[9];

	for( int y = 0; y < 3; y ++ ) {
		for( int x = 0; x < 3; x ++ ) {
			p[y*3 + x] = noise_tex( world_vertex.xyz + tex_speed * TIME + vec3( mokomoko * vec2( float(x - 1), float(y - 1) ), 0.0 ) );
		}
	}

	float smoke = clamp( sin( UV.x * 3.1415926535 ) * smoke_volume, 0.0, 1.0 );
	float smoke_noise = smoke * ( ( ( smoke + smoke_aperture ) * p[4] - smoke_aperture ) * 75.0 ) * UV.y;

	vec2 sobel_filter = clamp(
		vec2(
			(
				p[0] * -1.0
			+	p[3] * -2.0
			+	p[6] * -1.0
			+	p[2] * 1.0
			+	p[5] * 2.0
			+	p[8] * 1.0
			)
		,	(
				p[0] * -1.0
			+	p[1] * -2.0
			+	p[2] * -1.0
			+	p[6] * 1.0
			+	p[7] * 2.0
			+	p[8] * 1.0
			)
		)
	,	vec2( -1.0, -1.0 )
	,	vec2( 1.0, 1.0 )
	) * 0.5;

	NORMALMAP = normalize( vec3( sobel_filter.x + 0.5, -sobel_filter.y + 0.5, 1.0 ) );
	ALBEDO = smoke_color.rgb;
	ALPHA = clamp( smoke_noise, 0.0, 1.0 );
}
Tags
Smoke
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from arlez80

Outline Silhouette Shader

Magical Shield Shader #2

Sonar Effect Shader

Related shaders

Omen’s Smoke Bomb from Valorant

2D Hologram Shader

Half-Tone Comic Shader

guest
5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
NekotoArts
NekotoArts (@nekotoarts)
4 months ago

Wonderful smoke effect!

telmo
telmo (@telmo)
4 months ago

Can you give a bit more detail into how to use your shader? I’ve tried creating a ShaderMaterial loaded with “smoke.shader” and then use this material in a PlaneMesh (MeshInstance), but I couldn’t get it to work in billboard mode…

telmo
telmo (@telmo)
4 months ago
Reply to  telmo

To get the billboard effect I ended up implementing a less than ideal solution – Attaching the following script to the node:

extends MeshInstance

func _process(delta):
  if is_instance_valid(GameState.Player):
    var copy_of_player_position = GameState.Player.transform.origin
    copy_of_player_position.y = 100 # Assume player's position is high
    look_at(copy_of_player_position, Vector3.UP)

I’m positive there’s a much better solution out there though.

NekotoArts
NekotoArts (@nekotoarts)
4 months ago
Reply to  telmo

Oh, you can actually do the billboard from the shader itself.
In his shader, in the vertex function, put this line of code:

MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],CAMERA_MATRIX[1],CAMERA_MATRIX[2],WORLD_MATRIX[3]);

So the vertex function should look like this:

void vertex( )
{
	world_vertex = WORLD_MATRIX * vec4( VERTEX * scale, 1.0 );
	MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],CAMERA_MATRIX[1],CAMERA_MATRIX[2],WORLD_MATRIX[3]);
}

The code above is for a full billboard effect, taken straight from Godot’s spatial material code.

If you want just a Y-Billboard, you can use this:

MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],WORLD_MATRIX[1],vec4(normalize(cross(CAMERA_MATRIX[0].xyz,WORLD_MATRIX[1].xyz)), 0.0),WORLD_MATRIX[3]);
	MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(1.0, 0.0, 0.0, 0.0),vec4(0.0, 1.0/length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0),vec4(0.0, 0.0, 0.0 ,1.0));

So all in all, if you want Y-Billboard, the vertex function should look like this:

void vertex( )
{
	world_vertex = WORLD_MATRIX * vec4( VERTEX * scale, 1.0 );
	MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],WORLD_MATRIX[1],vec4(normalize(cross(CAMERA_MATRIX[0].xyz,WORLD_MATRIX[1].xyz)), 0.0),WORLD_MATRIX[3]);
	MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(1.0, 0.0, 0.0, 0.0),vec4(0.0, 1.0/length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0),vec4(0.0, 0.0, 0.0 ,1.0));
}

Hope that helps!

telmo
telmo (@telmo)
4 months ago
Reply to  NekotoArts

Thank you very much for your detailed answer, yours is a much more elegant solution than mine. The only problem I had with your Y-Billboard vertex function is that I can’t get it to look right when the player moves behind the smoke when I’m using

render_mode cull_disabled;

The shader goes upside down.

Last edited 4 months ago by telmo