热浪扭曲

噪声推动屏幕 UV,并遮罩到画面下半部分,好让差别一眼看清。

上方的编辑器正在运行这个项目。改一行代码,它就会重新加载。

用到的 Godot 类

ColorRectNoiseTexture2DCanvasLayer

代码

scripts/main.gd
extends Node


func _ready() -> void:
	_add_environment()
	_add_light()
	_add_ground()

	for i in 7:
		_add_pillar(Vector3(-6.0 + i * 2.0, 0, -2.0 - float(i % 3) * 3.0), float(i) / 7.0)

	var camera: Camera3D = Camera3D.new()
	camera.position = Vector3(0, 2.2, 8)
	add_child(camera)
	camera.look_at(Vector3(0, 1.4, 0), Vector3.UP)
	camera.current = true

	var layer: CanvasLayer = CanvasLayer.new()
	layer.layer = 10
	add_child(layer)

	var material: ShaderMaterial = ShaderMaterial.new()
	material.shader = Get.shader('haze')
	material.set_shader_parameter('noise_texture', _make_noise())

	var screen: ColorRect = ColorRect.new()
	screen.material = material
	layer.add_child(screen)
	screen.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

	print('the lower half wobbles, the top stays put so the difference is visible')


func _make_noise() -> NoiseTexture2D:
	var noise: FastNoiseLite = FastNoiseLite.new()
	noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
	noise.frequency = 0.02

	var texture: NoiseTexture2D = NoiseTexture2D.new()
	texture.noise = noise
	texture.seamless = true
	texture.width = 256
	texture.height = 256

	return texture


func _add_pillar(at: Vector3, hue: float) -> void:
	var mesh: BoxMesh = BoxMesh.new()
	mesh.size = Vector3(1.1, 3.2, 1.1)

	var material: StandardMaterial3D = StandardMaterial3D.new()
	material.albedo_color = Color.from_hsv(hue * 0.15 + 0.05, 0.6, 0.95)
	material.roughness = 0.6

	var pillar: MeshInstance3D = MeshInstance3D.new()
	pillar.mesh = mesh
	pillar.material_override = material
	pillar.position = at + Vector3(0, 1.6, 0)
	add_child(pillar)


func _add_ground() -> void:
	var mesh: PlaneMesh = PlaneMesh.new()
	mesh.size = Vector2(60, 60)

	var material: StandardMaterial3D = StandardMaterial3D.new()
	material.albedo_color = Color('#3a2f26')
	material.roughness = 0.95

	var ground: MeshInstance3D = MeshInstance3D.new()
	ground.mesh = mesh
	ground.material_override = material
	add_child(ground)


func _add_environment() -> void:
	var sky_material: ProceduralSkyMaterial = ProceduralSkyMaterial.new()
	sky_material.sky_top_color = Color(0.25, 0.35, 0.6)
	sky_material.sky_horizon_color = Color(0.8, 0.7, 0.5)
	sky_material.ground_bottom_color = Color(0.2, 0.16, 0.12)
	sky_material.ground_horizon_color = Color(0.6, 0.5, 0.38)

	var sky: Sky = Sky.new()
	sky.sky_material = sky_material

	var env: Environment = Environment.new()
	env.background_mode = Environment.BG_SKY
	env.sky = sky
	env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
	env.ambient_light_energy = 1.0

	var world: WorldEnvironment = WorldEnvironment.new()
	world.environment = env
	add_child(world)


func _add_light() -> void:
	var light: DirectionalLight3D = DirectionalLight3D.new()
	light.rotation_degrees = Vector3(-60, -40, 0)
	light.light_energy = 1.5
	add_child(light)
shaders/haze.gdshader
shader_type canvas_item;

uniform sampler2D screen_texture: hint_screen_texture, filter_linear;
uniform sampler2D noise_texture;
uniform float strength = 0.012;
uniform float speed = 0.35;
uniform float band_top = 0.25;

void fragment() {
	vec2 flow = SCREEN_UV * vec2(2.0, 1.0) + vec2(0.0, -TIME * speed);
	vec2 offset = texture(noise_texture, flow).rg * 2.0 - 1.0;

	// only the lower band distorts, the top stays as a reference
	float mask = smoothstep(band_top, band_top + 0.25, SCREEN_UV.y);

	COLOR = vec4(texture(screen_texture, SCREEN_UV + offset * strength * mask).rgb, 1.0);
}

更多着色器示例