Godot Engine : Révéler du contenu avec une loupe en utilisant les shaders ! : Godot Engine

pictureProfil_cevantime
Ecrit par cevantime
Catégorie Godot Engine
Mis à jour le 14/02/2021
Godot Engine : Révéler du contenu avec une loupe en utilisant les shaders !

Dans ce tutoriel, on voit comment utiliser les shaders avec Godot afin de révéler le contenu d'une image au passage de la souris.

La version vidéo est disponible sur youtube.

11 min Intermédiaire

Les shaders ne sont pas réservés aux graphistes et ne servent pas qu'à générer de jolis effets, loin s'en faut.
Dans ce tutoriel, on va voir comment on peut utiliser un fragment shader très simple de façon à révéler un contenu caché dans notre scène !

 

Mise en place de notre scène de départ

 

Commençons par créer un projet standard avec Godot. Ajoutons-y le répertoire assets contenants les images du projet :

Créons ensuite notre scène à partir d'un Node2D et renommons-la RevealTuto.

Nous y ajoutons ensuite notre image de fond effectuant un glisser-déposer de l'asset old_desk.jpeg ainsi que de notre parchemin vierge (parchment_alpha.png), par dessus. Nous obtenons deux sprites que nous pouvons renommer ainsi :

 

 

En lançant notre jeu, nous obtenons ce visuel de départ :

 

 

Remplacer le curseur par une loupe

 

La première chose que nous allons faire est remplacer le curseur standard par une loupe, au passage de la souris sur le parchemin.

Ceci peut-être réalisé très simplement grâce à la méthode Input.set_custom_mouse_cursor(texture) laquelle prend en paramètre la texture à afficher à la place du curseur.

Une façon de faire (peut-être pas la plus élégante mais facile à comprendre/implémenter) consiste à vérifier à chaque frame si la souris est à l'intérieur du parchemin, et à adapter la texture du curseur en conséquence.

Ajoutons un script à notre sprite Parchment (appelons-le Parchment.gd) et implémentons la fonction _process pour vérifier la position de la souris à chaque frame :
 

extends Sprite

var glass_texture = preload("res://assets/glass.png")

func _process(delta):
	
	if get_rect().has_point(get_local_mouse_position()):
		Input.set_custom_mouse_cursor(glass_texture)
	else:
		Input.set_custom_mouse_cursor(null)

Ce code est assez explicite, n'est-ce-pas laugh ?

On stocke une bonne fois pour toutes notre texture de loupe dans une variable liée à notre script (glass_texture) et on l'assigne à notre curseur si le rectangle formé par le sprite (get_rect()) contient (has_point()) la position de notre souris par rapport au sprite (get_local_mouse_position()). Si la souris est à l'extérieur du sprite, on remet le curseur par défaut (en passant simplement null à Input.set_custom_mouse_cursor).

Notez que ce code assez simple ne fonctionne pas bien si l'on souhaite avoir plusieurs zones faisant apparaître un curseur alternatif. Une solution plus robuste consiste à créer un groupe contenant les surfaces d'intérêt et à vérifier la position de la souris pour chacun des éléments de ce groupe, au sein d'un script unique.
Par ex :
func _process(delta):
	var mouse_tex = null
	for s in get_tree().get_nodes_in_group("RevealSurfaces"):
		if s.get_rect().has_point(s.get_local_mouse_position()):
			mouse_tex = s.cursor_texture
			break
	Input.set_custom_mouse_cursor(mouse_tex)

 

f

 
 

Révéler le contenu situé autour du curseur

 

Pour révéler le contenu situé autour de notre curseur, nous allons utiliser un fragment shader. Les fragment shader sont des programmes s'exécutant pour chacun des pixels d'une texture.

La logique de notre shader est très simple et tient en une ligne :
Si le pixel parcouru par le shader est proche du curseur, alors on affiche le contenu de la texture avec le message secret (parchment_written.png) sinon, on affiche la texture par défaut.

Pour assigner un shader à notre Sprite Parchment,  cliquons sur la valeur [empty] dans la propriété material puis sur New ShaderMaterial puis sur [empty] dans la propriété Shader puis New Shader. Nous voyons alors apparaître en bas notre éditeur de shader.

 

Nous devons d'abord définir le type de notre shader. Celui-ci étant appliqué à un noeud 2D, nous devons utiliser le type canvas_item :

shader_type canvas_item;

 

Nous allons maintenant définir nos propriétés uniform c'est-à-dire les propriétés dont les valeurs devront être transmises à notre shader depuis l'extérieur (via un script ou via l'éditeur). Ces propriétés uniformes seront :

  • la texture révélée (parchment_written.png)
  • la position de la souris
  • le rayon de la loupe (distance en deça de laquelle la texture est révélée)

En terme de code, cela donne :

uniform sampler2D revealed_texture;
uniform vec2 mouse_position;
uniform float glass_radius = 50.0;

Passons maintenant à l'implémentation de la fonction fragment() qui permet de définir la couleur de chacun des pixels de notre sprite.

void fragment() {

}

On commence par récupérer la couleur "au naturel" de notre sprite, c'est-à-dire la couleur de la texture par défaut, de cette manière :

vec4 color = texture(TEXTURE, UV);

color est la couleur du pixel de notre texture à la position UV c'est-à-dire la position du pixel au sein de notre texture, exprimée de manière normalisée (de 0 à 1 en largeur et en hauteur).

On peut récupérer de la même manière la couleur du pixel, mais sur notre texture révélée cette fois :

vec4 revealed_color = texture(revealed_texture, UV);

La question va être maintenant de décider si la couleur finale du pixel sera celle de notre texture normale ou celle de notre texture révélée.

Pour cela, on va récupérer la position du pixel à l'écran et voir à quelle distance il se situe du curseur. Pour récupérer la position du pixel à l'écran, on utilise FRAGCOORD.xy :

vec2 pos = FRAGCOORD.xy;

La position renvoyée par FRAGCOORD.xy -orientée vers le haut à partir du coin inférieur gauche- ne suit pas les mêmes conventions que celle de l'API de godot pour la position du curseur - orientée vers le bas et à partir du coin supérieur droit. On va trafiquer un peu notre position pour pouvoir la comparer à celle de la souris :

vec2 pos = FRAGCOORD.xy;
pos.y = -pos.y + (1.0 / SCREEN_PIXEL_SIZE).y;

où (1.0/SCREEN_PIXEL_SIZE).y est la hauteur de l'écran.

Maintenant que pos est comparable avec la position de la souris, on n'a plus qu'à calculer la distance entre les deux et à arbitrer la couleur finale du pixel grâce à une condition :

if(length(pos - mouse_position) < glass_radius) {
	COLOR = revealed_color;
} else {
	COLOR = color;
}

avec ce code final :

shader_type canvas_item;

uniform sampler2D revealed_texture;
uniform vec2 mouse_position;
uniform float glass_radius = 50.0;

void fragment() {
	vec4 color = texture(TEXTURE, UV);
	vec4 revealed_color = texture(revealed_texture, UV);
	vec2 pos = FRAGCOORD.xy;
	pos.y = -pos.y + (1.0 / SCREEN_PIXEL_SIZE).y;
	float diff = length(pos - mouse_position - vec2(8.0, 8.0));

	if(length(pos - mouse_position) < glass_radius) {
		COLOR = revealed_color;
	} else {
		COLOR = color;
	}
}

On peut aussi réécrire le if de manière plus condensée en combinant la fonction mix et la fonction step.

shader_type canvas_item;

uniform sampler2D revealed_texture;
uniform vec2 mouse_position;
uniform float glass_radius = 50.0;

void fragment() {
	vec4 color = texture(TEXTURE, UV);
	vec4 revealed_color = texture(revealed_texture, UV);
	vec2 pos = FRAGCOORD.xy;
	pos.y = -pos.y + (1.0 / SCREEN_PIXEL_SIZE).y;
	float diff = length(pos - mouse_position - vec2(8.0, 8.0));
	COLOR = mix(color, revealed_color, step(diff, glass_radius));
}

qui revient exactement au même.

Notre shader est terminé, il ne reste plus qu'à définir les valeurs de nos uniforms. La position de la souris sera bien entendu transmise par notre script...

func _process(delta):
	#...
	material.set_shader_param("mouse_position", get_global_mouse_position());
	

tandis que les deux autres uniforms seront définis depuis l'éditeur :

On obtient finalement :

On peut adoucir (flouter) les contours de notre loupe en utilisant smoothstep au lieu de step. Cela permet un petit dégradé :

COLOR = mix(color, revealed_color, smoothstep(diff - 5.0, diff + 5.0, glass_radius));

Avec le résultat suivant :

 

Retrouvez la vidéo support sur youtube  

C'est tout pour ce chapitre. Restez connecté !

Vues: 478

Licence:



Vous aimerez aussi...

Blog et Tutoriels

Roll The Ball 3D avec Godot Engine (partie I)

Roll The Ball 3D avec Godot Engine (partie I)

par cevantime

Ce cours détaille la mise en place des éléments de notre scène. Au programme :

  • Description et utilisation de quelques nœuds 3D de base (StaticBody, Area, RigidBody et Camera)
  • Mise en place du sol, de murs, d'une balle et contrôle de cette dernière ainsi que de la caméra
applause logo1