Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Créditos
Authors
Alan Zucconi
Kenneth Lammers
Reviewer
Kenneth Lammers
Commissioning Editor
Priya Singh
Acquisition Editors
Rahul Nair
Erol Staveley
Content Development Editor
Mehvash Fatima
Technical Editors
Pranil Pathare
Danish Shaikh
Copy Editor
Tasneem Fatehi
Project Coordinator
Kinjal Bari
Proofreader
Safis Editing
Indexer
Monica Ajmera Mehta
Graphics
Kirk D’Penha
Disha Haria
Production Coordinator
Nilesh Mohite
Cover Work
Nilesh Mohite
Prefacio
Unity 5.x Shaders and Effects Cookbook es su guía para familiarizarse con los
Creación de shaders y efectos posteriores en Unity 5. Iniciará su viaje en el
Empezando, creando los shaders más básicos y aprendiendo cómo se estructura el código de
shader.
Este conocimiento fundamental le armará con los medios para progresar más a través de
Cada capítulo, aprendiendo técnicas avanzadas como explosiones volumétricas y sombreado
de pieles.
Esta edición del libro está escrita específicamente para Unity 5 y le ayudará a dominar
Renderizado físicamente e iluminación global para acercarse al fotorrealismo como
posible.
Al final de cada capítulo, habrá ganado nuevos conjuntos de habilidades que aumentarán la
Calidad de sus shaders e incluso hacer su proceso de la escritura del sombreador más eficiente.
Estas
Capítulos se han adaptado para que pueda saltar en cada sección y aprender un
Habilidad de principiante a experto. Para aquellos que son nuevos para escribir sombreado en
Unity, puede
Avance en cada capítulo, uno a la vez, para construir sobre su conocimiento. De cualquier
manera, usted
Aprenderá las técnicas que hacen que los juegos modernos se vean como lo hacen.
Una vez que haya completado este libro, tendrá un conjunto de sombreadores que puede
utilizar en
Sus juegos de Unity 3D, así como la comprensión de añadir a ellos, lograr nuevos efectos,
Y atender las necesidades de rendimiento. ¡Entonces empecemos!
El Capítulo 2, Shaders de Superficie y Mapeo de Textura, cubre las más comunes y útiles
Técnicas que puede implementar con Shaders de superficie, incluyendo cómo usar texturas
Y mapas normales para sus modelos.
Capítulo 5, Vertex Functions, le enseña cómo los shaders se pueden utilizar para alterar la
geometría de un objeto; Este capítulo introduce modificadores de vértices y los utiliza para
traer volumetría Explosiones, sombreadores de nieve, y otros efectos a la vida.
El capítulo 6, Fragment Shaders y Grab Passes, explica cómo usar los pases grab para hacer
Materiales que emulan las deformaciones generadas por estos materiales semitransparentes.
Capítulo 7, Ajuste de sombreado móvil, le ayuda a optimizar sus sombreadores para sacar el
máximo provecho De cualquier dispositivo.
Capítulo 8, Efectos de pantalla con Unity Render Textures, muestra cómo crear
Efectos y efectos visuales que de otro modo serían imposibles de lograr.
Capítulo 10, Técnicas avanzadas de sombreado, introduce las técnicas más avanzadas en
Este libro, como sombreado de pieles y renderizado de mapa de calor.
Secciones
En este libro, encontrará varios títulos que aparecen con frecuencia (Preparándose, Cómo
hacerlo, Cómo funciona, Hay más y Vea también).
Para dar instrucciones claras sobre cómo completar una receta, usamos estas secciones de la
siguiente manera:
Preparándose
Esta sección le dice qué esperar en la receta y describe cómo configurar
Software o cualquier configuración preliminar requerida para la receta.
Cómo hacerlo…
Esta sección contiene los pasos necesarios para seguir la receta.
Cómo funciona…
Esta sección generalmente consiste en una explicación detallada de lo ocurrido en el
sección.
Hay más…
Esta sección consiste en información adicional sobre la receta para que el lector tenga más
conocimiento sobre la receta.
Ver también
Esta sección proporciona enlaces útiles a otra información útil para la receta.
Convenciones
En este libro, encontrará una serie de estilos de texto que distinguen entre diferentes tipos de
información. Aquí hay algunos ejemplos de estos estilos y una explicación de su significado.
Codifique palabras en texto, nombres de tablas de bases de datos, nombres de carpetas,
nombres de archivos, extensiones de archivos,
Los nombres de ruta, las URL falsas, la entrada de usuario y las descripciones de Twitter se
muestran de la siguiente manera: "Enter El siguiente código en el bloque de propiedades de su
shader. "
Cuando queremos llamar su atención sobre una parte particular de un bloque de código, las
líneas o elementos relevantes se definen en negrita:
Los nuevos términos y palabras importantes se muestran en negrita. Palabras que usted ve en
la pantalla, Por ejemplo, en menús o cuadros de diálogo, aparecen en el texto como este: "En
la pestaña Proyecto de Su editor Unity, haga clic con el botón derecho en la carpeta Activos y
seleccione Crear | Carpeta."
Nota
Las advertencias o notas importantes aparecen en un cuadro como este.
Consejo
Consejos y trucos aparecen así.
Atención al cliente
Ahora que usted es el dueño orgulloso de un libro de Packt, tenemos un número de cosas a
ayudar Usted para sacar el máximo provecho de su compra.
Errata
Aunque hemos tomado todas las precauciones para garantizar la exactitud de nuestro
contenido, los errores se producen. Si encuentras un error en uno de nuestros libros -tal vez un
error en el texto o el código- estaríamos agradecidos si pudieras informarnos esto. Al hacerlo,
puede salvar a otros lectores de la frustración y ayudarnos a mejorar las versiones posteriores
de este libro. Si encuentra alguna errata, infórmenos visitando
http://www.packtpub.com/submit-errata, seleccionando su libro, haciendo clic en el enlace
Formulario de Envío de Erratas e ingresando los detalles de sus erratas. Una vez verificadas sus
erratas, se aceptará su envío y las erratas se cargarán en nuestro sitio web o se agregarán a
cualquier lista de erratas existentes en la sección Erratas de ese título.
Para ver las erratas presentadas anteriormente, vaya a
https://www.packtpub.com/books/content/support e ingrese el nombre del libro en el campo
de búsqueda. La información requerida aparecerá en la sección Errata.
Piratería
La piratería de material protegido por derechos de autor en Internet es un problema continuo
en todos los medios de comunicación. En Packt, tomamos muy en serio la protección de
nuestros derechos de autor y licencias. Si encuentra alguna copia ilegal de nuestras obras en
cualquier forma en Internet, sírvase proporcionarnos la dirección de la ubicación o el nombre
del sitio web inmediatamente para que podamos buscar un remedio. Póngase en contacto con
nosotros en <copyright@packtpub.com> con un enlace al material pirateado sospechoso.
Agradecemos su ayuda en la protección de nuestros autores y nuestra capacidad para
ofrecerle contenido valioso.
Preguntas
Si tiene algún problema con cualquier aspecto de este libro, puede ponerse en contacto con
nosotros en <Questions@packtpub.com>, y haremos todo lo posible para resolver el
problema.
Introducción
Imaginemos un cubo que ha sido pintado de blanco de manera uniforme. Incluso si el color
utilizado es el mismo en cada cara, todos tendrán diferentes tonos de blanco dependiendo de
la dirección de la que proviene la luz y el ángulo que estamos mirando. Este nivel adicional de
realismo se logra en gráficos 3D por shaders, programas especiales que se utilizan
principalmente para simular cómo funciona la luz. Un cubo de madera y uno de metal pueden
compartir el mismo modelo 3D, pero lo que los hace parecer diferentes es el sombreado que
utilizan. Receta tras receta, este primer capítulo le presentará la codificación de shader en
Unity. Si tiene poca o ninguna experiencia previa con shaders, este capítulo es lo que necesita
para entender qué son los shaders, cómo funcionan y cómo personalizarlos.
Al final de este capítulo, habrás aprendido a construir shaders básicos que realizan
operaciones básicas. Armado con este conocimiento, usted será capaz de crear casi cualquier
superficie Shader.
Creación de un Shader estándar básico
Cada desarrollador de juegos de Unity debe estar familiarizado con el concepto de
componentes. Todos los objetos que forman parte de un juego contienen una serie de
componentes que afectan su apariencia y comportamiento. Mientras que los scripts
determinan cómo deben comportarse los objetos, los renderers deciden cómo deben aparecer
en la pantalla. Unity viene con varios procesadores, dependiendo del tipo de objeto que
estamos tratando de visualizar; Cada modelo 3D normalmente tiene MeshRenderer. Un objeto
debe tener sólo un renderizador, pero el propio procesador puede contener varios materiales.
Cada material es una envoltura para un solo shader, el anillo final en la cadena alimenticia de
3D gráficos. Las relaciones entre estos componentes se pueden ver en los siguientes diagrama:
Preparándose
Para comenzar con esta prescripción, necesitará tener Unity 5 en funcionamiento y debe tener
Creado un nuevo proyecto. También habrá un proyecto de Unity incluido con este cookbook,
por lo que Usted puede utilizar ese también y apenas agregue sus propios shaders de encargo
a él como usted step A través de cada receta. ¡Con esto completado, ya está listo para entrar
en el maravilloso Mundo del shaders en tiempo real!
Cómo hacerlo…
Antes de entrar en nuestro primer shader, vamos a crear una pequeña escena para trabajar
con nosotros. Esto puede Se puede hacer navegando a GameObject | Crear vacío en el editor
Unity. De aquí, Usted puede crear un plano para actuar como un terreno, un par de esferas a
las que aplicaremos nuestro Shader, y una luz direccional para dar a la escena alguna luz. Con
nuestra escena generada, Puede pasar a los pasos de escritura de shaders:
1. En la pestaña Proyecto de su editor Unity, haga clic con el botón derecho en la carpeta
Crear Carpeta.
Nota
Si está utilizando el proyecto Unity que viene con el libro de cocina, puede saltar al
paso 4.
2. Cambie el nombre de la carpeta que creó a Shaders haciendo clic con el botón derecho en
Cambie el nombre de la lista desplegable o seleccione la carpeta y pulse F2 en la teclado.
3. Cree otra carpeta y cambie el nombre a Materiales.
4. Haga clic con el botón derecho del ratón en la carpeta Shaders y seleccione Crear | Shader.
Luego haga clic con el botón derecho en el Materiales y seleccione Crear | Material.
5. Cambie el nombre del sombreador y el material a StandardDiffuse.
6. Inicie el shader StandardDiffuse en MonoDevelop (el editor de secuencias de comandos
predeterminado para Unity) haciendo doble clic en él. Esto iniciará automáticamente el
editor para usted y Mostrar el código de sombreado.
Nota
Verá que Unity ya ha poblado a nuestro shader con un código básico. Esta, Por defecto,
obtendrá un shader difuso básico que acepta una textura. Estaremos Modificando este
código base para que pueda aprender a comenzar rápidamente a desarrollar su Propios
shaders de encargo.
7. Ahora vamos a dar a nuestro sombreador una carpeta personalizada de la que está
seleccionada. La primera línea De código en el shader es la descripción personalizada que
tenemos que dar al shader para que Unity puede hacer que esté disponible en la lista
desplegable de sombreado al asignar a los materiales.
Hemos cambiado el nombre de nuestro camino a Shader "CookbookShaders /
StandardDiffuse", pero Puedes nombrarlo a lo que quieras y renombrarlo en cualquier
momento. Así que no te preocupes Sobre cualquier dependencia en este punto. Guardar el
sombreador en MonoDevelop y volver a El editor Unity. Unity compilará automáticamente
el sombreador cuando reconozca que El archivo se ha actualizado. Esto es lo que su
sombreador debe verse en este punto:
Shader "CookbookShaders/StandardDiffuse" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on
all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
8. Técnicamente hablando, este es un Surface Shader basado en física Que la unity 5 ha
adoptado como su nuevo estándar. Como sugiere su nombre, este Tipo de sombreador
logra realismo simulando cómo la luz se comporta físicamente cuando Golpear objetos. Si
está utilizando una versión anterior de Unity (como Unity 4), su Código se verá muy
diferente. Antes de la introducción de shaders físicamente basados, Unity 4 utilizó técnicas
menos sofisticadas. Todos estos tipos diferentes de shader serán Explorado en los
próximos capítulos de este libro.
9. Después de crear su shader, necesitamos conectarlo a un material. Seleccione el material
Llamada StandardDiffuse que creamos en el paso 4 y mira la pestaña Inspector. En la lista
desplegable Shader, seleccione CookbookShaders | StandardDiffuse. (Su ruta de
sombreado puede ser diferente si decide utilizar un nombre de ruta de acceso diferente).
Asignará su sombreador a su material y lo preparará para asignar a un objeto.
Nota
Para asignar un material a un objeto, simplemente puede hacer clic y arrastrar su material
desde La pestaña Proyecto al objeto de la escena. También puede arrastrar un material
Inspector de un objeto en el editor de Unity para asignar un material. La captura de
pantalla de un ejemplo es la siguiente:
No hay mucho que ver en este punto, pero nuestro entorno de desarrollo de sombreado está
configurado y ahora podemos comenzar a modificar el sombreador para satisfacer nuestras
necesidades.
Cómo funciona…
Unity ha hecho la tarea de conseguir que su entorno de sombreado funcione, lo cual es muy
fácil para usted. Es simplemente una cuestión de unos pocos clics y usted es bueno para ir. Hay
muchos elementos que trabajan en el fondo con respecto al Shader superficial sí mismo. Unity
ha tomado el lenguaje de shader Cg y lo ha hecho más eficiente para escribir al hacer un
montón de la pesada Cg código de elevación para usted. El lenguaje Surface Shader es una
forma más basada en componentes de escribir sombreadores. Tareas como el procesamiento
de sus propias coordenadas de textura y matrices de transformación ya se han hecho para
usted, por lo que no tiene que empezar de cero más. En el pasado, tendríamos que empezar
un nuevo shader y reescribir una gran cantidad de código una y otra vez. A medida que gane
más experiencia con Surface Shaders, naturalmente querrá explorar más funciones
subyacentes del lenguaje Cg y cómo Unity está procesando todas las tareas de la unidad de
procesamiento gráfico (GPU) de bajo nivel.
Nota
Todos los archivos de un proyecto de Unity se hacen referencia independientemente de la
carpeta en la que se encuentran. Podemos mover shaders y materiales desde dentro del editor
sin el riesgo de breakingany conexión. Los archivos, sin embargo, nunca deben moverse desde
fuera del editor, ya que Unity no podrá actualizar sus referencias.
¡Así que, simplemente cambiando el nombre de la ruta de acceso del shader a un nombre de
nuestra elección, tenemos nuestro shader difuso básico trabajando en el entorno de Unity, con
luces y sombras y todo eso con sólo cambiar una línea de código!
Ver también
El código fuente de los shaders incorporados suele estar oculto en Unity 5. No se puede abrir
Esto del editor como lo haces con tus propios shaders.
Para obtener más información sobre dónde encontrar una gran parte de las funciones
integradas de Cg para Unity, vaya al directorio de instalación de Unity y vaya a Unity45 \
Editor \ Data \ CGIncludes. En esta carpeta, puede encontrar el código fuente de la Shaders
enviados con Unity. Con el tiempo, han cambiado mucho; UNITY DESCARGAR
ARCHIVE (https://unity3d.com/get-unity/download/archive) es el lugar a donde ir si
Necesidad de acceder a los códigos fuente de un shader utilizado en una versión diferente de
Unity. Después Seleccionando la versión correcta, seleccione Construido en shaders en la lista
desplegable, como se muestra en La siguiente imagen. Hay tres archivos que son de la nota en
este punto-UnityCG.cginc, Lighting.cginc y UnityShaderVariables.cginc. Nuestro shader actual
está haciendo uso de Todos estos archivos en este momento:
Cómo hacerlo…
Hay dos opciones principales si desea migrar los shaders integrados: actualizar su
Proyecto automáticamente o cambiar a Shaders estándar en su lugar.
Actualización automática
Esta opción es la más fácil. Unity 5 puede importar un proyecto realizado con una versión
anterior Y actualizarlo. Usted debe notar que una vez que la conversión se hace, usted no será
capaz de Utilice Unity 4; Incluso si ninguno de sus activos puede haber cambiado
directamente, los metadatos de Unity han Han sido convertidos. Para continuar con esto, abra
Unity 5 y haga clic en OPEN OTHER para seleccionar La carpeta de su antiguo proyecto. Se le
preguntará si desea convertirlo; haga clic en Actualizar para continuar. Unity reimportará
todos sus activos y recompilará todos sus guiones. El proceso puede durar varias horas si su
proyecto es grande. Una vez realizada la conversión Se hace, los shaders integrados de Unity 4
deberían haber sido reemplazados con su legado equivalente. Usted puede comprobar esto
del inspector de sus materiales que deben tener Cambiado (por ejemplo) de Bumped Diffuse a
Legacy Shader / Bumped Diffuse.
Nota
Incluso si Diffuse, Specular y los otros shaders integrados de Unity 4 están ahora obsoletos,
Unity 5 los mantiene compatibles con versiones anteriores. Se pueden encontrar en el menú
desplegable Menú de un material bajo la carpeta Legacy Shaders.
Uso de shader estándar En lugar de usar el Legacy Shaders, usted podría decidir reemplazarlos
por los nuevos Shaders estándar de Unity 5. Antes de hacer esto, debe tener en cuenta que
como ellos Se basan en un modelo de iluminación diferente, sus materiales muy
probablemente se verán diferentes.
Unity 4 viene con más de ochenta diferentes shaders integrados divididos en seis diferentes
Familias (Normal, Transparente, Recorte Transparente, Auto-Iluminado y Reflexivo). En
Unity 5, todos ellos son reemplazados por el Shader estándar introducido en la receta anterior.
Desafortunadamente, no hay ninguna receta mágica para convertir tus shaders directamente.
Sin embargo, puede Utilice la siguiente tabla como punto de partida para entender cómo
puede usarse el Shader estándar
Configurado para simular Unity 4 Legacy Shaders:
Puede cambiar el sombreado utilizado por su material antiguo mediante el menú desplegable
Shader En Inspector. Todo lo que necesitas hacer es simplemente seleccionar el Shader
estándar adecuado. Si tu El shader utiliza texturas, colores y mapas normales, se utilizarán
automáticamente en el Nuevo Shader estándar. Es posible que tenga que configurar los
parámetros del estándar Shader para obtener lo más cerca posible de su modelo de
iluminación original. La siguiente foto Muestra el omnipresente conejo de Stanford prestado
con un Shader Diffuse Legacy (derecha), Shader estándar convertido (izquierda) y Shader
estándar con suavidad ajustado a cero
(medio):
Cómo funciona…
Escribir shaders es siempre un trade-off entre el realismo y la eficiencia; Shaders realistas
Requieren una computación intensiva, lo que potencialmente introduce un desfase
significativo. Es importante Utilice únicamente aquellos efectos estrictamente necesarios: si un
material no necesita Reflexiones, entonces no hay necesidad de usar un sombreador que los
calcula. Esta ha sido la Razón principal por la que Unity 4 ha sido enviado con tantos shaders
diferentes. El nuevo Shader estándar de Unity 5 potencialmente puede reemplazar todos los
shaders anteriores ya que Incorpora mapeo normal, transparencia y reflexión. Sin embargo, ha
sido hábilmente Optimizado para que sólo se calculen los efectos realmente necesarios. Si su
estándar El material no tiene reflejos, no serán calculados.
A pesar de esto, el Shader estándar está diseñado principalmente para materiales realistas. El
legado Los shaders difusos y especulares, en comparación, no fueron realmente diseñados
para realistas Materiales. Esta es la razón por la que cambiar de Shaders Legacy a Standard
Introduzca cambios ligeros en la forma en que se renderizan sus objetos.
Ver también
El capítulo 3, Entendiendo los Modelos de Iluminación, explora en profundidad cómo
Los shaders especulares funcionan. Incluso si está obsoleto en la Unidad 5, entenderlas
es Esencial si desea diseñar nuevos modelos de iluminación.
Capítulo 4, Rendimiento con base física en Unity 5, le mostrará cómo desbloquear el
Potencial del Shader estándar en Unity 5.
Cómo hacerlo…
Una vez que el shader StandardDiffuse2 esté listo, podemos empezar a cambiar sus
propiedades:
1. En nuestro bloque de propiedades de nuestro shader, elimine la propiedad actual
Siguiente código de nuestro shader actual:
_MainTex ("Albedo (RGB)", 2D) = " white " {}
2. Como hemos eliminado una propiedad esencial, este sombreador no compilará hasta
que el otro Las referencias a _MainTex se eliminan. Vamos a eliminar esta otra línea:
Sampler2D _MainTex;
3. El shader original utilizó _MainTex para colorear el modelo. Cambiemos esto
reemplazando La primera línea de código de la función surf () con esto:
fixed4 c = _Color;
4. Cuando guarde y vuelva a Unity, el shader compilará y verá que Ahora la pestaña
Inspector de nuestro material ya no tiene una muestra de textura. A Completar el
reajuste de este shader, vamos a agregar una propiedad más y ver qué pasa.
Introduzca el código siguiente:
_AmbientColor("Ambient Color", Color) = (1,1,1,1)
5. Hemos añadido otra muestra de color a la pestaña Inspector del material. Ahora
vamos a agregar Uno más para tener una idea de otros tipos de propiedades que
podemos crear. Añade el Siguiente código al bloque de propiedades:
_MySliderValue("This is a Slider", Range(0,10)) = 2.5
6. Ahora hemos creado otro elemento GUI que nos permite interactuar visualmente con
nuestro Shader Esta vez, creamos un control deslizante con el nombre This is a Slider,
como se muestra en la Siguiente captura de pantalla:
Las propiedades le permiten crear una forma visual de ajustar los shaders sin tener
que cambiar Valores en el propio código de shader. La siguiente fórmula le mostrará
cómo estas propiedades pueden En realidad se utiliza para crear un shader más
interesante.
Nota
Mientras que las propiedades pertenecen a shaders, los valores asociados con ellas se
almacenan en Materiales. El mismo shader se puede compartir de forma segura entre
muchos materiales diferentes. Sobre el Por otro lado, cambiar la propiedad de un
material afectará la apariencia de todos los objetos que Lo están utilizando
actualmente.
Cómo funciona…
Cada shader Unity tiene una estructura incorporada que está buscando en su código. El bloque
de propiedades es una de esas funciones que se esperan de Unity. La razón detrás de esto es
darle, el programador del sombreador, medios de crear rápidamente los elementos de la GUI
que atan directamente en su código del sombreador. Estas propiedades que se declaran en el
bloque Propiedades se pueden utilizar en su código de sombreado para cambiar valores,
colores y texturas. La sintaxis para definir una propiedad es la siguiente:
Echemos un vistazo a lo que está pasando bajo el capó aquí. Cuando empiece a escribir una
nueva propiedad, necesitará darle un nombre de variable. El nombre de variable va a ser el
nombre que su código de shader va a utilizar para obtener el valor del elemento GUI. Esto nos
ahorra mucho tiempo porque no tenemos que configurar este sistema nosotros mismos.
Los siguientes elementos de una propiedad son el Inspector GUI Nombre y Tipo de la
propiedad, que se encuentra entre paréntesis. El Inspector GUI Name es el nombre que va a
aparecer en la pestaña Inspector del material cuando el usuario está interactuando y
ajustando el sombreado. El Tipo es el tipo de datos que esta propiedad va a controlar.
Hay muchos tipos que podemos definir para propiedades dentro de los shaders de Unity. la
siguiente tabla describe los tipos de variables que podemos tener en nuestros shaders:
Cómo hacerlo…
Los pasos siguientes muestran cómo usar las propiedades en un Shader de superficie:
1. Para comenzar, vamos a eliminar las siguientes líneas de código, ya que eliminamos la
propiedad llamada _MainTex en la receta de Shader estándar básico de este capítulo:
_MainTex ("Albedo (RGB)", 2D) = "white" {}
sampler2D _MainTex;
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
2. A continuación, agregue las siguientes líneas de código al shader, debajo de la línea
CGPROGRAM:
float4 _AmbientColor;
float _MySliderValue;
3. Con el paso 2 completo, ahora podemos usar los valores de las propiedades de nuestro
shader.
Hagamos esto añadiendo el valor de la propiedad _Color a la _AmbientColor Y dando
el resultado de esto a la línea de código de o.Albedo. Por lo tanto, vamos a añadir el
Siguiente código al shader en la función surf ():
void surf (Input IN, inout SurfaceOutputStandard o) {
fixed4 c = pow((_Color + _AmbientColor), _MySliderValue);
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
4. Por último, su sombreador debe ser similar al siguiente código de sombreado. Si
guarda su Shader en MonoDevelop y vuelva a entrar en Unity, su shader compilará. Si
hubiera Sin errores, ahora tendrá la posibilidad de cambiar los colores ambientales y
emisivos de El material, así como aumentar la saturación del color final utilizando el
valor deslizante. Muy bonito, ¿eh?
Shader "CookbookShaders/Chapter01/StandardDiffuse3" {
// We define Properties in the properties block
Properties {
_Color ("Color", Color) = (1,1,1,1)
_AmbientColor("Ambient Color", Color) = (1,1,1,1)
_MySliderValue("This is a Slider", Range(0,10)) = 2.5
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
struct Input {
float2 uv_MainTex;
};
fixed4 _Color;
float4 _AmbientColor;
float _MySliderValue;
Propina
Descargando el código de ejemplo
Puede descargar los archivos de código de ejemplo de todos los libros Packt que haya
comprado Su cuenta en http://www.packtpub.com. Si ha comprado este libro en otro lugar,
puede Visite http://www.packtpub.com/support y regístrese para que los archivos se envíen
por correo electrónico directamente a su mail.
Nota
La función pow (arg1, arg2) es una función incorporada que realizará la matemática
equivalente Función del poder. Así, el argumento 1 es el valor que queremos elevar a una
potencia y El argumento 2 es el poder al que queremos elevarlo.
Para obtener más información sobre la función pow (), consulte el tutorial de Cg. Es una gran
libertad Recurso que puede utilizar para aprender más sobre sombreado y obtener un glosario
de Funciones disponibles en el lenguaje de sombreado Cg:
Http://http.developer.nvidia.com/CgTutorial/cg_tutorial_appendix_e.html
La siguiente captura de pantalla muestra el resultado obtenido usando nuestras propiedades
para controlar Los colores y la saturación de nuestro material desde la pestaña Inspector del
material:
Cómo funciona…
Cuando declara una nueva propiedad en el bloque Propiedades, está proporcionando un
shader para recuperar el valor tweaked de la pestaña Inspector del material. Este valor se
almacena en la parte de nombre de variable de la propiedad. En este caso, _AmbientColor,
_Color y _MySliderValue son las variables en las que almacenamos los valores tweaked. Para
que pueda utilizar el valor en el bloque SubShader {}, debe crear tres nuevas variables con los
mismos nombres que el nombre de la variable de la propiedad. Esto establece
automáticamente un enlace entre estos dos para que sepan que tienen que trabajar con los
mismos datos. Además, declara el tipo de datos que queremos almacenar en nuestras
variables subshader, lo que resultará útil si analizamos la optimización de los shaders en un
capítulo posterior.
Una vez que haya creado las variables subshader, puede utilizar los valores de la función surf
(). En este caso, queremos agregar las variables _Color y _AmbientColor juntas y llevarla a una
potencia de lo que la variable _MySliderValue sea igual en la pestaña Inspector del material.
La gran mayoría de los shaders comienzan como Shaders estándar y se modifican hasta que
coinciden con el aspecto deseado. Ahora hemos creado la base para cualquier sombreado de
superficie que vaya a crear que requiera un componente difuso.
Nota
Los materiales son activos. Esto significa que cualquier cambio hecho a ellos mientras su juego
es Que se ejecutan en el editor son permanentes. Si ha cambiado el valor de una propiedad
por Error, puede deshacerlo usando Ctrl + Z.
Hay más…
Como cualquier otro lenguaje de programación, Cg no permite errores. Como tal, su shader no
funcionará si tiene un error tipográfico en su código. Cuando esto sucede, los materiales se
representan en magenta sin sombrear:
Cuando un script no compila, Unity evita que su juego se exporte o incluso se ejecute. Por el
contrario, los errores en los shaders no impiden que su juego se ejecute.
Si uno de sus shaders aparece como magenta, es hora de investigar dónde está el problema. Si
selecciona el sombreador incriminado, verá una lista de errores que se muestran en su pestaña
Inspector:
A pesar de mostrar la línea que planteó el error, rara vez significa que esta es la línea que tiene
que ser fijo. El mensaje de error mostrado en la imagen anterior se genera al eliminar la
variable sampler2D _MainTex del bloque SubShader {}. Sin embargo, el error se plantea por la
primera línea que intenta tener acceso a dicha variable.
Encontrar y corregir lo que está mal con el código es un proceso llamado depuración. El más
Los errores comunes que debe buscar son los siguientes:
Un punto y coma faltante. Uno de los errores más comunes, pero afortunadamente
uno de los más fáciles de detectar y arreglar. Los errores se plantean a menudo por la
línea siguiente.
A la inversa, a lo que podría estar acostumbrado en los scripts C #, los valores de coma
flotante en Cg no necesitan el seguido por un f: es 1.0, no 1.0f.
Los mensajes de error generados por shaders pueden ser muy engañosos, especialmente
debido a sus estrictas restricciones sintácticas. Si tiene dudas sobre su significado, es mejor
buscar en Internet. Los foros de Unity están llenos de otros desarrolladores que
probablemente hayan encontrado (y arreglado) su problema antes.
Ver también
Más información sobre cómo dominar los Shaders de Superficie y sus propiedades se puede
encontrar en el Capítulo 2, Shaders de Superficie y Mapeo de Textura. Si tiene curiosidad de
ver lo que los shaders pueden hacer realmente cuando se usan con todo su potencial, eche un
vistazo al Capítulo 10, Técnicas avanzadas de sombreado, para algunas de las técnicas más
avanzadas que se incluyen en este libro.
2. Shaders de Superficie y Mapeo de Textura
Capítulo 2. Shaders de Superficie y Mapeo de Textura
En este capítulo, exploraremos Shaders de Superficie. Comenzaremos con un material mate
muy simple y terminaremos con proyecciones holográficas y mezclas de terrenos avanzados.
También podemos usar texturas para animar, mezclar y manejar cualquier otra propiedad que
deseemos. En este capítulo, aprenderá sobre los siguientes métodos:
Sombreado difuso
Uso de matrices empaquetadas
Adición de una textura a un sombreado
Desplazamiento de las texturas mediante la modificación de los valores UV
Mapeo normal
Creación de un material transparente
Creación de un sombreador holográfico
Embalaje y mezcla de texturas
Crear un círculo alrededor de su terreno
Introducción
Shaders de Surface se han introducido en el capítulo 1, Crear su primer Shader, como el tipo
principal de sombreado utilizado en Unity. Este capítulo mostrará en detalle lo que realmente
son y cómo funcionan. En general, hay dos pasos esenciales en cada Shader de Surface. En
primer lugar, debe especificar ciertas propiedades físicas del material que desea describir,
como su color difuso, suavidad y transparencia. Estas propiedades se inicializan en una función
llamada función de Surface y se almacenan en una estructura llamada salida de Surface. En
segundo lugar, la salida Surface se pasa a un modelo de iluminación. Esta es una función
especial que también tendrá información sobre las luces cercanas en la escena. Ambos
parámetros se utilizan para calcular el color final de cada píxel de su modelo. La función de
iluminación es donde tienen lugar los cálculos reales de un shader, ya que es el código que
determina cómo debe comportarse la luz cuando toca un material.
El siguiente diagrama resume de forma flexible cómo funciona un Shader de superficie. Los
modelos de iluminación personalizada se explorarán en el Capítulo 3, Comprensión de los
modelos de iluminación, mientras que el Capítulo 5, Funciones de vértice, se centrará en los
modificadores de vértices:
Sombreado difuso
Antes de comenzar nuestro viaje en el mapeo de textura, es importante entender cómo
funcionan los materiales difusos. Ciertos objetos pueden tener un color uniforme y una
superficie lisa, pero no lo suficientemente suave como para brillar en la luz reflejada. Estos
materiales mate se representan mejor con un sombreado difuso. Mientras que, en el mundo
real, los materiales difusos puros no existen; Los shaders difusos son relativamente fáciles de
implementar y encontrar una aplicación grande en juegos con baja estética de poliuretano.
Preparándose
Hay varias formas en las que puede crear su propio shader difuso. Una forma rápida es
comenzar con el Shader estándar en Unity 5 y editarlo para eliminar cualquier textura, de
manera similar a lo que se hizo anteriormente en el Capítulo 1, Creación de su primer Shader.
Cómo hacerlo…
Comencemos con nuestro Shader estándar y apliquemos los siguientes cambios:
1. Quite todas las propiedades excepto _Color:
_Color ("Color", Color) = (1,1,1,1)
2. En la sección SubShader {}, quite el _MainTex, _Glossiness y _Metallic
Variables. No debe quitar la referencia a uv_MainTex como Cg no permite
La estructura de entrada está vacía. El valor será simplemente ignorado.
3. Elimine el contenido de la función surf () y cámbielo por lo siguiente:
o.Albedo = _Color.rgb;
4. Su sombreador debe mirar como sigue:
Shader "CookbookShaders/Chapter02/Diffuse" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows
#pragma target 3.0
struct Input {
float2 uv_MainTex;
};
fixed4 _Color;
Dado que este sombreador ha sido reajustado de un Shader estándar, utilizará componentes
físicos Rendering para simular cómo se comporta la luz en sus modelos. Si está tratando de
lograr un No-fotorrealista, puede cambiar la primera directiva #pragma para que use Lambert
En lugar de Estándar. Si lo hace, también debe reemplazar SurfaceOutputStandard con
SurfaceOutput.
Cómo funciona…
La forma en que los shaders le permiten comunicar las propiedades de renderizado de su
material a su modelo de iluminación es a través de una salida de superficie. Es básicamente
una envoltura alrededor de todos los parámetros que necesitan el modelo actual de
iluminación. No debe sorprendernos que los diferentes modelos de iluminación con
estructuras de salida de superficie diferentes. La tabla siguiente muestra las tres estructuras de
salida usadas en Unity 5 y cómo se puede usar:
Tipo de shaders Unity 4 Unity 5
Diffuse (difuso) Cualquier Surface Shader Standard
Que carece de claridad SurfaceOutput SurfaceOutputStandard
Estándar (configuración
Cualquier Surface Shader
Specular (De espejo) especular)
SurfaceOutput
SurfaceOutputStandardSpecular
Cómo hacerlo…
Hay dos tipos de variables en Cg: valores únicos y matrices empaquetadas. Este último puede
ser identificado porque su tipo termina con un número tal como float3 o int4. Como sugieren
sus nombres, estos tipos de variables son similares a las estructuras, lo que significa que cada
una contiene varios valores individuales. Cg los llama arrays llenos, aunque no son
exactamente arreglos en el sentido tradicional.
Los elementos de una matriz empaquetada se pueden acceder como una estructura normal.
Normalmente se les llama x, y, z, y w. Sin embargo, Cg también le proporciona otro alias para
ellos, es decir, r, g, b, y a. A pesar de que no hay diferencia entre el uso de x o r, puede hacer
una gran diferencia para los lectores. La codificación Shader, de hecho, a menudo implica el
cálculo con posiciones y colores. Podrías haber visto esto en las Shaders Estándar:
o.Alpha = _Color.a;
Aquí, era una estructura y era una matriz empaquetada. Es por eso Que Cg prohíbe el uso de
estas dos sintaxis: no puede utilizar _Color.xgz. También hay otra característica importante de
matrices empaquetadas que no tiene equivalente en C #: swizzling. Cg permite direccionar y
reordenar elementos dentro de matrices empaquetadas en una sola línea. Una vez más, esto
aparece en el Shader estándar:
o.Albedo = _Color.rgb;
Albedo es fixed3, lo que significa que contiene tres valores del tipo fijo. Sin embargo, _Color se
define como fixed4. Una asignación directa resultaría en un error de compilador como _Color
es más grande que Albedo. La forma C # de hacer esto sería la siguiente:
o.Albedo.r = _Color.r;
o.Albedo.g = _Color.g;
o.Albedo.b = _Color.b;
Sin embargo, se puede comprimir en Cg:
o.Albedo = _Color.rgb;
Cg también permite reorganizar elementos, por ejemplo, utilizando _Color.bgr para
intercambiar los canales rojo y azul. Por último, cuando se asigna un valor único a una matriz
empaquetada, se copia en todos sus campos:
o.Albedo = 0; // Black =(0,0,0)
o.Albedo = 1; // White =(1,1,1)
Esto se conoce como manchas.
Swizzling también se puede usar en el lado izquierdo de una expresión, permitiendo que sólo
se sobrescriban ciertos componentes de una matriz empaquetada:
o.Albedo.rg = _Color.rg;
En cuyo caso, se denomina enmascaramiento.
Matrices empaquetadas
Donde swizzling realmente muestra su pleno potencial es cuando se aplica a matrices
empaquetadas. Cg permite tipos como float4x4, que representa una matriz de flotantes con
cuatro filas y cuatro columnas. Puede acceder a un solo elemento de la matriz usando la
float4x4 matrix;
// ...
float first = matrix._m00;
float last = matrix._m33;
La notación _mRC también se puede encadenar:
float4 diagonal = matrix._m00_m11_m22_m33;
Se puede seleccionar una fila entera utilizando corchetes:
float4 firstRow = matrix[0];
// Equivalent to
float4 firstRow = matrix._m00_m01_m02_m03;
Ver también
Las matrices empaquetadas son una de las características más agradables de Cg. Puedes
descubrir más sobre ellos aquí:
http://http.developer.nvidia.com/CgTutorial/cg_tutorial_chapter02.html
Cómo hacerlo…
Agregar una textura a su modelo usando el Shader estándar es increíblemente simple, como
sigue:
1. Cree un nuevo Shader estándar denominado TexturedShader.
2. Cree un nuevo material llamado TexturedMaterial.
3. Asigne el sombreado al material arrastrándolo.
4. Después de seleccionar el material, arrastre su textura al rectángulo vacío llamado
Albedo (RGB). Si ha seguido correctamente todos estos pasos, su pestaña de Inspector
de materiales
Debe verse así:
El Shader estándar sabe cómo asignar una imagen 2D a un modelo 3D utilizando sus datos UV.
Cómo funciona…
Cuando el shader estándar se utiliza del inspector de un material, el proceso detrás del
trazamiento de la textura es completamente transparente a los reveladores. Si queremos
entender cómo funciona, es necesario echar un vistazo más de cerca de TexturedShader.
Desde la sección Propiedades, podemos ver que la textura de Albedo (RGB) es en realidad
referida en el código como _MainTex:
_MainTex ("Albedo (RGB)", 2D) = "white" {}
En la sección CGPROGRAM, esta textura se define como sampler2D, el tipo estándar para
texturas 2D:
sampler2D _MainTex;
La siguiente línea muestra una estructura llamada Entrada. Este es el parámetro de entrada
para la función de superficie y contiene una matriz empaquetada llamada uv_MainTex:
struct Input {
float2 uv_MainTex;
};
Cada vez que se llama a la función de superficie surf (), la estructura de entrada contendrá el
UV de _MainTex para el punto específico del modelo 3D que necesita ser procesado. El Shader
estándar reconoce que el nombre uv_MainTex se refiere a _MainTex y lo inicializa
automáticamente. Si está interesado en comprender cómo se mapea realmente la radiación
UV desde un espacio 3D a una textura 2D, puede consultar el Capítulo 3, Descripción de los
modelos de iluminación. Finalmente, los datos UV se usan para muestrear la textura en la
primera línea de la función superficial:
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
Esto se hace usando la función tex2D () de Cg; Toma una textura y UV y devuelve el color del
píxel en esa posición.
Nota
Las coordenadas U y V van de 0 a 1, donde (0,0) y (1,1) corresponden a dos esquinas opuestas.
Diferentes implementaciones asocian UV con diferentes esquinas; Si su textura aparece
invertida, trate de invertir el componente V.
Hay más…
Cuando importa una textura a Unity, está configurando algunas de las propiedades que
utilizará sampler2D. El más importante es el modo de filtro, que determina cómo se interpolan
los colores cuando se muestrea la textura. Es muy improbable que los datos UV apunten
exactamente al centro de un píxel; En todos los demás casos, es posible que desee interpolar
entre los píxeles más cercanos para obtener un color más uniforme. A continuación, se
muestra la captura de pantalla de la pestaña Inspector de un ejemplo de textura:
Para la mayoría de las aplicaciones, Bilinear proporciona una manera barata pero eficaz de
alisar la textura. Si está creando un juego 2D, sin embargo, Bilinear podría producir azulejos
borrosos. En este caso, puede utilizar Point para eliminar cualquier interpolación del muestreo
de textura. Cuando se ve una textura desde un ángulo pronunciado, es probable que el
muestreo de textura produzca artefactos visualmente desagradables. Puede reducirlos
estableciendo Aniso Level a un valor más alto. Esto es particularmente útil para texturas de
suelo y techo, donde los fallos pueden romper la ilusión de continuidad.
Ver también
Si desea saber más sobre el funcionamiento interno de cómo las texturas se asignan a una
superficie 3D, puede leer la información disponible en
http://http.developer.nvidia.com/CgTutorial/cg_tutorial_chapter03.html.
Para obtener una lista completa de las opciones disponibles al importar una textura 2D, puede
consultar el siguiente sitio web:
Http://docs.unity3d.com/Manual/class-TextureImporter.html
Preparándose
Para comenzar esta fórmula, necesitará crear un nuevo archivo y material de shader. Esto nos
pondrá con un bonito shader limpio que podemos usar para estudiar el efecto de
desplazamiento por sí mismo.
Cómo hacerlo…
Para empezar, lanzaremos nuestro nuevo archivo de sombreado que acabamos de crear e
ingresaremos el código mencionado en los siguientes pasos:
1. El shader necesitará dos nuevas propiedades que nos permitan controlar la velocidad
de desplazamiento de la textura. Por lo tanto, vamos a agregar una propiedad de
velocidad para la dirección X y una velocidad Propiedad para la dirección Y, como se
muestra en el siguiente código:
Properties {
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_ScrollXSpeed ("X Scroll Speed", Range(0,10)) = 2.0
_ScrollYSpeed ("Y Scroll Speed", Range(0,10)) = 2.0}
2. Modifique las propiedades de Cg en la sección CGPROGRAM y cree nuevas variables
para que podamos acceder a los valores de nuestras propiedades:
fixed4 _MainTint;
fixed _ScrollXSpeed;
fixed _ScrollYSpeed;
sampler2D _MainTex;
3. Modifique la función de superficie para cambiar los UVs dados a la función tex2D (). A
continuación, utilice la variable _Time incorporada para animar los UVs con el tiempo
cuando se pulsa el botón de reproducción en el editor:
void surf (Input IN, inout SurfaceOutput o)
{
// Crear una variable separada para almacenar nuestros UVs
// antes de pasarlos a la función tex2D ()
fixed2 scrolledUV = IN.uv_MainTex;
// Crear variables que almacenan las variables individuales xyy
// componentes para los UV's escalados por el tiempo
fixed xScrollValue = _ScrollXSpeed * _Time;
fixed yScrollValue = _ScrollYSpeed * _Time;
// Aplicar el offset UV final
scrolledUV += fixed2(xScrollValue, yScrollValue);
// Aplicar texturas y tintes
half4 c = tex2D (_MainTex, scrolledUV);
o.Albedo = c.rgb * _MainTint;
o.Alpha = c.a;
}
La siguiente imagen muestra el resultado de la utilización del sistema de desplazamiento UV
para crear un simple movimiento fluvial para sus entornos. Puede notar este efecto en la
escena llamada ScrollingUVs de los archivos de código proporcionados con este libro:
Cómo funciona…
El sistema de desplazamiento comienza con la declaración de un par de propiedades, lo que
permitirá al usuario de este sombreador aumentar o disminuir la velocidad del efecto de
desplazamiento en sí. En su núcleo, son valores flotantes que pasan de la pestaña Inspector del
material a la función de superficie del sombreador. Para obtener más información sobre las
propiedades del sombreador, consulte el Capítulo 1,
Creación de su primer Shader.
Una vez que tengamos estos valores de flotador desde la pestaña Inspector del material,
podemos usarlos para compensar nuestros valores de UV en el sombreado.
Para comenzar este proceso, primero almacenamos los UVs en una variable separada llamada
scrolledUV.
Esta variable tiene que ser float2 / fixed2 porque los valores UV se nos pasan desde la
estructura Input:
struct Input
{
float2 uv_MainTex;
}
Una vez que tengamos acceso a las UV de la malla, podemos compensarlas usando nuestras
variables de velocidad de desplazamiento y la variable _Time incorporada. Esta variable
incorporada devuelve una variable del tipo float4, lo que significa que cada componente de
esta variable contiene diferentes valores de
Tiempo en lo que respecta al tiempo de juego.
Una descripción completa de estos valores de tiempo individuales se describe en el siguiente
enlace: http://docs.unity3d.com/Manual/SL-UnityShaderVariables.html
Esta variable _Time nos dará un valor flotante incrementado basado en el reloj de tiempo de
juego de Unity. Por lo tanto, podemos utilizar este valor para mover nuestros UVs en una
dirección UV y escala que el tiempo con nuestras variables de velocidad de desplazamiento:
// Crear variables que almacenan las variables individuales xyy
// componentes para los uv's escalados por el tiempo
fixed xScrollValue = _ScrollXSpeed * _Time;
fixed yScrollValue = _ScrollYSpeed * _Time;
Con el offset correcto calculado por el tiempo, podemos añadir el nuevo valor de
compensación de nuevo a la posición UV original. Esta es la razón por la que estamos usando
el operador + = en la línea siguiente. Queremos tomar la posición UV original, añadir el nuevo
valor de desplazamiento y pasarlo a la función tex2D () como las nuevas UVs de la textura. Esto
crea el efecto de la textura que se mueve sobre la superficie. Realmente estamos manipulando
los UVs, por lo que estamos fingiendo el efecto de la textura en movimiento:
ScrolledUV + = fixed2 (xScrollValue, yScrollValue);
Half4 c = tex2D (_MainTex, scrolledUV);
Mapeo normal
Cada triángulo de un modelo 3D tiene una dirección de orientación, que es la dirección hacia la
que apunta. A menudo se representa con una flecha colocada en el centro del triángulo y
ortogonal a la superficie. La dirección de enfrentamiento juega un papel importante en la
forma en que la luz se refleja en una superficie. Si dos triángulos adyacentes se enfrentan a
diferentes direcciones, reflejarán luces en ángulos diferentes, por lo que se sombrearán de
manera diferente. Para los objetos curvos, esto es un problema: es obvio que la geometría está
hecha de triángulos planos. Para evitar este problema, la forma en que la luz se refleja en un
triángulo no tiene en cuenta su orientación, sino su dirección normal. Como se indica en la
adición de una textura a una receta de sombreado, los vértices pueden almacenar datos; La
dirección normal es la información más utilizada después de los datos UV. Se trata de un
vector de longitud unitaria que indica la dirección a la que se enfrenta el vértice.
Independientemente de la dirección de enfrentamiento, cada punto dentro de un triángulo
tiene su propia dirección normal que es una interpolación lineal de los almacenados en sus
vértices. Esto nos da la capacidad de falsificar el efecto de la geometría de alta resolución en
un modelo de baja resolución. La siguiente imagen muestra la misma forma geométrica
rendida con diferentes por vértices normales. En la imagen de la izquierda, las normales son
ortogonales a la cara representada por sus vértices; Esto indica que hay una clara separación
entre cada cara. A la derecha, las normales son interpoladas a lo largo de la superficie,
indicando que incluso si la superficie es áspera, la luz debe reflejar como si fuera suave. Es fácil
ver que incluso si los tres objetos de la siguiente imagen comparten la misma geometría,
reflejan la luz de forma diferente. A pesar de estar hecho de triángulos planos, el objeto de la
derecha refleja la luz como si su superficie fuera realmente curvada:
Los objetos suaves con bordes ásperos son una clara indicación de que las normales por vértex
han sido interpoladas. Esto se puede ver si dibujamos la dirección del normal almacenado en
cada vértice, como se muestra en la siguiente imagen. Usted debe notar que cada triángulo
tiene sólo tres normales, pero como triángulos múltiples pueden compartir el mismo vértice,
más de una línea puede salir de ella:
El cálculo de las normales a partir del modelo 3D es una técnica que ha disminuido
rápidamente a favor de una asignación más avanzada de una normalidad. Similar a lo que
sucede con el mapeo de textura, las direcciones normales pueden proporcionarse usando una
textura adicional, normalmente llamada mapa normal o mapa de bump. Los mapas normales
son normalmente imágenes RGB, donde los componentes RGB se utilizan para indicar los
componentes X, Y y Z de la dirección normal. Hay muchas maneras de crear mapas normales
en estos días. Algunas aplicaciones como CrazyBump (http://www.crazybump.com/) y NDO
Painter (http://quixel.se/ndo/) recibirán datos 2D y lo convertirán en datos normales para
usted. Otras aplicaciones como Zbrush 4R7 (http://www.pixologic.com/) y AUTODESK
(http://usa.autodesk.com) tomarán datos 3D esculpidos y crearán mapas normales para usted.
El proceso real de crear mapas normales está definitivamente fuera del alcance de este libro,
pero los enlaces en el texto anterior deberían ayudarlo a empezar.
Unity hace que el proceso de agregar normales a tus shaders sea un proceso muy sencillo en el
reino Surface Shader usando la función UnpackNormals (). Veamos cómo se hace esto.
Preparándose
Cree un nuevo material y un shader y configúrelos en un nuevo objeto en la vista de escena.
Esto nos dará un espacio de trabajo limpio en el que podemos ver sólo la técnica normal de
mapeo. Necesitará un mapa normal para esta fórmula, pero también hay uno en el proyecto
Unity incluido con este libro. Un ejemplo de mapa normal incluido con el contenido de este
libro se muestra aquí:
Cómo hacerlo…
Los siguientes son los pasos para crear un sombreado de mapas normal:
1. Vamos a obtener el bloque de propiedades establecidas con el fin de tener un tono de
color y textura:
Properties
{
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_NormalTex ("Normal Map", 2D) = "bump" {}
}
Nota
Al inicializar la textura como bump, le decimos a Unity que _NormalTex contendrá
Un mapa normal. Si la textura no está establecida, será reemplazada por una textura
gris. El color utilizado (0,5,0,5,0,5,1) indica que no hay ningún golpe en absoluto.
Nota
Shaders puede tener tanto un mapa de textura como un mapa normal. No es raro utilizar los
mismos datos UV para tratar ambos. Sin embargo, es posible proporcionar un conjunto
secundario de UVs en los datos de vértice (UV2) utilizados específicamente para el mapa
normal.
Cómo funciona…
La matemática real para realizar el efecto normal de mapeo está definitivamente más allá del
alcance de este capítulo, pero Unity ya lo ha hecho todo para nosotros. Ha creado las
funciones para que no tengamos que seguir haciéndolo una y otra vez. Esta es otra razón por
la cual los Shaders de Superficie son una manera realmente eficiente de escribir shaders.
Si busca en el archivo UnityCG.cginc que se encuentra en la carpeta Data de su instalación de
Unity
, Encontrará las definiciones de la función UnpackNormal (). Cuando tú
Declare esta función en su Surface Shader, Unity toma el mapa normal proporcionado y lo
procesa para usted y le da el tipo correcto de datos para que pueda usarlo en su función de
iluminación por pixel. ¡Es un gran ahorro de tiempo! Cuando muestre una textura, obtiene
valores RGB de 0 a 1; Sin embargo, las direcciones de un vector normal van de -1 a +1.
UnpackNormal () lleva estos componentes al rango correcto.
Una vez que haya procesado el mapa normal con la función UnpackNormal (), lo devolverá a su
estructura SurfaceOutput para que pueda utilizarse en la función de iluminación. Esto se hace
por o.Normal = normalMap.rgb ;. Veremos cómo se usa realmente la normalidad para calcular
el color final de cada píxel en el Capítulo 3, Comprensión de los modelos de iluminación.
Hay más…
También puede agregar algunos controles a su shader de mapa normal que permite al usuario
ajustar el Intensidad del mapa normal. Esto se hace fácilmente modificando los componentes
xyy de la variable de mapa normal y agregándolos de nuevo juntos. Agregue otra propiedad al
bloque de propiedades y denomínela _NormalMapIntensity:
_NormalMapIntensity("Normal intensity", Range(0,1)) = 1
Multiplique los componentes x e y del mapa normal desempaquetado y vuelva a aplicar este
valor a la variable de mapa normal:
fixed3 n = UnpackNormal(tex2D(_BumpTex, IN.uv_ uv_MainTex)).rgb;
n.x *= _NormalMapIntensity;
n.y *= _NormalMapIntensity;
o.Normal = normalize(n);
Nota
Se supone que los vectores normales tienen longitudes iguales a uno. Multiplicarlos por
_NormalMapIntensity cambia su longitud, haciendo necesaria la normalización.
Ahora, puede permitir que un usuario ajuste la intensidad del mapa normal en la pestaña
Inspector del material. La siguiente imagen muestra el resultado de modificar el mapa normal
con nuestro escalar
valores:
Preparándose
Esta fórmula requiere un nuevo shader, al que llamaremos Transparent, y un nuevo
Material para que pueda unirse a un objeto. Como esto va a ser una ventana de cristal
transparente, un quad o avión es perfecto. También necesitaremos varios otros objetos no
transparentes para probar el efecto. En este ejemplo, usaremos un PNG para la textura del
vidrio. El canal alfa de la imagen se utilizará para determinar la transparencia del vidrio. El
proceso de crear una imagen de este tipo depende del software que esté utilizando. Sin
embargo, éstos son los pasos principales que usted necesitará seguir:
1. Encuentre la imagen del cristal que desea para sus ventanas.
2. Abra con un software de fotoedición, como GIMP o Photoshop.
3. Seleccione las partes de la imagen que desea que sean semitransparentes.
4. Cree una máscara de capa blanca (opacidad completa) en su imagen.
5. Utilice la selección hecha previamente para llenar la máscara de capa con un color más
oscuro.
6. Guarde la imagen e impórtela a Unity.
La imagen del juguete usada en esta receta es una imagen de un vitral de la catedral de Meaux
en Francia (https://en.wikipedia.org/wiki/Stained_glass). Si siguió todos los pasos, su imagen
debería tener este aspecto (canales RGB a la izquierda y canal A a la derecha):
Cómo hacerlo…
Como se mencionó anteriormente, hay algunos aspectos que debemos cuidar al usar un
Shader Transparente:
1. En la sección SubShader {} del sombreador, agregue las siguientes etiquetas que
shader es transparente:
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
}
2. Como este shader está diseñado para materiales 2D, asegúrese de que la geometría
posterior de su modelo no se dibuja agregando lo siguiente:
Cull Back
3. Dígale al shader que este material es transparente y necesita ser mezclado con lo que
fue dibujado en la pantalla antes:
#pragma surface surf Standard alpha:fade
4. Utilice este Shader de Superficie para determinar el color final y la transparencia del
vidrio:
void surf(Input IN, inout SurfaceOutputStandard o)
{
float4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
Cómo funciona…
Este sombreador introduce varios nuevos conceptos. En primer lugar, las etiquetas se utilizan
para agregar información acerca de cómo se va a mostrar el objeto. El realmente interesante
aquí es cola. Unity, de forma predeterminada, ordenará los objetos según la distancia de la
cámara. Por lo tanto, a medida que un objeto se acerca a la cámara, se va a dibujar sobre
todos los objetos que están más lejos de la cámara. Para la mayoría de los casos, esto funciona
muy bien para los juegos, pero encontrarás ciertas situaciones en las que querrás tener más
control sobre la clasificación de tus objetos en tu escena. Unity nos ha proporcionado algunas
colas de procesamiento predeterminadas, cada una con un valor único que dirige Unity cuando
dibujar el objeto a la pantalla. Estas colas de procesamiento incorporadas se denominan
Fondo, Geometría, AlphaTest, Transparente y Superposición. Estas colas no fueron creadas
arbitrariamente; En realidad sirven a un propósito para hacer nuestra vida más fácil al escribir
shaders e interactuar con el renderizador en tiempo real. Consulte la tabla siguiente para
obtener descripciones sobre el uso de cada una de estas colas de procesamiento individuales:
Render
Render
Render queue descripción queue
queue
value
Backgrou Esta cola de renderizado se procesa primero. Se utiliza para 1000
nd skyboxes y así sucesivamente.
Geometry
Esta es la cola de procesamiento predeterminada. Esto se utiliza 2000
para la mayoría de los objetos. La geometría opaca utiliza esta cola.
La geometría con prueba de alfa utiliza esta cola. Es diferente del
AlphaTes
t Geometry y Queue, ya que es más eficiente renderizar objetos 2450
probados alfa después de dibujar todos los objetos sólidos.
Esta cola de renderizado se procesa después de las filas Geometry y
Transpar AlphaTest en orden de inicio. Cualquier cosa alfa mezclada (es decir, 3000
ent shaders que no escriben en el buffer de profundidad) debe ir aquí,
por ejemplo, efectos de vidrio y partículas.
Esta cola de procesamiento está destinada a efectos de
Overlay superposición. Cualquier cosa rendida por último debe ir aquí, por 4000
ejemplo, bengalas de lente.
Por lo tanto, una vez que sepa a qué fila de procesamiento pertenece su objeto, puede asignar
su etiqueta de cola de procesamiento incorporada. Nuestro shader utilizó la cola Transparent,
por lo que escribimos Tags {"Queue" = "Trasparent"}.
Nota
El hecho de que la cola Transparente se procese después de Geometría no significa que
nuestro vidrio aparecerá encima de todos los demás objetos sólidos. La unidad dibujará el
último cristal, pero no hará píxeles que pertenecen a las piezas de geometría ocultadas detrás
de otra cosa. Este control se realiza mediante una técnica llamada ZBuffering. Puede encontrar
más información sobre cómo se procesan los modelos en
http://docs.unity3d.com/Manual/SLCullAndDepth. Html. La etiqueta IgnoreProjector hace que
este objeto no sea afectado por los proyectores de Unity. Por último, RenderType desempeña
un papel en el reemplazo de sombreado, un tema que se tratará brevemente en el Capítulo 9,
Jugabilidad y Efectos de pantalla. El último concepto introducido es alpha: fade. Esto indica
que todos los píxeles de este material tienen que ser mezclados con lo que estaba en la
pantalla antes de acuerdo a sus valores alfa. Sin esta directiva, los píxeles se dibujarán en el
orden correcto, pero no tendrán ninguna transparencia.
Código de Transparencia
Shader "CookbookShaders/Transparent" {
Properties {
_Color("Color", Color) = (1,1,1,1)
_MainTex("Albedo (RGB)", 2D) = "white" {}
}
SubShader {
Tags {
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
}
CGPROGRAM
#pragma surface surf Standard alpha:fade
sampler2D _MainTex;
fixed4 _Color;
struct Input {
float2 uv_MainTex;
};
void surf(Input IN, inout SurfaceOutputStandard o) {
float4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
Cómo hacerlo…
Los cambios siguientes modificarán nuestro sombreador actual en uno holográfico:
1. Agregue la propiedad siguiente al sombreador:
_DotProduct("Rim effect", Range(-1,1)) = 0.25
2. Agregue su respectiva variable a la sección CGPROGRAM:
float _DotProduct;
3. Como este material es transparente, agregue las siguientes etiquetas:
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
}
Nota
Según el tipo de objeto que va a utilizar, es posible que desee que su parte trasera de
Aparecer. Si este es el caso, agregue Cull Off para que la parte posterior del modelo no sea
Eliminado (sacrificado).
4. Este sombreado no está tratando de simular un material realista, por lo que no hay
necesidad de usar el modelo de iluminación PBR. La reflectancia lambertiana, que es
muy barata, se utiliza en su lugar. Además, debemos desactivar cualquier iluminación
con nolighting y señalar a Cg que se trata de un Shader Transparente usando alpha:
fade:
5. Cambie la estructura de entrada para que Unity lo llene con la dirección de la vista real
y la dirección normal del mundo:
struct Input
{
float2 uv_MainTex;
float3 worldNormal;
float3 viewDir;
};
6. Utilice la siguiente función de superficie. Recuerde que como este shader está usando
la reflectancia Lambertiana como su función de iluminación, el nombre de la
estructura de salida de la superficie debe cambiarse en consecuencia a SurfaeOutput
en lugar de SurfaceOutputStandard:
void surf(Input IN, inout SurfaceOutput o)
{
float4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
float border = 1 - (abs(dot(IN.viewDir, IN.worldNormal)));
float alpha = (border * (1 - _DotProduct) + _DotProduct);
o.Alpha = c.a * alpha;
}
Ahora puede utilizar el deslizador de efectos Rim para elegir la intensidad del efecto
holográfico.
Cómo funciona…
Como se mencionó anteriormente, este sombreado funciona mostrando sólo la silueta de un
objeto. Si miramos el objeto desde otro ángulo, su contorno cambiará. Geométricamente
hablando, los bordes de un modelo son todos aquellos triángulos cuya dirección normal es
ortogonal (90 grados) a la dirección de la vista actual. La estructura de entrada declara estos
parámetros, worldNormal y viewDir, respectivamente.
El problema de entender cuando dos vectores son ortogonales se puede resolver usando el
producto punto. Es un operador que toma dos vectores y devuelve cero si son ortogonales.
Utilizamos _DotProduct para determinar cuán cerca de cero el producto de punto tiene que
ser para que el triángulo se desvanezca completamente. El segundo aspecto que se utiliza en
este sombreado es el suave desvanecimiento entre el borde del modelo (totalmente visible) y
el ángulo determinado por _DotProduct (invisible). Esta interpolación lineal se realiza de la
siguiente manera:
float alpha = (border * (1 - _DotProduct) + _DotProduct);
Finalmente, el alfa original de la textura se multiplica con el nuevo cálculo Coeficiente para
lograr el aspecto final.
Hay más…
Esta técnica es muy simple y relativamente simple. Sin embargo, puede utilizarse para una
gran variedad de efectos, como los siguientes:
La atmósfera ligeramente coloreada de un planeta en juegos de ciencia ficción
El borde de un objeto que ha sido seleccionado o que se encuentra actualmente bajo
el ratón
Un fantasma o espectro
Humo que sale de un motor
La onda de choque de una explosión
El escudo de la burbuja de una nave espacial bajo ataque
Ver también
El producto punto juega un papel importante en la forma en que se calculan las reflexiones. El
capítulo 3, Descripción de los modelos de iluminación, explicará en detalle cómo funciona y
por qué es ampliamente utilizado en tantos shaders.
Shader "CookbookShaders/Silueta" {
Properties {
_Color("Color", Color) = (1,1,1,1)
_MainTex("Albedo (RGB)", 2D) = "white" {}
_DotProduct("Rim effect", Range(-1,1)) = 0.25
}
SubShader {
Tags {
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
}
LOD 200
CGPROGRAM
#pragma surface surf Lambert alpha:fade
sampler2D _MainTex;
fixed4 _Color;
float _DotProduct;
struct Input {
float2 uv_MainTex;
float3 worldNormal;
float3 viewDir;
};
void surf(Input IN, inout SurfaceOutput o) {
float4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
float border = 1 - (abs(dot(IN.viewDir, IN.worldNormal)));
float alpha = (border * (1 - _DotProduct) + _DotProduct);
o.Alpha = c.a * alpha;
}
ENDCG
}
FallBack "Diffuse"
}
¿Por qué es útil? Bueno, en términos de la cantidad de memoria real que su aplicación ocupa,
las texturas son una gran parte del tamaño de su aplicación. Por lo tanto, para comenzar a
reducir el tamaño de su aplicación, podemos ver todas las imágenes que estamos usando en
nuestro shader y ver si podemos combinar estas texturas en una sola textura.
Cualquier textura que esté en escala de grises puede ser empaquetada en uno de los canales
RGBA de otra textura. Esto podría sonar un poco extraño al principio, pero esta receta va a
demostrar uno de los usos de embalar una textura y el uso de estas texturas en un sombreado.
Un ejemplo de uso de estas texturas empaquetadas es cuando se desea mezclar un conjunto
de texturas en una sola superficie. Usted ve esto a menudo en shaders del tipo del terreno,
donde usted necesita mezclarse en otra textura agradable usando una cierta clase de textura
del control o de la textura embalada, en este caso. Esta receta cubre esta técnica y te muestra
cómo puedes construir los inicios de un agradable tono de textura de cuatro texturas
Preparándose
Vamos a crear un nuevo archivo de sombreado en su carpeta Shaders y luego crear un nuevo
material para este sombreado. La convención de nomenclatura es totalmente suya para su
shader y archivos de material, así que intente lo mejor para mantenerlos organizados y fáciles
de hacer referencia más adelante.
Una vez que tengas tu shader y material listo, crea una nueva escena en la que podemos
probar nuestro shader. También tendrá que recoger cuatro texturas que usted desea mezclar.
Éstos pueden ser cualquier cosa, pero para un sombreador agradable del terreno, usted
deseará la hierba, la suciedad, la suciedad rocosa, y las texturas de la roca. Estas son las
texturas de color que vamos a utilizar para esta receta, que se incluyen con este libro. Por
último, también necesitará una textura de mezcla que está repleta de imágenes en escala de
grises. Esto nos dará las cuatro texturas de mezcla que podemos usar para dirigir cómo las
texturas de color se colocarán en la superficie del objeto. Podemos utilizar texturas de mezcla
muy intrincadas para crear una distribución muy realista de texturas de terreno sobre una
malla de terreno, como se ve en la siguiente imagen:
Cómo hacerlo…
Aprendamos cómo usar texturas empaquetadas ingresando el código mostrado en los
siguientes pasos:
1. Tenemos que añadir algunas propiedades a nuestro bloque de propiedades.
Necesitaremos cinco objetos sampler2D, o texturas, y dos propiedades de color:
Properties{
_MainTint ("Diffuse Tint", Color) =(1,1,1,1)
// Agregue las propiedades a continuación para poder ingresar todas nuestras texturas
_ColorA ("Terrain Color A", Color) = (1,1,1,1)
_ColorB ("Terrain Color B", Color) = (1,1,1,1)
_RTexture ("Red Channel Texture", 2D) = ""{}
_GTexture ("Green Channel Texture", 2D) = ""{}
_BTexture ("Blue Channel Texture", 2D) = ""{}
_ATexture ("Alpha Channel Texture", 2D) = ""{}
_BlendTex ("Blend Texture", 2D) = ""{}
}
2. Entonces necesitamos crear las variables de sección de SubShader {} que serán nuestro
enlace a los datos en el bloque Properties:
CGPROGRAM
#pragma surface surf Lambert
float4 _MainTint;
float4 _ColorA;
float4 _ColorB;
sampler2D _RTexture;
sampler2D _GTexture;
sampler2D _BTexture;
sampler2D _BlendTex;
sampler2D _ATexture;
3. Así que ahora tenemos nuestras propiedades de textura y las estamos pasando a
nuestra función SubShader {}. Con el fin de permitir al usuario cambiar las tarifas de
baldosas en una base por textura, tendremos que modificar nuestra estructura de
entrada. Esto nos permitirá utilizar los parámetros de mosaico y offset en cada textura:
struct Input {
float2 uv_RTexture;
float2 uv_GTexture;
float2 uv_BTexture;
float2 uv_ATexture;
float2 uv_BlendTex;
};
4. En la función surf (), obtenga la información de textura y guárdela en su propio
Variables para que podamos trabajar con los datos de una manera limpia y fácil de
entender:
// Obtener los datos de píxeles de la textura de mezcla
// necesitamos un flotador 4 aquí porque la textura
// devolverá R, G, B y A o X, Y, Z y W
float4 blendData = tex2D(_BlendTex, IN.uv_BlendTex);
// Obtener los datos de las texturas que queremos mezclar
float4 rTexData = tex2D(_RTexture, IN.uv_RTexture);
float4 gTexData = tex2D(_GTexture, IN.uv_GTexture);
float4 bTexData = tex2D(_BTexture, IN.uv_BTexture);
float4 aTexData = tex2D(_ATexture, IN.uv_ATexture);
5. Combinemos cada una de nuestras texturas utilizando la función lerp (). Se necesitan
tres argumentos, lerp (valor: a, valor: b, mezcla: c). La función lerp () toma dos texturas
y las mezcla con el valor de flotador dado en el último argumento:
// No, necesitamos construir un nuevo valor RGBA y agregar todo
// la mezcla de la textura de nuevo juntos
float4 finalColor;
finalColor = lerp(rTexData, gTexData, blendData.g);
finalColor = lerp(finalColor, bTexData, blendData.b);
finalColor = lerp(finalColor, aTexData, blendData.a);finalColor.a =1.0;
6. Finalmente, multiplicamos nuestras texturas mezcladas con los valores de tinte de
color y usamos el color rojo
Canal para determinar dónde se encuentran los dos colores de tinte de terreno
diferentes:
// Añada a nuestros colores tintométricos para el terreno
float4 terrainLayers = lerp(_ColorA, _ColorB, blendData.r);
finalColor *= terrainLayers;
finalColor = saturate(finalColor);
o.Albedo = finalColor.rgb * _MainTint.rgb;
o.Alpha = finalColor.a;
El resultado de combinar cuatro texturas de terreno y crear una técnica de tintado de terreno
se puede ver en la siguiente imagen:
Cómo funciona…
Esto puede parecer como unas pocas líneas de código, pero el concepto detrás de la mezcla es
en realidad bastante simple. Para que la función funcione, hay que emplear la función
incorporada de built-in lerp() de la biblioteca estándar de CgFX. Esta función nos permite
seleccionar un valor entre el argumento uno y el argumento de utilizar el argumento tres como
la cantidad de mezcla:
Función Descripción
Esto implica una interpolación lineal:
(1 - f) * a + b * f
lerp( a , b,f ) Aquí, a y b son tipos vectoriales o escalares coincidentes. El
parámetro f puede ser un escalar o un vector de
Del mismo tipo que a y b.
Así, por ejemplo, si queríamos encontrar el valor medio entre 1 y 2, podríamos alimentar el
valor 0.5 como el tercer argumento a la función lerp () y devolvería el valor 1.5. Esto funciona
perfectamente para nuestras necesidades de mezcla ya que los valores de un canal individual
en una textura RGBA son valores de flotador único, generalmente en el rango de 0 a 1.
En el shader, simplemente tomamos uno de los canales de nuestra textura de mezcla y lo
usamos para
Unidad el color que se recoge en una función lerp () para cada píxel. Por ejemplo, tomamos
nuestra textura de hierba y textura de suciedad, usamos el canal rojo de nuestra textura de
mezcla, y alimentamos esto a una función lerp (). Esto nos dará el resultado de color mezclado
correcto para cada píxel en la superficie. Una representación más visual de lo que está
sucediendo cuando se utiliza la función lerp () se muestra en la siguiente imagen:
El código de sombreado simplemente utiliza los cuatro canales de la textura de mezcla y todas
las texturas de color para crear una textura final mezclada. Esta textura final se convierte
entonces en nuestro color que podemos multiplicar con nuestra iluminación difusa.
Código Mezcla Textura
Shader "CookbookShaders/MesclaTextura" {
Properties {
_MainTint ("Diffuse Tint", Color) =(1,1,1,1)
//Agregue las propiedades a continuación para poder ingresar todas nuestras texturas
_ColorA ("Terrain Color A", Color) = (1,1,1,1)
_ColorB ("Terrain Color B", Color) = (1,1,1,1)
_RTexture ("Red Channel Texture", 2D) = ""{}
_GTexture ("Green Channel Texture", 2D) = ""{}
_BTexture ("Blue Channel Texture", 2D) = ""{}
_ATexture ("Alpha Channel Texture", 2D) = ""{}
_BlendTex ("Blend Texture", 2D) = ""{}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert alpha:fade
float4 _MainTint;
float4 _ColorA;
float4 _ColorB;
sampler2D _RTexture;
sampler2D _GTexture;
sampler2D _BTexture;
sampler2D _BlendTex;
sampler2D _ATexture;
struct Input {
float2 uv_RTexture;
float2 uv_GTexture;
float2 uv_BTexture;
float2 uv_ATexture;
float2 uv_BlendTex;
};
}
ENDCG
}
FallBack "Diffuse"
}
Ahora puedes mover tu personaje y esto creará un círculo bonito alrededor de él.
Cambiar las propiedades de la secuencia de comandos Radius también cambiará el radio.
Cómo funciona…
Los parámetros relevantes para dibujar un círculo son su centro, radio y color. Todos ellos
están disponibles en el sombreador con los nombres _Center, _Radius y _RadiusColor. Al
agregar la variable worldPos a la estructura Input, estamos pidiendo a Unity que nos
proporcione la posición del píxel que estamos dibujando expresado en coordenadas
mundiales. Esta es la posición real de un objeto en el editor.
La función surf () es donde se dibuja realmente el círculo. Calcula la distancia desde
El punto que se está dibujando y el centro del radio, entonces comprueba si está entre
_Radius y _Radius + _RadiusWidth; Si este es el caso, utiliza el color elegido. En el otro caso,
sólo muestra el mapa de textura como todos los otros shaders vistos hasta ahora.
Shader "CookbookShaders/RadiusShader" {
Properties {
_Color("Color", Color) = (1,1,1,1)
_MainTex("Albedo (RGB)", 2D) = "white" {}
_Center("Center", Vector) = (0,0,0,0)
_Radius("Radius", Float) = 0.5
_RadiusColor("Radius Color", Color) = (1,0,0,1)
_RadiusWidth("Radius Width", Float) = 2
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
struct Input {
float2 uv_MainTex; // The UV of the terrain texture
float3 worldPos; // The in-world position
};
}
ENDCG
}
FallBack "Diffuse"
}
Introducción
Simular la forma en que funciona la luz es una tarea muy desafiante y que consume muchos
recursos. Durante muchos años, los videojuegos han utilizado modelos de iluminación muy
sencillos que, a pesar de su falta de realismo, eran muy creíbles. Incluso si la mayoría de los
motores 3D utilizan ahora procesadores físicos, vale la pena explorar algunas técnicas más
sencillas. Los que se presentan en este capítulo son razonablemente realistas y ampliamente
adoptados en dispositivos con bajos recursos como los teléfonos móviles. La comprensión de
estos modelos de iluminación sencilla también es esencial si desea crear uno propio.
Creación de un modelo de iluminación difusa personalizado
Si está familiarizado con Unity 4, puede saber que el sombreado predeterminado que
proporcionó se basó en un modelo de iluminación llamado reflectancia lambertiana. Esta
receta le mostrará cómo es posible crear un sombreado con un modelo de iluminación
personalizado y explicar las matemáticas y la aplicación detrás de él. La siguiente imagen
muestra la misma geometría renderizada con un Shader estándar (derecha) y Lambert difuso
uno (izquierda):
Este concepto simple tiene que traducirse en una forma matemática. En álgebra vectorial, el
ángulo entre dos vectores unitarios se puede calcular a través de un operador llamado
producto punto. Cuando el producto punto es igual a cero, dos vectores son ortogonales, lo
que significa que hacen un ángulo de 90 grados. Cuando es igual a uno (o menos uno), son
paralelos entre sí. Cg tiene una función llamada dot (), que implementa el producto punto de
manera muy eficiente. La siguiente imagen muestra una fuente de luz (sol) que brilla sobre una
superficie compleja. L indica la dirección de la luz (llamada lightDir en el shader) y N es la
normal a la superficie. La luz se refleja con el mismo ángulo que golpea la superficie:
Cómo hacerlo…
La estética toon se puede lograr con los siguientes cambios en el sombreado:
1. Agregue una nueva propiedad para una textura llamada _RampTex:
_RampTex ("Ramp", 2D) = "white" {}
2. Añada su variable relativa en la sección CGPROGRAM:
sampler2D _RampTex;
3. Cambie la directiva # pragma para que señale a una función llamada LightingToon ():
#pragma surface surf Toon
4. Utilice este modelo de iluminación:
fixed4 LightingToon (SurfaceOutput s, fixed3 lightDir, fixed atten){
half NdotL = dot(s.Normal, lightDir);
NdotL = tex2D(_RampTex, fixed2(NdotL, 0.5));
fixed4 c;
c.rgb = s.Albedo * _LightColor0.rgb * NdotL * atten;
c.a = s.Alpha;
return c;
}
Cómo funciona…
La característica principal del sombreado de toon es la forma en que se hace la luz; Las
superficies no están sombreadas uniformemente. Para lograr este efecto, necesitamos un
mapa de rampa. Su propósito es reasignar la intensidad de luz Lambertian NdotL a otro valor.
Utilizando un mapa de rampa sin gradiente, podemos forzar la iluminación para que sea
renderizada en pasos. La siguiente imagen muestra cómo se utiliza el mapa de rampa para
corregir la intensidad de la luz:
Hay más…
Hay muchas maneras diferentes se puede lograr un efecto de sombreado toon. El uso de
diferentes rampas puede producir cambios dramáticos en la apariencia de sus modelos, por lo
que debe experimentar para encontrar el mejor. Una alternativa a las texturas en rampa es
ajustar la intensidad de luz NdotL de modo que sólo pueda asumir un cierto número de valores
equidistantemente muestreados de 0 a 1:
CGPROGRAM
#pragma surface surf Toon
sampler2D _RampTex;
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
fixed4 c;
c.rgb = s.Albedo * _LightColor0.rgb * NdotL * atten * 2;
c.a = s.Alpha;
return c;
}
ENDCG
}
FallBack "Diffuse"
}
Preparándose
Para comenzar con esta receta, realice los siguientes pasos:
1. Cree un nuevo shader, material y objeto, y déles nombres apropiados para que pueda
encontrarlos más tarde.
2. Conecte el sombreador al material y al material al objeto. Para terminar tu nueva
escena, crea una nueva luz direccional para que podamos ver nuestro efecto Especular
como lo codificamos.
Cómo hacerlo…
Siga los siguientes pasos para crear un modelo de iluminación Phong:
1. Es posible que esté viendo un patrón en este punto, pero siempre nos gusta comenzar
con nuestra parte más básica del proceso de escritura de sombreado: la creación de
propiedades. Por lo tanto, vamos a agregar las siguientes propiedades al shader:
Properties
{
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_SpecularColor ("Specular Color", Color) = (1,1,1,1)
_SpecPower ("Specular Power", Range(0,30)) = 1
}
2. A continuación, debemos asegurarnos de agregar las variables correspondientes a
nuestro bloque CGPROGRAM en nuestro bloque SubShader {}:
float4 _SpecularColor;
sampler2D _MainTex;
float4 _MainTint;
float _SpecPower;
3. Ahora tenemos que añadir nuestro modelo de iluminación personalizado para que
podamos calcular nuestro propio especular de Phong. No se preocupe si no tiene
sentido en este punto; Cubriremos cada línea de código en la sección Cómo
funciona .... Agregue el siguiente código a la función SubShader {} del shader:
fixed4 LightingPhong (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten){
// Reflection
float NdotL = dot(s.Normal, lightDir);
float3 reflectionVector = normalize(2.0 * s.Normal * NdotL -
lightDir);
// Specular
float spec = pow(max(0, dot(reflectionVector, viewDir)), _SpecPower);
float3 finalSpec = _SpecularColor.rgb * spec;
// Final effect
fixed4 c;
c.rgb = (s.Albedo * _LightColor0.rgb * max(0,NdotL) * atten) +
(_LightColor0.rgb * finalSpec);
c.a = s.Alpha;
return c;
}
4. Finalmente, tenemos que decirle al bloque CGPROGRAM que necesita usar nuestra
función de iluminación personalizada en lugar de una de las incorporadas. Hacemos
esto cambiando la sentencia #pragma a lo siguiente:
CGPROGRAM
#pragma surface surf Phong
La siguiente captura de pantalla muestra el resultado de nuestro modelo de iluminación Phong
personalizado usando nuestro propio vector de reflexión personalizado:
Cómo funciona…
Vamos a desglosar la función de iluminación por sí mismo, como el resto del sombreador debe
ser bastante familiar a usted en este momento. En las recetas anteriores, hemos utilizado una
función de iluminación que proporciona sólo la dirección de la luz, lightDir. Unity viene con un
conjunto de funciones de iluminación que puede utilizar, incluyendo una que proporciona la
dirección de la vista, viewDir. Consulte la tabla siguiente o vaya a
http://docs.unity3d.com/Documentation/Components/SL-SurfaceShaderLighting.html:
Not viewdependent half4 Lighting Name You choose (SurfaceOutput s, half3 lightDir, half atten);
View-dependent half4 Lighting Name You choose (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten);
En nuestro caso, estamos haciendo un shader Specular, por lo que necesitamos tener la
estructura de función de iluminación dependiente de la vista. Por lo tanto, tenemos que
escribir lo siguiente:
CPROGRAM
#pragma surface surf Pong
fixed4 LightingPhong (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
{
// ...
}
Esto le dirá al shader que queremos crear nuestro propio shader dependiente de la vista.
Siempre asegúrese de que su nombre de función de iluminación es el mismo en su declaración
de función de iluminación y la instrucción #pragma, o Unity no podrá encontrar su modelo de
iluminación. Los componentes que juegan un papel en el modelo Phong se describen en la
siguiente imagen. Tenemos la dirección de la luz L (junto con su reflejo perfecto R) y la
dirección normal N. Todos ellos han sido encontrados antes en el modelo de Lambertian, con
la excepción de V, que es la dirección de la vista:
El modelo de Phong supone que la intensidad luminosa final de una superficie reflectante se
da y dos componentes: su color difuso y valor especular, como sigue:
I=D+S
El componente difuso D permanece inalterado desde el modelo de Lambertiano:
D=N*L
El componente especular S se define como sigue:
S=(R*V)P
Aquí, p es la potencia especular definida como _SpecPower en el sombreado. El único
parámetro desconocido es R, que es el reflejo de L de acuerdo con N. En álgebra vectorial, esto
se puede calcular de la siguiente manera:
R=2N*(N*L)-L
Esto es exactamente lo que se calcula en lo siguiente:
float3 reflectionVector = normalize(2.0 * s.Normal * NdotL - lightDir);
Esto tiene el efecto de doblar la normal hacia la luz; Como un vértice normal apunta lejos de la
luz, se ve obligado a mirar a la luz. Consulte la siguiente captura de pantalla para obtener una
representación más visual. La secuencia de comandos que produce este efecto de depuración
se incluye en la página de soporte del libro en
https://www.packtpub.com/books/content/support:
Preparándose
Para comenzar con esta fórmula, realice los siguientes pasos:
1. Esta vez, en lugar de crear una escena totalmente nueva, vamos a usar los objetos y la
escena que tenemos, y crear un nuevo shader y material y nombrarlos BlinnPhong.
2. Una vez que tenga un nuevo shader, haga doble clic en él para lanzar MonoDevelop
para poder comenzar a editar nuestro shader.
Cómo hacerlo…
Realice los siguientes pasos para crear un modelo de iluminación BlinnPhong:
1. En el primer lugar, tenemos nuestras propias propiedades al bloque de propiedades
para controlar la apariencia del restablecimiento Especular:
Properties
{
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_SpecularColor ("Specular Color", Color) = (1,1,1,1)
_SpecPower ("Specular Power", Range(0.1,60)) = 3
}
2. A continuación, debemos asegurarnos de que hemos creado las variables
correspondientes en nuestro bloque CGPROGRAM para poder acceder a los datos de
nuestro bloque de propiedades en nuestro subshader:
sampler2D _MainTex;
float4 _MainTint;
float4 _SpecularColor;
float _SpecPower;
3. Ahora es el momento de crear nuestro modelo de iluminación personalizado que
procesará nuestros cálculos Difuso y Especular. El código es el siguiente:
fixed4 LightingCustomBlinnPhong (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten){
float NdotL = max(0,dot(s.Normal, lightDir));
float3 halfVector = normalize(lightDir + viewDir);
float NdotH = max(0, dot(s.Normal, halfVector));
float spec = pow(NdotH, _SpecPower) * _SpecularColor;
float4 c;
c.rgb = (s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb *
_SpecularColor.rgb * spec) * atten;
c.a = s.Alpha;
return c;
}
4. Para completar nuestro shader, tendremos que decirle a nuestro bloque CGPROGRAM que use nuestro
modelo de iluminación personalizado en lugar de uno incorporado modificando la instrucción #pragma
con el siguiente código:
CGPROGRAM
#pragma surface surf CustomBlinnPhong
La siguiente captura de pantalla muestra los resultados de nuestro modelo de iluminación BlinnPhong:
Cómo funciona…
El Specular de BlinnPhong es casi exactamente como el Specular de Phong, excepto que es más
eficiente porque usa menos código para lograr casi el mismo efecto. Antes de la introducción
de renderizado físicamente, este enfoque fue la opción por defecto para la reflexión especular
en la Unidad 4. El cálculo del vector de reflexión R es generalmente caro. El Specular de
BlinnPhong lo reemplaza con el medio vector H entre la dirección de la vista V y la dirección de
la luz L:
Aquí, |V+L| Es la longitud del vector V+L . En Cg, simplemente necesitamos agregar la dirección
de la vista y la dirección de la luz juntos y luego normalizar el resultado a un vector unitario:
float3 halfVector = normalize(lightDir + viewDir);
Entonces, simplemente necesitamos puntear el vértice normal con este nuevo medio vector
para obtener nuestro valor especular principal. Después de esto, lo llevamos a un poder de
_SpecPower y lo multiplicamos por la variable de color Specular. Es mucho más ligero en el
código y las matemáticas, pero todavía nos da un buen resaltado especular que trabajará para
un montón de situaciones en tiempo real.
Ver también
Los modelos de luz que se ven en este capítulo son extremadamente simples; Ningún material
real es perfectamente mate o perfectamente especular. Por otra parte, no es infrecuente que
los materiales complejos tales como ropa, madera, y piel requieran el conocimiento de cómo
la luz se dispersa en las capas debajo de la superficie. Use la siguiente tabla para recapitular los
diferentes modelos de iluminación encontrados hasta ahora:
CGPROGRAM
#pragma surface surf CustomBlinnPhong
sampler2D _MainTex;
float4 _MainTint;
float4 _SpecularColor;
float _SpecPower;
struct Input {
float2 uv_MainTex;
};
fixed4 LightingCustomBlinnPhong (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed
atten){
float NdotL = max(0,dot(s.Normal, lightDir));
float3 halfVector = normalize(lightDir + viewDir);
float NdotH = max(0, dot(s.Normal, halfVector));
float spec = pow(NdotH, _SpecPower) * _SpecularColor;
float4 c;
c.rgb = (s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb * _SpecularColor.r
gb * spec) * atten;
c.a = s.Alpha;
return c;
}
La siguiente captura de pantalla muestra el mapa normal de anisotropía que usaremos para
esta receta. Está disponible en la página de soporte del libro en
https://www.packtpub.com/books/content/support:
Cómo hacerlo…
Para crear un efecto anisotrópico, necesitamos realizar los siguientes cambios en el
sombreador creado anteriormente:
1. Primero necesitamos agregar las propiedades que vamos a necesitar para nuestro
shader. Estos permitirán un gran control artístico sobre la apariencia final de la
superficie:
Properties
{
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_SpecularColor ("specular Color", Color) = (1,1,1,1)
_Specular ("Specular Amount", Range(0,1)) = 0.5
_SpecPower ("Specular Power", Range(0,1)) = 0.5
_AnisoDir ("Anisotropic Direction", 2D) = "" {}
_AnisoOffset ("Anisotropic Offset", Range(-1,1)) = -0.2
}
2. A continuación, necesitamos hacer la conexión entre nuestro bloque de propiedades y
nuestro bloque SubShader {} para que podamos usar los datos proporcionados por el
bloque de propiedades:
sampler2D _MainTex;
sampler2D _AnisoDir;
float4 _MainTint;
float4 _SpecularColor;
float _AnisoOffset;
float _Specular;
float _SpecPower;
3. Ahora podemos crear nuestra función de iluminación que producirá el efecto
anisotrópico correcto en nuestra superficie. Usaremos el siguiente código para esto:
fixed4 LightingAnisotropic(SurfaceAnisoOutput s, fixed3 lightDir, half3 viewDir, fixed atten){
fixed3 halfVector = normalize(normalize(lightDir) + normalize(viewDir));
float NdotL = saturate(dot(s.Normal, lightDir));
fixed HdotA = dot(normalize(s.Normal + s.AnisoDirection), halfVector);
float aniso = max(0, sin(radians((HdotA + _AnisoOffset) * 180)));
float spec = saturate(pow(aniso, s.Gloss * 128) * s.Specular);
fixed4 c;
c.rgb = ((s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb *_SpecularColor.rgb * spec)) * atten;
c.a = s.Alpha;
return c;
}
4. Para usar esta nueva función de iluminación, necesitamos decirle a la instrucción
#pragma del subshader que la busque en lugar de usar una de las funciones de
iluminación incorporadas. También le decimos al sombreador que apunte al modelo
de shader 3.0 para que tengamos más espacio para texturas en nuestro programa:
CGPROGRAM
#pragma Surface surf Anisotropic
#pragma target 3.0
5. También hemos dado el mapa normal anisotrópico sus propias UVs declarando el
siguiente código en la estructura de entrada. Esto no es totalmente necesario, ya que
sólo podemos usar los UVs de la textura principal, pero esto nos da un control
independiente sobre el embaldosado de nuestro efecto de metal cepillado para que
podamos escalar a cualquier tamaño que queremos:
struct Input{
float2 uv_MainTex;
float2 uv_AnisoDir;
};
6. También necesitamos agregar la estructura SurfaceAnisoOutput:
struct SurfaceAnisoOutput{
fixed3 Albedo;
fixed3 Normal;
fixed3 Emission;
fixed3 AnisoDirection;
half Specular;
fixed Gloss;
fixed Alpha;
};
7. Finalmente, necesitamos usar la función surf () para pasar los datos correctos a
nuestra función de iluminación. Por lo tanto, obtendremos la información por píxel de
nuestro mapa normal anisotrópico y estableceremos nuestros parámetros especulares
de la siguiente manera:
void surf(Input IN, inout SurfaceAnisoOutput o){
half4 c = tex2D(_MainTex, IN.uv_MainTex) * _MainTint;
float3 anisoTex = UnpackNormal(tex2D(_AnisoDir, IN.uv_AnisoDir));
o.AnisoDirection = anisoTex;
o.Specular = _Specular;
o.Gloss = _SpecPower;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
El mapa normal anisotrópico nos permite dar la dirección de la superficie y nos ayuda a
dispersar el resalte especular alrededor de la superficie. La siguiente captura de pantalla
muestra el resultado de nuestro Shader anisotrópico:
Cómo funciona…
Vamos a dividir este sombreado en sus componentes básicos y explicar por qué estamos
recibiendo el efecto. En su mayoría cubriremos la función de iluminación personalizada aquí,
ya que el resto del sombreador debería ser bastante autoexplicativo en este punto.
Comenzamos primero declarando nuestra propia estructura SurfaceAnisoOutput. Necesitamos
hacer esto para obtener la información por píxel del mapa normal anisotrópico, y la única
manera que podemos hacer esto en un Shader de superficie es usar una función tex2D () en la
función surf (). El código siguiente muestra la estructura de salida de superficie personalizada
utilizada en nuestro sombreado:
struct SurfaceAnisoOutput{
fixed3 Albedo;
fixed3 Normal;
fixed3 Emission;
fixed3 AnisoDirection;
half Specular;
fixed Gloss;
fixed Alpha;
};
Podemos utilizar la estructura SurfaceAnisoOutput como una forma de interactuar entre la
función de iluminación y la función de superficie. En nuestro caso, almacenamos la
información de textura por píxel en la variable llamada anisoTex en nuestra función surf () y
luego pasando estos datos a la estructura SurfaceAnisoOutput almacenándola en la variable
AnisoDirection. Una vez que tengamos esto, podemos usar la información por pixel en la
función de iluminación usando
S.AnisoDirection. Con esta conexión de datos establecida, podemos pasar a nuestros cálculos
de iluminación reales. Esto comienza por obtener lo habitual fuera del camino, el medio
vector, por lo que no tenemos que hacer el cálculo de reflexión completa y la iluminación
difusa, que es el vértice normal punteado con el vector de luz o la dirección. Esto se hace en Cg
con las líneas siguientes:
Fixed3 halfVector = normalizar (normalizar (lightDir) + normalizar (viewDir));
Float NdotL = saturate (punto (s.Normal, lightDir));
Entonces, comenzamos la modificación real al Specular para conseguir la mirada derecha.
Primero punteamos la suma normalizada de los vectores normal y per-pixel del vértice de
nuestro mapa normal anisotrópico con medio vector calculado en el paso anterior. Esto nos da
un valor flotante que da un valor de 1 como la superficie normal, que es modificada por el
mapa normal Anisotrópico cuando se convierte en paralelo con medioVector y 0 como es
perpendicular. Finalmente, modificamos este
Valor con una función sin () para que básicamente obtengamos un relieve medio más oscuro y
finalmente un efecto de anillo basado en medioVector. Todas las operaciones mencionadas
anteriormente se resumen en las dos líneas siguientes de código Cg:
Fijo HdotA = punto (normalizar (s.Normal + s.AnisoDirection), medioVector);
Float aniso = max (0, sin (radians ((HdotA + _AnisoOffset) * 180)));
Finalmente, escala el efecto del valor del aniso llevándolo a una potencia de la pérdida de
peso, y después disminuye globalmente su fuerza multiplicándola por s. Especular:
Float spec = saturate (pow (aniso, s.Gloss * 128) * specular);
Este efecto es ideal para crear superficies de metal más avanzadas, especialmente las que se
cepillan y parecen tener direccionalidad para ellas. También funciona bien para el cabello o
cualquier tipo de superficie blanda con la direccionalidad a la misma. La siguiente captura de
pantalla muestra el resultado de mostrar el cálculo de iluminación anisotrópico final:
Shader "CookbookShaders/anisotropico" {
Properties {
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_SpecularColor ("specular Color", Color) = (1,1,1,1)
_Specular ("Specular Amount", Range(0,1)) = 0.5
_SpecPower ("Specular Power", Range(0,1)) = 0.5
_AnisoDir ("Anisotropic Direction", 2D) = "" {}
_AnisoOffset ("Anisotropic Offset", Range(-1,1)) = -0.2
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Anisotropic
#pragma target 3.0
sampler2D _MainTex;
sampler2D _AnisoDir;
float4 _MainTint;
float4 _SpecularColor;
float _AnisoOffset;
float _Specular;
float _SpecPower;
struct SurfaceAnisoOutput{
fixed3 Albedo;
fixed3 Normal;
fixed3 Emission;
fixed3 AnisoDirection;
half Specular;
fixed Gloss;
fixed Alpha;
};
fixed4 c;
c.rgb = ((s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb * _SpecularColor.rgb
* spec)) * (atten);
c.a = 1.0;
return c;
}
struct Input{
float2 uv_MainTex;
float2 uv_AnisoDir;
};
o.AnisoDirection = anisoTex;
o.Specular = _Specular;
o.Gloss = _SpecPower;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
Introducción
Todos los modelos de iluminación encontrados en el Capítulo 3, Comprensión de los Modelos
de Iluminación, eran descripciones muy primitivas de cómo se comporta la luz. El aspecto más
importante durante su elaboración fue la eficiencia. El sombreado en tiempo real es costoso, y
técnicas como Lambertian o BlinnPhong son un compromiso entre el coste computacional y el
realismo. Tener una unidad de procesamiento gráfico (GPU) más potente nos ha permitido
escribir progresivamente modelos de iluminación y motores de renderización más sofisticados,
con el objetivo de simular cómo la luz se comporta realmente. Esto es, en pocas palabras, la
filosofía detrás de PBR. Como su nombre sugiere, trata de acercarse lo más posible a la física
detrás de los procesos que dan un aspecto único a cada material. A pesar de esto, el término
PBR ha sido ampliamente utilizado en las campañas de marketing y es más un sinónimo de
renderización de vanguardia en lugar de una técnica bien definida. Unity 5 implementa PBR
introduciendo dos cambios importantes. El primero es un modelo de iluminación
completamente nuevo (llamado Estándar). Shaders de superficie permiten a los
desarrolladores especificar las propiedades físicas de un material, pero no imponen
restricciones físicas reales sobre ellos. PBR rellena esta brecha utilizando un modelo de
iluminación que hace cumplir los principios de la física como la conservación de la energía (un
objeto no puede reflejar más luz que la cantidad que recibe), la dispersión de la microescala
(las superficies rugosas reflejan la luz más erráticamente en comparación con las lisas)
Reflexiones aparecen en los ángulos de pastoreo), y la oclusión superficial (el oscurecimiento
de las esquinas y otras geometrías que son difíciles de iluminar). Todos estos aspectos, y
muchos otros, se utilizan para calcular el modelo de iluminación estándar. El segundo aspecto
que hace al PBR tan realista se llama Iluminación Global (GI) y es la simulación del transporte
ligero físicamente. Significa que los objetos no se dibujan en la escena como si fueran
entidades separadas. Todos ellos contribuyen a la interpretación final como la luz puede
reflexionar sobre ellos antes de golpear algo más. Este aspecto no se captura en los shaders
mismos, pero es una parte esencial de cómo funciona el motor de renderizado.
Desafortunadamente, simular con precisión cómo los rayos de luz realmente rebotan sobre las
superficies en tiempo real está más allá de las capacidades de las modernas GPUs. Unity 5 hace
algunas optimizaciones inteligentes que permiten retener la fidelidad visual sin sacrificar el
rendimiento. Algunas de las técnicas más avanzadas (como las reflexiones), sin embargo,
requieren la entrada del usuario. Todos estos aspectos se tratarán en este capítulo. Es
importante recordar que PBR y GI no garantizan automáticamente que su juego sea
fotorrealista. Conseguir el fotorealismo es una tarea muy desafiante y, como cualquier arte,
requiere gran expertize y habilidades excepcionales.
Cómo hacerlo…
Hay dos texturas principales que deben configurarse en el Shader estándar: Albedo y Metallic.
Para utilizar eficazmente el flujo de trabajo metálico, debemos inicializar estos mapas
correctamente:
1. El mapa Albedo debe ser inicializado con la textura no iluminada del modelo 3D.
2. Para crear el mapa metálico, comience por duplicar el archivo para su mapa Albedo.
Puede hacerlo seleccionando el mapa en la pestaña Proyecto y presionando Ctrl + D.
3. Utilice blanco (#ffffff) para colorear las regiones del mapa que corresponden a
materiales que están hechos de metal puro. Utilice negro (# 000000) para todos los
demás colores. Los tonos de gris deben usarse para superficies metálicas polvorientas,
resistidas o desgastadas, óxido, pintura rayada, etc. De hecho, Unity utiliza sólo el
canal rojo para almacenar el valor metálico; Los verdes y los azules son ignorados.
4. Utilice el canal alfa de la imagen para proporcionar información sobre la suavidad del
material.
5. Asigne el mapa metálico al material. Los controles deslizantes Metalizado y Suavizado
desaparecerán, ya que estas dos propiedades ahora están controladas por el mapa.
Cómo funciona…
Legacy Shaders permite a los artistas crear materiales que rompen fácilmente la ilusión del
fotorealismo al tener condiciones de iluminación que son imposibles en la realidad. Esto pasa
Porque todas las propiedades de un material expuesto en un Shader Legacy Surface no están
correlacionadas. Al introducir el flujo de trabajo metálico, Unity 5 impone más restricciones en
el aspecto de los objetos, haciendo más difícil para los artistas crear materiales ilógicos. Los
metales son conocidos por la conducción de electricidad; La luz está en forma de ondas
electromagnéticas, lo que significa que casi todos los metales se comportan de manera similar
en comparación con los no conductores (a menudo referidos como aislantes). Los conductores
tienden a reflejar la mayoría de los fotones (70- 100%), lo que resulta en alta reflectancia. La
luz restante es absorbida, en lugar de difundida, lo que sugiere que los conductores tienen un
componente difuso muy oscuro. Los aislantes, por el contrario, tienen una baja reflectancia
(4%); El resto de la luz se dispersa en la superficie, contribuyendo a su aspecto difuso. En el
Shader estándar, los materiales puramente metálicos tienen componentes difusos oscuros y el
color de sus reflejos especulares está determinado por el mapa de Albedo. Por el contrario, el
componente difuso de materiales puramente no metálicos está determinado por el mapa de
Albedo; El color de sus reflejos especulares está determinado por el color de la luz entrante.
Siguiendo estos principios se permite que el flujo de trabajo metálico combine el albedo y el
especular en el mapa de Albedo, imponiendo comportamientos físicamente precisos. Esto
también permite ahorrar más espacio, dando por resultado una aceleración significativa a los
costes de control reducido sobre la mirada de sus materiales.
Ver también
Ver también
Para obtener más información sobre la configuración metálica, puede consultar estos enlaces:
Gráfico de calibración: Cómo calibrar un material metálico
(Http://blogs.unity3d.com/wp-content/uploads/2014/11/UnityMetallicChart.png)
Tabla de materiales: Cómo inicializar los parámetros de Shader estándar para
Materiales (http://docs.unity3d.com/Manual/StandardShaderMaterialCharts.html)
Quixel MEGASCANS: Una vasta biblioteca de materiales, incluyendo texturas y PBR
Parámetros (http://quixel.se/megascans)
Conversión de texturas PBR: Cómo se pueden convertir los shaders tradicionales en shaders
PBR
(Http://www.marmoset.co/toolbag/learn/pbr-conversion)
Diseñador de sustancias: un software basado en nodos para trabajar con PBR
(Https://www.allegorithmic.com/products/substance-designer)
La teoría de la representación físicamente basada: Una guía completa sobre PBR
(Https://www.allegorithmic.com/pbr-guide)
Preparándose
Esta receta utilizará el Shader estándar, por lo que no hay necesidad de crear uno nuevo:
1. Crear un nuevo material.
2. Asegúrese de que la propiedad Shader está establecida en Estándar o Estándar
(configuración especular) en la ficha Inspector del material.
3. Asigne el material recién creado al objeto 3D que desea que sea transparente.
Cómo hacerlo…
El Shader estándar proporciona tres tipos diferentes de transparencias. A pesar de ser muy
similares, tienen diferencias sutiles y encajan contextos diferentes.
Materiales semi-transparentes
Algunos materiales tales como plásticos claros, cristal y vidrio son semitransparentes. Esto
significa que ambos requieren todos los efectos realistas de PBR (como reflejos especulares y
refracción y reflexión de Fresnel) pero permiten ver la geometría detrás. Si esto es lo que
necesita, realice los pasos siguientes:
1. En la pestaña Inspector del material, establezca el Modo de renderizado en
Transparente.
2. La cantidad de transparencia está determinada por el canal alfa del color Albedo o el
mapa Albedo (si existe).
La siguiente imagen muestra la escena de calibración de Unity 5 con cuatro esferas de plástico
muy pulidas. De izquierda a derecha, su transparencia se incrementa. La última esfera es
totalmente transparente, pero conserva todos los efectos añadidos de PBR:
Objetos de decoloración
A veces, desea que un objeto desaparezca completamente con un efecto de atenuación. En
este caso, las reflexiones especulares y la refracción y reflexión de Fresnel también deberían
desaparecer. Cuando un objeto de desvanecimiento es totalmente transparente, también
debe ser invisible. Para ello, realice los pasos siguientes:
1. En la pestaña Inspector del material, establezca el modo de renderizado en Fade.
2. Como antes, use el canal alfa del color o mapa Albedo para determinar la
transparencia final.
La siguiente imagen muestra las esferas de desvanecimiento. Resulta claro de la imagen que
los efectos PBR se desvanecen con la esfera también. Como se puede ver en la siguiente
imagen, la última de la derecha es casi invisible:
Este modo de renderizado funciona mejor para objetos no realistas, como hologramas, rayos
láser, luces falsas, fantasmas y efectos de partículas.
Ver también
Los ejemplos de esta receta se han creado utilizando el Unity 5 ShaderEscena de
calibración, que está disponible gratuitamente en la
Https://www.assetstore.unity3d.com/en/#!/content/25422.
Cómo hacerlo…
Cómo hacerlo…
Si se han seguido correctamente los pasos anteriores, debe tener un quad en el centro de su
escena, cerca de una sonda de reflexión. Para hacerla en un espejo, algunos cambios
Necesitan hacerse:
1. Cambie el sombreado del material a Estándar y su Modo de renderizado a Opaco.
2. Cambie sus propiedades metálicas y suavidad a una. Usted debe ver el material que
refleja el cielo más claramente.
3. Seleccione la sonda de reflexión y cambie su tamaño y origen de la sonda hasta que
esté delante del cuadrángulo y que encierre todos los objetos que desee reflejar.
4. Finalmente, cambie su Tipo a Real time. Asegúrese de que Culling Mask está
establecido en Everything.
Su sonda de reflexión debe estar configurada, como se muestra en la siguiente imagen:
Si su sonda se utiliza para un espejo real, debe comprobar el indicador de proyección de caja.
Si se utiliza para otras superficies reflectantes, como piezas brillantes de mesas de metal o de
vidrio, puede desmarcarla.
Cómo funciona…
Cuando un sombreador quiere información sobre su entorno, por lo general se proporciona en
la estructura llamada mapas de cubo. Se han mencionado brevemente en el Capítulo 1,
Creación de su primer Shader, como uno de los tipos de propiedades de sombreado, entre
Color, 2D, Float y Vector. Hablando francamente, los mapas de cubos son el equivalente 3D de
las texturas 2D; Representan una vista de 360 grados del mundo, como se ve desde un punto
central. Unity 5 visualiza mapas de cubos con una proyección esférica, como se ve en la
siguiente imagen:
Ver también
Cuando los mapas de cubo se adjuntan con una cámara, se les conoce como skyboxes, ya que
se utilizan para proporcionar una forma de reflejar el cielo. Pueden usarse para reflejar
geometrías que no están en la escena real, como nebulosas, nubes, estrellas, etc. La razón por
la que se llaman mapas de cubo es debido a la forma en que se crean: un mapa de cubo se
compone de seis texturas diferentes, cada uno unido a la cara de un cubo. Puede crear
manualmente un mapa de cubos o delegarlo en una sonda de reflexión. Usted puede imaginar
una sonda de reflexión como una colección de seis cámaras, la creación de un mapa de 360 de
la zona circundante. Esto también le da una idea de por qué las sondas son tan caras. Al crear
uno en nuestra escena, permitimos que la Unidad sepa qué objetos están alrededor del
espejo. Si necesita más superficies reflectantes, puede agregar varias sondas. No necesita más
acción para que las sondas de flexión funcionen. Los Shaders Estándar los utilizarán
automáticamente. Debería notar que cuando se configuran en Real time, representan su mapa
de cubos al principio de cada fotograma. Hay un truco para hacer esto más rápido; Si sabes
que parte de la geometría que quieres reflejar no se mueve, puedes hornear la reflexión. Esto
significa que Unity puede calcular la reflexión antes de iniciar el juego, lo que permite cálculos
más precisos (y computacionalmente caros). Para ello, su sonda de reflexión debe establecerse
en Baked y funcionará sólo para objetos que estén marcados como estáticos. Los objetos
estáticos no pueden moverse o cambiar, lo que los hace perfectos para terrenos, edificios y
accesorios. Cada vez que se mueve un objeto estático, Unity regenerará los mapas de cubo
para sus sondas de reflexión al horno. Esto puede llevar de unos minutos a varias horas. Usted
puede mezclar Realtime y Baked sondas para aumentar el realismo de su juego. Las sondas al
horno proporcionarán reflexiones ambientales de muy alta calidad, mientras que las de tiempo
real se pueden utilizar para mover objetos como coches o espejos. Las siguientes luces de
hornear en su receta escena le explicará en detalle cómo funciona la cocción ligera.
Ver también
Si está interesado en aprender más acerca de las sondas de reflexión, debe consultar estos
enlaces: Unity 5 manual about Reflection Probe:
http://docs.unity3d.com/Manual/class-ReflectionProbe.html
Preparándose
La cocción ligera requiere que usted tenga una escena lista. Debe tener geometrías y,
obviamente, luces. Para esta receta, dependeremos de las características estándar de Unity
para que no haya necesidad de crear shaders o materiales adicionales. Para un mejor control,
es posible que desee acceder a la ventana de iluminación. Si no lo ve, seleccione Ventana |
Iluminación desde el menú y acoplarlo donde es más conveniente para usted.
Cómo hacerlo…
La cocción ligera requiere alguna configuración manual. Hay tres pasos esenciales, pero
independientes, que usted necesita tomar. Configuración de la geometría estática Estos pasos
deben seguirse para la configuración:
1. Identifique todos los objetos de su escena que no cambian de posición, tamaño y
material. Los candidatos posibles son edificios, paredes, terrenos, accesorios, árboles y
otros.
2. Seleccione estos objetos y marque el cuadro Estática de la ficha Inspector, como se
muestra en la siguiente imagen. Si alguno de los objetos seleccionados tiene hijos, Unity
le preguntará si desea que se consideren estáticos también. Si cumplen con los
requisitos (posición fija, tamaño y material), seleccione Sí, cambie los niños en el cuadro
emergente:
3. Si una luz califica como un objeto estático pero ilumina la geometría no estática,
Que su propiedad Baking está establecida en Mixed. Si sólo afectará a objetos estáticos,
establézcalo en Baked.
Configuración de las sondas de luz
Hay objetos en tu juego que se moverán, como el personaje principal, los enemigos y los otros
personajes no jugables (NPCs). Si entran en una región estática que está iluminada, es posible
que desee rodearla con sondas de luz. Para ello, siga los pasos indicados:
1. En el menú, vaya a GameObject | Luz | Grupo de sondas de luz. Un nuevo objeto
llamado Light Probe Group aparecerá en Jerarquía.
1. Una vez seleccionadas, aparecerán cuatro esferas interconectadas. Haz clic y muévelas
por la escena para que encierren la región estática en la que pueden entrar tus
personajes. La siguiente imagen muestra un ejemplo de cómo se pueden usar sondas
de luz para encerrar el volumen de un espacio de oficina estático:
Decidir dónde y cuándo usar las sondas de luz es un problema crítico; Más información sobre
esto se puede encontrar en la sección Cómo funciona ....
Hornear las luces
Para hornear las luces, siga los pasos indicados:
1. Para finalmente cocer las luces, abra la ventana de iluminación y seleccione su pestaña
Lightmaps.
2. Si la casilla de verificación Auto está activada, Unity ejecutará automáticamente el
proceso de cocción en segundo plano. Si no, haga clic en Construir.
Nota
La cocción ligera puede tardar varias horas incluso en una escena relativamente pequeña. Si
está constantemente moviendo objetos estáticos o luces, Unity reiniciará el proceso desde
cero, causando una severa desaceleración en el editor. Puede desmarcar la casilla de
verificación Auto de la opción Iluminación | Lightmaps para evitar esto para que pueda decidir
cuándo iniciar el proceso manualmente.
Cómo funciona…
La parte más complicada de la representación es el transporte ligero. Durante esta fase, la GPU
calcula cómo los rayos de luz rebotan entre objetos. Si un objeto y sus luces no se mueven,
este cálculo se puede hacer sólo una vez que nunca cambiará durante el juego. Marcar un
objeto como estático es cómo le está diciendo a Unity que se puede hacer una optimización de
este tipo. Hablando francamente, light baking se refiere al proceso de calcular la iluminación
global de un objeto estático y guardarlo en lo que se llama un mapa de luz. Una vez terminada
la cocción, los mapas de luz se pueden ver en la pestaña de Lightmaps de la ventana de
iluminación:
La cocción ligera tiene un gran costo: la memoria. De hecho, cada superficie está retextured de
modo que ya incluye su condición de iluminación. Imaginemos que tienes un bosque de
árboles, todos compartiendo la misma textura. Una vez que se hacen estáticos, cada árbol
tendrá su propia textura. La cocción ligera no sólo aumenta el tamaño de su juego, sino que
puede tomar mucha memoria de textura si se usa indiscriminadamente. El segundo aspecto
introducido en esta receta es la luz de sondeo. La cocción ligera produce resultados de altísima
calidad para geometrías estáticas, pero no funciona en objetos en movimiento. Si su personaje
está entrando en una región estática, puede verse de alguna manera separado del entorno. Su
sombreado no coincide con el entorno, resultando en un resultado estéticamente
desagradable. Otros objetos, como los procesadores de malla de piel, no recibirán iluminación
global incluso si están estáticos. Las luces de cocción en tiempo real no son posibles, aunque
las sondas de luz ofrecen una alternativa efectiva. Cada sonda de luz mide la iluminación global
en un punto específico del espacio. Un grupo de sonda de luz puede tomar muestras de varios
puntos en el espacio, lo que permite interpolar la iluminación global dentro de un volumen
específico. Esto nos permite proyectar una mejor luz sobre objetos en movimiento, incluso a
pesar de que la iluminación global ha sido calculada sólo por unos pocos puntos. Es importante
recordar que las sondas de luz necesitan encerrar un volumen para poder trabajar. Es mejor
colocar sondas de luz en regiones donde hay un cambio repentino en la condición de luz. De
forma similar a los mapas de luz, las sondas consumen memoria y deben colocarse
sabiamente; Recuerde que existen sólo para la geometría no estática. Incluso usando sondas
de luz, hay algunos aspectos que la iluminación global de Unity no puede capturar. Los objetos
no estáticos, por ejemplo, no pueden reflejar luz sobre otros objetos.
Ver también
Puede leer más sobre las sondas de luz en
Http://docs.unity3d.com/Manual/LightProbes.html.
5. Funciones Vertex
Introducción
En el Capítulo 1, Creando su primer Shader, explicamos que los modelos 3D no son sólo una
colección de triángulos. Cada vértice puede contener datos que son esenciales para
representar correctamente el modelo. Este capítulo explorará cómo acceder a esta
información para usarla en un sombreado. También exploraremos en detalle cómo la
geometría de un objeto puede ser deformada simplemente usando código Cg.
Preparándose
Para escribir este shader, necesitamos preparar algunos activos. Los siguientes pasos nos
configurarán para crear este Vertex Shader:
1. Para ver los colores de un vértice, necesitamos tener un modelo que tenga el color
aplicado a sus vértices. Si bien podría utilizar Unity para aplicar colores, tendría que
escribir una herramienta para permitir que un individuo aplicar los colores o escribir
algunos scripts para lograr la aplicación de color. En el caso de esta receta, simplemente
utilizamos Maya para aplicar los colores a nuestro modelo. Este modelo está disponible
en la página de soporte del libro en https://www.packtpub.com/books/content/support.
2. Cree una nueva escena y coloque el modelo importado en la escena.
3. Cree un nuevo sombreador y material. Cuando haya terminado, asigne el sombreado al
material y luego al material al modelo importado.
Cómo hacerlo…
Con nuestra escena, shader y material creado y listo para comenzar, podemos comenzar a
escribir el código para nuestro shader. Inicie el sombreado haciendo doble clic en él en la
pestaña Proyecto del editor Unity. Realice los pasos siguientes:
1. Como estamos creando un shader muy simple, no necesitaremos incluir ninguna
propiedad en nuestro bloque de propiedades. Todavía incluiremos un color de tinte
global, sólo para mantener la coherencia con los otros shaders de este libro.
Introduzca el código siguiente en el bloque de propiedades de su sombreado:
Properties
{
_MainTint("Global Color Tint", Color) = (1,1,1,1)
}
2. Este siguiente paso le dice a Unity que vamos a incluir una función de vértice en
nuestro sombreado:
CGPROGRAM
#pragma surface surf Lambert vertex:vert
3. Como de costumbre, si hemos incluido propiedades en nuestro bloque de
propiedades, debemos asegurarnos de crear una variable correspondiente en nuestra
declaración CGPROGRAM. Escriba el siguiente código justo debajo de la instrucción
#pragma:
float4 _MainTint;
4. Ahora volvemos nuestra atención a la estructura de entrada. Necesitamos agregar una
nueva variable para que nuestra función surf () pueda acceder a los datos que nos
proporciona nuestra función vert ():
struct Input
{
float2 uv_MainTex;
float4 vertColor;
};
5. Ahora, podemos escribir nuestra función simple vert () para obtener acceso a los
colores almacenados en cada vértice de nuestra malla:
void vert(inout appdata_full v, out Input o)
{
o.vertColor = v.color;
}
6. Finalmente, podemos usar los datos de color de vértice de nuestra estructura de
entrada para ser asignados a los parámetros de o.Albedo en la estructura de
SurfaceOutput incorporada:
void surf (Input IN, inout SurfaceOutput o){
o.Albedo = IN.vertColor.rgb * _MainTint.rgb;
}
7. Con nuestro código completado, podemos volver a entrar en el editor de Unity y dejar
que el shader compile. Si todo va bien, deberías ver algo similar a la siguiente captura
de pantalla:
Cómo funciona…
Cómo funciona…
Unity nos proporciona una forma de acceder a la información de vértice del modelo al que
está conectado un sombreador. Esto nos da el poder de modificar cosas como la posición y el
color de los vértices. Con esta receta, hemos importado una malla de Maya (aunque casi
cualquier aplicación de software 3D se puede utilizar), donde los colores de vértice se
agregaron a Verts. Observará que al importar el modelo, el material predeterminado no
mostrará los colores de vértice. En realidad tenemos que escribir un shader para extraer el
color del vértice y mostrarlo en la superficie del modelo. Unity nos proporciona una gran
cantidad de funcionalidad incorporada al usar Surface Shaders, que hacen que el proceso de
extracción de esta información de vértice sea rápido y eficiente. Nuestra primera tarea es
decirle a Unity que usaremos una función de vértice al crear nuestro shader. Lo hacemos
añadiendo el parámetro vértice: vert a la instrucción #pragma de CGPROGRAM. Esto
automáticamente hace que Unity busque una función vertex llamada vert () cuando vaya a
compilar el hader. Si no encuentra uno, Unity lanzará un error de compilación y le pedirá que
agregue una función vert () a su shader. Esto nos lleva al siguiente paso. Tenemos que
realmente codificar la función vert (), como se ve en el paso 5. Al tener esta función, podemos
acceder a la estructura de datos integrada llamada appdata_full. Esta estructura incorporada
es donde se almacena la información de vértice. Entonces, extraemos la información de color
del vértice pasándola a nuestra estructura de entrada añadiendo el código, o.vertColor =
v.color. La variable o representa nuestra estructura de entrada y la variable v es nuestros datos
de vértice appdata_full. En este caso, simplemente tomamos la información de color de la
estructura appdata_full y la colocamos en nuestra estructura de entrada. Una vez que el color
del vértice está en nuestra estructura de entrada, podemos usarlo en nuestra función surf ().
En el caso de esta receta, simplemente aplicamos el color al parámetro o.Albedo a la
estructura SurfaceOutput incorporada.
Hay más…
También se puede acceder a un cuarto componente a partir de los datos de color verde. Si
observa, la variable vertColor que declaramos en la estructura de entrada es del tipo float4.
Esto significa que también estamos pasando el valor alfa de los colores de vértice. Sabiendo
esto, puede utilizarlo para su ventaja con el fin de almacenar un color de vértice cuarto para
realizar efectos como la transparencia o darse una más máscara para mezclar dos texturas.
Depende de usted y de su producción determinar si realmente necesita usar el cuarto
componente, pero vale la pena mencionarlo aquí. Con Unity 5, ahora tenemos la capacidad de
dirigir shaders a DirectX 11. Esto es genial, pero significa que el proceso de compilación para
los shaders es ahora un poco pickier. Esto significa que necesitamos incluir una línea más de
código en nuestro shader para inicializar la salida de la información de vértice correctamente.
El siguiente código muestra cómo se ve el código de función de vértice, si está utilizando
DirectX 11 en su sombreado:
void vert(inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input, o);
o.vertColor = v.color;
}
Al incluir esta línea de código, el Vertex Shader no lanzará ninguna advertencia, que dice que
no se compilará correctamente en DirectX 11.
Preparándose
Vamos a reunir nuestros activos juntos para que podamos crear el código para nuestro Vertex
Shader:
1. Cree una nueva escena y coloque una malla plana en el centro de la escena.
2. Luego cree un nuevo shader y material.
3. Finalmente, asignar el sombreador al material y al material a la malla plana.
Su escena debe ser similar a la siguiente captura de pantalla:
Cómo hacerlo…
Cómo funciona…
Con nuestra escena lista para salir, vamos a hacer doble clic en nuestro shader recién creado
para abrirlo en MonoDevelop:
1. Comencemos con nuestro sombreado completando el bloque Propiedades:
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_tintAmount ("Tint Amount", Range(0,1)) = 0.5
_ColorA ("Color A", Color) = (1,1,1,1)
_ColorB ("Color B", Color) = (1,1,1,1)
_Speed ("Wave Speed", Range(0.1, 80)) = 5
_Frequency ("Wave Frequency", Range(0, 5)) = 2
_Amplitude ("Wave Amplitude", Range(-1, 1)) = 1
}
2. Ahora necesitamos decirle a Unity que vamos a usar una función de vértice agregando
lo siguiente a la sentencia #pragma:
CGPROGRAM
#pragma surface surf Lambert vertex:vert
3. Para acceder a los valores que nos han dado nuestras propiedades, debemos declarar
una variable correspondiente en nuestro bloque CGPROGRAM:
sampler2D _MainTex;
float4 _ColorA;
float4 _ColorB;
float _tintAmount;
float _Speed;
float _Frequency;
float _Amplitude;
float _OffsetVal;
4. Usaremos la modificación de la posición del vértice como un color verde también. Esto
nos permitirá matizar nuestro objeto:
struct Input
{
float2 uv_MainTex;
float3 vertColor;
};
5. En este punto, podemos realizar nuestra modificación de vértices usando una onda
senoidal y función de vértice. Introduzca el código siguiente después de la estructura
de entrada:
void vert(inout appdata_full v, out Input o)
{
float time = _Time * _Speed;
float waveValueA = sin(time + v.vertex.x * _Frequency) * _Amplitude;
v.vertex.xyz = float3(v.vertex.x, v.vertex.y + waveValueA,v.vertex.z);
v.normal = normalize(float3(v.normal.x + waveValueA, v.normal.y,v.normal.z));
o.vertColor = float3(waveValueA,waveValueA,waveValueA);
}
6. Finalmente, completaremos nuestro shader realizando una función lerp () entre dos
colores para que podamos teñir los picos y valles de nuestra nueva malla, modificados
por nuestra función de vértice:
void surf (Input IN, inout SurfaceOutput o)
{
half4 c = tex2D (_MainTex, IN.uv_MainTex);
float3 tintColor = lerp(_ColorA, _ColorB, IN.vertColor).rgb;
o.Albedo = c.rgb * (tintColor * _tintAmount);
o.Alpha = c.a;
}
Después de completar el código de su shader, vuelva a Unity y deje que el shader compile. Una
vez compilado, debería ver algo similar al siguiente creenshot:
Cómo funciona…
Este shader particular utiliza el mismo concepto de la última receta, excepto que esta vez,
estamos modificando las posiciones de los vértices en la malla. Esto es realmente útil si no
desea montar objetos simples, como un indicador, y luego animarlos utilizando una estructura
esquelética o una jerarquía de transformaciones. Simplemente creamos un valor de onda
senoidal utilizando la función sin () que está incorporada en el lenguaje Cg. Después de calcular
este valor, lo añadimos al valor y de cada posición de vértice, creando un efecto parecido a una
onda. También hicimos un poco de modificación a la normal en la malla sólo para darle un
sombreado más realista basado en el valor de onda sinusoidal. Verá lo fácil que es realizar
efectos de vértice más complejos mediante la utilización de los parámetros de vértice
incorporados que Surface Shaders nos proporciona.
Shader "Custom/onda" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_tintAmount ("Tint Amount", Range(0,1)) = 0.5
_ColorA ("Color A", Color) = (1,1,1,1)
_ColorB ("Color B", Color) = (1,1,1,1)
_Speed ("Wave Speed", Range(0.1, 80)) = 5
_Frequency ("Wave Frequency", Range(0, 5)) = 2
_Amplitude ("Wave Amplitude", Range(-1, 1)) = 1
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Lambert vertex:vert
sampler2D _MainTex;
float4 _ColorA;
float4 _ColorB;
float _tintAmount;
float _Speed;
float _Frequency;
float _Amplitude;
float _OffsetVal;
struct Input {
float2 uv_MainTex;
float3 vertColor;
};
void vert(inout appdata_full v, out Input o){
UNITY_INITIALIZE_OUTPUT(Input,o);
float time = _Time * _Speed;
float waveValueA = sin(time + v.vertex.x * _Frequency) * _Amplitude;
v.vertex.xyz = float3(v.vertex.x, v.vertex.y + waveValueA,v.vertex.z + waveValueA);
v.normal = normalize(float3(v.normal.x + waveValueA, v.normal.y,v.normal.z));
o.vertColor = float3(waveValueA,waveValueA,waveValueA);
}
ENDCG
}
FallBack "Diffuse"
}
Cómo hacerlo…
Cómo hacerlo…
Para crear este efecto, comience modificando el shader duplicado:
1. Comencemos agregando una propiedad a nuestro shader, que se usará para modular
su extrusión. El rango presentado aquí va de -1 a +1, pero puede que tenga que
ajustarlo de acuerdo a sus propias necesidades:
_Amount ("Extrusion Amount", Range(-1,1)) = 0
2. Agrupar la propiedad con su respectiva variable:
float _Amount;
3. Cambie la directiva # pragma para que ahora utilice un modificador de vértice. Puede
hacerlo añadiendo vértice: nombre_función al final del mismo. En nuestro caso, hemos
llamado la función, vert:
#pragma surface surf Lambert vertex:vert
4. Agregue el siguiente modificador de vértice:
void vert (inout appdata_full v) {
v.vertex.xyz += v.normal * _Amount;
}
5. El sombreador está listo; Puede utilizar el control deslizante Extrusión en la pestaña
Inspector del material para que su modelo sea más delgado o más rechoncho.
Cómo funciona…
Surface Shaders funciona en dos pasos. En todos los capítulos anteriores, sólo exploramos su
último: la función superficial. Hay otra función que se puede utilizar: el modificador de vértice.
Toma la estructura de datos de un vértice (que se llama generalmente appdata_full) y aplica
una transformación a él. Esto nos da la libertad de hacer prácticamente todo con la geometría
de nuestro modelo. Señalamos a la unidad de procesamiento gráfico (GPU) que dicha función
existe añadiendo vértice: vert a la directiva # pragma del Surface Shader. Puede consultar el
Capítulo 6, Fragment Shaders y Grab Passes, para saber cómo se pueden definir los
modificadores de vértices en un Vertex y Shader de fragmentos.
Una de las técnicas más simples, pero eficaces, que se pueden utilizar para alterar la geometría
de un modelo se denomina extrusión normal. Funciona proyectando un vértice a lo largo de su
dirección normal. Esto se hace con la siguiente línea de código:
v.vertex.xyz += v.normal * _Amount;
La posición de un vértice se desplaza por _Amount unidades hacia el vértice normal. Si
_Amount llega demasiado alto, los resultados pueden ser bastante desagradables. Con valores
más pequeños, sin embargo, puede agregar una gran cantidad de variaciones a sus modelos.
Hay más…
Si tienes varios enemigos y quieres que cada uno tenga su propio peso, tienes que crear un
material diferente para cada uno de ellos. Esto es necesario ya que los materiales
normalmente se comparten entre los modelos y el cambio uno cambiará todos ellos. Hay
varias maneras en que usted puede hacer esto; El más rápido es crear un script que
automáticamente lo haga por usted. El siguiente script, una vez unido a un objeto con un
Renderer, duplicará su primer material y establecerá automáticamente la propiedad _Amount:
using UnityEngine;
public class NormalExtruder : MonoBehaviour {
[Range(-0.0001f, 0.0001f)]
public float amount = 0;
// Use this for initialization
void Start () {
Material material = GetComponent<Renderer>().sharedMaterial;
Material newMaterial = new Material(material);
newMaterial.SetFloat("_Amount", amount);
GetComponent<Renderer>().material = newMaterial;
}
}
struct Input {
float2 uv_MainTex;
};
float _Amount;
void vert (inout appdata_full v) {
v.vertex.xyz += v.normal * _Amount;
}
sampler2D _MainTex;
void surf(Input IN, inout SurfaceOutputStandard o) {
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
}
FallBack "Diffuse"
}
Preparándose
Este efecto se basa puramente en shaders. Necesitaremos lo siguiente:
1. Cree un nuevo shader para el efecto de nieve.
2. Cree un nuevo material para el sombreador.
3. Asigne el material recién creado al objeto que desea que esté nevado.
Cómo hacerlo…
Para crear un efecto nevado, abra su sombreador y realice los siguientes cambios:
1. Sustituya las propiedades del sombreador por las siguientes:
_MainColor("Main Color", Color) = (1.0,1.0,1.0,1.0)
_MainTex("Base (RGB)", 2D) = "white" {}
_Bump("Bump", 2D) = "bump" {}
_Snow("Level of snow", Range(1, -1)) = 1
_SnowColor("Color of snow", Color) = (1.0,1.0,1.0,1.0)
_SnowDirection("Direction of snow", Vector) = (0,1,0)
_SnowDepth("Depth of snow", Range(0,1)) = 0
2. Completarlos con sus variables relativas:
sampler2D _MainTex;
sampler2D _Bump;
float _Snow;
float4 _SnowColor;
float4 _MainColor;
float4 _SnowDirection;
float _SnowDepth;
3. Reemplace la estructura de entrada por la siguiente:
struct Input {
float2 uv_MainTex;
float2 uv_Bump;
float3 worldNormal;
INTERNAL_DATA
};
4. Sustituya la función de superficie por la siguiente. Coloreará las partes nevadas de el
modelo blanco:
void surf(Input IN, inout SurfaceOutputStandard o) {
half4 c = tex2D(_MainTex, IN.uv_MainTex);
o.Normal = UnpackNormal(tex2D(_Bump, IN.uv_Bump));
if (dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) >=
_Snow)
o.Albedo = _SnowColor.rgb;
else
o.Albedo = c.rgb * _MainColor;
o.Alpha = 1;
}
5. Configure la directiva # pragma para que utilice modificadores de vértice:
#pragma surface surf Standard vertex:vert
6. Añada los siguientes modificadores de vértices, que expulsan los vértices cubiertos de
nieve:
void vert(inout appdata_full v) {
float4 sn = mul(UNITY_MATRIX_IT_MV, _SnowDirection);
if (dot(v.normal, sn.xyz) >= _Snow)
v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow;
}
Ahora puede usar la pestaña Inspector de materiales para seleccionar la cantidad de su
modelo que va a cubrir y la densidad de la nieve.
Cómo funciona…
Este shader funciona en dos pasos.
Colorear la superficie
El primero altera el color de los triángulos que están mirando hacia el cielo. Afecta a todos los
triángulos con una dirección normal similar a _SnowDirection. Como se ha visto antes en el
Capítulo 3, Comprensión de los Modelos de Iluminación, la comparación de los vectores
unitarios se puede hacer usando el producto punto. Cuando dos vectores son ortogonales, su
producto punto es cero; Es uno (o menos uno) cuando están paralelos entre sí. La propiedad
_Snow se utiliza para decidir cómo alineados deben ser considerados frente al cielo. Si observa
de cerca la función de superficie, puede ver que no estamos salpicando la dirección normal y la
dirección de la nieve directamente. Esto se debe a que normalmente se definen en un espacio
diferente. La dirección de la nieve se expresa en coordenadas del mundo, mientras que las
normales del objeto son generalmente relativas al modelo sí mismo. Si giramos el modelo, sus
normales no cambiarán, lo cual no es lo que queremos. Para arreglar esto, necesitamos
convertir las normales de sus coordenadas de objeto a coordenadas del mundo. Esto se hace
con la función WorldNormalVector (), como se ve en el código siguiente:
if (dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) >= _Snow)
o.Albedo = _SnowColor.rgb;
else
o.Albedo = c.rgb * _MainColor;
Este sombreado simplemente colorea el modelo blanco; Una más avanzada debería inicializar
la estructura SurfaceOutputStandard con texturas y parámetros de un material de nieve
realista.
Alteración de la geometría
El segundo efecto de este sombreador altera la geometría para simular la acumulación de
nieve. En primer lugar, identificamos qué triángulos se han coloreado de blanco probando la
misma condición utilizada en la función superficial. Esta vez, por desgracia, no podemos
confiar en WorldNormalVector () ya que la estructura SurfaceOutputStandard todavía no se ha
inicializado en El modificador de vértices. Utilizamos este otro método en su lugar, que
convierte _SnowDirection a las coordenadas del objeto:
float4 sn = mul(UNITY_MATRIX_IT_MV, _SnowDirection);
Entonces, podemos extruir la geometría para simular la acumulación de nieve:
if (dot(v.normal, sn.xyz) >= _Snow)
v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow;
Una vez más, este es un efecto muy básico. Uno podría utilizar un mapa de la textura para
controlar la acumulación de la nieve más exactamente o dar una mirada peculiar, desigual.
Ver también
Si necesitas efectos de nieve de alta calidad y accesorios para tu juego, también puedes
consultar estos recursos en Unity Asset Store:
Winter Suite ($ 30): Una versión mucho más sofisticada del sombreador de nieve presentado
En esta receta se puede encontrar en
Https://www.assetstore.unity3d.com/en/#!/content/13927
Winter Pack ($ 60): Un conjunto muy realista de accesorios y materiales para snowy
Pueden encontrarse en
Https://www.assetstore.unity3d.com/en/#!/content/13316
Shader "Custom/shaderNieve" {
Properties {
_MainColor("Main Color", Color) = (1.0,1.0,1.0,1.0)
_MainTex("Base (RGB)", 2D) = "white" {}
_Bump("Bump", 2D) = "bump" {}
_Snow("Level of snow", Range(1, -1)) = 1
_SnowColor("Color of snow", Color) = (1.0,1.0,1.0,1.0)
_SnowDirection("Direction of snow", Vector) = (0,1,0)
_SnowDepth("Depth of snow", Range(0,1)) = 0
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard vertex:vert
sampler2D _MainTex;
sampler2D _Bump;
float _Snow;
float4 _SnowColor;
float4 _MainColor;
float4 _SnowDirection;
float _SnowDepth;
struct Input {
float2 uv_MainTex;
float2 uv_Bump;
float3 worldNormal;
INTERNAL_DATA
};
ENDCG
}
FallBack "Diffuse"
}
Implementación de una explosión volumétrica
El arte del desarrollo del juego es un compromiso inteligente entre el realismo y la eficiencia.
Esto es particularmente cierto para las explosiones; Están en el corazón de muchos juegos, sin
embargo, la física detrás de ellos es a menudo más allá del poder computacional de las
máquinas modernas. Las explosiones son, esencialmente, nada más que bolas de gas muy
calientes; Por lo tanto, la única manera de simularlas correctamente es mediante la
integración de una simulación de fluidos en su juego. Como se puede imaginar, esto es
imposible para la aplicación en tiempo de ejecución, y muchos juegos simulan simplemente
con partículas. Cuando un objeto explota, es común simplemente instanciar muchas partículas
de fuego, humo y escombros que, juntas, pueden lograr resultados creíbles. Este enfoque, por
desgracia, no es muy realista y es fácil de detectar. Hay una técnica intermedia que se puede
utilizar para lograr un efecto mucho más realista: explosiones volumétricas. La idea detrás de
este concepto es que las explosiones no se tratan más como un manojo de partículas; Están
evolucionando objetos 3D, no sólo texturas 2D planas.
Preparándose
Inicie esta receta con los siguientes pasos:
1. Cree un nuevo shader para este efecto.
2. Cree un nuevo material para alojar el sombreador.
3. Conecte el material a una esfera. Puedes crear uno directamente desde el editor,
navegando a GameObject | Objeto 3D | Esfera.
Nota
Esta receta funciona bien con la Unity Sphere estándar, pero si necesita grandes
explosiones, puede que tenga que usar una esfera de mayor polietileno. De hecho, una
función de vértice sólo puede modificar los vértices de una malla. Todos los demás
puntos serán interpolados usando las posiciones de los vértices cercanos. Menos
vértices significan una resolución más baja para sus explosiones.
4. Para esta receta, también necesitará una textura de rampa que tiene, en un gradiente,
todos los colores que sus explosiones tendrán. Puede crear una textura como la
siguiente con GIMP o Photoshop:
5. Una vez que tengas la imagen, importa a Unity. A continuación, desde su inspector,
asegúrese de que el modo de filtro esté ajustado en modo bilineal y de ajuste en
abrazadera. Estos dos ajustes aseguran que la textura de la rampa se muestre sin
problemas.
6. Por último, usted necesitará una textura ruidosa. Puede buscar en Internet texturas de
ruido libremente disponibles. Los más usados son generados usando ruido de Perlin.
Cómo hacerlo…
Este efecto funciona en dos pasos: una función de vértice para cambiar la geometría y la
función de superficie para darle el color correcto. Los pasos son los siguientes:
1. Agregue las propiedades siguientes al sombreador:
_RampTex("Color Ramp", 2D) = "white" {}
_RampOffset("Ramp offset", Range(-0.5,0.5))= 0
_NoiseTex("Noise tex", 2D) = "gray" {}
_Period("Period", Range(0,1)) = 0.5
_Amount("_Amount", Range(0, 1.0)) = 0.1
_ClipRange("ClipRange", Range(0,1)) = 1
2. Agregue sus variables relativas para que el código Cg del sombreador pueda acceder a
ellas:
sampler2D _RampTex;
half _RampOffset;
sampler2D _NoiseTex;
float _Period;
half _Amount;
half _ClipRange;
3. Cambie la estructura de entrada para que reciba los datos UV de la textura de la
rampa:
struct Input {
float2 uv_NoiseTex;
};
4. Agregue la siguiente función de vértice:
void vert(inout appdata_full v) {
float3 disp = tex2Dlod(_NoiseTex, float4(v.texcoord.xy,0,0));
float time = sin(_Time[3] *_Period + disp.r*10);
v.vertex.xyz += v.normal * disp.r * _Amount * time;
}
5. Añada la siguiente función de superficie:
void surf(Input IN, inout SurfaceOutput o) {
float3 noise = tex2D(_NoiseTex, IN.uv_NoiseTex);
float n = saturate(noise.r + _RampOffset);
clip(_ClipRange - n);
half4 c = tex2D(_RampTex, float2(n,0.5));
o.Albedo = c.rgb;
o.Emission = c.rgb*c.a;
}
6. Especificamos la función de vértice en la directiva # pragma, añadiendo el parámetro
nolightmap para evitar que Unity agregue iluminaciones realistas a nuestra explosión:
#pragma surface surf Lambert vertex:vert nolightmap
7. El último paso es seleccionar el material, y de su inspector, adjuntar las dos texturas en
las ranuras relativas. Este es un material animado, lo que significa que evoluciona
a través del tiempo. Puede ver el cambio de material en el editor haciendo clic en
Materiales animados de la ventana Escena:
Cómo funciona…
Si estás leyendo esta receta, ya deberías estar familiarizado con cómo funcionan los
modificadores de superficie y los modificadores de vértices. La idea principal detrás de este
efecto es alterar la geometría de la esfera de una manera aparentemente caótica,
exactamente como ocurre en una explosión real. La siguiente imagen muestra cómo se verá tal
explosión dentro del editor. Puede ver que la malla original ha sido fuertemente deformada:
La función de vértice es una variante de la técnica denominada extrusión normal introducida
en la receta de Extrusión de sus modelos de este capítulo. La diferencia aquí es que la cantidad
de la extrusión se determina tanto por la textura de tiempo como de ruido.
Nota
Cuando necesita un número aleatorio en Unity, puede confiar en la función Random.Range ().
No existe una forma estándar de obtener números aleatorios en un shader, por lo que la forma
más fácil es probar una textura de ruido.
No hay una forma estándar de hacerlo, por lo que tome esto como un ejemplo solamente:
float time = sin(_Time[3] *_Period + disp.r*10);
La variable _Time [3] incorporada se utiliza para obtener el tiempo actual desde el interior del
sombreado y el canal rojo de la dispersión de ruido disp.r se utiliza para asegurarse de que
cada vértice se mueve independientemente. La función sin () hace que los vértices suban y
bajen, simulando el comportamiento caótico de una explosión. Entonces, la extrusión normal
tiene lugar:
v.vertex.xyz += v.normal * disp.r * _Amount * time;
Usted debe jugar con estos números y variables hasta que encuentre un patrón de ovement
Que usted es feliz con. La última parte del efecto se consigue mediante la función superficial.
Aquí, la textura del ruido se utiliza para probar un color aleatorio de la textura de la rampa. Sin
embargo, hay dos aspectos más que vale la pena notar. La primera es la introducción de
_RampOffset. Su uso obliga a la explosión a muestrear colores desde el lado izquierdo o
derecho de la textura. Con valores positivos, la superficie de la explosión tiende a mostrar más
tonos grises; Exactamente lo que sucede cuando se está disolviendo. Puede usar _RampOffset
para determinar cuánto fuego o humo debe haber en su explosión. El segundo aspecto
introducido en la función superficial es el uso de clip (). Lo que clip () hace es clips (elimina)
píxeles de la canalización de renderizado. Cuando se invoca con un valor negativo, el píxel
actual no se dibuja. Este efecto es controlado por _ClipRange, que determina qué píxeles de
las explosiones volumétricas van a ser transparentes.
Controlando tanto _RampOffset como _ClipRange, tiene control total para determinar cómo se
comporta la explosión y se disuelve.
Hay más…
El shader presentado en esta receta hace que una esfera parezca una explosión. Si realmente
quieres usarlo, deberías asociarlo con algunas secuencias de comandos para sacarle el máximo
provecho. Lo mejor es crear un objeto de explosión y convertirlo en un prefabricado para que
pueda reutilizarlo cada vez que lo necesite. Puede hacerlo arrastrando la esfera de nuevo a la
ventana de proyecto. Una vez hecho esto, puede crear tantas explosiones como desee
utilizando la función Instantiate ().
Vale la pena notar, sin embargo, que todos los objetos con el mismo material comparten la
misma mirada. Si tiene múltiples explosiones al mismo tiempo, no debe usar el mismo
material. Cuando está instanciando una nueva explosión, también debe duplicar su material.
Usted puede hacer esto fácilmente con este pedazo de código:
GameObject explosion = Instantiate(explosionPrefab) as GameObject;
Renderer renderer = explosion.GetComponent<Renderer>();
Material material = new Material(renderer.sharedMaterial);
renderer.material = material;
Por último, si va a utilizar este sombreador de una manera realista, debe adjuntar un script que
cambie su tamaño, _RampOffset y _ClipRange según el tipo de explosión que desea volver a
crear.
Ver también
Mucho más se puede hacer para hacer las explosiones realistas. El enfoque presentado en esta
receta sólo crea un shell vacío; Dentro de él, la explosión está realmente vacía. Un truco fácil
para mejorar esto es crear partículas dentro de él. Sin embargo, sólo puede ir tan lejos con
esto. El corto, The Butterfly Effect (http://unity3d.com/pages/butterfly) creado por Unity
Technologies en colaboración con Passion Pictures y Nvidia, es el ejemplo perfecto. Se basa en
el mismo concepto de alterar la geometría de una esfera, pero lo hace con una técnica llamada
fundición de rayo de volumen. En pocas palabras, muestra la geometría como si estuviera
llena. Puede ver un ejemplo en la siguiente imagen:
El sombreado presentado aquí es muy simple, y se utilizará como base de partida para todos
los otros Vertex y Fragmento Shaders.
Preparándose
Para esta fórmula, necesitaremos un nuevo shader. Sigue estos pasos:
1. Cree un nuevo shader.
2. Cree un nuevo material y asigne el shader al mismo.
Cómo hacerlo…
En todos los capítulos anteriores, siempre hemos sido capaces de reajustar Shaders de
superficie. Esto ya no es el caso ya que los Shaders de Superficie y Fragmento son
estructuralmente diferentes. Necesitaremos los siguientes cambios:
1. Elimine todas las propiedades del shader, sustituyéndolas por las siguientes:
_Color ("Color", Color) = (1,0,0,1) // Red
_MainTex ("Base texture", 2D) = "white" {}
2. Elimine todo el código del bloque SubShader y reemplácelo por éste:
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
half4 _Color;
sampler2D _MainTex;
struct vertInput {
float4 pos : POSITION;
float2 texcoord : TEXCOORD0;
};
struct vertOutput {
float4 pos : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
vertOutput vert(vertInput input) {
vertOutput o;
o.pos = mul(UNITY_MATRIX_MVP, input.pos);
o.texcoord = input.texcoord;
return o;
}
half4 frag(vertOutput output) : COLOR{
half4 mainColour = tex2D(_MainTex, output.texcoord);
return mainColour * _Color;
}
ENDCG
}
Esto también será la base para todos los futuros Vertex y Fragment Shaders.
Cómo funciona…
Como su nombre indica, Vertex y Fragment Shaders trabajan en dos pasos. El modelo se pasa
primero a través de una función de vértice; El resultado se introduce entonces en una función
de fragmento. Ambas funciones se asignan mediante directivas pragma:
#pragma vertex vert
#pragma fragment frag
En este caso, se llaman simplemente vert y frag.
Conceptualmente hablando, los fragmentos están estrechamente relacionados con los píxeles;
El término fragmento se utiliza a menudo para referirse a la recopilación de datos necesarios
para dibujar un píxel. Esta es también la razón por la que Vertex y Fragment Shaders a menudo
se llaman Shaders de píxeles. La función de vértice toma los datos de entrada en una
estructura que se define como vertInput en el shader:
Hay más…
struct vertInput {
float4 pos : POSITION;
float2 texcoord : TEXCOORD0;
};
Su nombre es totalmente arbitrario, pero su contenido no lo es. Cada campo de estructura
debe estar decorado con una semántica vinculante. Esta es una característica de Cg que nos
permite marcar variables para que se inicialicen con ciertos datos, como vectores normales y
posición de vértice. La POSICIÓN semántica de enlace indica que cuando vertInput se
introduce en la función de vértice, pos contendrá la posición del vértice actual. Esto es similar
al campo de vértices de la estructura appdata_full en un Shader de superficie. La principal
diferencia es que pos se representa en coordenadas del modelo (relativo al objeto 3D), que
necesitamos convertir para ver las coordenadas manualmente (en relación con la posición en
la pantalla).
Nota
La función de vértice en un Surface Shader se utiliza para alterar la geometría del modelo
solamente. En un Vertex y Shader de fragmentos, en cambio, la función de vértice es necesario
para proyectar las coordenadas del modelo a la pantalla. La matemática detrás de esta
conversión está más allá del alcance de este capítulo. Sin embargo, esta transformación puede
lograrse multiplicando pos por una matriz especial proporcionada por Unity:
UNITY_MATRIX_MVP. A menudo se denomina matriz de proyección-proyección, y es esencial
encontrar la posición de un vértice en la pantalla:
vertOutput o;
o.pos = mul(UNITY_MATRIX_MVP, input.pos);
La otra información inicializada es textcoord, que utiliza la semántica de enlace TEXCOORD0
para obtener los datos UV de la primera textura. No se requiere ningún procesamiento
adicional y este valor se puede pasar directamente a la función de fragmento:
o.texcoord = input.texcoord;
Mientras que Unity inicializará vertInput para nosotros, somos responsables de la inicialización
de vertOutput. A pesar de esto, sus campos aún necesitan ser decorados con semántica
vinculante:
struct vertOutput {
float4 pos : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
Una vez que la función de vértice ha inicializado vertOutput, la estructura se pasa a la función
de fragmento. Este muestra la textura principal del modelo y lo multiplica por el color
proporcionado. Como puede ver, el Vertex y Shader de Fragmento no tiene conocimiento de
las propiedades físicas del material; Comparado con un Shader de superficie, trabaja más cerca
de la arquitectura de la unidad de procesamiento gráfico (GPU).
Hay más…
Uno de los aspectos más confusos de Vertex y Fragment Shaders es la semántica vinculante.
Hay muchos otros que puedes usar y su significado depende del contexto.
Semántica de entrada
La semántica de enlace en la siguiente tabla se puede utilizar en vertInput, que es la estructura
que Unity proporciona a la función de vértice. Los campos decorados con estas semánticas se
inicializarán automáticamente:
Semántica de encuadernación Description
POSITION, SV_POSITION La posición de un vértice en las coordenadas del mundo (espacio del objeto)
NORMAL La normal de un vértice, con relación al mundo (no a la cámara)
COLOR, COLOR0, DIFFUSE, SV_TARGET La información de color almacenada en el vértice
COLOR1, SPECULAR La información de color secundario almacenada en el vértice (generalmente el
especular)
TEXCOORD0, TEXCOORD1, …, TEXCOORDi Los i-ésimo datos UV almacenados en el vértice
Semántica de salida
Cuando se enlaza, la semántica se utiliza en vertOutput; No garantizan automáticamente que
los campos se inicializarán. Todo lo contrario; Es nuestra responsabilidad hacerlo. El
compilador hará todo lo posible para asegurarse de que los campos se inicializan con los datos
correctos:
Semántica de encuadernación Description
POSITION, SV_POSITION, HPOS La posición de un vértice en las coordenadas de la cámara (espacio de clip, de cero a
uno para cada dimensión)
COLOR, COLOR0, COL0, COL, El color primario delantero
SV_TARGET
COLOR1, COL1 El color secundario delantero
TEXCOORD0, TEXCOORD1, …, Los i-ésimo datos UV almacenados en el vértice
TEXCOORDi, TEXi
WPOS La posición, en píxeles, en la ventana (origen en la esquina inferior izquierda)
Si, por alguna razón, necesita un campo que contenga un tipo diferente de datos, puede
decorarlo con uno de los muchos datos TEXCOORD disponibles. El compilador no permitirá que
los campos queden sin decorar.
Ver también
Ver también
Puede consultar el Manual de referencia de NVIDIA para comprobar la semántica de enlace
disponible en Cg:
http://developer.download.nvidia.com/cg/Cg_3.1/Cg-3.1_April2012_ReferenceManual.pdf
Usando el pase de agarre
En la adición de transparencia a la receta PBR del Capítulo 4, Creación de casos de prueba y
escenarios de escritura para el desarrollo impulsado por el comportamiento en Symfony,
hemos visto cómo un material puede ser transparente. Incluso si un material transparente
puede dibujar sobre una escena, no puede cambiar lo que se ha dibujado debajo de ella. Esto
significa que los shaders transparentes no pueden crear distorsiones como las que
normalmente se ven en el vidrio o el agua. Para simularlos, necesitamos introducir otra técnica
llamada grab pass. Esto nos permite acceder a lo que se ha dibujado en la pantalla hasta el
momento para que un sombreador pueda usarlo (o alterarlo) sin restricciones. Para aprender a
usar los pases de agarre, crearemos un material que agarra lo que se muestra detrás de él y lo
dibuja de nuevo en la pantalla. Es un sombreador que, paradójicamente, utiliza varias
operaciones para no mostrar ningún cambio en absoluto.
Preparándose
Preparándose
Esta receta requiere las siguientes operaciones:
1. Cree un shader que inicializaremos más adelante.
2. Cree un material para alojar el shader.
3. Conecte el material a una pieza plana de geometría, como un cuadrante. Colóquelo
delante de algún otro objeto para que no pueda ver a través de él. El quad aparecerá
transparente tan pronto como el shader esté completo.
Cómo hacerlo…
Cómo hacerlo…
Para usar el pase de agarre, debe seguir estos pasos:
1. Quite la sección Propiedades; Este sombreador no usará ninguno de ellos.
2. En la sección SubShader, agregue GrabPass:
GrabPass{ }
3. Después del pase de agarre, tendremos que añadir este pase extra:
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _GrabTexture;
struct vertInput {
float4 vertex : POSITION;
};
struct vertOutput {
float4 vertex : POSITION;
float4 uvgrab : TEXCOORD1;
};
// Vertex function
vertInput vert(vertexInput v) {
vertexOutput o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uvgrab = ComputeGrabScreenPos(o.vertex);
return o;
}
// Fragment function
half4 frag(vertexOutput i) : COLOR {
fixed4 col = tex2Dproj(_GrabTexture,
UNITY_PROJ_COORD(i.uvgrab));
return col + half4(0.5,0,0,0);
}
ENDCG
}
Cómo funciona…
Esta receta no sólo introduce pases de agarre sino también Vertex y Fragment Shaders; Por
esta razón, tenemos que analizar el shader en detalle. Hasta ahora, todo el código siempre se
ha colocado directamente en la sección de SubShader. Esto se debe a que nuestros shaders
anteriores solo requerían un solo pase. Esta vez, se requieren dos pases. El primero es el pase
grab, que se define simplemente por GrabPass {}. El resto del código se coloca en el segundo
paso, que está contenido en un bloque de paso. El segundo paso no es estructuralmente
diferente del sombreado mostrado en la primera receta de este capítulo; Utilizamos la función
vertex vert para obtener la posición del vértice y luego le damos un color en la fragción frag. La
diferencia es que vert calcula otro detalle importante: los datos UV para el pase de agarre. El
pase de captura crea automáticamente una textura a la que se puede hacer referencia de la
siguiente manera:
sampler2D _GrabTexture;
Para probar esta textura, necesitamos sus datos UV. La función ComputeGrabScreenPos
devuelve datos que podemos usar más tarde para probar la textura de agarre correctamente.
Esto se hace en el Shader de fragmentos utilizando la línea siguiente:
fixed4 col = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab));
Esta es la manera estándar en la que una textura es agarrada y aplicada a la pantalla en su
posición correcta. Si todo se ha hecho correctamente, este sombreado simplemente clonará lo
que se ha representado detrás de la geometría. Veremos en las siguientes recetas cómo esta
técnica se puede utilizar para crear materiales como el agua y el vidrio.
Hay más…
Cada vez que utilice un material con GrabPass {}, Unity tendrá que renderizar la pantalla a una
textura. Esta operación es muy costosa y limita el número de pases de agarre que puedes usar
en un juego. Cg ofrece una variación ligeramente diferente:
GrabPass {"TextureName"}
Esta línea no sólo le permite dar un nombre a la textura, sino que también comparte la textura
con todos los materiales que tienen un pase grab llamado TextureName. Esto significa que si
usted tiene diez materiales, Unity sólo hará un solo pase de agarre y compartirá la textura con
todos ellos. El principal problema de esta técnica es que no permite efectos que se pueden
apilar. Si está creando un vaso con esta técnica, no podrá tener dos vasos uno tras otro.
Cómo hacerlo…
Comencemos editando el Vertex y Fragment Shaders:
1. Agregue estas dos propiedades al bloque de propiedades:
_MainTex("Base (RGB) Trans (A)", 2D) = "white" {}
_BumpMap("Noise text", 2D) = "bump" {}
_Magnitude("Magnitude", Range(0,1)) = 0.05
2. Añada sus variables en el segundo pase:
sampler2D _MainTex;
sampler2D _BumpMap;
float _Magnitude;
3. Agregue la información de textura en las estructuras de entrada y salida:
float2 texcoord : TEXCOORD0;
4. Transfiera los datos UV de la entrada a la estructura de salida:
o.texcoord = v.texcoord;
5. Utilice la siguiente función de fragmento:
half4 frag(vertOutput i) : COLOR {
half4 mainColour = tex2D(_MainTex, i.texcoord);
half4 bump = tex2D(_BumpMap, i.texcoord);
half2 distortion = UnpackNormal(bump).rg;
i.uvgrab.xy += distortion * _Magnitude;
fixed4 col = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab));
return col * mainColour * _Colour;
}
6. Este material es transparente por lo que cambia sus etiquetas en el bloque SubShader:
Tags{ "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Opaque" }
7. Lo que queda ahora es establecer la textura para el vidrio y un mapa normal para
desplazar la textura de agarre.
Cómo funciona…
El núcleo que utiliza este sombreador es un pase grab para tomar lo que ya se ha representado
en la pantalla. La parte donde se produce la distorsión se encuentra en la función de
fragmento. Aquí, un mapa normal se desempaqueta y se utiliza para compensar los datos UV
de la textura agarrar:
half4 bump = tex2D(_BumpMap, i.texcoord);
half2 distortion = UnpackNormal(bump).rg;
i.uvgrab.xy += distortion * _Magnitude;
La diapositiva Magnitud se utiliza para determinar la fuerza del efecto.
Hay más…
Este efecto es muy genérico; Agarra la pantalla y crea una distorsión basada en un mapa
normal. No hay ninguna razón por la que no se debe utilizar para simular cosas más
interesantes. Muchos juegos usan distorsiones alrededor de explosiones u otros dispositivos
de ciencia ficción. Este material se puede aplicar a una esfera y, con un mapa normal diferente,
simularía perfectamente la ola de calor de una explosión.
Preparándose
Esta receta se basa en los vértices y sombreadores de fragmentos descritos en la receta de uso
de pases de agarrar, ya que dependerá en gran medida del paso de agarre.
1. Cree un nuevo shader de paso de captura; Usted puede escribir el suyo propio o
comenzar con el presentado en la receta del uso del paso del asimiento.
2. Cree un nuevo material para su shader.
3. Asigne el material a una geometría plana que represente su agua 2D. Para que este
efecto funcione, debe tener algo representado detrás de él para que pueda ver el
desplazamiento similar al agua.
4. Esta receta requiere una textura de ruido, que se utiliza para obtener valores pseudo-
aleatorios. Es importante que elija una textura de ruido sin fisuras, como las generadas
por el ruido perlable 2D Perlin, como se muestra en la siguiente imagen. Esto asegura
que cuando el material se aplica a un objeto grande, no verá ninguna discontinuidad.
Para que este efecto funcione, la textura debe importarse en el modo Repetir. Si desea
un aspecto suave y continuo para su agua, también debe establecer a Bilinear de
Inspector. Estos ajustes garantizan que la textura se muestrea correctamente desde el
shader:
Cómo hacerlo…
Cómo hacerlo…
Para crear este efecto animado, puede empezar por reajustar el sombreado. Sigue estos pasos:
1. Agregue las siguientes propiedades:
_NoiseTex("Noise text", 2D) = "white" {}
_Colour ("Colour", Color) = (1,1,1,1)
_Period ("Period", Range(0,50)) = 1
_Magnitude ("Magnitude", Range(0,0.5)) = 0.05
_Scale ("Scale", Range(0,10)) = 1
2. Agregue sus respectivas variables a la segunda pasada del shader:
sampler2D _NoiseTex;
fixed4 _Colour;
float _Period;
float _Magnitude;
float _Scale;
3. Defina la siguiente estructura de salida para la función vértice:
struct vertInput {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 worldPos : TEXCOORD1;
float4 uvgrab : TEXCOORD2;
};
4. Este shader necesita saber la posición exacta del espacio de cada fragmento. Para ello,
agregue la línea siguiente a la función vértice:
o.worldPos = mul(_Object2World, v.vertex);
5. Utilice la siguiente función de fragmento:
fixed4 frag (vertInput i) : COLOR {
float sinT = sin(_Time.w / _Period);
float2 distortion = float2(
tex2D(_NoiseTex, i.worldPos.xy / _Scale + float2(sinT, 0) ).r - 0.5,
tex2D(_NoiseTex, i.worldPos.xy / _Scale + float2(0, sinT) ).r - 0.5
);
i.uvgrab.xy += distortion * _Magnitude;
fixed4 col = tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(i.uvgrab));
return col * _Colour;
}
Cómo funciona…
Este sombreado es muy similar al que se introdujo en la implementación de una receta de
sombreado de vidrio. La principal diferencia es que se trata de un material animado; El
desplazamiento no se genera a partir de un mapa normal, sino que tiene en cuenta el tiempo
actual para crear una animación constante. El código que desplaza los datos UV de la textura
de agarre parece bastante complicado; Vamos a tratar de entender cómo se ha generado. La
idea detrás de esto es que se utiliza una función sinusoidal para hacer que el agua oscile. Este
efecto necesita evolucionar con el tiempo; Para lograr este efecto, la distorsión generada por
el sombreado depende del tiempo actual que se recupera con la variable incorporada, _Time.
La variable _Period determina el período de la sinusoide, lo que significa la rapidez con que
aparecen las ondas:
float2 distortion = float2( sin(_Time.w/_Period), sin(_Time.w/_Period) ) – 0.5;
El problema con este código es que tiene el mismo desplazamiento en los ejes X e Y; Como
resultado, la textura agarrar entera se girará en un movimiento circular, que no se parece nada
al agua. Obviamente tenemos que añadir algo de aleatoriedad a esto. La forma más común de
agregar comportamientos aleatorios a los shaders es mediante la inclusión de una textura de
ruido. El problema ahora es encontrar una manera de probar la textura en posiciones
aparentemente aleatorias. La mejor manera de evitar ver un patrón sinusoidal obvio es usar
las ondas senoidales como un offset en los datos UV de la textura del ruido:
El resultado es una distorsión agradable, sin fisuras, que no se mueve en ninguna dirección
clara.
Nota
Como sucede con todos estos efectos especiales, no hay una solución perfecta. Esta receta le
muestra una técnica para crear distorsión similar al agua, pero se le anima a jugar con ella
hasta que encuentre un efecto que se ajuste a la estética de su juego.
Introducción
Aprender el arte de optimizar sus shaders surgirá en casi cualquier proyecto de juego en el que
trabaje. Siempre llegará un punto en cualquier producción donde un shader necesita ser
optimizado, o tal vez necesita usar menos texturas, pero producir el mismo efecto. Como
artista técnico o programador de shader, usted tiene que entender estos fundamentos básicos
para optimizar sus shaders para que pueda aumentar el rendimiento de su juego mientras
sigue obteniendo la misma fidelidad visual. Tener este conocimiento también puede ayudar a
establecer la forma en que se escribe su shader desde el principio. Por ejemplo, al saber que el
juego construido con su shader se reproducirá en un dispositivo móvil, podemos configurar
automáticamente todas nuestras funciones de iluminación para utilizar un medio vector como
dirección de la vista o establecer todos nuestros tipos de variable flotante a fijo o medio. Estas
y muchas otras técnicas contribuyen a que tus shaders funcionen eficientemente en tu
hardware de destino. Comencemos nuestro viaje y comencemos a aprender cómo optimizar
nuestros shaders.
Preparándose
Para conseguir esta receta comenzada, necesitamos recolectar algunos recursos juntos. Así
que realicemos las siguientes tareas:
1. Cree una nueva escena y llénela con un objeto de esfera simple y una luz direccional
única.
2. Cree un nuevo shader y material y asigne el shader al material.
3. Entonces necesitamos asignar el material que acabamos de crear a nuestro objeto de
esfera en nuestra nueva escena.
4. Finalmente, modifique el shader para que utilice una textura difusa y un mapa normal e
incluya su propia función de iluminación personalizada. La siguiente imagen muestra el
resultado de modificar nuestro shader predeterminado que creamos en el paso 1:
Shader "Custom/MovilShader" {
Properties{
_MainTex ("Base (RGB)", 2D) = "white" {}
_NormalMap ("Normal Map", 2D) = "bump" {}
}
SubShader{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf SimpleLambert
sampler2D _MainTex;
sampler2D _NormalMap;
struct Input{
float2 uv_MainTex;
float2 uv_NormalMap;
};
inline float4 LightingSimpleLambert (SurfaceOutput s, float3 lightDir, float atten){
float diff = max (0, dot (s.Normal, lightDir));
float4 c;
c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
c.a = s.Alpha;
return c;
}
Cómo hacerlo…
Vamos a construir un sombreado difuso simple para echar un vistazo a algunas formas en las
que puede optimizar sus shaders en general. En primer lugar, optimizaremos nuestros tipos de
variables para que usen menos memoria cuando procesan datos:
1. Comencemos con la estructura Input en nuestro shader. Actualmente, nuestras UVs se
almacenan en una variable del tipo float2. Necesitamos cambiar esto para usar half2
en su lugar:
struct Input{
half2 uv_MainTex;
half2 uv_NormalMap;
};
2. A continuación, podemos pasar a nuestra función de iluminación y reducir la huella de
la memoria de la variable cambiando sus tipos a lo siguiente:
inline fixed4 LightingSimpleLambert (SurfaceOutput s, fixed3 lightDir, fixed atten){
fixed diff = max (0, dot (s.Normal, lightDir));
fixed4 c;
c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
c.a = s.Alpha;
return c;
}
3. Finalmente, podemos completar este pase de optimización actualizando las variables
en nuestra función surf ():
void surf (Input IN, inout SurfaceOutput o){
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
}
Ahora que tenemos nuestras variables optimizadas, vamos a aprovechar una variable de
función de iluminación integrada para que podamos controlar cómo las luces son procesadas
por este shader. Haciendo esto, podemos reducir en gran medida la cantidad de luces que
procesa el shader. Modifique la instrucción #pragma en su shader con el código siguiente:
CGPROGRAM
#pragma surface surf SimpleLambert noforwardadd
Podemos optimizar esto aún más compartiendo UVs entre el mapa normal y la textura difusa.
Para hacer esto, simplemente cambiamos la búsqueda de UV en nuestra función
UnpackNormal () para usar UVs _MainTex en lugar de UVs de _NormalMap:
void surf (Input IN, inout SurfaceOutput o){
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
fixed: Un valor fixed es el más pequeño en tamaño de los tres tipos, pero se puede
utilizar para cálculos de iluminación y colores y tiene los valores correspondientes de
fixed2, fixed3 y fixed4.
Nuestra segunda fase de optimización de nuestro shader simple fue declarar el valor
noforwardadd a nuestra sentencia # pragma. Esto es básicamente un interruptor que indica
automáticamente a Unity que cualquier objeto con este sombreador particular recibe sólo luz
por píxel de una sola luz direccional. Cualquier otra luz que sea calculada por este sombreado
será forzada a ser procesada como luces por vértice utilizando valores de armónica esférica
producidos internamente por Unity. Esto es especialmente obvio cuando colocamos otra luz
en la escena para iluminar nuestro objeto de esfera porque nuestro sombreador está haciendo
una operación por pixel usando el mapa normal. Esto es genial, pero ¿qué pasa si usted quería
tener un montón de luces direccionales en la escena y el control sobre cuál de estas luces se
utiliza para la luz principal por pixel? Bueno, si te das cuenta, cada luz tiene un menú
desplegable de Render Mode. Si hace clic en este menú desplegable, verá un par de banderas
que se pueden establecer. Estos son Auto, Importante y No Importante. Al seleccionar una luz,
puede decirle a Unity que una luz debe considerarse más como una luz por píxel que una luz
por vértice, estableciendo su modo de renderizado en Importante y viceversa. Si deja una luz
establecida en Auto, entonces dejará que Unity decida el mejor curso de acción. Coloque otra
luz en su escena y eliminar la textura que actualmente está en la textura principal de nuestro
sombreado. Observará que la segunda luz del punto no reacciona con el mapa normal, sólo la
luz direccional que creamos primero. El concepto aquí es que ahorras en operaciones por píxel
simplemente calculando todas las luces adicionales como luces de vértice y ahorrando
rendimiento simplemente calculando la luz direccional principal como una luz por píxel. La
siguiente imagen demuestra visualmente este concepto cuando la luz del punto no reacciona
con el mapa normal:
Finalmente, hicimos un poco de limpieza y simplemente le dijimos a la textura del mapa
normal que usara los valores UV de la textura principal, y nos deshicimos de la línea de código
que tiraba de un conjunto separado de valores UV específicamente para el mapa normal. Esto
siempre es una buena manera de simplificar su código y limpiar los datos no deseados.
También declaramos exclude_pass: prepass en nuestra sentencia #pragma para que este
shader no acepte ninguna iluminación personalizada del renderizador diferido. Esto significa
que realmente podemos usar este sombreado de manera efectiva en el renderizador hacia
adelante solamente, que se establece en la configuración de la cámara principal.
Al tomar un poco de tiempo, usted se sorprenderá de cuánto se puede optimizar un
sombreador. Has visto cómo podemos empaquetar texturas en escala de grises en una sola
textura RGBA así como usar texturas de búsqueda para iluminación falsa. Hay muchas maneras
en que un sombreador puede ser optimizado, por lo que siempre es una pregunta ambigua
para preguntar en primer lugar, pero conociendo estas diferentes técnicas de optimización,
puede atender a sus sombreadores a su juego y la plataforma de destino, Shaders muy
aerodinámicos y un agradable framerate constante.
Preparándose
Vamos a utilizar nuestro Profiler por tener algunos activos listos y lanzar la ventana Profiler:
1. Utilicemos la escena de la última receta y lanzamos el Unity Profiler desde Window
Profiler o Ctrl + 7.
2. Vamos a duplicar nuestra esfera un par de veces más para ver cómo afecta a nuestra
representación.
Debería ver algo similar a la siguiente imagen:
Cómo hacerlo…
Para usar el Profiler, echaremos un vistazo a algunos de los elementos de la interfaz de usuario
de esta ventana. Antes de que toquemos el juego, echemos un vistazo a cómo obtener la
información que necesitamos del perfilador:
1. Primero, haga clic en los bloques más grandes de la ventana Profiler denominados Uso
de GPU, Uso de CPU y Rendimiento. Encontrará estos bloques en el lado izquierdo de
la ventana superior:
Usando estos bloques, podemos ver diferentes datos específicos a las funciones principales de
nuestro juego. El uso de la CPU nos está mostrando lo que la mayoría de nuestros scripts están
haciendo así como la física y la representación general. El bloque de uso de la GPU nos
proporciona información detallada sobre los elementos que son específicos de nuestra
iluminación, sombras y colas de procesamiento. Por último, el bloque Rendering nos da
información sobre los drawcalls y la cantidad de geometría que tenemos en nuestra escena en
cualquier marco. Al hacer clic en cada uno de estos bloques, podemos aislar el tipo de datos
que vemos durante nuestra sesión de creación de perfiles.
2. Ahora, haga clic en los diminutos bloques de color en uno de estos bloques de Perfil y
presione la reproducción o Ctrl + P para ejecutar la escena. Esto nos permite
profundizar aún más en nuestra sesión de creación de perfiles para poder filtrar lo que
se está reportando para nosotros. Mientras se está ejecutando la escena, desmarque
todas las casillas, excepto para Opaco en el bloque Uso de la GPU. Observe que ahora
podemos ver cuánto tiempo se está utilizando para procesar los objetos que se
establecen en la cola de procesamiento de Opaque:
3. Otra gran función de la ventana Profiler es la acción de hacer clic y arrastrar en la vista
de gráfico. Esto pondrá pausas automáticamente su juego de modo que usted pueda
analizar más lejos cierto punto en el gráfico para descubrir exactamente qué artículo
está causando el problema del funcionamiento. Haga clic y arrastre alrededor en la
vista de gráfico para pausar el juego y ver el efecto de usar esta funcionalidad:
4. Volviendo nuestra atención ahora hacia la mitad inferior de la ventana del Profiler,
notará que hay un elemento desplegable disponible cuando tenemos el bloque de
GPU seleccionado. Podemos ampliar esto para obtener información aún más detallada
sobre la sesión de creación de perfiles activa actual y, en este caso, obtener más
información sobre lo que la cámara está procesando actualmente y cuánto tiempo
tarda en ocuparse:
Esto nos da una mirada completa al funcionamiento interno de lo que la Unidad está
procesando en este marco particular. En este caso, podemos ver que nuestras tres esferas con
nuestro sombreado optimizado están tomando aproximadamente 0.14 milisegundos para
dibujar a la pantalla, están tomando siete drawcalls, y este proceso está tomando el 3.1 por
ciento del tiempo de la GPU en cada marco. Es este tipo de información que podemos utilizar
para diagnosticar y resolver problemas de rendimiento con respecto a shaders. Hagamos una
prueba para ver los efectos de agregar una textura más a nuestro shader y mezclar dos
texturas difusas juntas usando una función lerp. Verá los efectos en el generador de perfiles
con bastante claridad.
5. Modifique el bloque de propiedades de su sombreador con el código siguiente para
darnos otra textura a utilizar:
Una vez que guardes las modificaciones en tu shader y regresas al editor de Unity,
podemos ejecutar nuestro juego y ver el aumento en milisegundos de nuestro nuevo
shader. Pulse play una vez que haya regresado a Unity y echemos un vistazo a los
resultados en nuestro profiler:
Ahora puedes ver que la cantidad de tiempo para renderizar a nuestros Shaders opacos en
esta escena toma 0.179 milisegundos, arriba de 0.140 milisegundos. Añadiendo otra textura y
utilizando la función lerp (), aumentamos el tiempo de renderización de nuestras esferas. Si
bien es un pequeño cambio, imagine tener 20 shaders todos trabajando de diferentes maneras
en diferentes objetos. Utilizando la información dada aquí, puede identificar las áreas que
están causando disminuciones de rendimiento más rápidamente y resolver estos problemas
utilizando las técnicas de la receta anterior.
Cómo funciona…
Aunque está completamente fuera del alcance de este libro para describir cómo esta
herramienta realmente funciona internamente, podemos suponer que Unity nos ha dado una
manera de ver el rendimiento de la computadora mientras nuestro juego se está ejecutando.
Básicamente, esta ventana está muy unida a la CPU y la GPU para darnos retroalimentación en
tiempo real de cuánto tiempo se está tomando para cada uno de nuestros scripts, objetos y
colas de procesamiento. Usando esta información, hemos visto que podemos rastrear la
eficiencia de nuestra escritura de shader para eliminar áreas problemáticas y código.
Hay más…
También es posible crear perfiles específicos para plataformas móviles. Unity nos proporciona
un par de características adicionales cuando el objetivo de compilación de Android o IOS se
establece en la configuración de compilación. Realmente podemos obtener información en
tiempo real de nuestros dispositivos móviles mientras el juego se está ejecutando. Esto se
convierte en muy útil porque es capaz de perfilar directamente en el propio dispositivo en
lugar de crear perfiles directamente en su editor. Para obtener más información sobre este
proceso, consulte la documentación de Unity en el siguiente enlace:
Http://docs.unity3d.com/Documentation/Manual/MobileProfiling.html
Preparándose
Antes de empezar, obtengamos una nueva escena y la llenamos con algunos objetos para
aplicar nuestro shader móvil:
1. Cree una nueva escena y llénela con una esfera predeterminada y una sola luz
direccional.
2. Cree un nuevo material y sombreador y asigne el sombreado al material.
3. Finalmente, asignar el material a nuestro objeto de esfera en nuestra escena.
Una vez completado, debe tener una escena similar a la de la siguiente imagen:
Cómo hacerlo…
Para esta receta, escribiremos un shader móvil desde el principio y discutiremos los elementos
que lo hacen más móvil:
1. Primero llene nuestro bloque de Propiedades con las texturas necesarias. En este caso,
vamos a utilizar una textura difusa única con el mapa de brillo en su canal alfa, mapa
normal y deslizador para la intensidad especular:
Cómo funciona…
Cómo funciona…
Entonces, comencemos la descripción de este shader explicando lo que hace y lo que no hace.
Primero, excluye el paso de iluminación diferida. Esto significa que si creó una función de
iluminación conectada al prepass del renderizador diferido, no utilizaría esa función de
iluminación en particular y buscaría la función de iluminación predeterminada como las que
hemos estado creando hasta ahora en este libro. Este sombreador en particular no admite
Lightmapping por el sistema de mapeo de luz interno de Unity. Esto sólo evita que el
sombreador intente encontrar mapas de luz para el objeto al que está conectado el
sombreador, lo que hace que el sombreador resulte más amigable porque no tiene que
realizar la verificación lightmapping. Hemos incluido la declaración noforwardadd para que
procesemos sólo texturas por píxel con una única luz direccional. Todas las demás luces se ven
obligadas a convertirse en luces por vértice y no se incluirán en ninguna operación por pixel
que pueda realizar en la función surf (). Finalmente, estamos utilizando la declaración
halfasview para decirle a Unity que no vamos a usar el parámetro viewDir principal encontrado
en una función de iluminación normal. En su lugar, vamos a utilizar el medio vector como la
dirección de la vista y procesar nuestro especular con esto. Esto se hace mucho más rápido
para que el sombreador procese, ya que se hará sobre una base por vértex. No es
completamente preciso cuando se trata de simular especular en el mundo real, pero
visualmente en un dispositivo móvil, se ve muy bien y el sombreado es más optimizado. Sus
técnicas como éstas que hacen un sombreador más eficiente y más limpio, codewise. Siempre
asegúrese de que está utilizando sólo los datos que necesita mientras pesa esto en contra de
su hardware de destino y la calidad visual que el juego requiere. Al final, se convierte en un
cóctel de estas técnicas que en última instancia, componen sus shaders para sus juegos.
Introducción
Uno de los aspectos más impresionantes de aprender a escribir shaders es el proceso de crear
sus propios efectos de pantalla, también conocido como efectos de post. Con estos efectos de
pantalla, podemos crear impresionantes imágenes en tiempo real con Bloom, Motion Blur,
efectos HDR, etc. La mayoría de los juegos modernos en el mercado hoy en día hacen un uso
intensivo de estos efectos de pantalla por su profundidad de efectos de campo, efectos de
floración e incluso efectos de corrección de color. A lo largo de este capítulo, aprenderá cómo
crear el sistema de secuencias de comandos que nos proporciona el control para crear estos
efectos de pantalla. Cubriremos Render Textures, lo que es el buffer de profundidad y cómo
crear efectos que le den el control de Photoshop sobre la imagen renderizada final de su juego.
Utilizando efectos de pantalla para tus juegos, no sólo redondeas tu conocimiento de escritura
de sombreado, sino que también tendrás el poder de crear tus propios increíbles
renderizaciones en tiempo real con Unity.
Preparándose
Para que nuestro sistema de efectos de pantalla funcione, necesitamos crear algunos activos
para nuestro proyecto Unity actual. Haciendo esto, nos prepararemos para los pasos de las
siguientes secciones:
1. En el proyecto actual, necesitamos crear un nuevo script C # y llamarlo
TestRenderImage.cs.
2. Cree un nuevo shader y llámelo como ImageEffect.shader.
3. Crear una esfera simple en la escena y asignarle un nuevo material. Este nuevo
material puede ser cualquier cosa, pero para nuestro ejemplo, haremos un simple
material rojo, especular.
4. Finalmente, cree una nueva luz direccional y guarde la escena.
Con todos nuestros activos listos, usted debe tener una configuración de escena simple, que se
parece a la siguiente imagen:
Cómo hacerlo…
Para que nuestro efecto de pantalla en escala de grises funcione, necesitamos un script y un
shader. Por lo tanto, completaremos estos dos nuevos elementos aquí y los rellenaremos con
el código apropiado para producir nuestro primer efecto de pantalla. Nuestra primera tarea es
completar el script C #. Esto hará funcionar todo el sistema. Después de esto, completaremos
el sombreador y veremos los resultados de nuestro efecto de pantalla. Vamos a completar
nuestro script y shader con los siguientes pasos:
1. Abra el script TestRenderImage.cs C # y comencemos escribiendo algunas variables
que necesitaremos para almacenar objetos y datos importantes. Escriba el código
siguiente en la parte superior de la clase TestRenderImage:
2. Para que podamos editar el efecto de pantalla en tiempo real, cuando el editor Unity
no está reproduciendo, necesitamos introducir la siguiente línea de código justo
encima de la declaración de la clase TestRenderImage:
3. Como nuestro efecto de pantalla está usando un sombreado para realizar las
operaciones de píxeles en nuestra imagen de pantalla, tenemos que crear un material
para ejecutar el sombreado. Sin esto, no podemos acceder a las propiedades del
shader. Para ello, crearemos una propiedad C # para comprobar un material y
crearemos uno si no lo encontramos. Ingrese el siguiente código justo después del
Declaración de las variables del paso 1:
En este punto, ahora podemos aplicar este script a la cámara, si se compila sin errores,
en Unity. Vamos a aplicar el script TestRenderImage.cs a nuestra cámara principal en
nuestra escena. Debería ver el valor grayScaleAmount y un campo para un shader,
pero el script arroja un error a la ventana de la consola. Dice que falta una instancia a
un objeto y por lo tanto no procesará apropiadamente. Si recuerda de paso 4, estamos
haciendo algunas comprobaciones para ver si tenemos un sombreado y la plataforma
actual soporta el sombreado. Como no hemos dado al script Screen Effect un shader
con el que trabajar, la variable curShader es simplemente null, lo que arroja el error.
Continuemos con nuestro sistema de efectos de pantalla completando el sombreado.
8. Para comenzar nuestro sombreado, vamos a poblar nuestras propiedades con algunas
variables para que podamos enviar datos a este sombreado:
9. Nuestro sombreado ahora va a utilizar el código puro del sombreador del CG en vez de
utilizar el código incorporado de la sombra de la superficie de la unidad. Esto hará que
nuestro efecto de pantalla esté más optimizado, ya que solo necesitamos trabajar con
los píxeles de la textura de render. Así, crearemos un nuevo bloque de Paso en nuestro
shader y lo rellenaremos con algunas nuevas sentencias #pragma que no hemos visto
antes:
10. Para acceder a los datos que se envían al sombreado desde el editor Unity,
necesitamos crear las variables correspondientes en nuestro CGPROGRAM:
11. Finalmente, todo lo que necesitamos hacer es configurar nuestra función de píxeles,
en este caso, llamada frag (). Su es donde está la carne del Efecto de la Pantalla. Esta
función procesará cada píxel de la textura de render y devolverá una nueva imagen a
nuestro script TestRenderImage.cs:
Una vez que el shader esté completo, regrese a Unity y deje que compile para ver si se
produjeron errores. Si no es así, asigne el nuevo sombreado al script TestRenderImage.cs y
cambie el valor de la variable de escala de grises. Usted debe ver la vista del juego ir de una
versión coloreada del juego a una versión en escala de grises del juego. La siguiente imagen
muestra este efecto de pantalla:
Con esto completo, ahora tenemos una manera fácil de probar los nuevos shaders de Screen
Effect sin tener que escribir todo nuestro sistema Screen Effect una y otra vez. Vamos a bucear
en un poco más profundo y aprender acerca de lo que está pasando con la textura de
procesamiento y cómo se procesa a lo largo de su existencia.
Cómo funciona…
Para obtener un efecto de pantalla en funcionamiento dentro de Unity, necesitamos crear un
script y shader. El script controla la actualización en tiempo real en el editor y también es
responsable de capturar la Textura de procesamiento de la cámara principal y pasarla al
sombreador. Una vez que la textura de render llega al shader, podemos usar el shader para
realizar operaciones por pixel. Al inicio de la secuencia de comandos, realizamos algunas
comprobaciones para asegurarnos de que la plataforma de compilación actualmente
seleccionada admita realmente los efectos de pantalla y el propio sombreado. Hay casos en los
que una plataforma actual no admite Screen Effects o el shader que estamos utilizando. Así
que las comprobaciones que hacemos en la función Start () nos aseguramos de que no
obtengamos ningún error si la plataforma no soporta el sistema de pantalla. Una vez que el
script pasa estas comprobaciones, iniciamos el sistema Screen Effects llamando a la función
integrada OnRenderImage (). Esta función es responsable de agarrar la renderTexture, dándole
al shader usando la función Graphics.Blit () y devolviendo la imagen procesada al renderizador
Unity. Puede encontrar más información sobre estas dos funciones en las siguientes URL:
OnRenderImage:http://docs.unity3d.com/Documentation/ScriptReference/MonoBeh
Graphics.Blit:http://docs.unity3d.com/Documentation/ScriptReference/Graphics.Blit
Una vez que la textura de renderizado actual llega al sombreado, el sombreador lo toma, lo
procesa a través de la función frag () y devuelve el color final de cada píxel. Usted puede ver
cómo es poderoso esto se convierte como nos da Photoshop-como el control sobre la imagen
rendida final de nuestro juego. Estos efectos de pantalla funcionan secuencialmente como las
capas de Photoshop en la cámara. Cuando coloque estos efectos de pantalla uno tras otro, se
procesarán en ese orden. Estos son sólo los pasos básicos para obtener un efecto de pantalla
de trabajo, pero es el núcleo de cómo funciona el sistema de efectos de pantalla.
Hay más…
Ahora que tenemos nuestro sencillo sistema Screen Effect instalado y funcionando, echemos
un vistazo a algunas de las otras informaciones útiles que podemos obtener del renderizador
de Unity:
5. Con nuestro shader completo, volvamos nuestra atención a nuestro script Screen
Effects. Necesitamos agregar la variable depthPower al script para que podamos
permitir que los usuarios cambien el valor en el editor:
6. Nuestra función OnRenderImage () necesita ser actualizada para que pase el valor
correcto a nuestro shader:
Con todo el código establecido, guarde su script y shader y regrese a Unity para que ambos
compilen. Si no se encuentran errores, debería ver un resultado similar al siguiente:
Uso de brillo, saturación y contraste con efectos de pantalla
Ahora que tenemos nuestro sistema de efectos de pantalla en funcionamiento, podemos
explorar cómo crear operaciones de píxeles más implicadas para realizar algunos de los efectos
de pantalla más comunes que se encuentran en los juegos de hoy. Para empezar, utilizar un
efecto de pantalla para ajustar los colores finales generales de tu juego es crucial para dar a los
artistas un control global sobre el aspecto final del juego. Técnicas como los controles de
ajuste de color para ajustar la intensidad de los rojos, azules y verdes del último juego o
técnicas como poner cierto tono de color en toda la pantalla como se ve en algo como un
efecto de película sepia. Para esta receta en particular, vamos a cubrir algunas de las
operaciones de ajuste de color más básicas que podemos realizar en una imagen. Estos son
brillo, saturación y contraste. Aprender a codificar estos ajustes de color nos da una buena
base para aprender el arte de los efectos de pantalla.
Preparándose
Necesitaremos crear un par de nuevos activos. Podemos utilizar la misma escena que nuestra
escena de prueba, pero necesitaremos un nuevo script y shader:
1. Cree un nuevo script y llámelo BSC_ImageEffect.
2. Cree un nuevo shader llamado BSC_Effect.
3. Ahora simplemente necesitamos copiar el código que teníamos del script C # en la
receta anterior a nuestro nuevo script C #. Esto nos permitirá centrarnos sólo en las
matemáticas para el brillo, la saturación y el efecto de contraste.
4. Copie el código del sombreador en la receta anterior a nuestro nuevo sombreador.
5. Cree un par de objetos nuevos en la escena, configure algunos materiales difusos de
diferentes colores y asignelos al azar a los nuevos objetos de la escena. Esto nos dará
una buena gama de colores para probar con nuestro nuevo efecto de pantalla.
Una vez completado, debe tener una escena similar a la siguiente imagen:
Cómo hacerlo…
Ahora que hemos completado nuestra configuración de escena y hemos creado nuestro nuevo
script y shader, podemos comenzar a rellenar el código necesario para lograr el brillo, la
saturación y el efecto de pantalla de contraste. Nos centraremos sólo en la operación de los
píxeles y en la configuración de las variables para nuestro script y shader, ya que obtener un
sistema Screen Effect instalado y ejecutado se describe en la sección Configuración de la receta
del sistema de scripts de efectos de pantalla:
1. Comencemos lanzando nuestro nuevo shader y script en MonoDevelop. Simplemente
haga doble clic en los dos archivos de la vista del proyecto para realizar esta acción.
2. Editar el shader primero tiene más sentido para que sepamos qué tipo de variables
necesitaremos para nuestro script C #. Comencemos esto introduciendo las
propiedades apropiadas para nuestro brillo, saturación y efecto de contraste.
Recuerde, tenemos que mantener la_MainTex propiedad en nuestro sombreado ya
que esta es la propiedad que los objetivos RenderTexture al crear Efectos de pantalla:
3. Como es habitual, para que podamos acceder a los datos procedentes de nuestras
propiedades en nuestro CGPROGRAM, necesitamos crear las variables
correspondientes en el CGPROGRAM:
4. Ahora necesitamos crear las operaciones que realizarán los efectos de brillo,
saturación y contraste. Introduzca la nueva función siguiente en nuestro shader, justo
encima de la función frag (). No se preocupe si todavía no tiene sentido; Todo el código
se explicará en la siguiente receta:
5. Finalmente, solo necesitamos actualizar nuestra función frag () para usar la función
ContrastSaturationBrightness (). Esto procesará todos los píxeles de nuestra textura de
render y lo devolverá a nuestro script:
Con el código introducido en el shader, regrese al editor de Unity para permitir que el nuevo
shader compile. Si no hay errores, podemos volver a MonoDevelop para trabajar en nuestro
script. Empecemos por crear un par de nuevas líneas de código que enviarán los datos
adecuados a nuestro shader:
1. Nuestro primer paso en la modificación de nuestro script es añadir las variables
adecuadas que impulsarán los valores de nuestro efecto de pantalla. En este caso,
necesitaremos un deslizador para el brillo, un control deslizante para la saturación y un
control deslizante para el contraste:
2. Con nuestras variables configuradas, ahora necesitamos decirle al guión que pase sus
datos al shader. Lo hacemos en la función OnRenderImage ():
3. Finalmente, todo lo que necesitamos hacer es sujetar los valores de las variables
dentro de un rango que sea razonable. Estos valores de abrazadera son totalmente
preferenciales, por lo que puede utilizar cualquiera de los valores que vea en forma:
La siguiente imagen muestra otro ejemplo de lo que se puede hacer ajustando los colores de la
imagen renderizada:
Cómo funciona…
Dado que ahora sabemos cómo funciona el sistema básico de Screen Effects, vamos a cubrir
las operaciones perpixel que creamos en la función ContrastSaturationBrightness (). La función
comienza tomando algunos argumentos. La primera y más importante es la textura de render
actual. Los otros argumentos simplemente ajustan el efecto general del efecto de pantalla y
están representados por deslizadores en la pestaña Inspector de los efectos de pantalla. Una
vez que la función recibe la textura de render y los valores de ajuste, declara algunos valores
constantes que usamos para modificar y comparar contra la textura de render original. La
variable luminanceCoeff almacena los valores que nos darán el brillo general de la imagen
actual. Estos coeficientes se basan en las funciones de coincidencia de colores CIE y son
bastante estándar en toda la industria. Podemos encontrar el brillo general de la imagen
obteniendo el producto punto de la imagen actual punteada con estos coeficientes de
luminancia. Una vez que tenemos el brillo, simplemente utilizamos un par de funciones de lerp
para mezclar desde la versión en escala de grises de la operación de brillo y la imagen original
multiplicada por el valor de brillo, pasando a la función. Los efectos de pantalla, como este,
son cruciales para lograr gráficos de alta calidad para sus juegos, ya que le permite ajustar la
apariencia final de su juego sin tener que editar cada material en su escena de juego actual.
Uso de modos básicos de mezcla de Photoshop con efectos de pantalla
Los efectos de pantalla no se limitan sólo a ajustar los colores de una imagen renderizada de
nuestro juego. También podemos usarlas para combinar otras imágenes con nuestra textura
de render. Esta técnica no es diferente de crear una nueva capa en Photoshop y elegir un
modo de mezcla para mezclar dos imágenes juntos o, en nuestro caso, una textura con una
textura Render. Esto se convierte en una técnica muy potente, ya que da a los artistas en un
entorno de producción una forma de simular sus modos de mezcla en el juego en lugar de sólo
en Photoshop. Para esta receta en particular, vamos a echar un vistazo a algunos de los modos
de mezcla más comunes, como Multiply, Add y Overlay. Verá lo sencillo que es tener el poder
de los modos de Photoshop Blend en su juego.
Preparándose
Cómo hacerlo…
Cómo funciona…
Hay más…
Uso del modo Overlay Blend con efectos de pantalla
Preparándose
Cómo hacerlo…
Cómo funciona…
Preparándose
Antes de empezar a bucear en la carne de escribir el sombreado, tenemos que conseguir
algunos elementos establecidos en nuestra escena. Vamos a crear lo siguiente y luego abrir el
sombreado en MonoDevelop:
1. Cree una nueva escena y llénela con un modelo de esfera simple.
2. Cree un nuevo shader y material.
3. Conecte el nuevo sombreado al nuevo material y asigne el material a la esfera.
4. Entonces, vamos a crear una luz direccional y la posición por encima de nuestra esfera.
5. Por último, vamos a querer abrir el archivo UnityCG.cginc desde la carpeta CgInclude
de Unity ubicada en el directorio de instalación de Unity. Esto nos permitirá analizar
algunos de los códigos de la función auxiliar para que podamos entender mejor lo que
está sucediendo cuando los usamos.
6. Debería tener una escena simple configurada para trabajar en el shader. Consulte la
siguiente captura de pantalla como ejemplo:
Cómo hacerlo…
Con la escena preparada, ahora podemos comenzar el proceso de experimentar con algunas
de las funciones auxiliares integradas incluidas con el archivo UnityCG.cginc. Haga doble clic en
el sombreado que se creó para esta escena con el fin de abrirlo en MonoDevelop e insertar el
código dado en los siguientes pasos:
1. Agregue el siguiente código al bloque Propiedades del nuevo archivo de sombreado.
Necesitaremos una sola textura y diapositiva para nuestro ejemplo de sombreado:
Properties{
_MainTex ("Base (RGB)", 2D) = "white" {}
_DesatValue ("Desaturate", Range(0,1)) = 0.5
}
2. A continuación, debemos asegurarnos de crear la conexión de datos entre nuestros
bloques de propiedades y CGPROGRAM, con el siguiente código colocado después de
la declaración CGPROGRAM y las directivas #pragma:
sampler2D _MainTex;
fixed _DesatValue;
3. Finalmente, solo tenemos que actualizar nuestra función surf () para incluir el siguiente
código. Introducimos una nueva función que aún no hemos visto, que está integrada
en el archivo UnityCG.cginc de Unity:
void surf (Input IN, inout SurfaceOutput o){
half4 c = tex2D (_MainTex, IV.uv_MainTex);
c.rgb = lerp(c.rgb, Luminance(r.rgb), _DesatValue);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
Con el código de shader modificado, debería ver algo similar a la siguiente captura de pantalla.
Simplemente hemos utilizado una función de ayuda, integrada en el archivo CgInclude de
Unity, para darnos un efecto de desaturar la textura principal de nuestro sombreado:
Cómo funciona…
Utilizando la función de ayuda integrada llamada Luminance (), podemos obtener rápidamente
un efecto de desaturación o escala de grises en nuestros shaders. Todo esto es posible porque
el archivo UnityCG.cginc se transfiere automáticamente a nuestro shader cuando estamos
usando un sombreado de superficie. Si busca a través del archivo UnityCG.cginc, abierto en
MonoDevelop, encontrará la implementación de esta función en la línea 276. El fragmento
siguiente se toma del archivo:
inline fixed Luminance (fixed3 c){
return dot(c, fixed3(0.22, 0.707, 0.071));
}
Como esta función se incluye en el archivo y Unity compila automáticamente con este archivo,
podemos utilizar la función en nuestro código, así, reduciendo así la cantidad de código que
tenemos que escribir una y otra vez. Si observa que también hay un archivo Lighting.cginc que
Unity viene con. Este archivo contiene todos los modelos de iluminación que usamos cuando
declaramos algo como #pragma Surface surf Lambert. Examinar a través de este archivo revela
que todos los modelos de iluminación incorporados se definen aquí para su reutilización y
modularidad.
Shader "Custom/ShaderCgInclude" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_DesatValue ("Desaturate", Range(0,1)) = 0.5
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert
//#pragma Surface surf Lambert
sampler2D _MainTex;
fixed _DesatValue;
struct Input {
float2 uv_MainTex;
};
ENDCG
}
FallBack "Diffuse"
}
Los píxeles blancos en la máscara de control serán extruidos del material original, simulando
una piel. Es importante que la distribución de estos píxeles blancos sea escasa con el fin de dar
la ilusión de que el material está hecho de muchos cabellos pequeños. Una forma suelta de
crear una textura es la siguiente:
1. Aplique un umbral a su textura original para capturar mejor los parches donde la piel
es menos densa.
2. Aplique un filtro de ruido que pixelice la imagen. Los canales RGB de ruido no deben
depender para producir un resultado en blanco y negro.
3. Para una mirada más realista, sobreponga un filtro de ruido de Perlin que agregue a la
variabilidad de la piel.
4. Finalmente, aplique de nuevo un filtro umbral para separar mejor los píxeles en su
textura.
Como todos los otros shaders antes, tendrá que crear un nuevo sombreado estándar y
material para alojarlo.
Cómo hacerlo…
Para esta receta, podemos comenzar a modificar un sombreado estándar:
1. Agregue las siguientes propiedades:
_FurLength ("Fur Length", Range (.0002, 1)) = .25
_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
_CutoffEnd ("Alpha cutoff end", Range(0,1)) = 0.5
_EdgeFade ("Edge Fade", Range(0,1)) = 0.4
_Gravity ("Gravity direction", Vector) = (0,0,1,0)
_GravityStrength ("G strenght", Range(0,1)) = 0.25
2. Este sombreado requiere que repita el mismo pase varias veces. Utilizaremos la
técnica introducida en la sección Hacer su mundo de sombreado modular con la
sección CgIncludes para agrupar todo el código necesario desde una sola pasada en un
archivo externo. Comencemos creando un nuevo archivo de CgInclude llamado
FurPass.cginc con el código siguiente:
#pragma target 3.0
fixed4 _Color;
sampler2D _MainTex;
half _Glossiness;
half _Metallic;
uniform float _FurLength;
uniform float _Cutoff;
uniform float _CutoffEnd;
uniform float _EdgeFade;
uniform fixed3 _Gravity;
uniform fixed _GravityStrength;
void vert (inout appdata_full v)
{
fixed3 direction = lerp(v.normal, _Gravity * _GravityStrength +
v.normal * (1-_GravityStrength), FUR_MULTIPLIER);
v.vertex.xyz += direction * _FurLength * FUR_MULTIPLIER *
v.color.a;
}
struct Input {
float2 uv_MainTex;
float3 viewDir;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
//o.Alpha = step(_Cutoff, c.a);
o.Alpha = step(lerp(_Cutoff,_CutoffEnd,FUR_MULTIPLIER), c.a);
float alpha = 1 - (FUR_MULTIPLIER * FUR_MULTIPLIER);
alpha += dot(IN.viewDir, o.Normal) - _EdgeFade;
o.Alpha *= alpha;
}
3. Vuelva a su sombreador original y agregue este pase extra después de la sección
ENDCG:
CGPROGRAM
#pragma surface surf Standard fullforwardshadows alpha:blend
vertex:vert
#define FUR_MULTIPLIER 0.05
#include "FurPass.cginc"
ENDCG
4. Añadir más pases, aumentando progresivamente FUR_MULTIPLIER. Puede obtener
resultados decentes con 20 pases, de 0,05 a 0,95.
Una vez que el sombreador se compila y se adjunta a un material, puede cambiar su apariencia
desde el Inspector. La propiedad Longitud de Piel determina el espacio entre las conchas de
pieles, que estarán alterando la longitud de la piel. Una piel más larga puede requerir más
pases para parecer realistas. Alpha Cutoff y Alpha Cutoff End se utilizan para controlar la
densidad de la piel y cómo se hace progresivamente más delgada. Edge Fade determina la
transparencia final de la piel, resultando en un aspecto más borroso. Los materiales más
suaves deben tener un alto Edge Fade. Finalmente, la dirección de la gravedad y la fuerza de la
gravedad curvan las cáscaras de la piel para simular el efecto de la gravedad.
Cómo funciona…
La técnica presentada en esta receta se conoce como técnica de la cáscara de piel concéntrica
de Lengyel o, simplemente, técnica de la cáscara. Trabaja creando copias progresivamente más
grandes de la geometría que necesita ser rendida. Con la transparencia correcta, da la ilusión
de un hilo continuo de pelo:...
Hay más…
El Fur Shader se ha utilizado para simular pieles. Sin embargo, se puede utilizar para una
variedad de otros materiales. Funciona muy bien para los materiales que se hacen
naturalmente de capas múltiples, tales como marquesinas del bosque, nubes borrosas, pelo
humano, e incluso hierba. Hay muchas otras mejoras que pueden aumentar drásticamente su
realismo. Puede agregar una animación de viento muy simple cambiando la dirección de la
gravedad dependiendo de la hora actual. Si se calibra correctamente, esto puede dar la
impresión de que la piel se está moviendo debido al viento. Además, puedes hacer que tu piel
se mueva cuando el personaje se está moviendo. Todos estos pequeños retoques contribuyen
a la creibilidad de su piel, dando la ilusión de que no es sólo un material estático dibujado en la
superficie. Desafortunadamente, este shader tiene un precio: 20 pases son muy pesados para
computar. El número de pases determina aproximadamente cuán creíble es el material. Usted
debe jugar con la longitud de la piel y pasa a fin de obtener el efecto que funciona mejor para
usted. Dado el impacto de rendimiento de este sombreado, es aconsejable tener varios
materiales con diferentes números de pases; Usted puede utilizarlos en distancias diferentes y
ahorrar mucho de computación....
Preparándose
Cómo hacerlo…
Cómo funciona…
Índice
Unidad