Annexe H · Hash

Fonctions de hash

Une Hash function est un algorithme qui transforme un entier en un autre entier qui semble aléatoire mais est entièrement déterministe. C'est la brique fondamentale du bruit procédural, de la génération de terrain, et de tout ce qui nécessite du "pseudo-aléatoire reproductible" sur GPU.

/* ************************************************************************** */
/*                                                                            */
/*                                                        :::      ::::::::   */
/*   ft_toupper.c                                       :+:      :+:    :+:   */
/*                                                    +:+ +:+         +:+     */
/*   By: rgraling <rgraling@student.s19.be>         +#+  +:+       +#+        */
/*                                                +#+#+#+#+#+   +#+           */
/*   Created: 2024/09/03 19:39:48 by rgraling          #+#    #+#             */
/*   Updated: 2024/09/03 20:02:35 by rgraling         ###   ########.fr       */
/*                                                                            */
/* ************************************************************************** */

int ft_toupper(int c)
{
    if (c > 96 && c < 123)
        return (c - 32);
    return (c);
}

// Cette structure offre un temps de calcul de 𝑂⁡(𝑛)
unsigned int hash(const char* word)
{
    return ft_toupper(word[0]) - 'A';
}

L'idée est de prendre une valeur et de pouvoir produire une valeur qui devient un raccourci vers elle plus tard, devient plus petit et plus prévisible. Implémentée, une table de hachage est un tableau de pointeurs vers les nœuds.

// Illustration :
                -------------------------
  Yoshi  -->    |         hash()        |    -->  23
                -------------------------
// Yoshi   : une chaine de caractères de taille quelconque (const char *)
// 23      : un entier signé de taille fixe (toujours positif) (unsigned int)

On dit que cette fonction de hachage est non cryptographique et est très faible. Tous les mots commençant par la même lettre donnent la même valeur. Bref, ne l'utilise pas elle est nulle.

En tant que programmeur, vous devez prendre une décision sur les avantages d'utiliser plus de mémoire pour avoir une grande table de hachage et potentiellement réduire le temps de recherche ou d'utiliser moins de mémoire et potentiellement augmenter le temps de recherche.

Propriété essentielle — l'effet avalanche

Une bonne fonction de hash doit changer ~50% de ses bits de sortie quand un seul bit d'entrée change. C'est l'effet avalanche.

// Input:  00000000000000000000000000000001
// Output: 10110101011010010011010110101011  ← ~50% des bits changent
//
// Input:  00000000000000000000000000000010  (1 seul bit décalé)
// Output: 01101010110100100110101101010110  ← complètement différent
//
// Sans effet avalanche : des patterns apparaissent dans le bruit (rayures, grilles)

5 Fonctions pour un fichier Utils.metal

hashPerlin — Wellons / Schechter-Bridson

// Chris Wellons — excellent équilibre vitesse/qualité
inline uint hashPerlin(uint x)
{
    x = ((x >> 16) ^ x) * 0x45d9f3b;  // mélange les bits hauts dans les bas
    x = ((x >> 16) ^ x) * 0x45d9f3b;  // deuxième passe (améliore l'avalanche)
    x = (x >> 16) ^ x;                 // finalisation
    return x;
}
// Qualité : ★★★★☆ — Très bon, utilisable pour le bruit Perlin
// Vitesse  : ★★★★★ — 3 multiplications, 3 XOR

murmur — MurmurHash3 finalizer

// Austin Appleby / Thomas Wang — considéré "excellent" en qualité statistique
inline uint murmur(uint x)
{
    x ^= x >> 16;
    x *= 0x85ebca6b;  // constante choisie pour maximiser l'avalanche
    x ^= x >> 13;
    x *= 0xc2b2ae35;
    x ^= x >> 16;
    return x;
}
// C'est le finalizer de MurmurHash3 — extrait pour un usage seul
// Qualité : ★★★★★ — Excellent, passe les tests SmallCrush
// Vitesse  : ★★★★☆ — 2 multiplications, 3 XOR-shift

pcg — PCG-like

// Inspiré de PCG (Melissa O'Neill) — très bonne qualité
inline uint pcg(uint x)
{
    uint state = x * 747796405u + 2891336453u;  // LCG step
    uint word  = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; // output mix
    return (word >> 22u) ^ word;
}
// Qualité : ★★★★★ — Excellent, rotation dépendante de l'état = très difficile à prédire
// Vitesse  : ★★★☆☆ — Plus lourd : shift variable selon l'état

wang / wang_hash — Wang hash

// Thomas Wang — classique du GPU, largement utilisé en shaders
inline uint wang(uint x)
{
    x = (x ^ 61u) ^ (x >> 16u);
    x *= 9u;
    x ^= x >> 4u;
    x *= 0x27d4eb2du;
    x ^= x >> 15u;
    return x;
}
// Ton fichier a deux versions identiques (wang et wang_hash) — même algorithme
// Qualité : ★★★★☆ — Très bon, quelques patterns mineurs à haute fréquence
// Vitesse  : ★★★★☆ — 2 multiplications, 3 XOR-shift

lcg — Linear Congruential Generator

// Simple — LCG classique
inline uint lcg(uint x)
{
    return x * 1664525u + 1013904223u;
}
// ⚠ Qualité : ★★☆☆☆ — MÉDIOCRE
// Produit des patterns en diagonale visibles dans le bruit
// Les bits de poids faible sont très peu aléatoires
// Utiliser seulement comme générateur de séquence, JAMAIS pour du bruit spatial

Comparaison — quand utiliser quoi

Fonction Qualité Vitesse GPU À utiliser pour
pcg ★★★★★ ★★★☆☆ Valeurs critiques, simulation physique
murmur ★★★★★ ★★★★☆ Hash de cellules, IDs de biomes
hashPerlin ★★★★☆ ★★★★★ Bruit Perlin, texture haute fréquence
wang ★★★★☆ ★★★★☆ Bruit général, compatible with tout
lcg ★★☆☆☆ ★★★★★ Ne pas utiliser pour du bruit spatial

hash2D et hash3D — combiner des dimensions

// hash2(uint2) — combine x et y :
inline uint hash2(uint2 v)
{
    return hashPerlin(v.x ^ hashPerlin(v.y));
}
// XOR naïf sans hashing préalable serait catastrophique :
// hash(x ^ y) → (1,2) et (2,1) donnent le même résultat → aliasing de biomes

// hash3(uint3) — même principe :
inline uint hash3(uint3 v)
{
    return hashPerlin(v.x ^ hashPerlin(v.y ^ hashPerlin(v.z)));
}

// hashToFloat — convertir en float [0, 1] :
inline float hashToFloat(uint h)
{
    return float(h) / float(0xFFFFFFFF);  // divise par 2^32-1
}
// Utilisation dans voronoi2D pour créer les points de cellule

Seed — rendre le hash reproductible

// Le seed permet de générer des mondes différents avec la même fonction

// Mauvais — le seed affecte peu les bits bas :
uint h = hashPerlin(x + seed);  // si seed = 0 et seed = 1 → très similaire

// Bon — hasher le seed d'abord :
uint h = hashPerlin(x ^ murmur(seed));

// Dans ton fbm2D :
// seed + i * 1337  → chaque octave a un seed différent
// Le 1337 est arbitraire — n'importe quel entier impair marche
// Important : ne pas utiliser seed + i * 2 (trop corrélé)

NoiseParams — la structure de paramètres

// Dans Helpers.metal :
struct NoiseParams
{
    float2   worldOffset; // décalage dans le monde (streaming de chunks)
    float    scale;       // zoom du bruit (plus grand = plus lisse)
    uint     biomeId;     // influence les paramètres de bruit par biome
    uint64_t seed;        // graine globale du monde
};

// À passer en constant buffer :
constant NoiseParams& params [[buffer(2)]]

float h = fbm2D((worldPos.xz + params.worldOffset) * params.scale, 6, 0.5, 2.0, (uint)params.seed ^ params.biomeId);

Tests de qualité — SmallCrush

Pour valider une fonction de hash GPU, la référence est TestU01 (SmallCrush / BigCrush). murmur et pcg passent les deux. lcg échoue à SmallCrush.

// Règle simple : si tu vois des patterns dans ton bruit
// (lignes, grilles, répétitions) → change la fonction de hash
// La plupart du temps : hashPerlin ou murmur règlent le problème