Plástico
Difusa colorida com um brilho Blinn-Phong branco que desliza pela superfície quando você gira a câmera.
- 01ObjetivoLuz
- 02TécnicaBrilhos especulares
- 03ExecuçãoPlástico
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.
maxMaxmaxmaxmax NormalVisão01 Luz difusa
dotDotProductdotdotdot Entrada02 Normal contra o vetor médio
powPowpowpowpow Entrada03 Concentrar num ponto de brilho
+Add+++ Entrada04 Cor mais brilho branco
Como funciona
Plástico vermelho são duas coisas em camadas: a tinta vermelha, o pigmento espalhado por igual dentro do material, e uma superfície transparente que espelha um pouco da luz sem colori-la. O shader monta as mesmas duas camadas. A camada difusa é a de Lambert, tingida pela cor do plástico; a camada especular é um ponto branco e estreito onde a superfície rebateria o sol para a câmera.
Passo a passo
- Luz difusa. O produto escalarMultiplica dois vetores componente a componente e soma os resultados. Para vetores unitários é o cosseno do ângulo entre eles, de 1 (mesma direção) a -1 (opostos).Fundamento
Produto escalar
Multiplica dois vetores componente a componente e soma os resultados. Para vetores unitários é o cosseno do ângulo entre eles, de 1 (mesma direção) a -1 (opostos).
dot(a, b) = |a| |b| cos θFontesentre a normalUm vetor unitário perpendicular a uma superfície, apontando para onde ela está virada. Iluminação e efeitos Fresnel o comparam com outras direções.- dot (abre em uma nova aba)Referência
- Dot product (abre em uma nova aba)Referência
FundamentoNormal
Um vetor unitário perpendicular a uma superfície, apontando para onde ela está virada. Iluminação e efeitos Fresnel o comparam com outras direções.
Fontese a direção para o sol, cortado em zero, como na técnica Difusa. Não muda quando você gira a câmera. - Normal contra o vetor médio.
normalize(toSun + i.view)é o vetor médioA direção unitária a meio caminho entre a direção para a luz e a direção para a câmera. Onde ela se alinha com a normal, a superfície espelharia a luz direto para a câmera; os brilhos de Blinn-Phong medem esse alinhamento.TécnicaVetor médio
A direção unitária a meio caminho entre a direção para a luz e a direção para a câmera. Onde ela se alinha com a normal, a superfície espelharia a luz direto para a câmera; os brilhos de Blinn-Phong medem esse alinhamento.
H = normalize(L + V), a direção que a normal de um espelho precisaria ter para mandar o sol à câmera. O produto escalar dele com a normal de verdade vale 1 exatamente ali e cai em volta. Gire a prévia neste passo: a região clara segue a câmera, porque a direção de visão agora entra na conta. - Concentrar num ponto de brilho. Elevar esse alinhamento a uma potência alta (64 por padrão) mantém só os valores muito perto de 1, então o brilho largo encolhe para um ponto. Cada vez que o expoente dobra, a largura angular do ponto cai mais ou menos por √2. Multiplicar pelo fator difuso apaga o ponto no terminador e o tira do lado que o sol não alcança, onde o Blinn-Phong ainda acenderia uma lasca em ângulos rasantes.
- Cor mais brilho branco. A cor do plástico multiplica a luz difusa e um pequeno termo ambiente, e o brilho é somado por cima, branco. Somá-lo sem tingir é o que parece plástico: tinja-o com a cor base e a mesma esfera parece metal.
Aqui o polimento e a força do brilho são independentes, o que é prático para a direção de arte mas não é físico: na
realidade um brilho mais estreito também é mais forte, porque a mesma energia refletida se concentra num ponto menor.
Um fator de normalização de (shininess + 8) / 8 mantém a energia mais ou menos constante, se você precisar de
controles que se comportem como uma rugosidade.
Monte no GLSL
O brilho precisa de uma direção para a câmera por pixel. Calcule-a em espaço de mundo como normalize(cameraPosition - worldPosition) no shader de fragmento em vez de interpolar um vetor normalizado vindo dos vértices, o que entorta o brilho em triângulos grandes.
O botão de copiar entrega um shader de fragmento WebGL2 completo. O seu shader de vértice preenche os varyings
v_normal e v_view em espaço de mundo (v_view como cameraPosition - worldPosition, sem normalizar, para interpolar
exato; o main() gerado normaliza os dois por pixel) e declara também v_position e v_uv. Envie o parâmetro de cor
decodificado para linear, como diz o comentário do cabeçalho; o main() codifica o resultado em sRGB uma vez.
sunDirection() dá o mesmo vetor para todo pixel; no seu próprio renderizador passe a direção como um uniform e
economize 8 das 20 operações. Várias luzes são um laço sobre as linhas da difusa e do brilho, somando a contribuição
de cada luz antes de aplicar a cor base.
Monte no Godot
O shader exportado é unshaded e traz o próprio sol. Para iluminar o mesmo Blinn-Phong com as luzes da cena, tire o unshaded, defina ALBEDO em fragment() e numa função light() acrescente DIFFUSE_LIGHT += max(dot(NORMAL, LIGHT), 0.0) * ATTENUATION * LIGHT_COLOR / PI; e SPECULAR_LIGHT += pow(max(dot(NORMAL, normalize(LIGHT + VIEW)), 0.0), shininess) * specular * ATTENUATION * LIGHT_COLOR / PI; (o Godot multiplica DIFFUSE_LIGHT pelo ALBEDO, mas não SPECULAR_LIGHT, então o brilho continua branco). A iluminação nativa do Godot já oferece um visual parecido com Blinn-Phong num StandardMaterial3D com rugosidade baixa.
Crie um MeshInstance3D com uma SphereMesh, dê a ele um novo ShaderMaterial e cole o shader. Aumente Shininess (o polimento) em Shader Parameters e o ponto encolhe; baixe para menos de uns 8 e o brilho se espalha num lustro pelo lado iluminado.
No VisualShader, use nós Input para NORMAL e VIEW (os dois em espaço de visão, então leve a direção do sol para espaço de visão com um TransformVecMult por VIEW_MATRIX, ou leve as duas entradas para espaço de mundo). O fator difuso é um DotProduct num FloatOp Max com 0. O vetor médio é um VectorOp Add do sol com VIEW num VectorFunc Normalize; um DotProduct com NORMAL, um Max com 0 e um FloatOp Pow por um FloatParameter fazem o brilho. Multiplique-o pela força e pelo fator difuso e some-o (Add) à cor base vezes a luz difusa 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// Specular highlights → Plastic, 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 vec3 p_base; // Plastic color: vec3(0.8, 0.12, 0.1), sRGB; upload it decoded to linearuniform float p_shininess; // Shininess: 64.0 (2.0 to 512.0)uniform float p_specular; // Highlight strength: 0.8 (0.0 to 2.0)uniform float p_ambient; // Ambient light: 0.08 (0.0 to 0.5)uniform float p_azimuth; // Sun azimuth (degrees): -20.0 (-180.0 to 180.0)uniform float p_elevation; // Sun elevation (degrees): 40.0 (-30.0 to 90.0)in 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));}// The angles turn the same way as the preview's orbit camera: azimuth around the vertical axis from +Z toward +X,// elevation up from the horizon.vec3 sunDirection(float azimuthDegrees, float elevationDegrees) { float a = radians(azimuthDegrees); float e = radians(elevationDegrees); return vec3(cos(e) * sin(a), sin(e), cos(e) * cos(a));}vec4 effect(Inputs i) { vec3 toSun = sunDirection(p_azimuth, p_elevation); float diffuse = max(dot(i.normal, toSun), 0.0); // Blinn-Phong: the halfway direction lines up with the normal exactly where a mirror would bounce the sun into // the camera, and stays within 90° of it, so the highlight never cuts off the way Phong's reflect() can. vec3 halfway = normalize(toSun + i.view); float alignment = max(dot(i.normal, halfway), 0.0); // Times diffuse: no highlight on the side the sun can't reach, and it fades out at the terminator. float highlight = pow(alignment, p_shininess) * p_specular * diffuse; vec3 color = p_base * (diffuse + p_ambient) + vec3(highlight); return vec4(color, 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);}// Specular highlights → Plastic, 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 vec3 base : source_color = vec3(0.8, 0.12, 0.1);uniform float shininess : hint_range(2.0, 512.0, 1.0) = 64.0;uniform float specular : hint_range(0.0, 2.0, 0.05) = 0.8;uniform float ambient : hint_range(0.0, 0.5, 0.01) = 0.08;uniform float azimuth : hint_range(-180.0, 180.0, 1.0) = -20.0;uniform float elevation : hint_range(-30.0, 90.0, 1.0) = 40.0;struct Inputs { vec2 uv; vec3 position; vec3 normal; vec3 view; float time; vec2 fragCoord; vec2 resolution;};// The angles turn the same way as the preview's orbit camera: azimuth around the vertical axis from +Z toward +X,// elevation up from the horizon.vec3 sunDirection(float azimuthDegrees, float elevationDegrees) { float a = radians(azimuthDegrees); float e = radians(elevationDegrees); return vec3(cos(e) * sin(a), sin(e), cos(e) * cos(a));}vec4 effect(Inputs i) { vec3 toSun = sunDirection(azimuth, elevation); float diffuse = max(dot(i.normal, toSun), 0.0); // Blinn-Phong: the halfway direction lines up with the normal exactly where a mirror would bounce the sun into // the camera, and stays within 90° of it, so the highlight never cuts off the way Phong's reflect() can. vec3 halfway = normalize(toSun + i.view); float alignment = max(dot(i.normal, halfway), 0.0); // Times diffuse: no highlight on the side the sun can't reach, and it fades out at the terminator. float highlight = pow(alignment, shininess) * specular * diffuse; vec3 color = base * (diffuse + ambient) + vec3(highlight); return vec4(color, 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.