Creación de sombreadores con Babylon.js
Publicado: 2022-03-10Los sombreadores son un concepto clave si desea liberar la potencia bruta de su GPU. Te ayudaré a comprender cómo funcionan e incluso a experimentar con su poder interior de una manera fácil, gracias a Babylon.js.
¿Como funciona?
Antes de experimentar, debemos ver cómo funcionan las cosas internamente.
Cuando se trata de 3D acelerado por hardware, tendrá que lidiar con dos CPU: la CPU principal y la GPU. La GPU es una especie de CPU extremadamente especializada.
Lectura adicional en SmashingMag:
- Creación de un juego WebGL multiplataforma con Babylon.js
- Uso de la API Gamepad en juegos web
- Introducción al modelado poligonal y Three.js
- Cómo crear una caja de ritmos receptiva de 8 bits
La GPU es una máquina de estado que configuras usando la CPU. Por ejemplo, la CPU configurará la GPU para representar líneas en lugar de triángulos; definirá si la transparencia está activada; y así.
Una vez que se establecen todos los estados, la CPU puede definir qué renderizar: la geometría.
La geometría se compone de:
- una lista de puntos que se llaman vértices y se almacenan en una matriz llamada búfer de vértices,
- una lista de índices que definen las caras (o triángulos) almacenados en una matriz denominada búfer de índice.
El paso final para la CPU es definir cómo renderizar la geometría; para esta tarea, la CPU definirá sombreadores en la GPU. Los sombreadores son piezas de código que la GPU ejecutará para cada uno de los vértices y píxeles que tiene que renderizar. (Un vértice —o vértices cuando hay varios— es un “punto” en 3D).
Hay dos tipos de sombreadores: sombreadores de vértices y sombreadores de píxeles (o fragmentos).
Canalización de gráficos
Antes de profundizar en los sombreadores, retrocedamos. Para renderizar píxeles, la GPU tomará la geometría definida por la CPU y hará lo siguiente:
- Usando el búfer de índice, se juntan tres vértices para definir un triángulo.
- El búfer de índice contiene una lista de índices de vértices. Esto significa que cada entrada en el búfer de índice es el número de un vértice en el búfer de vértice.
- Esto es realmente útil para evitar la duplicación de vértices.
Por ejemplo, el siguiente búfer de índice es una lista de dos caras: [1 2 3 1 3 4]. La primera cara contiene el vértice 1, el vértice 2 y el vértice 3. La segunda cara contiene el vértice 1, el vértice 3 y el vértice 4. Entonces, hay cuatro vértices en esta geometría:
El sombreador de vértices se aplica a cada vértice del triángulo. El objetivo principal del sombreador de vértices es producir un píxel para cada vértice (la proyección en la pantalla 2D del vértice 3D):
Usando estos tres píxeles (que definen un triángulo 2D en la pantalla), la GPU interpolará todos los valores adjuntos al píxel (al menos sus posiciones), y el sombreador de píxeles se aplicará a cada píxel incluido en el triángulo 2D para generar un color para cada píxel:
Este proceso se realiza para cada cara definida por el búfer de índice.
Obviamente, debido a su naturaleza paralela, la GPU puede procesar este paso para muchas caras simultáneamente y lograr un rendimiento realmente bueno.
GLSL
Acabamos de ver que para renderizar triángulos, la GPU necesita dos sombreadores: el sombreador de vértices y el sombreador de píxeles. Estos shaders están escritos en un lenguaje llamado Graphics Library Shader Language (GLSL). parece c
Aquí hay una muestra de un sombreador de vértices común:
precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }
Estructura del sombreador de vértices
Un sombreador de vértices contiene lo siguiente:
- Atributos . Un atributo define una porción de un vértice. Por defecto, un vértice debe contener al menos una posición (un
vector3:x, y, z
). Sin embargo, como desarrollador, puede decidir agregar más información. Por ejemplo, en el sombreador anterior, hay unvector2
llamadouv
(es decir, coordenadas de textura que le permiten aplicar una textura 2D a un objeto 3D). - uniformes Un uniforme es una variable utilizada por el sombreador y definida por la CPU. El único uniforme que tenemos aquí es una matriz que se usa para proyectar la posición del vértice (x, y, z) en la pantalla (x, y).
- Variando Las variables variables son valores creados por el sombreador de vértices y transmitidos al sombreador de píxeles. Aquí, el sombreador de vértices transmitirá un
vUV
(una copia simple deuv
) al sombreador de píxeles. Esto significa que un píxel se define aquí con una posición y coordenadas de textura. Estos valores serán interpolados por la GPU y utilizados por el sombreador de píxeles. - principal La función denominada
main
es el código ejecutado por la GPU para cada vértice y debe producir al menos un valor paragl_position
(la posición del vértice actual en la pantalla).
Podemos ver en nuestra muestra que el sombreador de vértices es bastante simple. Genera una variable del sistema (que comienza con gl_
) denominada gl_position
para definir la posición del píxel asociado y establece una variable variable denominada vUV
.
El vudú detrás de las matrices
Lo que pasa con nuestro sombreador es que tenemos una matriz llamada worldViewProjection
, y usamos esta matriz para proyectar la posición del vértice en la variable gl_position
. Eso está bien, pero ¿cómo obtenemos el valor de esta matriz? Es un uniforme, por lo que tenemos que definirlo en el lado de la CPU (usando JavaScript).
Esta es una de las partes complejas de hacer 3D. Debe comprender matemáticas complejas (o tendrá que usar un motor 3D como Babylon.js, que veremos más adelante).
La matriz worldViewProjection
es la combinación de tres matrices diferentes:
El uso de la matriz resultante nos permite transformar vértices 3D en píxeles 2D, teniendo en cuenta el punto de vista y todo lo relacionado con la posición, escala y rotación del objeto actual.
Esta es su responsabilidad como desarrollador 3D: crear y mantener actualizada esta matriz.
De vuelta a los sombreadores
Una vez que se ejecuta el sombreador de vértices en cada vértice (tres veces, entonces), tendremos tres píxeles con la gl_position
correcta y un valor de vUV
. La GPU interpolará estos valores en cada píxel contenido en el triángulo producido con estos píxeles.
Luego, para cada píxel, ejecutará el sombreador de píxeles:
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); }
Estructura de sombreado de píxeles (o fragmentos)
La estructura de un sombreador de píxeles es similar a la de un sombreador de vértices:
- Variando Las variables variables son valores creados por el sombreador de vértices y transmitidos al sombreador de píxeles. Aquí, el sombreador de píxeles recibirá un valor
vUV
del sombreador de vértices. - uniformes Un uniforme es una variable utilizada por el sombreador y definida por la CPU. El único uniforme que tenemos aquí es un muestreador, que es una herramienta que se usa para leer los colores de las texturas.
- principal La función denominada
main
es el código ejecutado por la GPU para cada píxel y que debe producir al menos un valor paragl_FragColor
(es decir, el color del píxel actual).
Este sombreador de píxeles es bastante simple: lee el color de la textura utilizando las coordenadas de textura del sombreador de vértices (que, a su vez, lo obtiene del vértice).
El problema es que cuando se desarrollan los sombreadores, solo estás a mitad de camino, porque luego tienes que lidiar con una gran cantidad de código WebGL. De hecho, WebGL es realmente poderoso pero también de muy bajo nivel, y usted tiene que hacer todo usted mismo, desde crear los búferes hasta definir estructuras de vértices. También tiene que hacer todos los cálculos, establecer todos los estados, manejar la carga de texturas, etc.
¿Demasiado duro? BABYLON.ShaderMaterial al rescate
Sé lo que estás pensando: "Los sombreadores son geniales, pero no quiero preocuparme por la plomería interna de WebGL o incluso por las matemáticas".
¡Y tienes razón! Esta es una pregunta perfectamente legítima, ¡y es exactamente por eso que creé Babylon.js!
Para usar Babylon.js, primero necesita una página web simple:
<!DOCTYPE html> <html> <head> <title>Babylon.js</title> <script src="Babylon.js"></script> <script type="application/vertexShader"> precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Normal varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; } </script> <script type="application/fragmentShader"> precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); } </script> <script src="index.js"></script> <style> html, body { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; margin: 0px; overflow: hidden; } #renderCanvas { width: 100%; height: 100%; touch-action: none; -ms-touch-action: none; } </style> </head> <body> <canvas></canvas> </body> </html>
Notarás que los sombreadores están definidos por etiquetas <script>
. Con Babylon.js, también puede definirlos en archivos separados (archivos .fx
).
- Fuente de Babylon.js
- repositorio GitHub
Finalmente, el código JavaScript principal es este:
"use strict"; document.addEventListener("DOMContentLoaded", startGame, false); function startGame() { if (BABYLON.Engine.isSupported()) { var canvas = document.getElementById("renderCanvas"); var engine = new BABYLON.Engine(canvas, false); var scene = new BABYLON.Scene(engine); var camera = new BABYLON.ArcRotateCamera("Camera", 0, Math.PI / 2, 10, BABYLON.Vector3.Zero(), scene); camera.attachControl(canvas); // Creating sphere var sphere = BABYLON.Mesh.CreateSphere("Sphere", 16, 5, scene); var amigaMaterial = new BABYLON.ShaderMaterial("amiga", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "uv"], uniforms: ["worldViewProjection"] }); amigaMaterial.setTexture("textureSampler", new BABYLON.Texture("amiga.jpg", scene)); sphere.material = amigaMaterial; engine.runRenderLoop(function () { sphere.rotation.y += 0.05; scene.render(); }); } };
Puede ver que uso BABYLON.ShaderMaterial
para deshacerme de la carga de compilar, vincular y manejar sombreadores.
Cuando crea BABYLON.ShaderMaterial
, debe especificar el elemento DOM utilizado para almacenar los sombreadores o el nombre base de los archivos donde se encuentran los sombreadores. Si elige usar archivos, debe crear un archivo para cada sombreador y usar el siguiente patrón: basename.vertex.fx
y basename.fragment.fx
. Luego, tendrás que crear el material así:
var cloudMaterial = new BABYLON.ShaderMaterial("cloud", scene, "./myShader", { attributes: ["position", "uv"], uniforms: ["worldViewProjection"] });
También debe especificar los nombres de los atributos y uniformes que utiliza.
Luego, puede establecer directamente los valores de sus uniformes y setTexture
usando las funciones setTexture, setFloat
, setFloats
, setColor3
, setColor4
, setVector2
, setVector3
, setVector4
, setMatrix
.
Bastante simple, ¿verdad?
¿Y recuerda la matriz worldViewProjection
anterior, usando Babylon.js y BABYLON.ShaderMaterial
? ¡Simplemente no tienes que preocuparte por eso! BABYLON.ShaderMaterial
lo calculará automáticamente porque lo declarará en la lista de uniformes.
BABYLON.ShaderMaterial
también puede manejar las siguientes matrices por usted:
-
world
, -
view
, -
projection
, -
worldView
, -
worldViewProjection
.
Ya no hay necesidad de matemáticas. Por ejemplo, cada vez que sphere.rotation.y += 0.05
, la matriz world
de la esfera se generará automáticamente y se transmitirá a la GPU.
Vea el resultado en vivo usted mismo.
Crea tu propio sombreador (CYOS)
Ahora, vayamos más grandes y creemos una página en la que pueda crear dinámicamente sus propios sombreadores y ver el resultado de inmediato. Esta página utilizará el mismo código que discutimos anteriormente y utilizará el objeto BABYLON.ShaderMaterial
para compilar y ejecutar los sombreadores que creará.
Usé el editor de código ACE para Create Your Own Shader (CYOS). Es un editor de código increíble, con resaltado de sintaxis. Siéntete libre de echarle un vistazo.
Usando el primer cuadro combinado, podrá seleccionar sombreadores predefinidos. Veremos cada uno de ellos justo después.
También puede cambiar la malla (es decir, el objeto 3D) utilizada para obtener una vista previa de sus sombreadores utilizando el segundo cuadro combinado.
El botón de compilación se usa para crear un nuevo BABYLON.ShaderMaterial
a partir de sus shaders. El código utilizado por este botón es el siguiente:
// Compile shaderMaterial = new BABYLON.ShaderMaterial("shader", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "normal", "uv"], uniforms: ["world", "worldView", "worldViewProjection"] }); var refTexture = new BABYLON.Texture("ref.jpg", scene); refTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE; refTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE; var amigaTexture = new BABYLON.Texture("amiga.jpg", scene); shaderMaterial.setTexture("textureSampler", amigaTexture); shaderMaterial.setTexture("refSampler", refTexture); shaderMaterial.setFloat("time", 0); shaderMaterial.setVector3("cameraPosition", BABYLON.Vector3.Zero()); shaderMaterial.backFaceCulling = false; mesh.material = shaderMaterial;
Increíblemente simple, ¿verdad? El material está listo para enviarle tres matrices precalculadas ( world
, worldView
y worldViewProjection
). Los vértices vendrán con coordenadas de posición, normales y de textura. También hay dos texturas cargadas para usted:
Finalmente, el renderLoop
es donde actualizo dos uniformes convenientes:
- Uno se llama
time
y tiene algunas animaciones divertidas. - El otro se llama
cameraPosition
, que obtiene la posición de la cámara en sus sombreadores (útil para ecuaciones de iluminación).
engine.runRenderLoop(function () { mesh.rotation.y += 0.001; if (shaderMaterial) { shaderMaterial.setFloat("time", time); time += 0.02; shaderMaterial.setVector3("cameraPosition", camera.position); } scene.render(); });
Sombreador básico
Comencemos con el primer shader definido en CYOS: el shader básico.
Ya conocemos este shader. Calcula gl_position
y usa coordenadas de textura para obtener un color para cada píxel.
Para calcular la posición del píxel, solo necesitamos la matriz worldViewProjection
y la posición del vértice:
precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }
Las coordenadas de textura ( uv
) se transmiten sin modificar al sombreador de píxeles.
Tenga en cuenta que debemos agregar precision mediump float
en la primera línea para los sombreadores de vértices y píxeles porque Chrome lo requiere. Especifica que, para un mejor rendimiento, no usamos valores flotantes de precisión completa.
El sombreador de píxeles es aún más simple, porque solo necesitamos usar coordenadas de textura y obtener un color de textura:
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); }
Anteriormente vimos que el uniforme textureSampler
se rellena con la textura amiga
. Entonces, el resultado es el siguiente:
Sombreado en blanco y negro
Continuemos con un nuevo shader: el shader blanco y negro. El objetivo de este shader es usar el anterior pero con un modo de renderizado solo en blanco y negro.
Para hacerlo, podemos mantener el mismo sombreador de vértices. El sombreador de píxeles se modificará ligeramente.
La primera opción que tenemos es tomar solo un componente, como el verde:
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = vec4(texture2D(textureSampler, vUV).ggg, 1.0); }
Como puede ver, en lugar de usar .rgb
(esta operación se llama swizzle), hemos usado .ggg
.
Pero si queremos un efecto en blanco y negro realmente preciso, sería mejor calcular la luminancia (que tiene en cuenta todos los componentes):
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { float luminance = dot(texture2D(textureSampler, vUV).rgb, vec3(0.3, 0.59, 0.11)); gl_FragColor = vec4(luminance, luminance, luminance, 1.0); }
La operación dot
(o producto dot
) se calcula así: result = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z
.
Entonces, en nuestro caso, luminance = r * 0.3 + g * 0.59 + b * 0.11
. (Estos valores se basan en el hecho de que el ojo humano es más sensible al verde).
Suena genial, ¿no?
Sombreador de sombreado de celdas
Pasemos a un sombreador más complejo: el sombreador de sombreado de celdas.
Este requerirá que obtengamos la normalidad del vértice y la posición del vértice en el sombreador de píxeles. Entonces, el sombreador de vértices se verá así:
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 world; uniform mat4 worldViewProjection; // Varying varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vPositionW = vec3(world * vec4(position, 1.0)); vNormalW = normalize(vec3(world * vec4(normal, 0.0))); vUV = uv; }
Tenga en cuenta que también usamos la matriz mundial porque la posición y la normal se almacenan sin ninguna transformación, y debemos aplicar la matriz mundial para tener en cuenta la rotación del objeto.
El sombreador de píxeles es el siguiente:
precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { float ToonThresholds[4]; ToonThresholds[0] = 0.95; ToonThresholds[1] = 0.5; ToonThresholds[2] = 0.2; ToonThresholds[3] = 0.03; float ToonBrightnessLevels[5]; ToonBrightnessLevels[0] = 1.0; ToonBrightnessLevels[1] = 0.8; ToonBrightnessLevels[2] = 0.6; ToonBrightnessLevels[3] = 0.35; ToonBrightnessLevels[4] = 0.2; vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); vec3 color = texture2D(textureSampler, vUV).rgb; if (ndl > ToonThresholds[0]) { color *= ToonBrightnessLevels[0]; } else if (ndl > ToonThresholds[1]) { color *= ToonBrightnessLevels[1]; } else if (ndl > ToonThresholds[2]) { color *= ToonBrightnessLevels[2]; } else if (ndl > ToonThresholds[3]) { color *= ToonBrightnessLevels[3]; } else { color *= ToonBrightnessLevels[4]; } gl_FragColor = vec4(color, 1.); }
El objetivo de este sombreador es simular la luz y, en lugar de calcular un sombreado suave, aplicaremos la luz de acuerdo con umbrales de brillo específicos. Por ejemplo, si la intensidad de la luz está entre 1 (máximo) y 0,95, el color del objeto (obtenido de la textura) se aplicaría directamente. Si la intensidad está entre 0,95 y 0,5, el color se atenuaría en un factor de 0,8. Y así.
Hay principalmente cuatro pasos en este shader.
Primero, declaramos constantes de umbrales y niveles.
Luego, calculamos la iluminación usando la ecuación de Phong (consideraremos que la luz no se mueve):
vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW));
La intensidad de la luz por píxel depende del ángulo entre la dirección normal y la de la luz.
Luego, obtenemos el color de la textura del píxel.
Finalmente, comprobamos el umbral y aplicamos el nivel al color.
El resultado parece un objeto de dibujos animados:
Sombreador de Phong
Usamos una parte de la ecuación de Phong en el shader anterior. Usémoslo completamente ahora.
El sombreador de vértices es claramente simple aquí porque todo se hará en el sombreador de píxeles:
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vUV = uv; vPosition = position; vNormal = normal; }
De acuerdo con la ecuación, debemos calcular las partes "difusa" y "especular" usando la dirección de la luz y la normal del vértice:
precision highp float; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; // Uniforms uniform mat4 world; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 vLightPosition = vec3(0, 20, 10); // World values vec3 vPositionW = vec3(world * vec4(vPosition, 1.0)); vec3 vNormalW = normalize(vec3(world * vec4(vNormal, 0.0))); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); vec3 color = texture2D(textureSampler, vUV).rgb; // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); // Specular vec3 angleW = normalize(viewDirectionW + lightVectorW); float specComp = max(0., dot(vNormalW, angleW)); specComp = pow(specComp, max(1., 64.)) * 2.; gl_FragColor = vec4(color * ndl + vec3(specComp), 1.); }
Ya usamos la parte difusa en el shader anterior, así que aquí solo necesitamos agregar la parte especular. Puede encontrar más información sobre el sombreado Phong en Wikipedia.
El resultado de nuestra esfera:
Descartar sombreador
Para el shader de descarte, me gustaría presentar un nuevo concepto: la palabra clave de discard
.
Este sombreador descarta todos los píxeles que no sean rojos y crea la ilusión de un objeto excavado.
El sombreador de vértices es el mismo que usa el sombreador básico:
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }
El sombreador de píxeles por su lado tendrá que probar el color y usar descartar cuando, por ejemplo, el componente verde sea demasiado alto:
precision highp float; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { vec3 color = texture2D(textureSampler, vUV).rgb; if (color.g > 0.5) { discard; } gl_FragColor = vec4(color, 1.); }
El resultado es un poco divertido:
Sombreador de onda
Hemos jugado mucho con el sombreador de píxeles, pero también quiero que sepas que podemos hacer muchas cosas con los sombreadores de vértices.
Para el sombreador de ondas, reutilizaremos el sombreador de píxeles de Phong.
El sombreador de vértices usará el time
con nombre uniforme para obtener algunos valores animados. Usando este uniforme, el shader generará una onda con las posiciones de los vértices:
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; uniform float time; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec3 v = position; vx += sin(2.0 * position.y + (time)) * 0.5; gl_Position = worldViewProjection * vec4(v, 1.0); vPosition = position; vNormal = normal; vUV = uv; }
Se aplica un seno a position.y
, y el resultado es el siguiente:
Mapeo de entorno esférico
Este se inspiró en gran medida en el artículo "Creación de un sombreador de mapeo de reflexión esférica/entorno". Te dejaré leer ese excelente artículo y jugar con el shader asociado.
Sombreador de Fresnel
Me gustaría concluir este artículo con mi favorito: el sombreador de Fresnel.
Este shader se utiliza para aplicar una intensidad diferente según el ángulo entre la dirección de la vista y la normal del vértice.
El sombreador de vértices es el mismo que usa el sombreador de sombreado de celdas, y podemos calcular fácilmente el término de Fresnel en nuestro sombreador de píxeles (porque tenemos la posición normal y de la cámara, que se puede usar para evaluar la dirección de la vista):
precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 color = vec3(1., 1., 1.); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Fresnel float fresnelTerm = dot(viewDirectionW, vNormalW); fresnelTerm = clamp(1.0 - fresnelTerm, 0., 1.); gl_FragColor = vec4(color * fresnelTerm, 1.); }
¿Tu sombreador?
Ahora está más preparado para crear su propio shader. ¡Siéntase libre de publicar en el foro de Babylon.js para compartir sus experimentos!
Si quieres ir más allá, aquí hay algunos enlaces útiles:
- Babylon.js, sitio web oficial
- Babylon.js, repositorio de GitHub
- Foro de Babylon.js, desarrolladores de juegos HTML5
- Cree su propio sombreador (CYOS), Babylon.js
- Lenguaje de sombreado OpenGL,” Wikipedia
- Lenguaje de sombreado OpenGL, documentación