Sombreadores procedimentales para modelos y avatares

Puedes adjuntar sombreadores a entidades materiales, los cuales luego se aplican a entidades de forma, zona o malla. También puedes hacer lo mismo con los avatares. Esta función es actualmente una función experimental y está en fase de pruebas por parte de la comunidad. Si quieres intentar aplicar materiales de procedimiento a modelos o avatares bajo tu propio riesgo, sigue leyendo para obtener más información.

Habilitación de Materiales de Procedimiento

Para habilitar materiales de procedimiento para modelos y avatares:

  • En la interfaz, habilita Settings > Developer Menu para mostrar el menú de Developer. Entonces, habilita Developer > Render > Enable Procedural Materials.

Hacer Sombreadores Procedimentales

Probablemente conozcas los materiales y cómo funcionan. El hecho es que todos los materiales son sombreadores. Pero cuando aplicamos un sombreador de vértices o fragmentos al material, nos da acceso de nivel inferior para personalizar sus efectos.

Los sombreadores de procedimiento (o simplemente sombreadores) son texturas que se crean por medios matemáticos y algorítmicos. Es un fragmento de código que se ejecuta en la GPU o tarjeta gráfica. Pueden proporcionar una variedad de efectos, como hacer que un objeto parezca caricaturesco o simular la llama de una vela. Si has utilizado programas como Blender o Substance Designer para crear imágenes de materiales, entonces has visto esto en acción. La diferencia es que estos programas generan automáticamente el código de sombreado. Para hacer diseños y efectos personalizados, tendrás que sumergirse en el código tú mismo.

The Book of Shaders amplía esto con una analogía:

Si ya tienes experiencia haciendo dibujos con computadoras, sabes que en ese proceso dibujas un círculo, luego un rectángulo, una línea, unos triángulos hasta componer la imagen que quieres. Ese proceso es muy similar a escribir una carta o un libro a mano; es un conjunto de instrucciones que realizan una tarea tras otra.

Los Sombreadores (Shaders) también son un conjunto de instrucciones, pero las instrucciones se ejecutan todas a la vez para cada píxel de la pantalla. Eso significa que el código que escribes debe comportarse de manera diferente según la posición del píxel en la pantalla. Al igual que una prensa de tipografía, tu programa funcionará como una función que recibe una posición y devuelve un color, y cuando se compile se ejecutará extraordinariamente rápido.

Overte has support for vertex and fragment shaders on shape and material entities, and avatars. These shaders are based on the GLSL shader language, which uses the syntax and features of the C programming language. It does not have support for geometry, tessellation and evaluation, or compute shaders.

Esta documentación no pretende ser un curso completo sobre cómo crear un sombreador. Este es un tema avanzado que requiere buenas habilidades matemáticas y de programación. Hay muchos libros gratuitos disponibles en Internet que pueden enseñar sobre los sombreadores. The Book of Shaders (El Libro de los sombreadores) es uno de esos libros que se cita a menudo.

An understanding of how to use JavaScript and JSON with Overte is important before you dive into this topic. If you intend to create shaders yourself, knowing the C programming language will also be important.

Differences Between Overte and GLSL Shaders

When exploring shaders online, you may have come across many shader examples. Or you might have seen websites and applications that allow you to experiment with shader code in a live environment. Most of the code from these sources will not work with Overte without modification.

The first thing to be aware of is that there are a few shader languages. GLSL is one that is used by OpenGL, and Overte’s shaders are based on this language. Another common one is HLSL, which is used often for DirectX programs. Although these languages share a lot of similarities, you cannot use HLSL code in an OpenGL application without modification.

Another consideration is that Overte does not expose the full range of what GLSL offers to scripters. Instead, users are offered a subset of GLSL, and a custom set of naming conventions for fragment or vertex components.

Método Básico para Usar Sombreadores

El código de los sombreadores se almacena como un archivo. Los sombreadores de fragmentos normalmente tendrán la extensión de archivo *.fs o *.frag y los sombreadores de vértices suelen tener la extensión de archivo *.vs or *.vert.

Los sombreadores se aplican a entidades y avatares adjuntándolos a un material. Luego, ese material se adjunta a su entidad o avatar, por lo que se aplica el sombreador según lo previsto.

Si no estás familiarizado con las entidades materiales, puedes encontrar más información aquí.

Las entidades materiales tienen datos que se almacenan en formato JSON y tienen una propiedad llamada materialData. La propiedad materialData``requiere los campos ``model y procedural. Aquí hay un ejemplo:

{
    "materials": [{
        "model": "hifi_shader_simple",
        "procedural": {
            "version": 3,
            "shaderUrl": "https://docs.overte.org/_static/resources/Proceduralv3.fs"
        }
    }]
}

The materialData JSON can be applied either via the Overte Interface’s edit tools or with a script. Here's another example:

Entities.addEntity({
    type: "Material",
    materialURL: "materialData",
    priority: 1,
    materialData: JSON.stringify({
            materials: {
                    "model": "hifi_shader_simple",
                    "procedural": {
                            "version": 3,
                            "shaderUrl": "https://docs.overte.org/_static/resources/Proceduralv3.fs"
                    }
            }
    })
});

Debes especificar el material model como "hifi_shader_simple" y proporcionar un enlace de sombreado (shader link). Para proporcionar un sombreador de fragmentos, establece fragmentShaderURL (o shaderUrl). Para proporcionar un sombreador de vértices, establece vertexShaderURL.

Para establecer el materialData utilizando las herramientas de edición, querrás establecer materialURL equivalente a "materialData". Luego ingresa la versión JSON.stringify teniendo materialData como se muestra arriba en el campo.

Plantilla de Sombreado

When you learn about shaders for other applications, the shader may have a function like main() that is run first. By contrast, Overte has a specific function name that must be called. Which function is used depends on which version of the shader you use.

A medida que se desarrollaron los sombreadores, sus características evolucionaron un poco con el tiempo. Como resultado, hay varias versiones de sombreado y cada versión tiene una firma de llamada diferente (call signature). Las Versiones 1 y 2 son las más viejas y seguirán funcionando. Las Versiones 3 y 4 son las más nuevas y exponen más funciones. La versión 4 proporciona posiciones por fragmento, sin embargo, también es la más costosa desde el punto de vista computacional. Por lo tanto, se recomienda utilizar la versión 3 si esa característica adicional de la versión 4 no es necesaria.

Un sombreador consta de dos piezas principales: la función principal que es responsable de colorear el píxel y cualquier función auxiliar deseada que ayude en esa lógica de procesamiento (que debe ir por encima de la función principal).

Una plantilla básica para un sombreador sin funciones auxiliares se parece a este ejemplo:

// Helper functions go here.

// version 3
float getProceduralFragment(inout ProceduralFragment data) {
    data.diffuse = vec3(0);
    data.occlusion = 0;
    data.roughness = 1;
    data.emissive = _positionMS.xyz;
    return 0; // "emissiveAmount", either <=0 or >0, suggest return 0 and use data.emissive
}

La función getProceduralFragment() es el punto de entrada principal por defecto para el sombreador de fragmentos. Debido a que el compilador siempre lee los sombreadores de arriba hacia abajo, esta función siempre debe ser la última en tu código de sombreado. También necesitarás saber qué está disponible para ti en la estructura data que se describe en Métodos, constantes y estructuras proporcionadas.

Variables Globales

Además de los valores proporcionados por los argumentos de la función de sombreado, hay una serie de variables globales que proporcionan datos útiles al calcular los efectos de procedimiento.

Se proporcionan las siguientes variables globales:

vec4 iDate; // year, month (0 based to match shadertoy), day, seconds
vec3 iWorldPosition; // entity position
mat3 iWorldOrientation; // entity orientation
vec3 iWorldScale; // entity scale
float iGlobalTime; // time since last shader recompilation
float iLocalCreatedTime; // time since first shader compilation
float iEntityTime; // time since entity creation
int iFrameCount; // frames since last shader recompilation
sampler2D iChannel0, iChannel1, iChannel2, iChannel3; // custom textures, if provided
vec3 iChannelResolution[4]; // resolution of each custom texture, if provided

Las siguientes variables están definidas pero actualmente no implementadas:

const vec3 iResolution = vec3(1.0); // Resolution doesn’t make sense in the VR context
const vec4 iMouse = vec4(0.0); // Mouse functions not enabled currently
const float iSampleRate = 1.0; // No support for audio input
const vec4 iChannelTime = vec4(0.0); // No support for video input

Los siguientes uniformes por fragmento también se proporcionan en todas las versiones de sombreado:

vec4 _positionMS; // position in "model space" (relative to the center of the object); (equal to _position)
vec4 _positionES; // position in "eye space" (relative to the center of your eye); (equal to _eyePosition)
vec3 _normalMS; // direction the current face is pointing in "model space" (without any rotations); (equal to _modelNormal)
vec3 _normalWS; // direction the current face is pointing in "world space" (after rotations applied); (equal to _normal)
vec4 _color; // color of the object
vec4 _texCoord01 // UV texture coordinates on this model (also split into vec2 _texCoord0 and vec2 _texCoord1)

Métodos, Constantes y Estructuras Proporcionadas

Aquí hay una lista completa de los métodos, constantes y estructuras proporcionadas:

float mod289(float x);
vec2 mod289(vec2 x);
vec3 mod289(vec3 x);
vec4 mod289(vec4 x);
float permute(float x);
vec3 permute(vec3 x);
vec4 permute(vec4 x);
float taylorInvSqrt(float r);
vec4 taylorInvSqrt(vec4 r);
vec4 grad4(float j, vec4 ip);
float F4 = 0.309016994374947451
float snoise(vec4 v);
float snoise(vec3 v);
float snoise(vec2 v);

// https://www.shadertoy.com/view/lsfGRr
float hifi_hash(float n);
float hifi_noise(in vec2 x);

// https://www.shadertoy.com/view/MdX3Rr
// https://en.wikipedia.org/wiki/Fractional_Brownian_motion
float hifi_fbm(in vec2 p);

TransformCamera getTransformCamera()

// where a TransformCamera is:
struct _TransformCamera {
    mat4 _view;
    mat4 _viewInverse;
    mat4 _projectionViewUntranslated;
    mat4 _projection;
    mat4 _projectionInverse;
    vec4 _viewport;
    vec4 _stereoInfo;
};

int gpu_InstanceID()
vec3 getEyeWorldPos()
bool cam_isStereo() // is user wearing a VR headset (or a 3D monitor?)
float cam_getStereoSide() // 1 for right eye in a stereo context, otherwise 0
float isUnlitEnabled()
float isEmissiveEnabled()
float isLightmapEnabled()
float isBackgroundEnabled()
float isObscuranceEnabled()
float isScatteringEnabled()
float isDiffuseEnabled()
float isSpecularEnabled()
float isAlbedoEnabled()
float isAmbientEnabled()
float isDirectionalEnabled()
float isPointEnabled()
float isSpotEnabled()
float isShowLightContour()
float isWireframeEnabled()
float isHazeEnabled()
float isBloomEnabled()
float isSkinningEnabled()
float isBlendshapeEnabled()

Versión 1 del Sombreador

// Must implement. Always emissive, returns a single color.
vec3 getProceduralColor()

Versión 2 del Sombreador

// Must implement.
float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess)

Opcionalmente, el método puede establecerse con diffuse (difuso), specular (especular) y shininess (brillo), pero no tiene por qué hacerlo. El rango de brillo va desde 0 a 128. El valor de retorno es emissiveAmount. Si el valor devuelto es mayor que 0, el objeto será tratado como emisivo.

Versión 3 del Sombreador

// Must implement.
float getProceduralFragment(inout ProceduralFragment proceduralData)

Estructura del ProceduralFragment:

struct ProceduralFragment {
    vec3 normal;
    vec3 diffuse;
    vec3 specular;
    vec3 emissive;
    float alpha;
    float roughness;
    float metallic;
    float occlusion;
    float scattering;
};

Los valores predeterminados para algunos de estos son:

const float DEFAULT_ROUGHNESS = 0.9;
const float DEFAULT_SHININESS = 10.0;
const float DEFAULT_METALLIC = 0.0;
const vec3 DEFAULT_SPECULAR = vec3(0.1);
const vec3 DEFAULT_EMISSIVE = vec3(0.0);
const float DEFAULT_OCCLUSION = 1.0;
const float DEFAULT_SCATTERING = 0.0;
const vec3 DEFAULT_FRESNEL = DEFAULT_EMISSIVE;

El método puede establecer opcionalmente cualquiera de los valores en la estructura para afectar la salida. El valor de retorno es emissiveAmount. Si el valor devuelto es mayor que 0, el objeto será tratado como emisivo.

Versión 4 del Sombreador

// Must implement.
float getProceduralFragmentWithPosition(inout ProceduralFragmentWithPosition proceduralData)

Estructura del ProceduralFragmentWithPosition:

struct ProceduralFragmentWithPosition {
    vec3 position;
    vec3 normal;
    vec3 diffuse;
    vec3 specular;
    vec3 emissive;
    float alpha;
    float roughness;
    float metallic;
    float occlusion;
    float scattering;
};

Es lo mismo que la versión 3 del sombreador pero con posición por fragmento. Al modificar la posición, puedes modificar la profundidad por fragmento. Esto te permite crear cosas como geometría de rayos que realizan pruebas de profundidad correctamente y se iluminan dinámicamente con entidades de luz. La compensación es que esta versión es mucho más costosa computacionalmente que la versión 3.

Entidades de Zona

Las entidades de zona operan de manera ligeramente diferente. Admiten las mismas definiciones globales pero no los métodos o constantes proporcionados. También proporcionan las siguientes entradas:

vec3 _normal;
Skybox skybox; // a struct containing vec4 color
samplerCube cubeMap; // the skybox texture

Y debes implementar el siguiente método, independientemente de la versión:

vec3 getSkyboxColor()

Las zonas también admiten uniformes y texturas personalizadas (actualmente solo texturas 2D).

Sombreadores de Vértices

Un sombreador de vértices debe implementar:

void getProceduralVertex(inout ProceduralVertexData proceduralData)

E incluirá esta estructura:

struct ProceduralVertexData {
    vec4 position;
    vec4 nonSkinnedPosition; // input only
    vec3 normal;
    vec3 nonSkinnedNormal; // input only
    vec3 tangent; // input only
    vec3 nonSkinnedTangent; // input only
    vec4 color;
    vec2 texCoord0;
};

Para Ambos Sombreadores de Fragmentos y Vértices

Texturas y Uniformes Personalizados

Los materiales de procedimiento también admiten hasta 4 texturas personalizadas y muchos uniformes personalizados. Estos se pueden definir de la siguiente manera:

{
    materials: {
            "model": "hifi_shader_simple",
            "procedural": {
                "version": 3,
                "shaderUrl": "https://docs.overte.org/_static/resources/Proceduralv3.fs",
                "uniforms": {
                    "_diffuse": [1, 0, 0],
                    "_alpha": 1.0,
                    "_emissive": [0, 0, 0],
                    "_emissiveAmount": 0.0
                }
                "channels": ["https://mario.nintendo.com/assets/img/home/intro/mario-pose2.png", "https://www.mariowiki.com/images/thumb/e/e1/Luigi_New_Super_Mario_Bros_U_Deluxe.png/200px-Luigi_New_Super_Mario_Bros_U_Deluxe.png"]
        }
    }
}

Cuando se proporciona la URL de textura, iChannel0 - iChannel3 serán llenados, así cómo iChannelResolution[0] - iChannelResolution[3].

Cuando proporciones uniformes (uniforms), también debes incluirlos en la parte superior de tu archivo de sombreado, con valores predeterminados opcionales:

uniform vec3 _diffuse = vec3(0.0);
uniform float _alpha = 1.0;
uniform vec3 _emissive = vec3(0.0);
uniform float _emissiveAmount = 0.0;

Los tipos de uniformes admitidos son: float, vec2, vec3, and vec4. (Se proporcionan varios valores como arrays)

Efectos Alfa (Transparencia)

Los Sombreadores que hacen uso del valor de proceduralData.alpha``no mostrarán alfa por mismos. Para que el alfa de un sombreador esté activo, la entidad a la que se aplica primero debe tener su propiedad alfa menor que ``1.0, o una propiedad del material que establece la opacidad a menos de 1.0.

Depuración de Sombreadores

La única forma de depurar sombreadores en este momento es mirar el archivo de registro de Interface. Los errores de compilación del sombreador (shader) aparecerán en este registro y pueden ayudar a localizar problemas.

Porque un sombreador creado por el usuario se integra en última instancia en un marco de sombreado interno más grande, notarás que un error en un sombreador de 20 líneas se informará en un número de línea mucho más alto, generalmente mayor que 1000. Como resultado, deberás ubicar el código de sombreado que corresponde a tu sombreador al final del contexto de sombreado interno más grande.

Ejemplos de Sombreador por Versión

// version 1
vec3 getProceduralColor() {
    return _positionMS.xyz;
}

// version 2
float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) {
    // diffuse is from the texture, others are hardcoded to DEFAULT_SPECULAR and DEFAULT_SHININESS
    diffuse = _positionMS.xyz;
    return 1.0; // emissive, between 0.0 - 1.0
}

// version 3
float getProceduralFragment(inout ProceduralFragment data) {
    data.diffuse = vec3(0);
    data.occlusion = 0;
    data.roughness = 1;
    data.emissive = _positionMS.xyz;
    return 0; // "emissiveAmount", either <=0 or >0, suggest return 0 and use data.emissive
}

// version 4
    float getProceduralFragmentWithPosition(inout ProceduralFragmentWithPosition data) {
    data.diffuse = vec3(0);
    data.occlusion = 0;
    data.roughness = 1;
    data.emissive = _positionMS.xyz;
    return 0; // "emissiveAmount", either <=0 or >0, suggest return 0 and use data.emissive
}

// skybox
vec3 getSkyboxColor() {
    vec3 normal = normalize(_normal);
    return texture(cubeMap, normal).rgb; // this should return the same value that the skybox texture has
}

Para obtener más detalles sobre cada versión, consulta Métodos, constantes y estructuras proporcionadas.

Una Nota de Advertencia sobre los Sombreadores

Overte does not enable seeing procedural shaders by default. This is because currently, they are an experimental feature. Shaders are a very powerful tool, and when used incorrectly, can harm the user experience for everyone on the domain. A poorly written shader or a shader created by a bad actor can slow things down to a crawl or interfere with a user’s view of the virtual world.

Los sombreadores se utilizan mejor como una especia muy fuerte en una receta. Intenta mantenerlos pequeños y eficientes. Los sombreadores pueden producir efectos maravillosos y alucinantes, pero el uso excesivo puede estropear el efecto final deseado. Si creas un sombreador que tiene cientos de líneas de código, considera recortarlo si es posible.

If you find yourself in a position where a shader is causing trouble for you, remember that you can disable them in the Overte Interface.