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