Rocha de penhasco
Uma textura de rocha repetida, projetada ao longo de x, y e z e misturada pela normal, para cobrir uma malha sem UVs e sem esticar.
- 01ObjetivoMateriais
- 02TécnicaMapeamento triplanar
- 03ExecuçãoRocha de penhasco
Parâmetros
Copie para a sua plataforma
- GLSLCopiar
- GodotCopiar
Cadeia de nós
Montada passo a passo, no estilo do GLSL, com os nomes de nós dele.
Montada passo a passo, no estilo do Godot, com os nomes de nós dele.
Este card ainda não tem versão para Unreal Engine 5, então os nós mostram os nomes do GLSL.
Este card ainda não tem versão para Unity, então os nós mostram os nomes do GLSL.
Este card ainda não tem versão para Blender, então os nós mostram os nomes do GLSL.
powPowpowpowpow PosiçãoNormalTextura01 Quanto encara cada eixo
/Divide/// Entrada02 Pesos que somam 1
textureTexture2Dtexturetexturetexture Entrada03 Projetar ao longo de X
textureTexture2Dtexturetexturetexture Entrada04 Projetar ao longo de Y
textureTexture2Dtexturetexturetexture Entrada05 Projetar ao longo de Z
x * w.x + y * w.y + z * w.zMultiplyAddx * w.x + y * w.y + z * w.zx * w.x + y * w.y + z * w.zx * w.x + y * w.y + z * w.z Entrada06 Misturar as três
Como funciona
A esfera de teste tem UVs, mas este card as ignora: a rocha vem só da posição da superfície. Uma única textura de rocha de 512×512 é projetada três vezes, uma ao longo de cada eixo do mundo, e cada pixel mistura as três conforme o quanto a superfície encara cada eixo. O topo e os lados ganham cada um uma projeção sem distorção, e as encostas no meio fazem a transição. Gire a prévia: as rachaduras continuam nítidas o caminho todo, sem beliscar nos polos e sem costura.
Passo a passo
- Quanto encara cada eixo.
abs(i.normal)é quanto a superfície olha ao longo de x, y e z, seja qual for o lado para onde ela está virada; mostrado como cor, é vermelho à esquerda e à direita, verde em cima e embaixo, azul na frente e atrás. Elevá-lo à Nitidez da transição estreita as zonas onde dois deles são grandes ao mesmo tempo. - Pesos que somam 1. Dividir pela soma dos três faz eles somarem 1, então onde duas projeções se sobrepõem a mistura fica tão clara quanto cada uma sozinha. Sem isso, as encostas a 45°, onde os componentes crus somam cerca de 1,4, saem mais claras que as faces.
- Projetar ao longo de X.
texture(t_rock, p.zy): a posição vista de lado, com y como a vertical da textura. À esquerda e à direita da esfera é uma imagem limpa; rumo à frente ela escorre em riscos, que os pesos vão esconder. Ela é deslocada meia repetição para o lado: numa aresta a 45° x é igual a z, e sem o deslocamento esta projeção e a de Z mostrariam ali os mesmos texels espelhados, uma costura simétrica que entrega o truque. - Projetar ao longo de Y.
p.xz, a vista de cima, deslocada meia repetição em z pelo mesmo motivo nas arestas onde y é igual a z. Limpa em cima e embaixo, riscada nos lados. - Projetar ao longo de Z.
p.xy, a vista de frente, de novo com y na vertical, então as duas projeções laterais mantêm as camadas da rocha niveladas. - Misturar as três. Cada projeção vezes o seu peso, somadas. Cada pixel tira quase toda a cor da projeção que o vê melhor, e os riscos das outras duas saem pelo peso.
Repetições por unidade escala a posição antes da amostragem, então define o tamanho da rocha em unidades do mundo; a textura se repete sem emenda, o que importa mais aqui do que com UVs, porque as projeções atravessam o objeto inteiro. A textura é sRGB e chega decodificada, então a soma ponderada mistura cor linear.
Custo
Três leituras de textura e cerca de 31 operações aritméticas: três para o abs, nove para os três pow (um logaritmo,
uma multiplicação e uma exponencial cada), cinco para a normalização, cinco para escalar e deslocar a posição e nove
para a soma ponderada. As leituras dominam. Cada textura a mais num material triplanar custa três leituras em vez de
uma, então um conjunto completo de cor, normal e rugosidade dá nove; é aí que o mapeamento biplanar, ou pular a projeção
mais fraca quando o peso dela fica perto de zero, compensa.
Monte no GLSL
Leve rock.jpg dos assets do card (ou qualquer textura que se repita sem emenda) como t_rock. Envie-a como SRGB8_ALPHA8 com UNPACK_FLIP_Y_WEBGL ligado, gere mipmaps e ponha os dois modos de wrap em REPEAT; o shader espera os quatro.
O botão de copiar entrega um shader de fragmento WebGL2 completo. O seu shader de vértice preenche v_position e
v_normal (em espaço de mundo para terrenos que devem se alinhar entre objetos, em espaço de objeto para objetos que
levam a textura junto) e declara v_view e v_uv, que este card não lê. O main() gerado normaliza a normal de novo
em cada pixel, então os pesos continuam certos em triângulos grandes.
Para texturizar materiais diferentes por direção, amostre uma segunda textura na linha top, musgo ou grama por
exemplo, e deixe a rocha nos dois lados; os pesos já a colocam nas superfícies viradas para cima. Troque o pow por um
smoothstep em weights.y se o musgo deve parar numa linha nítida em vez de esmaecer.
Monte no Godot
Sem código próprio, um StandardMaterial3D faz o mesmo com a rocha como textura de Albedo, UV1 Triplanar ligado, World Triplanar ligado (para igualar este export em espaço de mundo) e UV1 Scale para a repetição; Triplanar Sharpness é a mesma potência da Nitidez da transição (use 4 para igualar), mas o Godot calcula os pesos por vértice, então eles são interpolados em triângulos grandes. No VisualShader, o nó TextureParameterTriplanar é a cadeia inteira num nó só.
Crie um MeshInstance3D, dê a ele um novo ShaderMaterial, cole o shader e defina t_rock como o rock.jpg do card (ou
qualquer textura que se repita sem emenda). O hint source_color a decodifica para linear, como o card faz. O Godot
gera mipmaps para ela na primeira vez que um material 3D a usa; se a rocha cintilar à distância, confira se Mipmaps >
Generate está ligado na aba Import da textura.
No VisualShader, monte com um nó Input de NORMAL levado para espaço de mundo, um VectorFunc Abs e um VectorOp Pow pela Nitidez da transição, depois um Divide pela soma dos três componentes (um Vector3 Decompose e dois Adds). Três nós Texture2D amostram a rocha nos zy, xz e xy da posição em espaço de mundo (cada um um Vector3 Decompose num Vector2 Compose), e dois nós MultiplyAdd depois de um Multiply os somam pelos pesos na porta Albedo, com o material unshaded.
Ainda não há tutorial de Unreal Engine 5 para este card.
Ainda não há tutorial de Unity para este card.
Ainda não há tutorial de Blender para este card.
Código
#version 300 es// Triplanar mapping → Cliff rock, web GLSL (GLSL ES 3.00). From Creative Center, MIT license.precision highp float;uniform float u_time;uniform vec2 u_resolution; // this pass's output size in pixelsuniform float p_scale; // Tiles per unit: 0.75 (0.25 to 4.0)uniform float p_sharpness; // Blend sharpness: 4.0 (1.0 to 16.0)uniform sampler2D t_rock; // rock.jpg: sRGB color, uploaded as SRGB8_ALPHA8 so it samples linear; linear filter with mipmaps, repeatin vec2 v_uv;in vec3 v_position;in vec3 v_normal;in vec3 v_view;out vec4 fragColor;struct Inputs { vec2 uv; vec3 position; vec3 normal; vec3 view; float time; vec2 fragCoord; vec2 resolution;};vec3 u_toSrgb(vec3 c) { return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));}vec4 effect(Inputs i) { // How much the surface faces each world axis; the power narrows the seams where two projections cross-fade vec3 facing = pow(abs(i.normal), vec3(p_sharpness)); // Dividing by the sum keeps the three weights adding up to 1, so the blend neither brightens nor darkens vec3 weights = facing / (facing.x + facing.y + facing.z); vec3 p = i.position * p_scale; // Each projection drops the axis it looks along; y stays the texture's vertical on both sides, so the rock's // layers stay level there. On a 45° edge two coordinates are equal, so without the half-tile shifts two // projections would show the same texels mirrored there, a symmetric seam. vec3 sideX = texture(t_rock, p.zy + vec2(0.5, 0.0)).rgb; vec3 top = texture(t_rock, p.xz + vec2(0.0, 0.5)).rgb; vec3 sideZ = texture(t_rock, p.xy).rgb; vec3 rock = sideX * weights.x + top * weights.y + sideZ * weights.z; return vec4(rock, 1.0);}void main() { Inputs i; i.uv = v_uv; i.position = v_position; // Interpolation shortens the vectors between vertices i.normal = normalize(v_normal); i.view = normalize(v_view); i.time = u_time; i.fragCoord = gl_FragCoord.xy; i.resolution = u_resolution; vec4 frag_out = effect(i); // The output is sRGB: clamped and encoded once, here, with no tone mapping; opaque, so alpha is 1 fragColor = vec4(u_toSrgb(clamp(frag_out.rgb, 0.0, 1.0)), 1.0);}// Triplanar mapping → Cliff rock, Godot 4. From Creative Center, MIT license.shader_type spatial;// Unshaded shows the effect's color as is, like the site's preview; scene lights don't touch itrender_mode unshaded;uniform float scale : hint_range(0.25, 4.0, 0.05) = 0.75;uniform float sharpness : hint_range(1.0, 16.0, 0.5) = 4.0;uniform sampler2D t_rock : source_color, filter_linear_mipmap, repeat_enable; // rock.jpgstruct Inputs { vec2 uv; vec3 position; vec3 normal; vec3 view; float time; vec2 fragCoord; vec2 resolution;};// Godot's textures start at the top-left and the effect's uv at the bottom-left: each sample flips v backvec2 u_flipUv(vec2 uv) { return vec2(uv.x, 1.0 - uv.y);}vec4 effect(Inputs i) { // How much the surface faces each world axis; the power narrows the seams where two projections cross-fade vec3 facing = pow(abs(i.normal), vec3(sharpness)); // Dividing by the sum keeps the three weights adding up to 1, so the blend neither brightens nor darkens vec3 weights = facing / (facing.x + facing.y + facing.z); vec3 p = i.position * scale; // Each projection drops the axis it looks along; y stays the texture's vertical on both sides, so the rock's // layers stay level there. On a 45° edge two coordinates are equal, so without the half-tile shifts two // projections would show the same texels mirrored there, a symmetric seam. vec3 sideX = texture(t_rock, u_flipUv(p.zy + vec2(0.5, 0.0))).rgb; vec3 top = texture(t_rock, u_flipUv(p.xz + vec2(0.0, 0.5))).rgb; vec3 sideZ = texture(t_rock, u_flipUv(p.xy)).rgb; vec3 rock = sideX * weights.x + top * weights.y + sideZ * weights.z; return vec4(rock, 1.0);}void fragment() { Inputs i; // Godot's UV starts at the top-left; the effect expects GLSL's bottom-left i.uv = vec2(UV.x, 1.0 - UV.y); // VERTEX, NORMAL and VIEW are in view space here; the effect works in world space i.position = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz; i.normal = normalize((INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz); i.view = normalize((INV_VIEW_MATRIX * vec4(VIEW, 0.0)).xyz); i.time = TIME; // FRAGCOORD's origin is the top-left too i.resolution = VIEWPORT_SIZE; i.fragCoord = vec2(FRAGCOORD.x, i.resolution.y - FRAGCOORD.y); vec4 frag_out = effect(i); ALBEDO = frag_out.rgb;}Ainda não há código de Unreal Engine 5 para este card.
Ainda não há código de Unity para este card.
Ainda não há código de Blender para este card.
Leitura complementar
- GPU Gems 3, chapter 1: Generating Complex Procedural Terrains Using the GPU (abre em uma nova aba)Livro
- Biplanar mapping (abre em uma nova aba)Artigo
- Standard Material 3D and ORM Material 3D (abre em uma nova aba)Documentação
- VisualShaderNodeTextureParameterTriplanar (abre em uma nova aba)Referência
- Image Texture Node (abre em uma nova aba)Documentação
- texture (abre em uma nova aba)Referência
- abs (abre em uma nova aba)Referência
- pow (abre em uma nova aba)Referência