水面

波浪写在顶点着色器里,法线由导数重新计算,因此光照会跟着波浪走。

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

用到的 Godot 类

PlaneMeshShaderMaterialMeshInstance3D

代码

scripts/main.gd
extends Node


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

	var mesh: PlaneMesh = PlaneMesh.new()
	mesh.size = Vector2(30, 30)
	# the vertex shader has nothing to move unless the mesh has vertices
	mesh.subdivide_width = 120
	mesh.subdivide_depth = 120

	var material: ShaderMaterial = ShaderMaterial.new()
	material.shader = Get.shader('water')

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

	for i in 4:
		_add_buoy(Vector3(cos(i * 1.6) * 4.0, 0.3, sin(i * 1.6) * 4.0))

	var camera: Camera3D = Camera3D.new()
	camera.position = Vector3(0, 2.6, 8)
	add_child(camera)
	camera.look_at(Vector3(0, 0, -2), Vector3.UP)
	camera.current = true

	print('120 by 120 subdivisions, and the normal comes from the derivative of the wave')


func _add_buoy(at: Vector3) -> void:
	var mesh: SphereMesh = SphereMesh.new()
	mesh.radius = 0.4
	mesh.height = 0.8

	var material: StandardMaterial3D = StandardMaterial3D.new()
	material.albedo_color = Color('#fc7f7f')
	material.roughness = 0.4

	var buoy: MeshInstance3D = MeshInstance3D.new()
	buoy.mesh = mesh
	buoy.material_override = material
	buoy.position = at
	add_child(buoy)


func _add_environment() -> void:
	var sky_material: ProceduralSkyMaterial = ProceduralSkyMaterial.new()
	sky_material.sky_top_color = Color(0.1, 0.16, 0.34)
	sky_material.sky_horizon_color = Color(0.5, 0.55, 0.65)
	sky_material.ground_bottom_color = Color(0.05, 0.07, 0.12)
	sky_material.ground_horizon_color = Color(0.3, 0.34, 0.42)

	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(-35, -50, 0)
	light.light_energy = 1.4
	add_child(light)
shaders/water.gdshader
shader_type spatial;

uniform vec3 shallow_color: source_color = vec3(0.1, 0.55, 0.75);
uniform vec3 deep_color: source_color = vec3(0.02, 0.12, 0.35);
uniform vec3 foam_color: source_color = vec3(0.8, 0.95, 1.0);
uniform float wave_height = 0.22;
uniform float wave_length = 1.4;
uniform float speed = 1.2;

float wave(vec2 p) {
	return sin(p.x / wave_length + TIME * speed) * cos(p.y / wave_length * 0.8 + TIME * speed * 0.7);
}

void vertex() {
	float height = wave(VERTEX.xz);
	VERTEX.y += height * wave_height;

	// the normal comes from the derivative of the same function, without it the light ignores the waves
	float step_size = 0.05;
	float dx = wave(VERTEX.xz + vec2(step_size, 0.0)) - height;
	float dz = wave(VERTEX.xz + vec2(0.0, step_size)) - height;

	NORMAL = normalize(vec3(-dx * wave_height / step_size, 1.0, -dz * wave_height / step_size));
}

void fragment() {
	float facing = clamp(dot(normalize(NORMAL), normalize(VIEW)), 0.0, 1.0);
	float fresnel = pow(1.0 - facing, 3.0);

	ALBEDO = mix(deep_color, shallow_color, facing);
	ALBEDO = mix(ALBEDO, foam_color, fresnel * 0.6);
	ROUGHNESS = 0.08;
	METALLIC = 0.25;
	SPECULAR = 0.9;
}

更多着色器示例