Pular para o conteúdo
Luz · Difusa (Lambert)Iniciante

Um único sol

Uma luz distante e um preenchimento constante, sombreados por quanto cada ponto encara o sol de frente.

NormalSuperfície
Abrir no Playground
  1. 01ObjetivoLuz
  2. 02TécnicaDifusa (Lambert)
  3. 03ExecuçãoUm único sol
Malha de teste

Parâmetros

#d99e6b
#fff2d9
#4d546b
-20
40

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.

  1. dotDotProductdotdotdot
    Normal

    01 De frente para o sol

  2. maxMaxmaxmaxmax

    02 Sem luz negativa

  3. +MultiplyAdd+++

    03 Sol mais ambiente

  4. *Multiply***

    04 Iluminar a cor da superfície

Como funciona

Um sol está tão longe que a luz dele chega em raios paralelos: todo ponto da malha o vê na mesma direção. Quanto dessa luz um ponto recebe depende só do ângulo entre a superfície e os raios. De frente para o sol, o ponto junta um feixe inteiro; inclinado, o mesmo feixe se espalha por mais área, e cada pedaço de superfície recebe menos. Esse espalhamento segue o cosseno do ângulo, que é a lei dos cossenos de Lambert, e uma superfície fosca (argila, giz, madeira sem verniz) devolve o que recebe igualmente em todas as direções, então a posição da câmera não importa.

Passo a passo

  1. De frente para o sol. sunDirection() transforma os dois ângulos num vetor unitário que aponta para o sol, e o produto escalar com a normal da superfície é o cosseno do ângulo entre eles: 1 onde a superfície encara o sol, 0 onde os raios passam rente, negativo do lado oposto.
  2. Sem luz negativa. max(lambert, 0.0) zera o lado oposto. A prévia do primeiro passo já parecia preta ali, porque a tela não mostra valores abaixo de zero; o corte importa no passo seguinte, onde um valor negativo subtrairia da luz ambiente e abriria um buraco mais escuro do que o lado da sombra deveria ser.
  3. Sol mais ambiente. A cor do sol, escalada pelo fator difuso, mais uma cor ambiente constante. Sombras de verdade não são pretas: o céu e as superfícies próximas rebatem luz para dentro delas. Um termo ambiente constante é o substituto mais barato; um azulado parece céu.
  4. Iluminar a cor da superfície. A cor própria da superfície (o albedo) multiplica a luz. Uma superfície vermelha reflete a parte vermelha da luz que chega nela, então um sol quente e um ambiente frio tingem de jeitos diferentes o lado iluminado e o lado na sombra.

Tudo aqui é linear, como a iluminação precisa ser: os parâmetros de cor chegam decodificados do sRGB, e o quadro codifica o resultado uma vez, no fim. Multiplicar valores em sRGB escurece a faixa meio iluminada e desloca o terminador.

Gire a prévia: o sombreamento não acompanha a câmera, porque nada nele lê a direção de visão. Essa é a diferença entre luz difusa e luz especular.

Monte no GLSL

A direção do sol é a mesma para todo pixel. No seu próprio renderizador, calcule-a uma vez na CPU e passe-a como um uniform vec3; isso tira 8 das 13 operações.

O botão de copiar entrega um shader de fragmento WebGL2 completo. O seu shader de vértice escreve a normal em espaço de mundo no varying v_normal (e v_position, v_uv, v_view, que o quadro declara mesmo que este card os ignore); o main() gerado a normaliza de novo por pixel. Envie os parâmetros de cor como valores lineares: decodifique antes do sRGB as cores de um seletor, como dizem os comentários do cabeçalho. O main() codifica o resultado em sRGB uma vez, então desenhe no framebuffer padrão do canvas, não num sRGB, senão a imagem é codificada duas vezes e fica pálida.

Para usar só a iluminação num shader seu, copie sunDirection() e as linhas de effect(), e troque i.normal pela sua normal de mundo normalizada. Nada lê a direção de visão nem o tempo, então o resultado é estático e pode ser gravado numa textura ou em cores de vértice.

Monte no Godot

O shader exportado é unshaded e traz o próprio sol, então o DirectionalLight3D da cena não o afeta. Para que as luzes do Godot conduzam a mesma conta, tire o unshaded, defina ALBEDO como a cor da superfície em fragment() e acrescente uma função light() com DIFFUSE_LIGHT += clamp(dot(NORMAL, LIGHT), 0.0, 1.0) * ATTENUATION * LIGHT_COLOR / PI; que é Lambert para cada luz, com sombras via ATTENUATION.

Crie um MeshInstance3D (uma SphereMesh mostra melhor o terminador), dê a ele um novo ShaderMaterial e cole o shader num novo Shader. Os ângulos e as cores do sol aparecem em Shader Parameters no inspetor; o azimute gira em torno do eixo Y do mundo a partir de +Z.

No VisualShader, a cadeia é um nó Input para a normal (leve-a para espaço de mundo com um TransformVecMult por INV_VIEW_MATRIX, ou deixe tudo em espaço de visão e transforme o sol), um Vector3Parameter para a direção do sol, DotProduct, um FloatOp em Max com 0, depois um MultiplyAdd com a cor do sol e a cor ambiente, e um VectorOp Multiply com o albedo na porta Albedo de um 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

single-sun.fragGLSL
#version 300 es// Diffuse (Lambert) → Single sun, 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_albedo; // Surface color: vec3(0.85, 0.62, 0.42), sRGB; upload it decoded to linearuniform vec3 p_sunColor; // Sun color: vec3(1.0, 0.95, 0.85), sRGB; upload it decoded to linearuniform vec3 p_ambient; // Ambient light: vec3(0.3, 0.33, 0.42), sRGB; upload it decoded to linearuniform 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 lambert = dot(i.normal, toSun);	float diffuse = max(lambert, 0.0);	vec3 lighting = p_sunColor * diffuse + p_ambient;	vec3 color = p_albedo * lighting;	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);}
single-sun.gdshaderShader do Godot
// Diffuse (Lambert) → Single sun, 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 albedo : source_color = vec3(0.85, 0.62, 0.42);uniform vec3 sunColor : source_color = vec3(1.0, 0.95, 0.85);uniform vec3 ambient : source_color = vec3(0.3, 0.33, 0.42);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 lambert = dot(i.normal, toSun);	float diffuse = max(lambert, 0.0);	vec3 lighting = sunColor * diffuse + ambient;	vec3 color = albedo * lighting;	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.

Leitura complementar