Shaders propios
Escribe en GLSL cómo se ve una superficie: brillos, hologramas, disolver, agua que se mueve.
Un shader de superficie (.crshader) decide cómo se ve un material: su color,
su brillo, su relieve, si emite luz o si se mueve. Se escribe en GLSL (el lenguaje de
shaders de Vulkan/OpenGL), pero solo la parte que importa: el motor se encarga de la luz, las sombras, los reflejos, la
niebla y la lluvia, así que tu superficie queda integrada en la escena como cualquier otro material (igual que los
surface shaders de Unity).
Crear y usar un shader
- Panel Proyecto > clic derecho > Crear > Shader (GLSL). Se abre en el editor de scripts con una plantilla que ya funciona.
- Elige un material (
.crmat) y en su Inspector, Shader > tu archivo. O clic derecho sobre el.crshader> Crear material con este shader. - Arrastra el material a un objeto. Cada vez que guardes el shader (
Ctrl+S) se recompila y se ve al momento, también en Play.
Cómo es un .crshader
Assets/Shaders/Pulso.crshader
// Propiedades: se editan en el material y se leen aqui por su nombre.
property color tinte = 1.0, 0.4, 0.1
property range brillo = 2.0 (0, 10)
property float velocidad = 3.0
// Obligatoria: recibe la superficie ya calculada (texturas y valores del
// material) y la cambia.
void surface(inout Surface s) {
float pulso = 0.5 + 0.5 * sin(TIME * velocidad);
s.albedo *= tinte;
s.emission = tinte * brillo * pulso;
}
// Opcional: mover los vertices antes de dibujarlos.
void vertex(inout Vertex v) {
v.position += v.normal * 0.02 * sin(TIME * velocidad);
}
Puedes escribir más funciones tuyas (fuera de surface y vertex) y usar todo GLSL:
vec2, vec3, mix, smoothstep, sin, texture...
Propiedades
Una línea por propiedad, en cualquier parte del archivo. En el material aparecen con el control que les toca.
| Línea | En el código | En el material |
|---|---|---|
property float fuerza = 1.5 | float | Número. |
property range brillo = 2 (0, 10) | float | Deslizador entre 0 y 10. |
property color tinte = 1, 0.5, 0.2 | vec3 | Selector de color. |
property vector viento = 1, 0, 0 | vec3 | Tres números. |
property texture ruido | sampler2D | Hueco para una imagen de Assets (blanca si está vacío). |
Como mucho 8 propiedades de números (float, range, color, vector) y 4 texturas. Los valores se cambian en vivo desde el material sin recompilar nada. Clic derecho sobre una propiedad: Valor por defecto.
La superficie (Surface)
| Campo | Descripción |
|---|---|
s.albedo (vec3) | Color base, como se ve (el mismo espacio que las texturas). |
s.alpha (float) | Por debajo de 0.5 el píxel no se dibuja (recortes, agujeros, disolver). |
s.normal (vec3) | Normal en el mundo, ya con el normal map del material. |
s.metallic, s.roughness | Metálico y rugosidad (0..1): 0 de rugosidad es un espejo. |
s.occlusion | Sombra ambiental (1 = ninguna). |
s.emission (vec3) | Luz propia. 1 equivale a la emisión 1 de un material; valores altos dan bloom. |
s.uv | Coordenadas de textura (solo lectura). |
s.worldPosition | Posición del píxel en el mundo (solo lectura). |
s.vertexNormal | Normal de la malla, sin normal map (solo lectura). |
s.viewDirection | Dirección del píxel hacia la cámara (solo lectura). |
surface() recibe ya calculado lo que pone el material (su color y su textura de color, su normal map,
su metálico, su rugosidad...), así que puedes combinar las dos cosas: texturas en el material y el efecto en el shader.
Mover vértices (Vertex)
| Campo | Descripción |
|---|---|
v.position | Posición en el mundo (ya animada si el modelo tiene esqueleto). |
v.normal | Normal en el mundo. |
v.uv | Coordenadas de textura. |
Lo que tienes a mano
| Nombre | Descripción |
|---|---|
TIME | Segundos desde que empezó (para animar). |
CAMERA_POSITION | Posición de la cámara en el mundo. |
noise(vec2), noise(vec3) | Ruido suave de 0 a 1. |
fbm(vec2) | Ruido en varias capas (nubes, manchas, mármol). |
hash(vec2), hash(vec3) | Número pseudoaleatorio de 0 a 1 por posición. |
fresnel(s, potencia) | 0 de frente y 1 en el contorno (bordes brillantes). |
remap(v, a, b, c, d) | Pasa v del rango a..b al c..d. |
Ejemplo: holograma
Líneas que suben, borde brillante y parpadeo. Queda muy bien con un material oscuro.
Holograma.crshader
property color tinte = 0.2, 0.8, 1.0
property range lineas = 40 (5, 120)
property range velocidad = 2 (0, 10)
void surface(inout Surface s) {
float linea = step(0.5, fract(s.worldPosition.y * lineas * 0.1 - TIME * velocidad));
float borde = fresnel(s, 2.5);
float parpadeo = 0.85 + 0.15 * sin(TIME * 30.0);
s.albedo = vec3(0.02);
s.roughness = 0.3;
s.emission = tinte * (0.3 + linea * 0.7 + borde * 2.0) * parpadeo;
}
Ejemplo: disolver
El objeto se deshace con un borde incandescente. Anima cantidad desde un script para hacerlo desaparecer.
Disolver.crshader
property range cantidad = 0.3 (0, 1)
property color borde = 1.0, 0.45, 0.05
property float escala = 4
property texture ruido // opcional: una textura de ruido; sin ella se usa fbm()
void surface(inout Surface s) {
float n = fbm(s.uv * escala) * texture(ruido, s.uv).r;
if (n < cantidad) s.alpha = 0.0; // hueco
float filo = 1.0 - smoothstep(0.0, 0.06, n - cantidad);
s.emission += borde * filo * 4.0; // borde al rojo
}
Ejemplo: lava
Lava.crshader
property color frio = 0.08, 0.02, 0.01
property color caliente = 1.0, 0.35, 0.02
property range velocidad = 0.15 (0, 1)
void surface(inout Surface s) {
vec2 p = s.worldPosition.xz * 0.35;
float n = fbm(p + vec2(TIME * velocidad, TIME * velocidad * 0.7));
float grietas = smoothstep(0.45, 0.75, n);
s.albedo = mix(frio, caliente, grietas);
s.roughness = mix(0.9, 0.4, grietas);
s.emission = caliente * grietas * grietas * 3.0;
}
Ejemplo: agua que se mueve (vértices)
Para un plano con bastantes vértices (por ejemplo, una malla creada por código). Mueve los vértices y recalcula la normal.
Olas.crshader
property range altura = 0.25 (0, 2)
property range frecuencia = 1.2 (0.1, 5)
property color color = 0.05, 0.25, 0.35
float ola(vec2 p) {
return sin(p.x * frecuencia + TIME * 1.7) * 0.6 + sin((p.x + p.y) * frecuencia * 0.7 + TIME * 1.1) * 0.4;
}
void vertex(inout Vertex v) {
float h = ola(v.position.xz) * altura;
// Pendiente por diferencias: la normal de la ola.
float hx = ola(v.position.xz + vec2(0.1, 0.0)) * altura;
float hz = ola(v.position.xz + vec2(0.0, 0.1)) * altura;
v.position.y += h;
v.normal = normalize(vec3(h - hx, 0.1, h - hz));
}
void surface(inout Surface s) {
s.albedo = color;
s.roughness = 0.05;
s.metallic = 0.0;
}
Ejemplo: vegetación con viento
Mueve más lo que está más alto (la copa) que la base. Úsalo con el material de las hojas.
Viento.crshader
property vector direccion = 1, 0, 0.3
property range fuerza = 0.15 (0, 1)
property float alturaBase = 0
void vertex(inout Vertex v) {
float alto = max(v.position.y - alturaBase, 0.0);
float rafaga = sin(TIME * 2.0 + v.position.x * 0.5) + 0.5 * sin(TIME * 5.3 + v.position.z);
v.position += normalize(direccion) * rafaga * fuerza * alto * 0.3;
}
void surface(inout Surface s) {
}
Límites
- Las sombras y la selección con el ratón usan la forma original: lo que muevas en
vertex()no cambia la sombra. - Materiales opacos (con recortes por
s.alpha). El modo Transparente del material no usa el shader propio. - 8 propiedades de números y 4 texturas por shader.
- El shader se compila al usarse la primera vez (unas décimas de segundo). El juego exportado lleva el compilador (
shaderc_shared.dll) y los compila igual.
vec3(1.0, 0.5, 0.0) es un color o una posición; mix(a, b, t) mezcla;
smoothstep(a, b, x) pasa suave de 0 a 1; fract(x) es la parte decimal (repite);
step(a, x) es 0 o 1. Los decimales llevan punto: 1.0, no 1, cuando se mezclan con float.
¿Algo no queda claro o falta algo? Pregunta en el Discord.