Plano de agua

Olas en el vertex shader con la normal recalculada desde la derivada, así la luz las acompaña.

El editor de arriba está ejecutando este proyecto. Cambia una línea y se recarga.

Clases de Godot usadas

PlaneMeshShaderMaterialMeshInstance3D

El código

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;
}

Más ejemplos de Shaders