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

Hex Pixelization Shader

Post Effect Outline Shader

Procedural Stained-Glass Shader

Related shaders

2D Hologram Shader

Half-Tone Comic Shader

PS1 Shader

guest
3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
NekotoArts
NekotoArts
23 days ago

Wonderful smoke effect!

telmo
telmo
4 days 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
4 days 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.