Agregar una Entidad Particle Procedural

Las partículas son una herramienta poderosa para crear una variedad de efectos. Cuando añades una Entidad de Partículas a través de Crear, se te preguntará si quieres que sea procedimental o no.

Las Non-procedural particles están representadas por el tipo de entidad ParticleEffect. Tienen muchas opciones de configuración (véase EntityProperties-ParticleEffect) y son adecuadas para muchas situaciones. Sin embargo, también tienen algunas limitaciones, incluyendo el soporte para un máximo de 100.000 partículas / entidad, y movimiento a través de la integración de Euler (posición + velocidad + aceleración).

Si en su lugar creas una entidad procedural particle, obtendrás una entidad con el tipo de entidad ProceduralParticleEffect . Esto le da un control detallado sobre un sistema de partículas GPU, utilizando "shaders" para la actualización y renderizado. Además de mayores límites (hasta millones de partículas). Las partículas procedimentales tienen un conjunto diferente de propiedades (ver EntityProperties-ProceduralParticleEffect).

Antes de continuar, asegúrate de que estás familiarizado con la guía Procedural Shaders Guide para un recorrido más detallado de nuestros generadores de sombras (shaders).

Todos los generadores de sombras de partículas también exponen definiciones globales para NUM_PARTICLES, NUM_TRIS_PER_PARTICLE, y NUM_UPDATE_PROPS.

Renderización

La representación visual de tus partículas está controlada por la propiedad particleRenderData. Debes implementar tanto un generador de sombras de vértices como uno de fragmentos.

Un sombreador de vértices debe implementar:

vec3 getProceduralVertex(const int particleID)

Que devuelve la posición en el espacio global del vértice. De manera predeterminada, se dibuja 1 triángulo por partícula, por lo que esa función se ejecutará 3 veces por partícula (con diferentes gl_VertexIDs), pero esto puede ser controlado por numTrianglesPerParticle.

El generador de sombra de fragmento (fragment shader) debe implementar una de las funciones que nuestros materiales procedimentales existentes dependiendo de la versión:

vec3 getProceduralColor()
float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess)
float getProceduralFragment(inout ProceduralFragment proceduralData)
float getProceduralFragmentWithPosition(inout ProceduralFragmentWithPosition proceduralData)

En el "fragment shader" particleID está disponible como una variable global.

Una diferencia entre las partículas procedimentales y los materiales procedimentales en otras entidades es que las partículas no tienen normales por fragmento o UVs definidos de manera predeterminada. Debes calcularlas tú mismo si quieres partículas iluminadas o texturizadas.

Nota

Calcular las normales y las UV manualmente en los generadores de sombra (shaders) puede resultar difícil. En el futuro, proporcionaremos plantillas con formas que vienen con normales y UV.

Al igual que con los generadores de sombras de vértices de materiales procedimentales, puede controlar el cuadro delimitador, que se utiliza para la selección, cambiando las dimensiones de la entidad.

Al igual que las partículas no procedimentales, las partículas procedimentales transparentes (particleTransparent == true) se renderizan usando mezcla aditiva.

Actualizaciones

Las actualizaciones por partícula son opcionales. Se pueden utilizar para almacenar datos persistentes por partícula a través de los fotogramas o reducir los cálculos en los generadores de sombra de renderizado. Para activar las actualizaciones, establece numUpdateProps > 0 (el máximo es 5) y debes proporcionar un generador de sombras de fragmento (fragment shader) como parte de particleUpdateData.

Cada "update prop" son vec4 de valores float por-partícula . El generador de sombras (shader) debe definir:

void updateParticleProps(const int particleID, inout ParticleUpdateProps particleProps)

particleProps debe contener todas las propiedades de actualización que hayas especirficado. Por ejemplo, si numUpdateProps == 3, entonces debes tener particleProps.prop0, particleProps.prop1, y particleProps.prop2. Estas deben contener inicialmente solo 0s, y luego tendrán cualquier valor desde los cuadros anteriores. Puedes ver esas propiedades durante el renderizado(tanto en el generador de sombras de vértice y de fragmento) con:

vec4 getParticleProperty(const int propIndex, const int particleID)

Ejemplos

Actualiza el creador de sombra para calcular un cubo de movimiento sinusoidal:

void updateParticleProps(const int particleID, inout ParticleUpdateProps particleProps) {
    int SIDE = int(ceil(sqrt(NUM_PARTICLES)));
    vec3 position = vec3(float(particleID % SIDE) / SIDE - 0.5, 0.5 * cos(iGlobalTime + particleID), float(floor(particleID / SIDE)) / SIDE - 0.5);
    position = iWorldOrientation * (position * iWorldScale) + iWorldPosition;

    particleProps.prop0.xyz = position;
}

Generador de sombras de vértices ( Vertex shader) para representar un único triángulo utilizando una propiedad de posición almacenada:

uniform float radius = 0.01;

vec3 getProceduralVertex(const int particleID) {
    vec3 position = getParticleProperty(0, particleID).xyz;

    TransformCamera cam = getTransformCamera();
    mat3 viewInverse3 = mat3(cam._viewInverse);
    const vec3 UP = vec3(0, 1, 0);
    const vec3 FORWARD = vec3(0, 0, -1);
    vec3 upEye = viewInverse3 * UP;
    vec3 forwardEye = viewInverse3 * FORWARD;
    vec3 particleRight = normalize(cross(forwardEye, upEye));
    vec3 particleUp = cross(particleRight, forwardEye);

    const vec3 TRI[3] = vec3[3](
        particleUp,
        normalize(-particleUp + particleRight),
        normalize(-particleUp - particleRight)
    );

    position += radius * TRI[gl_VertexID % 3];

    return position;
}

Ver también