Este tutorial le mostrará cómo escribir un sombreador geométrico para generar briznas de hierba desde la parte superior de la malla entrante y utilizar la teselación para controlar la densidad de la hierba.
El artículo describe el proceso paso a paso de escribir un sombreador de césped en Unity. El sombreador recibe la malla entrante, y de cada vértice de la malla genera una brizna de hierba utilizando el
sombreador geométrico . En aras del interés y el realismo, las briznas de hierba tendrán un
tamaño y
rotación aleatorios , y también se verán afectados por el
viento . Para controlar la densidad de la hierba, utilizamos
teselación para separar la malla entrante. La hierba podrá
proyectar y
recibir sombras.
El proyecto terminado se publica al final del artículo. El archivo de sombreador generado contiene una gran cantidad de comentarios que facilitan la comprensión.
Requisitos
Para completar este tutorial, necesitará conocimientos prácticos sobre el motor de Unity y una comprensión inicial de la sintaxis y la funcionalidad de los sombreadores.
Descargue el borrador del proyecto (.zip) .
Llegar al trabajo
Descargue el borrador del proyecto y ábralo en el editor de Unity. Abra la escena
Main
y luego abra el sombreador
Grass
en su editor de código.
Este archivo contiene un sombreador que produce color blanco, así como algunas funciones que usaremos en este tutorial. Notará que estas funciones junto con el sombreador de vértices están incluidas en el bloque
CGINCLUDE
ubicado
fuera de SubShader
. El código colocado en este bloque se incluirá
automáticamente en todos los pases en el sombreador; Esto será útil más tarde porque nuestro sombreador tendrá varias pasadas.
Comenzaremos escribiendo un
sombreador geométrico que genere triángulos a partir de cada vértice en la superficie de nuestra malla.
1. Shaders geométricos
Los sombreadores geométricos son una parte opcional de la canalización de renderizado. Se ejecutan
después del sombreador de vértices (o sombreador de teselación si se utiliza la teselación) y antes de que se procesen los vértices para el sombreador de fragmentos.
Direct3D Graphics Pipeline 11. Observe que en este diagrama el sombreador de fragmentos se llama sombreador de píxeles .Los sombreadores geométricos reciben una
primitiva única en la entrada y pueden generar cero, una o muchas primitivas. Comenzaremos escribiendo un sombreador geométrico que reciba un
vértice (o
punto ) en la entrada, y que alimente
un triángulo que represente una brizna de hierba.
El código anterior declara un sombreador geométrico llamado
geo
con dos parámetros. El primer
triangle float4 IN[3]
informa que tomará un triángulo (que consta de tres puntos) como entrada. El segundo, como
TriangleStream
, configura un sombreador para generar una secuencia de triángulos para que cada vértice use la estructura
geometryOutput
para transmitir sus datos.
Dijimos anteriormente que el sombreador recibirá un vértice y generará una brizna de hierba. ¿Por qué entonces obtenemos un triángulo?Será menos costoso tomar un
como entrada. Esto se puede hacer de la siguiente manera.
void geo(point vertexOutput IN[1], inout TriangleStream<geometryOutput> triStream)
Sin embargo, dado que nuestra malla entrante (en este caso
GrassPlane10x10
, ubicada en la carpeta
Mesh
) tiene una
topología de triángulo , esto provocará una falta de coincidencia entre la topología de malla entrante y la primitiva de entrada requerida. Aunque esto está
permitido en DirectX HLSL, no está
permitido en OpenGL , por lo que se mostrará un error.
Además, agregamos el último parámetro entre corchetes sobre la declaración de la función:
[maxvertexcount(3)]
. Él le dice a la GPU que sacaremos (pero no estamos
obligados a hacerlo)
no más de 3 vértices. También hacemos que
SubShader
use un sombreador geométrico al declararlo dentro de
Pass
.
Nuestro sombreador geométrico no está haciendo nada todavía; Para dibujar un triángulo, agregue el siguiente código dentro del sombreador geométrico.
geometryOutput o; o.pos = float4(0.5, 0, 0, 1); triStream.Append(o); o.pos = float4(-0.5, 0, 0, 1); triStream.Append(o); o.pos = float4(0, 1, 0, 1); triStream.Append(o);
Esto dio resultados muy extraños. Cuando mueve la cámara, queda claro que el triángulo se representa en el
espacio de la
pantalla . Esto es lógico: dado que el sombreador geométrico se ejecuta inmediatamente antes de procesar los vértices, le quita al sombreador de vértices la responsabilidad de que los vértices se muestren en
el espacio de truncamiento . Cambiaremos nuestro código para reflejar esto.
Ahora nuestro triángulo se representa correctamente en el mundo. Sin embargo, parece que solo se crea uno. De hecho, se
dibuja un triángulo para cada vértice de nuestra malla, pero las posiciones asignadas a los vértices del triángulo son
constantes ; no cambian para cada vértice entrante. Por lo tanto, todos los triángulos están ubicados uno encima del otro.
Arreglaremos esto haciendo que las posiciones de vértice salientes se
desplacen en relación con el punto entrante.
¿Por qué algunos vértices no crean un triángulo?Aunque hemos determinado que la primitiva entrante será un
triángulo , una brizna de hierba se transmite solo desde
uno de los puntos del triángulo, descartando los otros dos. Por supuesto, podemos transferir una brizna de hierba desde los tres puntos entrantes, pero esto conducirá al hecho de que los triángulos vecinos crean excesivamente briznas de hierba una encima de la otra.
O puede resolver este problema tomando mallas que tengan el tipo de
puntos de topología como mallas entrantes del sombreador geométrico.
Los triángulos ahora se dibujan correctamente, y su base se encuentra en el pico que los emite. Antes de continuar, haga que el objeto
GrassPlane
inactivo en la escena y
GrassBall
objeto
GrassBall
. Queremos que el césped se genere correctamente en diferentes tipos de superficies, por lo que es importante probarlo en mallas de diferentes formas.
Hasta ahora, todos los triángulos se emiten en una dirección, y no hacia afuera de la superficie de la esfera. Para resolver este problema, crearemos briznas de hierba en un
espacio tangente .
2. Espacio tangente
Idealmente, nos gustaría crear briznas de hierba estableciendo un ancho, altura, curvatura y rotación diferentes, sin tener en cuenta el ángulo de la superficie desde la que se emite la brizna de hierba. En pocas palabras, definimos una brizna de hierba en un espacio
local para el vértice que la emite , y luego la transformamos para que sea
local a la malla . Este espacio se llama
espacio tangente .
En el espacio tangente, los ejes X , Y y Z se definen en relación con la posición normal de la superficie (en nuestro caso, los vértices).Como cualquier otro espacio, podemos definir el espacio tangente de un vértice con tres vectores:
derecho ,
adelante y
arriba . Usando estos vectores, podemos crear una matriz para convertir la brizna de hierba de la tangente al espacio local.
Puede acceder a los vectores a la
derecha y
hacia arriba agregando nuevos datos de vértice de entrada.
El tercer vector puede calcularse tomando el
producto del
vector entre otros dos. Un producto vectorial devuelve un vector
perpendicular a dos vectores entrantes.
¿Por qué el resultado del producto vectorial se multiplica por la coordenada de la tangente w?Al exportar una malla desde un editor 3D, generalmente tiene binormales (también llamados
tangentes a dos puntos ) ya almacenados en los datos de la malla. En lugar de importar estos binormales, Unity simplemente toma la dirección de cada binormal y los asigna a la coordenada de la tangente
w . Esto le permite ahorrar memoria y, al mismo tiempo, ofrece la posibilidad de recrear el binormal correcto. Una discusión detallada de este tema se puede encontrar
aquí .
Teniendo los tres vectores, podemos crear una matriz para la transformación entre tangentes y espacios locales. Multiplicaremos cada vértice de la brizna de hierba por esta matriz antes de pasarlo a
UnityObjectToClipPos
, que espera un vértice en el espacio local.
Antes de usar la matriz, transferimos el código de salida del vértice a la función para no escribir las mismas líneas de código una y otra vez. Esto se llama
el principio DRY , o
no se repita .
Finalmente, multiplicamos los vértices de salida por la matriz
tangentToLocal
, alineándolos correctamente con la normalidad de su punto de entrada.
triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(0.5, 0, 0)))); triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(-0.5, 0, 0)))); triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(0, 1, 0))));
Esto es más parecido a lo que necesitamos, pero no del todo correcto. El problema aquí es que inicialmente asignamos la dirección "arriba" (arriba) del eje
Y ; sin embargo, en el espacio tangente, la dirección hacia arriba generalmente se encuentra a lo largo del eje
Z. Ahora haremos estos cambios.
3. Apariencia de hierba
Para que los triángulos se vean más como briznas de hierba, debes agregar colores y variaciones. Comenzamos agregando un
gradiente que baja desde la parte superior de la brizna de hierba.
3.1 gradiente de color
Nuestro objetivo es permitir que el artista establezca dos colores: superior e inferior, e interpolar entre estos dos colores, inclinar a la base de la brizna de hierba. Estos colores ya están definidos en el archivo de sombreado como
_TopColor
y
_BottomColor
. Para su muestreo adecuado, debe pasar
las coordenadas UV al sombreador de fragmentos.
Creamos coordenadas UV para una brizna de hierba en forma de triángulo, cuyos dos vértices de la base se encuentran en la parte inferior izquierda y derecha, y el vértice de la punta se encuentra en el centro en la parte superior.
Coordenadas UV de los tres vértices de las briznas de hierba. Aunque pintamos las briznas de hierba con un degradado simple, una disposición similar de texturas le permite superponer texturas.Ahora podemos muestrear los colores superior e inferior en el fragment shader con UV y luego interpolarlos con
lerp
. También necesitaremos modificar los parámetros del sombreador de fragmentos, haciendo
geometryOutput
como entrada, y no solo la posición de
float4
.
3.2 Dirección aleatoria de la cuchilla
Para crear variabilidad y darle al césped un aspecto más natural, haremos que cada brizna se vea en una dirección aleatoria. Para hacer esto, necesitamos crear una matriz de rotación que rote la brizna de hierba una cantidad aleatoria alrededor de su eje
superior .
Hay dos funciones en el archivo de sombreador que nos ayudarán a hacer esto:
rand
, que genera un número aleatorio a partir de la entrada tridimensional, y
AngleAxis3x3
, que recibe el ángulo (en
radianes ) y devuelve una matriz que gira este valor alrededor del eje especificado. La última función funciona exactamente igual que la función C #
Quaternion.AngleAxis (solo
AngleAxis3x3
devuelve una matriz, no un cuaternión).
La función
rand
devuelve un número en el rango 0 ... 1; lo multiplicamos por
2 Pi para obtener el rango completo de valores angulares.
Utilizamos la posición de entrada como semilla para una rotación aleatoria. Debido a esto, cada brizna de hierba tendrá su propia rotación, constante en cada cuadro.
La rotación se puede aplicar a la brizna de hierba multiplicándola por la matriz
tangentToLocal
creada. Tenga en cuenta que la multiplicación de matrices
no es
conmutativa ; El orden de los operandos es
importante .
3.3 Doblado aleatorio hacia adelante
Si todas las briznas de hierba están perfectamente alineadas, aparecerán igual. Esto puede ser adecuado para césped bien cuidado, por ejemplo, en un césped recortado, pero en la naturaleza el césped no crece así. Crearemos una nueva matriz para rotar el césped a lo largo del eje
X , así como una propiedad para controlar esta rotación.
Nuevamente usamos la posición de la brizna de hierba como una semilla aleatoria, esta vez
barriendo para crear una semilla única. También multiplicaremos
UNITY_PI
por
0.5 ; Esto nos dará un intervalo aleatorio de 0 ... 90 grados.
Nuevamente aplicamos esta matriz a través de la rotación, multiplicando todo en el orden correcto.
3.4 Ancho y alto
Mientras que el tamaño de la brizna de hierba se limita a un ancho de 1 unidad y una altura de 1 unidad. Agregaremos propiedades para controlar el tamaño, así como propiedades para agregar variación aleatoria.
Los triángulos ahora son mucho más como briznas de hierba, pero también muy poco. Simplemente no hay suficientes picos en la malla entrante para crear la impresión de un campo densamente cubierto.
Una solución es crear una nueva malla más densa, ya sea usando C # o en un editor 3D. Esto funcionará, pero no nos permitirá controlar dinámicamente la densidad del césped. En su lugar, dividiremos la malla entrante mediante
teselación .
4. Teselación
La teselación es una etapa opcional de la canalización de representación, realizada después del sombreador de vértices y antes del sombreador geométrico (si lo hay). Su tarea es subdividir una superficie entrante en muchas primitivas. La teselación se implementa en dos pasos programables: sombreadores de
casco y
dominio .
Para los sombreadores de superficie, Unity tiene una
implementación de teselación incorporada . Sin embargo, dado que
no usamos sombreadores de superficie, tendremos que implementar nuestros propios sombreadores de shell y dominio. En este artículo, no discutiré la implementación de teselación en detalle, y simplemente usaremos el archivo
CustomTessellation.cginc
existente. Este archivo está adaptado del
artículo de Catlike Coding , que es una excelente fuente de información sobre la implementación de teselación en Unity.
Si incluimos el objeto
TessellationExample
en la escena, veremos que ya tiene material que implementa la teselación. Cambiar la propiedad
Uniforme de teselación demuestra el efecto de subdivisión.
Implementamos teselación en el sombreador de hierba para controlar la densidad del plano y, por lo tanto, para controlar la cantidad de briznas de hierba generadas. Primero debe agregar el archivo
CustomTessellation.cginc
. Nos referiremos a él por su ruta
relativa al sombreador.
Si abre
CustomTessellation.cginc
, notará que las
vertexOutput
vertexInput
y
vertexOutput
, así como los sombreadores de vértices, ya están definidos en él. No es necesario redefinirlos en nuestro sombreador de hierba; Pueden ser eliminados.
Tenga en cuenta que el sombreador de vértices
vert
en
CustomTessellation.cginc
simplemente pasa la entrada directamente a la etapa de teselación; La función
vertexOutput
, llamada dentro del sombreador de dominio, asume la tarea de crear la estructura
vertexOutput
.
Ahora podemos agregar sombreadores de
shell y
dominio al sombreador de hierba. También agregaremos una nueva propiedad
_TessellationUniform
para controlar el tamaño de la unidad: la variable correspondiente a esta propiedad ya se ha declarado en
CustomTessellation.cginc
.
Ahora, cambiar la propiedad
Uniforme de teselación nos permite controlar la densidad del césped. Encontré que se obtienen buenos resultados con un valor de
5 .
5. El viento
Implementamos el viento muestreando la
textura de distorsión . Esta textura se verá como
un mapa normal , solo en ella habrá solo dos en lugar de tres canales. Utilizaremos estos dos canales como direcciones del viento a lo largo de
X e
Y.Antes de muestrear la textura del viento, necesitamos crear una coordenada UV. En lugar de usar las coordenadas de textura asignadas a la malla, aplicamos la posición del punto entrante. Gracias a esto, si hay varias mallas de hierba en el mundo, se creará la ilusión de que todas son parte del mismo sistema eólico. También utilizamos la
variable incorporada _Time
shader para desplazar la textura del viento a lo largo de la superficie de la hierba.
Aplicamos la escala y el desplazamiento de
_WindDistortionMap
a la posición, y luego lo
_Time.y
a
_Time.y
, escalado a
_WindFrequency
. Ahora usaremos estos rayos UV para tomar muestras de la textura y crear una propiedad para controlar la fuerza del viento.
Tenga en cuenta que escalamos el valor muestreado de la textura del intervalo 0 ... 1 al intervalo -1 ... 1. A continuación, podemos crear un vector normalizado que denote la dirección del viento.
Ahora podemos crear una matriz para rotar alrededor de este vector y multiplicarlo por nuestra matriz de
transformationMatrix
.
Finalmente, transferimos la textura
Wind
(ubicada en la raíz del proyecto) al campo
Mapa de distorsión del
viento del material de hierba en el editor de Unity. También establecemos el parámetro de
mosaico de la textura en
0.01, 0.01
.
Si el césped no se está animando en la ventana de
Escena , haga clic en el botón
Alternar skybox, niebla y otros efectos para habilitar materiales animados.
, , , , - .
, ( ), ( )., , .
windRotation
bendRotationMatrix
, .
6.
Ahora las hojas individuales de hierba están definidas por un triángulo. A grandes distancias, esto no es un problema, pero cerca de la brizna de hierba se ven muy rígidos y geométricos, en lugar de orgánicos y vivos. Arreglaremos esto construyendo briznas de hierba a partir de varios triángulos y doblándolos a lo largo de la curva .Cada brizna de hierba se dividirá en varios segmentos . Cada segmento tendrá una forma rectangular y constará de dos triángulos, con la excepción del segmento superior: será un triángulo que denota la punta de la brizna de hierba.Hasta ahora, hemos dibujado solo tres vértices, creando un solo triángulo. ¿Cómo, entonces, si hay más vértices, el sombreador geométrico sabe cuáles unir y formar triángulos? La respuesta está en la estructura de datos.despojar triángulo . Los primeros tres vértices se unen y forman un triángulo, y cada nuevo vértice forma un triángulo con los dos anteriores.Una brizna de hierba subdividida, representada como una franja triangular y creaba un vértice a la vez. Después de los primeros tres vértices, cada nuevo vértice forma un nuevo triángulo con los dos vértices anteriores.Esto no solo es más eficiente en términos de uso de memoria, sino que también le permite crear de forma fácil y rápida secuencias de triángulos en su código. Si quisiéramos crear varias franjas de triángulos, podríamos llamar a RestartStrip para la TriangleStream
función . Antes de comenzar a dibujar más vértices desde el sombreador geométrico, necesitamos aumentarlo . Usaremos el diseño para permitir que el autor del sombreador controle el número de segmentos y calcule el número de vértices mostrados a partir de él.maxvertexcount
#define
Inicialmente, establecemos el número de segmentos en 3 y actualizamos maxvertexcount
para calcular el número de vértices en función del número de segmentos.Para crear una brizna de hierba segmentada, usamos un ciclo for
. Cada iteración del bucle agregará dos vértices : izquierdo y derecho . Después de completar la punta, agregamos el último vértice en la punta de la brizna de hierba.Antes de hacer esto, será útil mover parte de la posición informática de los vértices de las hojas de hierba del código a la función, porque usaremos este código varias veces dentro y fuera del bucle. Agregue lo CGINCLUDE
siguiente al bloque : geometryOutput GenerateGrassVertex(float3 vertexPosition, float width, float height, float2 uv, float3x3 transformMatrix) { float3 tangentPoint = float3(width, 0, height); float3 localPosition = vertexPosition + mul(transformMatrix, tangentPoint); return VertexOutput(localPosition, uv); }
Esta función realiza las mismas tareas porque pasa los argumentos que pasamos previamente VertexOutput
para generar los vértices de la brizna de hierba. Al obtener una posición, altura y ancho, transforma correctamente el vértice utilizando la matriz transmitida y le asigna una coordenada UV. Actualizaremos el código existente para que la función funcione correctamente.
La función comenzó a funcionar correctamente y estamos listos para mover el código de generación de vértices al bucle for
. Agregue lo float width
siguiente debajo de la línea : for (int i = 0; i < BLADE_SEGMENTS; i++) { float t = i / (float)BLADE_SEGMENTS; }
Anunciamos un ciclo que se ejecutará una vez para cada segmento de brizna de hierba. Dentro del bucle, agregue una variable t
. Esta variable almacenará un valor en el rango 0 ... 1, que indica qué tan lejos nos hemos movido a lo largo de la brizna de hierba. Usamos este valor para calcular el ancho y la altura del segmento en cada iteración del bucle.
Al subir una brizna de hierba, la altura aumenta y el ancho disminuye. Ahora podemos agregar llamadas al bucle GenerateGrassVertex
para agregar vértices a la secuencia de triángulos. También agregaremos una llamada GenerateGrassVertex
fuera del bucle para crear la punta de la brizna de hierba.
Eche un vistazo a la línea con la declaración float3x3 transformMatrix
: aquí seleccionamos una de las dos matrices de transformación: tomamos transformationMatrixFacing
los vértices de la base y transformationMatrix
todos los demás.Las briznas de hierba ahora se dividen en muchos segmentos, pero la superficie de la brizna todavía es plana: los nuevos triángulos aún no están involucrados. Vamos a añadir una brizna de hierba curvatura, cambiando la posición del vértice de la Y . Primero, necesitamos modificar la función GenerateGrassVertex
para que obtenga un desplazamiento en Y , que llamaremos forward
.
Para calcular el desplazamiento de cada vértice, sustituimos un pow
valor en la función t
. Después de subir t
a una potencia, su efecto sobre el desplazamiento hacia adelante será no lineal y convertirá la brizna de hierba en una curva.
Este es un código bastante extenso, pero todo el trabajo se realiza de manera similar al ancho y la altura de la brizna de hierba. Con valores más bajos _BladeForward
y _BladeCurve
obtenemos un césped ordenado y bien cuidado, y valores más grandes darán el efecto contrario.7. Iluminación y sombras.
Como paso final para completar el sombreador, agregaremos la capacidad de proyectar y recibir sombras. También agregaremos iluminación simple desde la fuente principal de luz direccional.7.1 Sombras de fundición
Para proyectar sombras en Unity, debe agregar una segunda pasada al sombreador. Este pasaje será utilizado por las fuentes de luz creadoras de sombras en la escena para representar la profundidad de la hierba en su mapa de sombras . Esto significa que el sombreador geométrico tendrá que ser lanzado en el pasaje de sombra, para que las hojas de hierba puedan proyectar sombras.Dado que el sombreador geométrico está escrito dentro de bloques CGINCLUDE
, podemos usarlo en cualquier paso del archivo. Cree un segundo pase que use los mismos sombreadores que el primero, con la excepción del sombreador de fragmentos: definiremos uno nuevo en el que escribiremos una macro que procese la salida.
Además de crear un nuevo sombreador de fragmentos, hay un par de diferencias importantes en este pasaje. La etiqueta LightMode
importa ShadowCaster
, no ForwardBase
: esto le dice a Unity que este pasaje debe usarse para representar el objeto en mapas de sombras. También hay una directiva de preprocesador aquí multi_compile_shadowcaster
. Asegura que el sombreador compila todas las opciones necesarias para proyectar sombras.Haz que el objeto del juego esté Fence
activo en la escena; entonces obtenemos una superficie sobre la cual las briznas de hierba pueden proyectar una sombra.7.2 Obteniendo sombras
Después de que Unity renderiza el mapa de sombras desde el punto de vista de la fuente de luz que crea la sombra, lanza un pasaje que "recoge" las sombras en la textura del espacio de la pantalla . Para muestrear esta textura, necesitaremos calcular las posiciones de los vértices en el espacio de la pantalla y transferirlos al sombreador de fragmentos.
En el sombreador de fragmentos del pasaje, ForwardBase
podemos usar una macro para obtener un valor que float
indique si la superficie está en sombras o no. Este valor está en el rango 0 ... 1, donde 0 es sombreado completo, 1 es iluminación completa.¿Por qué la coordenada UV del espacio de la pantalla se llama _ShadowCoord? Esto no cumple con las convenciones de nomenclatura anteriores.Unity ( ).
SHADOW_ATTENUATION
.
Autolight.cginc
, , .
#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
- , .
Finalmente, necesitamos hacer que el sombreador esté configurado correctamente para recibir sombras. Para hacer esto, agregaremos una ForwardBase
directiva de preprocesador al pase para que compile todas las opciones de sombreador necesarias.
Al acercar la cámara, podemos observar artefactos en la superficie de las briznas de hierba; son causadas por el hecho de que las hojas individuales de hierba proyectan sombras sobre sí mismas. Podemos solucionar esto aplicando un desplazamiento lineal o moviendo las posiciones de los vértices en el espacio de truncamiento ligeramente lejos de la pantalla. Utilizaremos la macro Unity para esto y la incluiremos en el diseño #if
para que la operación se realice solo en la ruta de la sombra.
Después de aplicar el desplazamiento de sombra lineal, los artefactos de sombra en forma de rayas desaparecen de la superficie de los triángulos.¿Por qué hay artefactos a lo largo de los bordes de las hojas de hierba sombreadas?(multisample anti-aliasing
MSAA ) Unity
, . , .
— , ,
Unity . ( );
Unity .
7.3 Iluminación
Implementaremos la iluminación utilizando un algoritmo de cálculo de iluminación difusa muy simple y común.... donde N es lo normal a la superficie, L es la dirección normalizada de la fuente principal de iluminación direccional, e I es la iluminación calculada. En este tutorial no implementaremos iluminación indirecta.Por el momento, las normales no están asignadas a los vértices de las briznas de hierba. Al igual que con las posiciones de vértice, primero calculamos las normales en el espacio tangente y luego las convertimos a locales.Cuando la cantidad de curvatura de la hoja es 1 , todas las hojas de hierba en el espacio tangente se dirigen en una dirección: directamente opuestas al eje Y. Como primer paso de nuestra solución, calculamos lo normal, suponiendo que no haya curvatura.
tangentNormal
, definido como directamente opuesto al eje Y , se transforma mediante la misma matriz que usamos para convertir los puntos tangentes al espacio local. Ahora podemos pasarlo a una función VertexOutput
, y luego a una estructura geometryOutput
.
Note que antes de la conclusión, transformamos lo normal en espacio mundial ; La unidad transmite a los sombreadores la dirección de la fuente principal de luz direccional en el espacio mundial, por lo que esta transformación es necesaria.Ahora podemos visualizar las normales en el fragmento de sombreador ForwardBase
para verificar el resultado de nuestro trabajo.
Como Cull
se asigna un valor en nuestro sombreador Off
, se representan ambos lados de la brizna de hierba. Para que lo normal se dirija en la dirección correcta, usamos un parámetro auxiliar VFACE
que agregamos al sombreador de fragmentos.El argumento fixed facing
devolverá un número positivo si mostramos la cara frontal de la superficie, y un número negativo si es lo contrario. Usamos esto en el código anterior para voltear lo normal si es necesario.Cuando la cantidad de curvatura de la cuchilla es mayor que 1, la posición Z tangente de cada vértice se desplazará por la cantidad que se forward
pasa a la función GenerateGrassVertex
. Usaremos este valor para escalar proporcionalmente el eje Z de las normales.
Finalmente, agregue el código al sombreador de fragmentos para combinar las sombras, la iluminación direccional y la iluminación ambiental. Recomiendo estudiar información más detallada sobre la implementación de iluminación personalizada en sombreadores en mi tutorial sobre sombreadores de toon .
Conclusión
En este tutorial, el césped cubre un área pequeña de 10x10 unidades. Para que el sombreador cubra grandes espacios abiertos mientras mantiene un alto rendimiento, se deben introducir optimizaciones. Puede aplicar la teselación en función de la distancia, de modo que se eliminen menos briznas de hierba de la cámara. Además, a largas distancias, en lugar de hojas individuales de hierba, se pueden dibujar grupos de hojas de hierba usando un solo cuadrilátero con una textura superpuesta., Standard Assets Unity. , ., Unity
GitHub , G-.
GitHub:
Sin interoperabilidad, los efectos gráficos pueden parecer estáticos o sin vida para los jugadores. Este tutorial ya es muy largo, por lo que no agregué una sección sobre la interacción de los objetos del mundo con la hierba.Una implementación ingenua de hierbas interactivas contendría dos componentes: algo en el mundo del juego que puede transmitir datos al sombreador para decirle con qué parte de la hierba se está interactuando y codificar en el sombreador para interpretar estos datos. Aquíse muestra un ejemplo de cómo se puede implementar esto con agua . Se puede adaptar para trabajar con hierba; en lugar de dibujar ondas en el lugar donde está el personaje, puedes girar la brizna de hierba hacia abajo para simular los efectos de los pasos.