O Materialize CLI implementa seis algoritmos principais, cada um executado como compute shader WGSL:
- Height from Diffuse - Extrai informação de altura da imagem colorida
- Normal from Height - Calcula vetores normais a partir do height map
- Metallic from Diffuse - Detecta metalicidade por análise de cor
- Smoothness - Base + contribuição do metallic (difusa + metallic como entrada)
- Edge from Normal - Gradiente da normal (X/Y) para detecção de bordas
- AO from Height - Cavity-style: amostras em 8 direções, oclusão quando vizinho > centro
Converter uma imagem RGB em um mapa de altura em escala de cinza onde:
- Branco (1.0) = pontos altos
- Preto (0.0) = pontos baixos
- Valores intermediários = gradiente de altura
Converte RGB para luminância usando pesos perceptuais:
fn rgb_to_luminance(rgb: vec3<f32>) -> f32 {
return dot(rgb, vec3<f32>(0.299, 0.587, 0.114));
}Rationale: Pesos baseados na sensibilidade do olho humano (verde mais perceptível).
Aplica Gaussian blur em múltiplas escalas para capturar detalhes em diferentes frequências:
Level 0: σ = 1.0 (detalhes finos)
Level 1: σ = 2.0 (detalhes médios)
Level 2: σ = 4.0 (formas grandes)
Level 3: σ = 8.0 (estrutura geral)
Level 4: σ = 16.0 (contornos grandes)
Level 5: σ = 32.0 (formas principais)
Level 6: σ = 64.0 (estrutura macro)
Pesos de combinação:
weights = [0.5, 0.3, 0.15, 0.03, 0.015, 0.003, 0.002]
Kernel Gaussiano 1D (para separação):
G(x) = (1.0 / (sqrt(2π) * σ)) * exp(-x² / (2σ²))
Implementação separável:
- Passo horizontal: amostra linha, aplica kernel 1D
- Passo vertical: amostra coluna, aplica kernel 1D
- Mais eficiente que kernel 2D (O(n) vs O(n²))
Aplica sigmoid para aumentar contraste:
fn enhance_contrast(value: f32, contrast: f32) -> f32 {
// Map [0,1] to [-1,1]
let centered = value * 2.0 - 1.0;
// Sigmoid function
let enhanced = centered / (1.0 + exp(-contrast * centered));
// Map back to [0,1]
return (enhanced + 1.0) * 0.5;
}Parâmetros:
contrast = 1.0: Linearcontrast > 1.0: Mais contrastecontrast < 1.0: Menos contraste
@compute @workgroup_size(8, 8)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let coords = vec2<i32>(gid.xy);
let dims = textureDimensions(input_texture);
if (coords.x >= i32(dims.x) || coords.y >= i32(dims.y)) {
return;
}
// Sample and convert to luminance
let color = textureLoad(input_texture, coords, 0).rgb;
let luminance = rgb_to_luminance(color);
// Multi-level blur
var height = 0.0;
let weights = array<f32, 7>(0.5, 0.3, 0.15, 0.03, 0.015, 0.003, 0.002);
for (var level = 0; level < 7; level++) {
let sigma = f32(1 << level); // 1, 2, 4, 8, 16, 32, 64
let blurred = gaussian_blur(luminance, coords, sigma, dims);
height += weights[level] * blurred;
}
// Contrast enhancement
height = enhance_contrast(height, 1.5);
textureStore(output_texture, coords, vec4<f32>(height, 0.0, 0.0, 1.0));
}| Parâmetro | Valor | Descrição |
|---|---|---|
| Blur levels | 7 | Número de níveis de blur |
| Max sigma | 64.0 | Blur mais amplo |
| Contrast | 1.5 | Fator de contraste |
Calcular vetores normais da superfície a partir do gradiente do height map.
Uma normal é perpendicular à superfície. Calculamos via:
normal = normalize(cross(dY, dX))
Onde:
dX= vetor tangente na direção X (1, 0, ∂height/∂x)dY= vetor tangente na direção Y (0, 1, ∂height/∂y)
Calcula gradientes usando kernels 3x3:
Sobel X (horizontal):
[-1, 0, +1]
[-2, 0, +2]
[-1, 0, +1]
Sobel Y (vertical):
[-1, -2, -1]
[ 0, 0, 0]
[+1, +2, +1]
fn sample_height(coords: vec2<i32>) -> f32 {
return textureLoad(height_texture, coords, 0).r;
}
@compute @workgroup_size(8, 8)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let coords = vec2<i32>(gid.xy);
let dims = textureDimensions(height_texture);
if (coords.x >= i32(dims.x) || coords.y >= i32(dims.y)) {
return;
}
// Sobel operator
let gx = sample_height(coords + vec2<i32>(-1, -1)) * -1.0
+ sample_height(coords + vec2<i32>(-1, 0)) * -2.0
+ sample_height(coords + vec2<i32>(-1, 1)) * -1.0
+ sample_height(coords + vec2<i32>( 1, -1)) * 1.0
+ sample_height(coords + vec2<i32>( 1, 0)) * 2.0
+ sample_height(coords + vec2<i32>( 1, 1)) * 1.0;
let gy = sample_height(coords + vec2<i32>(-1, -1)) * -1.0
+ sample_height(coords + vec2<i32>( 0, -1)) * -2.0
+ sample_height(coords + vec2<i32>( 1, -1)) * -1.0
+ sample_height(coords + vec2<i32>(-1, 1)) * 1.0
+ sample_height(coords + vec2<i32>( 0, 1)) * 2.0
+ sample_height(coords + vec2<i32>( 1, 1)) * 1.0;
// Normal vector (pointing up, gradient down)
var normal = vec3<f32>(-gx, -gy, 1.0);
normal = normalize(normal);
// Encode to [0,1] for storage
let encoded = normal * 0.5 + 0.5;
textureStore(output_texture, coords, vec4<f32>(encoded, 1.0));
}| Parâmetro | Valor | Descrição |
|---|---|---|
| Intensity | 1.0 | Escala dos gradientes (multiplica gx, gy) |
| Flip Y | false | Inverte eixo Y da normal |
Intensidade > 1.0: Normais mais "fortes", superfície parece mais rugosa Intensidade < 1.0: Normais mais "fracas", superfície mais plana
- Y-up:
normal.y = -normal.y(ou não flip) - Canais: RGB
- Y-up:
normal.y = normal.y(flip em relação ao DirectX) - Canais: RGB
O MVP usa formato DirectX (Y down na textura).
Detectar áreas metálicas na imagem difusa e gerar uma máscara em escala de cinza.
Metais puros (ouro, prata, cobre, ferro) têm características distintas:
- Saturação baixa: Metais são cinzentos (exceto ouro/cobre)
- Luminância alta: Metais refletem bem
- Matiz específico: Ouro é amarelo, cobre é laranja
Converte RGB para Hue/Saturation/Luminance para análise:
fn rgb_to_hsl(rgb: vec3<f32>) -> vec3<f32> {
let max_val = max(max(rgb.r, rgb.g), rgb.b);
let min_val = min(min(rgb.r, rgb.g), rgb.b);
let delta = max_val - min_val;
// Luminance
let l = (max_val + min_val) * 0.5;
// Saturation
var s = 0.0;
if (delta > 0.0) {
s = delta / (1.0 - abs(2.0 * l - 1.0));
}
// Hue
var h = 0.0;
if (delta > 0.0) {
if (max_val == rgb.r) {
h = (rgb.g - rgb.b) / delta;
} else if (max_val == rgb.g) {
h = 2.0 + (rgb.b - rgb.r) / delta;
} else {
h = 4.0 + (rgb.r - rgb.g) / delta;
}
h = h / 6.0;
if (h < 0.0) { h += 1.0; }
}
return vec3<f32>(h, s, l);
}fn detect_metallic(rgb: vec3<f32>) -> f32 {
let hsl = rgb_to_hsl(rgb);
let h = hsl.x; // Hue [0,1]
let s = hsl.y; // Saturation [0,1]
let l = hsl.z; // Luminance [0,1]
// Heurística: metal = baixa saturação + alta luminância
// ou matiz específico (ouro/cobre)
var metallic = 0.0;
// Metais cinzentos (prata, aço, alumínio)
if (s < 0.15 && l > 0.4) {
metallic = smoothstep(0.4, 0.8, l) * (1.0 - s * 6.0);
}
// Ouro (matiz ~0.08-0.15)
if (h > 0.08 && h < 0.15 && s > 0.3 && l > 0.3) {
metallic = smoothstep(0.3, 0.6, l) * smoothstep(0.3, 0.7, s);
}
// Cobre (matiz ~0.02-0.08)
if (h > 0.02 && h < 0.08 && s > 0.4 && l > 0.25) {
metallic = smoothstep(0.25, 0.5, l) * smoothstep(0.4, 0.8, s);
}
return saturate(metallic);
}@compute @workgroup_size(8, 8)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let coords = vec2<i32>(gid.xy);
let dims = textureDimensions(input_texture);
if (coords.x >= i32(dims.x) || coords.y >= i32(dims.y)) {
return;
}
let color = textureLoad(input_texture, coords, 0).rgb;
let metallic = detect_metallic(color);
textureStore(output_texture, coords, vec4<f32>(metallic, 0.0, 0.0, 1.0));
}| Parâmetro | Valor | Descrição |
|---|---|---|
| Saturation threshold | 0.15 | Max saturação para metais cinzentos |
| Luminance threshold | 0.4 | Min luminância para metais cinzentos |
| Gold hue range | 0.08-0.15 | Range de matiz para ouro |
| Copper hue range | 0.02-0.08 | Range de matiz para cobre |
Algoritmo atual:
- Funciona bem para materiais puros
- Pode falhar para:
- Metais sujos/oxidadas
- Metais pintados
- Materiais misturados
Melhorias futuras:
- Machine learning (classificação por ML)
- Amostras de cor definidas pelo usuário
- Análise de contexto (vizinhança)
- Separable convolution: Gaussian blur em 2 passos (H + V) em vez de kernel 2D
- Shared memory: Carregar blocos para
workgroupmemory para acessos coalesced - Texture cache: Reutilizar texturas intermediárias
- Early exit: Workgroups fora dos bounds retornam imediatamente
- R32Float para height: Preservar precisão intermediária
- RGBA8Unorm para normal: Suficiente para visualização
- R8Unorm para metallic: Um canal, valores 0-1
- Sobel Operator
- Gaussian Blur
- HSL Color Space
- Physically Based Rendering
- Materialize original shaders (HLSL/CG)