Introducción.
En la anterior entrada del blog, habíamos hecho solo un modelo de iluminación, el clásico modelo Lambert pero con la posibilidad de seleccionar otras 2 alternativas. Ahora vamos a agregar el modelo #BlinnPhong conocido comúnmente como iluminación especular.
Este modelo generalmente se usa para materiales duros y macizos como plástico rígido pulido, porcelana, algunos metales, aquellos materiales que generalmente tiene un punto de luz muy intenso.
Para poder completar el Blog tutorial, debes haber leído el anterior, dejaré entradas relacionadas.
Preparación.
Si ya has venido del Blog anterior, ya tienes la escena preparada, de lo contrario por favor lee el blog anterior y completa el tutorial.
Lee el blog anterior, para configurar la escena
Abre el archivo CustomLighting.cginc.
Haz una copia del shader del Blog anterior.
CustomLighting.cginc
Vamos a hacer una copia del modelo de iluminación #Lambert y a hacer algunos cambios.
inline fixed4 LightingCustomLambert(SurfaceOutput s, fixed3 lightDir, fixed atten)
{
//diffuse
fixed NdotL;
#if LAMBERTTYPE == _LAMBERTTYPE_SIMPLE
NdotL = DotClamped(s.Normal, lightDir);
#elif LAMBERTTYPE == _LAMBERTTYPE_WRAP
NdotL = DotClamped(s.Normal, lightDir)*0.5h + 0.5h;
#else
NdotL = pow(DotClamped(s.Normal, lightDir)*0.5h + 0.5h, 2);
#endif
return finalColor(s.Albedo, _LightColor0, NdotL, atten, s.Alpha);
}
Agrega un parámetro más: fixed3 viewDir y a cambiarle el nombre al modelo.
inline fixed4 LightingCustomBlinnPhong(
SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, fixed atten)
Para calcular la luz especular hay que obtener el vector medio entre el vector de luz y el vector de visión de la cámara. Ya tenemos el vector de luz gracias al modelo Lambert que es NdotL, vamos a calcular la visión NdotV y teniendo ambas calculamos el vector medio LdotV.
NdotL = DotClamped(s.Normal, lightDir); // Lambert
NdotV = DotClamped(s.Normal, viewDir);
LdotV = DotClamped(NdotL, NdotV); //HalfDir
Aquí LdotV es equivalente al vector medio HalfDir, ya lo que queda es hacer el cálculo especular, que es cerrar la luz a un punto bien intenso y concentrado.
fixed spec = pow(LdotV, 48.0);//Simple sin posibilidad de control
fixed spec = pow(LdotV, s.Gloss * 128) * s.Specular; //Control total
Usamos la función de potencia para elevar el resultado de LdotV y otro valor como exponente como 48 en la versión simple, en la versión personalizable elevamos a 128 * s.Gloss para controlar la concentración del punto y s.Specular para controlar la intensidad, ya solo quedaría multiplicar el valor resultante de spec por algún color cualquiera.
Ahora tenga en cuenta que tanto s.Gloss y s.Specular son valores flotantes que van de 0 a 1, lo que sugiero para controlar tanto el color como estos dos valores de intensidad sería usar el canal Alpha de la textura _MainTex en el shader; crear otra textura que contendrá el color del reflejo especular en el canal rgb y el canal Alpha para controlar la intensidad, esto es para que forme parte de un #textureAtlas y que no haya necesidad de crear cientos y cientos de materiales con configuraciones distintas, usando unas cuantas texturas que guarde la información del color especular y la intensidad, ya seria cuestión de manejar unas cartas de color para el cobre, porcelana, acero, madera, etc. así es como quedaría el modelo de iluminación completo.
inline fixed4 LightingCustomBlinnPhong(SurfaceOutput s, fixed3 lightDir,
fixed3 viewDir, fixed atten) {
//diffuse
fixed NdotL;
#if LAMBERTTYPE == _LAMBERTTYPE_SIMPLE
NdotL = DotClamped(s.Normal, lightDir);
#elif LAMBERTTYPE == _LAMBERTTYPE_WRAP
NdotL = DotClamped(s.Normal, lightDir)*0.5 + 0.5;
#else
NdotL = pow(DotClamped(s.Normal, lightDir)*0.5 + 0.5,2);
#endif
//Specular calculations
fixed NdotV = DotClamped(s.Normal, viewDir);
fixed LdotV = DotClamped(NdotL, NdotV);
fixed spec = pow(LdotV, 48);
fixed model = NdotL + spec;
return finalColor(s.Albedo, _LightColor0, model, atten, s.Alpha);
Como podrás observar en el retorno el valor especular solo se le suma al Lambert, así como resultado tendremos un modelo que puede escoger el lambert a propio gusto.
Vamos a abrir el archivo del Shader duplicado del Lambert anterior y vamos a modificar algunas líneas y guardar el resultado, verá un resultado similar al que se ve en la esfera.
Shader "Surface/Lighting/Darkcom/Custom Lambert" {
//Cambiar el nombre del Shader por
Shader "Surface/Lighting/Darkcom/Custom BlinnPhong" {
........
// cambiar el nombre del surf CustomLambert
#pragma surface surf CustomLambert
// Cambiarlo por CustomBlinnPhong
#pragma surface surf CustomBlinnPhong
Optimizando.
Vamos a mejorar este modelo y a optimizarlo un poco mas, primero vamos a modificar unas cuantas líneas.
inline fixed4 LightingCustomBlinnPhong(SurfaceOutput s,
fixed3 lightDir, fixed3 halfDir, fixed atten) {
.......
//Specular calculations
fixed NdotH = DotClamped(s.Normal, halfDir);
fixed spec = pow(NdotH, 48);
.......
}
En el Blog pasado la función Lighting tenía 3 parámetros, SurfaceOutput, lightDir y atten, en el modelo especular se puede agregar a la función Lighting fixed3 viewDir, que es útil si queremos hacer otros modelos más complejos, pero en nuestro caso que vamos directo al cálculo especular podemos cambiar viewDir por fixed3 halfDir, así podremos deshacernos del cálculo de NdotV y LdotV y reemplazarlo por NdotH.
Ahora cambie fixed3 viewDir, por fixed3 halfDir en los parámetros. Elimine las variables NdotV y LdotV y reemplazar por fixed NdotH y proceda a guardar para ver el resultado.
Ohhh Ohhh 😯 se quedó iluminado el centro que es la dirección de la vista, ¿qué pasó aquí?
No hay de qué preocuparse, solo vamos a agregar la etiqueta halfasview después de CustomBlinnPhong.
#pragma surface surf CustomBlinnPhong halfasview
Cuando nosotros cambiamos el nombre del parámetro "viewDir" por "halfDir", realmente no hicimos nada, esto solo lo hicimos por convención, el producto punto entre la normal y el "halfDir" es como si lo hubiéramos hecho entre la normal y el "viewDir".
Cuando agregamos la etiqueta "halfasview", unity reemplaza el parámetro viewDir y lo trata como halfDir, unity pasa este parámetro ya calculado por vértice en lugar de hacerlo por pixel, lo que garantiza mejor rendimiento.
Mejorando.
Vamos a hacer una opción que nos permita elegir el tipo de luz especular que queremos, aquí es necesario que repases los últimos 2 Blogs para esto. Lo que haremos es agregar un #Drawer al shader para poder elegir. Te dejo enlace al blog del uso de propiedades.
Volvamos al archivo CustomLighting.cginc y vamos a agregar las siguientes lineas donde estaba el cálculo especular.
inline fixed4 LightingCustomBlinnPhong(SurfaceOutput s, fixed3 lightDir,
fixed3 halfDir, fixed atten) {
//diffuse
fixed NdotL;
#if LAMBERTTYPE == _LAMBERTTYPE_SIMPLE
NdotL = DotClamped(s.Normal, lightDir);
#elif LAMBERTTYPE == _LAMBERTTYPE_WRAP
NdotL = DotClamped(s.Normal, lightDir)*0.5 + 0.5;
#else
NdotL = pow(DotClamped(s.Normal, lightDir)*0.5 + 0.5,2);
#endif
//Specular calculations
fixed NdotH = DotClamped(s.Normal, halfDir);
#if CUSTOMIZABLE
fixed spec = pow(NdotH, s.Gloss * 128) * s.Specular;
fixed3 specColor = _SpecularColor.rgb * spec;
fixed3 model = (NdotL + specColor);
#else
fixed spec = pow(NdotH, 48);
fixed model = NdotL + spec;
#endif
return finalColor(s.Albedo, _LightColor0, model, atten, s.Alpha);
}
Así queda el modelo completo en la iluminación, ahora vamos a modificar el shader.
Shader "Surface/Lighting/Darkcom/Custom BlinnPhong" {
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
[KeywordEnum(Simple,Wrap,Half)]_LambertType("Lambert type", Float) = 1
[Toggle(CUSTOMIZABLE)] _SpecularType("SpecularType", Float) = 1
_SpecularColor("Specular Color", Color) = (1,1,1,1)
_Spec("Specular Power", Range(0.1,60)) = 3
_Gloss("Gloss", Range(0.1,60)) = 3
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma shader_feature CUSTOMIZABLE
#pragma shader_feature _LAMBERTTYPE_SIMPLE _LAMBERTTYPE_WRAP _LAMBERTTYPE_HALF
#include "CustomLighting.cginc"
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf CustomBlinnPhong //halfasview
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
#if CUSTOMIZABLE
fixed _Gloss, _Spec;
#endif
struct Input
{
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o)
{
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
#if CUSTOMIZABLE
o.Gloss = _Gloss;
o.Specular = _Spec;
#endif
}
ENDCG
}
FallBack "Diffuse"
}
Conclusión.
Ahora tenemos el casi el mismo shader anterior con las prestaciones que nos ofrecía, pero hemos agregado unos pocos cambios y ha quedado mejor.
Hemos descubierto el poder de la etiqueta half as view, permitiendo hacer más livianos nuestros modelos de iluminación.
Cada vez mejoramos en el uso de drawers y preprocesadores para hacer shaders más prácticos como complejos.
Cada vez vemos mejora en el uso de la creación de archivos CGINCLUDE para poner dentro fragmentos de código que redundamos continuamente, cosa que nos permite depurarlo con el tiempo e ir mejorando.
No siendo mas eso es todo por esta ocasión. Hasta otra.
Comentarios