|
Grado en Ingeniería Informática
Realidad Virtual
Curso 2023/2024
|
Objetivos
|
|
El objetivo de esta práctica es representar objetos animados, es
decir, objetos que no son rígidos. Para ello se utilizarán dos
técnicas. La primera consiste en incorporar el tiempo como una
variable uniforme en los shader e incluir en ellos las ecuaciones
temporales para calcular su forma. La segunda consiste en calcular
las posiciones en función de las calculadas en la iteración
anterior. En este caso es necesario utilizar una característica de
OpenGL conocida como Transform Feedback.
La práctica se divide en tres proyectos. El proyecto Project12a
dibuja una superficie ondulatoria utilizando el tiempo como una
variable uniforme y proramando las ecuaciones temporales en los
shaders. El proyecto Project12b dibuja una fuente por medio
de partículas de agua genradas mediante Transform Feedback.
El proyecto Project12c utiliza la técnica de Transform
Feedback para generar las partículas que forman las llamas de
un fuego de campamento.
|
|
Project12a - Animación de una superficie
basada en ecuaciones temporales
|
|
Una forma de representar superficies que no son rígidas consiste en
utilizar ecuaciones temporales que describen la forma de la
superficie en función del tiempo. Para ello hay que considerar el
tiempo como una variable uniforme dentro del programa gráfico y
utilizar las ecuaciones temporales`para calcular las posiciones de
los puntos de la superficie en cada instante.
El proyecto Project12a desarrolla un ejemplo de este tipo
de animación. Se trata de mostrar una superficie cuadrada que
contenga ondulaciones que se desplacen a lo largo del tiempo. Si
consideramos la superficie como un conjunto de primitivas cuyos
vértices están situados en el plano XZ, la ondulación consiste en
modificar la altura de los puntos (coordenada Y) siguiendo una
ecuación de onda:

En la ecuación anterior la constante A es la amplitud
(valor máximo de la coordenada y), la constante k
es el número de onda (que es igual a 2·π/λ, siendo λ la
longitud de onda, es decir, la distancia entre dos máximos), la
constante v es la velocidad con que se mueve la onda y
t es el tiempo.
El VertexShader es el encargado de calcular la coordenada
y de cada vértice siguiendo la ecuación de onda, donde las
diferentes constantes se introducen como variables uniformes. El
vector normal se obtiene por medio de la derivada de la función de
onda respecto de x.
#version 400
layout(location=0) in vec3 VertexPosition;
uniform float Time; // The animation time
uniform float K; // Wavenumber
uniform float Velocity; // Wave's velocity
uniform float Amp; // Wave's amplitude
uniform mat4 MVP;
uniform mat4 ViewMatrix;
uniform mat4 ModelViewMatrix;
out vec3 Position;
out vec3 Normal;
void main()
{
vec4 pos = vec4(VertexPosition, 1.0);
// Translate the y coordinate
float u = K * (pos.x - Velocity * Time);
pos.y = Amp * sin( u );
// Compute the normal vector
vec4 n = normalize(vec4(-K*Amp*cos( u ), 1.0,0.0,0.0));
vec4 n4 = ModelViewMatrix * n;
vec4 v4 = ModelViewMatrix * pos;
Normal = vec3(n4);
Position = vec3(v4);
gl_Position = MVP * pos;
}
|
En este ejemplo se ha considerado tan solo el modelo básico de
iluminación de Phong para mostrar una superficie de un material sin
textura. El FragmentShader es el siguiente.
#version 400
in vec3 Position;
in vec3 Normal;
uniform mat4 ViewMatrix;
struct LightInfo
{
vec3 Ldir;
vec3 La;
vec3 Ld;
vec3 Ls;
};
uniform LightInfo Light;
struct MaterialInfo
{
vec3 Ka;
vec3 Kd;
vec3 Ks;
float Shininess;
};
uniform MaterialInfo Material;
out vec4 FragColor;
vec3 ads()
{
vec4 s4 = ViewMatrix*vec4(Light.Ldir, 0.0);
vec3 n = normalize(Normal);
vec3 v = normalize(-Position);
vec3 s = normalize(-vec3(s4));
vec3 r = reflect(-s, n);
float dRate = max(dot(s, n), 0.0);
float sRate = pow(max(dot(r, v), 0.0), Material.Shininess);
vec3 ambient = Light.La * Material.Ka;
vec3 difusse = Light.Ld * Material.Kd * dRate;
vec3 specular = Light.Ls * Material.Ks * sRate;
return ambient + difusse + specular;
}
void main()
{
vec3 Color = ads();
FragColor = vec4(Color,1.0);
}
|
Para modelar la superficie se ha creado una nueva clase que hemos
denominado CGCloth. Esta clase se limita a crear una
superficie cuadrada formada por 20.000 triángulos (cada lado de la
superficie se ha dividido en 100 segmentos). La clase contiene
también los valores de configuración de la ecuación de onda.
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "CGMaterial.h"
#include "CGShaderProgram.h"
#define VERTEX_DATA 0
#define INDEX_DATA 1
#define NORMAL_DATA 2
class CGCloth {
private:
GLfloat K; // Wavenumber
GLfloat Velocity; // Wave's velocity
GLfloat Amp; // Wave's amplitude
GLushort* indexes; // Array of indexes
GLfloat* vertices; // Array of vertices
GLuint numFaces; // Number of faces
GLuint numVertices; // Number of vertices
GLuint VBO[2];
GLuint VAO;
glm::mat4 location; // Model matrix
CGMaterial* material;
public:
CGCloth(GLfloat l1, GLfloat l2);
~CGCloth();
void InitBuffers();
void SetMaterial(CGMaterial* mat);
void ResetLocation();
void Translate(glm::vec3 t);
void Rotate(GLfloat angle, glm::vec3 axis);
void Draw(CGShaderProgram* program, glm::mat4 projection, glm::mat4 view);
};
|
El código de la clase es similar al de las figuras que hemos
considerado en prácticas anteriores. En este caso los vértices de la
figura solo utilizan el atributo de posición. El método Draw()
debe asignar los valores de las variables uniformes antes de lanzar
el dibujo de las primitivas que forman la figura.
#include "CGCloth.h"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
//
// FUNCIÓN: CGCloth::CGCloth()
//
// PROPÓSITO: Constructor de la figura
//
CGCloth::CGCloth(GLfloat width, GLfloat height)
{
K = 0.4f; // Wavenumber
Velocity = 4.0f; // Wave's velocity
Amp = 5.0f; // Wave's amplitude
int numRows = 101;
int numColumns = 101;
numVertices = numRows * numColumns;
numFaces = 2 * (numRows - 1) * (numColumns - 1);
indexes = new GLushort[numFaces * 3]; // Array of indexes
vertices = new GLfloat[3 * numVertices]; // Array of vertices
for (int i = 0; i < numRows; i++)
for (int j = 0; j < numColumns; j++)
{
vertices[3 * (i * numRows + j)] = 2 * width * i / (numRows - 1) - width;
vertices[3 * (i * numRows + j) + 1] = 0.0f;
vertices[3 * (i * numRows + j) + 2] = 2*height*j / (numColumns - 1) - height;
}
for (int i = 0; i < numRows - 1; i++)
for (int j = 0; j < numColumns - 1; j++)
{
indexes[6 * (i * (numColumns - 1) + j)] = i * numColumns + j;
indexes[6 * (i * (numColumns - 1) + j) + 1] = i * numColumns + j + 1;
indexes[6 * (i * (numColumns - 1) + j) + 2] = (i + 1) * numColumns + j;
indexes[6 * (i * (numColumns - 1) + j) + 3] = (i + 1) * numColumns + j;
indexes[6 * (i * (numColumns - 1) + j) + 4] = i * numColumns + j + 1;
indexes[6 * (i * (numColumns - 1) + j) + 5] = (i + 1) * numColumns + j + 1;
}
InitBuffers();
}
//
// FUNCIÓN: CGCloth::~CGCloth()
//
// PROPÓSITO: Destructor de la figura
//
CGCloth::~CGCloth()
{
if (vertices != NULL) delete[] vertices;
if (indexes != NULL) delete[] indexes;
// Delete vertex buffer objects
glDeleteBuffers(2, VBO);
glDeleteVertexArrays(1, &VAO);
}
//
// FUNCIÓN: CGCloth::InitBuffers()
//
// PROPÓSITO: Crea el VAO y los VBO y almacena todos los datos en la GPU.
//
void CGCloth::InitBuffers()
{
// Create the Vertex Array Object
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// Create the Vertex Buffer Objects
glGenBuffers(2, VBO);
// Copy data to video memory
// Vertex data
int buffsize = sizeof(GLfloat) * numVertices * 3;
glBindBuffer(GL_ARRAY_BUFFER, VBO[VERTEX_DATA]);
glBufferData(GL_ARRAY_BUFFER, buffsize, vertices, GL_STATIC_DRAW);
// Indexes
buffsize = sizeof(GLushort) * numFaces * 3;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBO[INDEX_DATA]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffsize, indexes, GL_STATIC_DRAW);
delete[] vertices;
delete[] indexes;
vertices = NULL;
indexes = NULL;
glEnableVertexAttribArray(0); // Vertex position
glBindBuffer(GL_ARRAY_BUFFER, VBO[VERTEX_DATA]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
location = glm::mat4(1.0f);
}
//
// FUNCIÓN: CGCloth::SetMaterial(CGMaterial* m)
//
// PROPÓSITO: Asigna el material de la figura
//
void CGCloth::SetMaterial(CGMaterial* mat)
{
material = mat;
}
//
// FUNCIÓN: CGCloth::ResetLocation()
//
// PROPÓSITO: Asigna la posición inicial de la figura
//
void CGCloth::ResetLocation()
{
location = glm::mat4(1.0f);
}
//
// FUNCIÓN: CGCloth::Translate(glm::vec3 t)
//
// PROPÓSITO: Añade un desplazamiento a la matriz de posición de la figura
//
void CGCloth::Translate(glm::vec3 t)
{
location = glm::translate(location, t);
}
//
// FUNCIÓN: CGCloth::Rotate(GLfloat angle, glm::vec3 axis)
//
// PROPÓSITO: Añade una rotación a la matriz de posición de la figura
//
void CGCloth::Rotate(GLfloat angle, glm::vec3 axis)
{
location = glm::rotate(location, glm::radians(angle), axis);
}
//
// FUNCIÓN: CGCloth::Draw(CGShaderProgram* program, glm::mat4 proj, glm::mat4 view)
//
// PROPÓSITO: Dibuja la figura
//
void CGCloth::Draw(CGShaderProgram* program, glm::mat4 projection, glm::mat4 view)
{
program->SetUniformF("K", K);
program->SetUniformF("Velocity", Velocity);
program->SetUniformF("Amp", Amp);
glm::mat4 mvp = projection * view * location;
program->SetUniformMatrix4("MVP", mvp);
program->SetUniformMatrix4("ViewMatrix", view);
program->SetUniformMatrix4("ModelViewMatrix", view * location);
material->SetUniforms(program);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, numFaces * 3, GL_UNSIGNED_SHORT, NULL);
}
|
La escena, descrita en la clase CGScene, está formada por
la luz y un objeto CGCloth. El modelo se describe en la
clase CGModel. Respecto a otras prácticas, la única
modificación del modelo es que añade un campo time que mide
el tiempo. En cada actualización del modelo (método update()
) el tiempo se incrementa en 1/60 s. El método render()
asigna el valor de la variable uniforme Time antes de
llamar a la función de dibujo de la escena.
#define TIME_LAPSE 0.0166666f
//
// FUNCIÓN: CGModel::initialize(int, int)
//
// PROPÓSITO: Initializa el modelo 3D
//
void CGModel::initialize(int w, int h)
{
// Crea el programa
program = new CGShaderProgram(IDR_SHADER1, IDR_SHADER2, -1, -1, -1);
// program = new CGShaderProgram("shaders/VertexShader.glsl",
// "shaders/FragmentShader.glsl", NULL, NULL, NULL);
if (program->IsLinked() == GL_FALSE) return;
program->Use();
// Crea la cámara
camera = new CGCamera();
camera->SetPosition(0.0f, 5.0f, 100.0f);
// Inicializa el tiempo
time = 0.0f;
// Crea la escena
scene = new CGScene();
// Asigna el viewport y el clipping volume
resize(w, h);
// Opciones de dibujo
glEnable(GL_DEPTH_TEST);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
//
// FUNCIÓN: CGModel::render()
//
// PROPÓSITO: Genera la imagen
//
void CGModel::render()
{
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
program->SetUniformF("Time", time);
glm::mat4 view = camera->ViewMatrix();
scene->Draw(program, projection, view);
}
//
// FUNCIÓN: CGModel::update()
//
// PROPÓSITO: Anima la escena
//
void CGModel::update()
{
time += TIME_LAPSE;
camera->MoveFront();
}
...
|
A
continuación se muestra una captura del resultado.

|
|
Project12b - Animación de una
fuente basada
en sistema de partículas con Transform Feedback
|
|
Los sistemas de partículas permiten modelar fenómenos difusos como
fuego, humo, sprays o explosiones. Para ello se utiliza un conjunto
de partículas que se representan como primitivas GL_POINTS o como
pequeños cuadrados con texturas (point sprites). OpenGL
permite generar estos cuadrados activando la opción GL_POINT_SPRITE
y asignando el tamaño de los puntos con el comando glPointSize().
El comportamiento de las partículas se puede modelar mediante una
función temporal. En ese caso los atributos de cada vértice
corresponderían a los datos iniciales de la partícula (posición y
velocidad inicial) y se calcularía la posición de cada instante en
función del tiempo. Este esquema es válido cuando la ecuación de
movimiento depende solo del tiempo y de valores constantes
(aceleración, posición inicial y velocidad inicial). Sin embargo no
permite considerar efectos como que la aceleración pueda cambiar,
por ejemplo si se añade un efecto de viento en algún momento
indeterminado. Para poder modelar estos efectos resulta más adecuado
calcular la posición y velocidad de la partícula en un instante a
partir de su posición y velocidad en un instante anterior por medio
del método de Euler:

Si almacenamos los valores de posición y velocidad como atributos de
las partículas utilizando Vertex Buffer Objects para poder
desarrollar el método de Euler es necesario que el renderizado
permita almacenar los valores de los atributos y utilizarlos de
nuevo en una iteración posterior. Esto se puede configurar en OpenGL
por medio de Transform Feedback Objects. Para modelar el
comportamiento de as partículas se utiliza una técnica conocida como
buffer ping-pong porque realiza el cálculo en dos pasadas. En la
primera pasada se utiliza el método de Euler para generar las nuevas
posiciones y velocidades en función de las posiciones y velocidades
anteriores En la segunda pasada se realiza el proceso de renderizado
habitual considerando las nuevas posiciones y velocidades.

El proyecto Project12b desarolla un ejemplo de esta
técnica. Se trata de simular una fuente por medio de un conjunto de
partículas representadas por medio de una textura en tono
verde-azulado. Las partículas tienen una pequeña aceleración hacia
abajo y se van difuminando (aumentando su nivel de transparencia) a
medida que transcurre el tiempo. Parten inicialmente del origen de
coordenadas de la escena y con una pequeña velocidad
inicial vertical también aleatoria con un grado de abertura de 30
grados. Las partículas tienen un tiempo
de vida y, una vez alcanzado, vuelven a surgir de nuevo en su
posición inicial de manera que la fuente se sigue mostrando
indefinidamente.
El
VertexShader recibe como atributos de entrada la posición, la
velocidad, el instante de nacimiento y la velocidad inicial de la
partícula y genera como salida los nuevos valores de posición,
velocidad e instante de nacimiento, así como el nivel de
transparencia. En la primera pasada, descrita por la rutina
update(), se calculan los nuevos valores de posición, velocidad
e instante de nacimiento siguiendo el modelo de Euler. Estos valores
se almacenarán en otros VBO. En la segunda pasada, descrita en la
rutina render(), se calcula el nivel de transparencia y la
posición en coordenadas clip.
#version 400
subroutine void RenderPassType();
subroutine uniform RenderPassType RenderPass;
layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexVelocity;
layout (location = 2) in float VertexStartTime;
layout (location = 3) in vec3 VertexInitialVelocity;
out vec3 Position; // To transform feedback
out vec3 Velocity; // To transform feedback
out float StartTime; // To transform feedback
out float Transp; // To fragment shader
uniform float Time; // Simulation time
uniform float H; // Elapsed time between frames
uniform vec3 Accel; // Particle acceleration
uniform float ParticleLifetime; // Particle lifespan
uniform mat4 MVP;
subroutine (RenderPassType)
void update() {
// Update position & velocity for next frame
Position = VertexPosition;
Velocity = VertexVelocity;
StartTime = VertexStartTime;
if( Time >= StartTime ) {
float age = Time - StartTime;
if( age > ParticleLifetime ) {
// The particle is past it's lifetime, recycle.
Position = vec3(0.0);
Velocity = VertexInitialVelocity;
StartTime += ParticleLifetime;
} else {
// The particle is alive, update.
Position += Velocity * H;
Velocity += Accel * H;
}
}
}
subroutine (RenderPassType)
void render() {
float age = Time - VertexStartTime;
Transp = 0.0;
if( Time >= VertexStartTime ) Transp = 1.0 - age / ParticleLifetime;
gl_Position = MVP * vec4(VertexPosition, 1.0);
}
void main()
{
// This will call either render() or update()
RenderPass();
}
|
El FragmentShader se limita a obtener el color del
fragmento a partir de la textura asociada a la partícula. La entrada
predefinida gl_PointCoord contiene la coordenada del pixel
dentro del point sprite. El nivel de transparencia del
fragmento se obtiene combinando el nivel leído de la textura con el
nivel de transparencia de la partícula generado en el
VertexShader.
#version 400
uniform sampler2D ParticleTex;
in float Transp;
layout ( location = 0 ) out vec4 FragColor;
void main()
{
FragColor = texture(ParticleTex, gl_PointCoord);
FragColor = vec4(mix( vec3(0,0,0), FragColor.xyz, Transp ), FragColor.a);
FragColor.a *= Transp;
}
|
Para utilizar la opción de Transform Feedback es necesario indicar
al programa gráfico cuales son las variables de salida del VertexShader que van a ser dirigidas a buffers de memoria en la
tarjeta gráfica. Esto debe realizarse después de compilar los
shaders y antes de linkar el programa. Para esto es necesario
modificar la clase CGShaderProgram de manera que la carga
de los shader se realice en el constructor, pero se incluyan métodos
para configurar la opción de Transform Feddback y para
linkar el programa. La modificaciones del fichero de cabecera de la
clase CGShaderProgram serían las siguientes:
class CGShaderProgram {
...
public:
...
GLboolean IsLinked();
GLvoid Link();
GLvoid Use();
GLvoid SetTransformFeedback(const char* name[], int length);
};
|
Para utilizar la opción de Transform Feedback es necesario indicar
al programa gráfico cuales son las variables de salida del VertexShader que van a ser dirigidas a buffers de memoria en la
tarjeta gráfica. Esto debe realizarse después de compilar los
shaders y antes de linkar el programa. Para esto es necesario
modificar la clase CGShaderProgram de manera que la carga
de los shader se realice en el constructor, pero se incluyan métodos
para configurar la opción de Transform Feddback y para
linkar el programa. La modificaciones del fichero de cabecera de la
clase CGShaderProgram serían las siguientes:
//
// FUNCIÓN: CGShaderProgram::CGShaderProgram()
//
// PROPÓSITO: Crea un programa gráfico
//
CGShaderProgram::CGShaderProgram(int vs, int fs, int gs, int tcs, int tes)
{
vertexShader = NO_SHADER;
fragmentShader = NO_SHADER;
geometryShader = NO_SHADER;
tessControlShader = NO_SHADER;
tessEvaluationShader = NO_SHADER;
linked = GL_FALSE;
// Crea y compila los shaders
if(vs != -1) vertexShader = CreateShader(GL_VERTEX_SHADER, vs);
if(fs != -1) fragmentShader = CreateShader(GL_FRAGMENT_SHADER, fs);
if(gs != -1) geometryShader = CreateShader(GL_GEOMETRY_SHADER, gs);
if(tcs != -1) tessControlShader = CreateShader(GL_TESS_CONTROL_SHADER, tcs);
if(tes != -1) tessEvaluationShader = CreateShader(GL_TESS_EVALUATION_SHADER, tes);
//Crea el programa y carga los shaders
program = glCreateProgram();
if(vertexShader != NO_SHADER) glAttachShader(program, vertexShader);
if(fragmentShader != NO_SHADER) glAttachShader(program, fragmentShader);
if(geometryShader != NO_SHADER) glAttachShader(program, geometryShader);
if(tessControlShader != NO_SHADER) glAttachShader(program, tessControlShader);
if(tessEvaluationShader!=NO_SHADER) glAttachShader(program, tessEvaluationShader);
}
//
// FUNCIÓN: CGShaderProgram::CGShaderProgram()
//
// PROPÓSITO: Crea un programa gráfico
//
CGShaderProgram::CGShaderProgram(const char* vs, const char* fs, const char* gs,
const char* tcs, const char* tes)
{
vertexShader = NO_SHADER;
fragmentShader = NO_SHADER;
geometryShader = NO_SHADER;
tessControlShader = NO_SHADER;
tessEvaluationShader = NO_SHADER;
linked = GL_FALSE;
// Crea y compila los shaders
if(vs != NULL) vertexShader = CreateShader(GL_VERTEX_SHADER, vs);
if(fs != NULL) fragmentShader = CreateShader(GL_FRAGMENT_SHADER, fs);
if(gs != NULL) geometryShader = CreateShader(GL_GEOMETRY_SHADER, gs);
if(tcs != NULL) tessControlShader = CreateShader(GL_TESS_CONTROL_SHADER, tcs);
if(tes != NULL) tessEvaluationShader=CreateShader(GL_TESS_EVALUATION_SHADER, tes);
//Crea el programa y carga los shaders
program = glCreateProgram();
if(vertexShader != NO_SHADER) glAttachShader(program, vertexShader);
if(fragmentShader != NO_SHADER) glAttachShader(program, fragmentShader);
if(geometryShader != NO_SHADER) glAttachShader(program, geometryShader);
if(tessControlShader != NO_SHADER) glAttachShader(program, tessControlShader);
if(tessEvaluationShader!=NO_SHADER) glAttachShader(program, tessEvaluationShader);
}
//
// FUNCIÓN: CGShaderProgram::Link()
//
// PROPÓSITO: Enlaza los shaders para dejar el programa preparado para su uso
//
GLvoid CGShaderProgram::Link()
{
glLinkProgram(program);
GLint status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
{
linked = GL_FALSE;
GLint logLength;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
char* logInfo = (char*)malloc(sizeof(char) * logLength);
GLsizei written;
glGetProgramInfoLog(program, logLength, &written, logInfo);
std::cout << logInfo << std::endl;
return;
}
linked = GL_TRUE;
}
//
// FUNCIÓN: CGShaderProgram::SetTransformFeedback(const char* names[], int length)
//
// PROPÓSITO: Configura las variables a almacenar como TransformFeedback
//
GLvoid CGShaderProgram::SetTransformFeedback(const char* names[], int length)
{
glTransformFeedbackVaryings(program, length, names, GL_SEPARATE_ATTRIBS);
}
...
|
El conjunto de partículas se ha descrito en la clase CGParticles.
Esta clase define los VBOs de almacenamiento de los atributos de las
partículas (VertexPosition, VertexVelocity, VertexStartTime
y VertexInitialVelocity). Para programar la técnica de
buffer ping-pong se utilizan dos VAOs, formados por 4 VBOs que
contiene las dos versiones de los atributos. El atributo
VertexInitialVelocity no sufre modificaciones así que no es
necesario duplicarlo. También es necesario una pareja de
TransformFeedbackObjects.
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "CGShaderProgram.h"
class CGParticles
{
public:
CGParticles(int n);
~CGParticles();
void Draw(CGShaderProgram* program, glm::mat4 proj, glm::mat4 view);
void Update();
private:
int nParticles;
GLuint textureId;
GLfloat time;
GLuint posBuf[2];
GLuint velBuf[2];
GLuint startTime[2];
GLuint initVel;
GLuint particleArray[2];
GLuint feedback[2];
int drawBuf;
void InitTexture(const char* filename);
void InitTexture(int idr);
void InitBuffers();
float randFloat();
};
|
Al construir las partículas se crean los buffers y se calculan sus
valores iniciales. Las velocidades iniciales se
calculan de forma aleatoria. El instante de nacimiento se calcula
con una tasa fija de manera que se estén produciendo partículas de
manera constante. Los VBOs se definen en este caso como
GL_DYNAMIC_COPY ya que su contenido se va a modificar. Además de los
VertexArrayObjects es necesario crear y configurar los
TransformFeedbackObjects. El método Draw() se encarga
de lanzar el dibujo de las partículas realizando dos pasadas. En la
primera pasada se desactiva la rasterización para que solo se
ejecute el VertexShader, se selecciona el TFO y se activa
la opción de Transform Feedback con el comando
glBeginTransformFeedback(). En este paso se escoge la rutina
update() para dirigir la salida del VertexShader a
los segundos VBOs. En la segunda pasada hay que volver a activar la
rasterización, desactivar la opción de Transform Feedback,
seleccionar la rutina render() y lanzar el comando de dibujo
indicando que los atributos de los vértices se lean de un TFO
mediante el comando glDrawTransformFeedback().
#include "CGParticles.h"
#include <GL/glew.h>
#include <Windows.h>
#include <FreeImage.h>
#include "resource.h"
#ifndef TIME_LAPSE
#define TIME_LAPSE 0.0166666f
#endif
//
// FUNCIÓN: CGParticles::CGParticles(n)
//
// PROPÓSITO: Crea el conjunto de partículas
//
CGParticles::CGParticles(int n)
{
time = 0.0f;
nParticles = n;
drawBuf = 1;
InitBuffers();
InitTexture(IDR_IMAGE1);
// InitTexture("textures/bluewater.png");
}
//
// FUNCIÓN: void CGParticles::InitTexture(const char* filename)
//
// PROPÓSITO: Carga una textura desde un fichero
//
void CGParticles::InitTexture(const char* filename)
{
FREE_IMAGE_FORMAT format = FreeImage_GetFileType(filename, 0);
FIBITMAP* bitmap = FreeImage_Load(format, filename);
FIBITMAP* pImage = FreeImage_ConvertTo32Bits(bitmap);
int nWidth = FreeImage_GetWidth(pImage);
int nHeight = FreeImage_GetHeight(pImage);
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, nWidth, nHeight,
0, GL_BGRA, GL_UNSIGNED_BYTE, (void*)FreeImage_GetBits(pImage));
FreeImage_Unload(pImage);
}
//
// FUNCIÓN: void CGParticles::InitTexture(int idr)
//
// PROPÓSITO: Carga una textura a partir de un recurso
//
void CGParticles::InitTexture(int idr)
{
HRSRC handle = FindResource(NULL, MAKEINTRESOURCE(idr), L"IMAGE");
HGLOBAL hGlobal = LoadResource(NULL, handle);
LPCTSTR rsc_ptr = static_cast<LPCTSTR>(LockResource(hGlobal));
DWORD mem_size = SizeofResource(NULL, handle);
BYTE* mem_buffer = (BYTE*)malloc((mem_size) * sizeof(BYTE));
memcpy(mem_buffer, rsc_ptr, mem_size * sizeof(BYTE));
FreeResource(hGlobal);
FIMEMORY* hmem = FreeImage_OpenMemory(mem_buffer, mem_size * sizeof(BYTE));
FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(hmem, 0);
FIBITMAP* check = FreeImage_LoadFromMemory(fif, hmem, 0);
FIBITMAP* pImage = FreeImage_ConvertTo32Bits(check);
int nWidth = FreeImage_GetWidth(pImage);
int nHeight = FreeImage_GetHeight(pImage);
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, nWidth, nHeight,
0, GL_BGRA, GL_UNSIGNED_BYTE, (void*)FreeImage_GetBits(pImage));
FreeImage_Unload(pImage);
FreeImage_CloseMemory(hmem);
free(mem_buffer);
}
//
// FUNCIÓN: CGParticles::InitBuffers()
//
// PROPÓSITO: Crea el conjunto de partículas
//
void CGParticles::InitBuffers()
{
GLfloat radius = 3.0f;
// Fill the first position and first velocity buffers with random values
GLfloat* firstVel = new GLfloat[nParticles * 3];
GLfloat* firstPos = new GLfloat[nParticles * 3];
for (int i = 0; i < nParticles * 3; i += 3) {
GLfloat theta = glm::radians(30.0f * randFloat());
GLfloat phi = glm::radians(360.0f * randFloat());
GLfloat velocity = 1.25f + 0.25f * randFloat();
firstPos[i] = 0.0f;
firstPos[i + 1] = 0.0f;
firstPos[i + 2] = 0.0f;
firstVel[i] = sinf(theta) * cosf(phi) * velocity;
firstVel[i+1] = cosf(theta) * velocity;
firstVel[i+2] = sinf(theta) * sinf(phi) * velocity;
}
GLfloat* start = new GLfloat[nParticles];
float time = 0.0f;
float rate = 0.00075f;
for (int i = 0; i < nParticles; i++) {
start[i] = time;
time += rate;
}
// Generate the buffers
glGenBuffers(2, posBuf); // position buffers
glGenBuffers(2, velBuf); // velocity buffers
glGenBuffers(2, startTime); // Start time buffers
glGenBuffers(1, &initVel); // Initial velocity buffer (only need one)
// Allocate space for all buffers
int size = nParticles * 3 * sizeof(float);
glBindBuffer(GL_ARRAY_BUFFER, posBuf[0]);
glBufferData(GL_ARRAY_BUFFER, size, firstPos, GL_DYNAMIC_COPY);
glBindBuffer(GL_ARRAY_BUFFER, posBuf[1]);
glBufferData(GL_ARRAY_BUFFER, size, NULL, GL_DYNAMIC_COPY);
glBindBuffer(GL_ARRAY_BUFFER, velBuf[0]);
glBufferData(GL_ARRAY_BUFFER, size, firstVel, GL_DYNAMIC_COPY);
glBindBuffer(GL_ARRAY_BUFFER, velBuf[1]);
glBufferData(GL_ARRAY_BUFFER, size, NULL, GL_DYNAMIC_COPY);
glBindBuffer(GL_ARRAY_BUFFER, initVel);
glBufferData(GL_ARRAY_BUFFER, size, firstVel, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, startTime[0]);
glBufferData(GL_ARRAY_BUFFER, nParticles * sizeof(float), start, GL_DYNAMIC_COPY);
glBindBuffer(GL_ARRAY_BUFFER, startTime[1]);
glBufferData(GL_ARRAY_BUFFER, nParticles * sizeof(float), NULL, GL_DYNAMIC_COPY);
delete[] firstPos;
delete[] firstVel;
delete[] start;
// Create vertex arrays for each set of buffers
glGenVertexArrays(2, particleArray);
// Set up particle array 0
glBindVertexArray(particleArray[0]);
glBindBuffer(GL_ARRAY_BUFFER, posBuf[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, velBuf[0]);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, startTime[0]);
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, initVel);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(3);
// Set up particle array 1
glBindVertexArray(particleArray[1]);
glBindBuffer(GL_ARRAY_BUFFER, posBuf[1]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, velBuf[1]);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, startTime[1]);
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, initVel);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(3);
glBindVertexArray(0);
// Setup the feedback objects
glGenTransformFeedbacks(2, feedback);
// Transform feedback 0
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, feedback[0]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, posBuf[0]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1, velBuf[0]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 2, startTime[0]);
// Transform feedback 1
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, feedback[1]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, posBuf[1]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1, velBuf[1]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 2, startTime[1]);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
}
//
// FUNCIÓN: CGParticles::~CGParticles()
//
// PROPÓSITO: Destruye el conjunto de partículas
//
CGParticles::~CGParticles()
{
// Destruye buffers
glDeleteBuffers(2, posBuf);
glDeleteBuffers(2, velBuf);
glDeleteBuffers(2, startTime);
glDeleteBuffers(1, &initVel);
// Destruye los VAOs
glDeleteVertexArrays(2, particleArray);
// Destruye los TFOs
glDeleteTransformFeedbacks(2, feedback);
// Destruye la textura
glDeleteTextures(1, &textureId);
}
//
// FUNCIÓN: CGParticles::Draw(CGShaderProgram* prog, glm::mat4 proj, glm::mat4 view)
//
// PROPÓSITO: Dibuja el conjunto de partículas
//
void CGParticles::Draw(CGShaderProgram* program, glm::mat4 proj, glm::mat4 view)
{
// Opciones de dibujo
glDisable(GL_DEPTH_TEST);
glEnable(GL_POINT_SPRITE);
// Asigna las variables uniformes
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureId);
program->SetUniformI("ParticleTex", 0);
program->SetUniformF("ParticleLifetime", 1.2f);
program->SetUniformVec3("Accel", glm::vec3(0.0f, -0.05f, 0.0f));
program->SetUniformMatrix4("MVP", proj*view);
program->SetUniformF("Time", time);
program->SetUniformF("H", TIME_LAPSE);
// Update pass
program->SetUniformSubroutine(GL_VERTEX_SHADER, "update");
glEnable(GL_RASTERIZER_DISCARD);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, feedback[drawBuf]);
glBeginTransformFeedback(GL_POINTS);
glBindVertexArray(particleArray[1 - drawBuf]);
glDrawArrays(GL_POINTS, 0, nParticles);
glEndTransformFeedback();
glDisable(GL_RASTERIZER_DISCARD);
// Render pass
program->SetUniformSubroutine(GL_VERTEX_SHADER,"render");
glBindVertexArray(particleArray[drawBuf]);
glDrawTransformFeedback(GL_POINTS, feedback[drawBuf]);
// Swap buffers
drawBuf = 1 - drawBuf;
// Reactiva el test de profundidad
glDisable(GL_POINT_SPRITE);
glEnable(GL_DEPTH_TEST);
}
//
// FUNCIÓN: CGParticles::Update()
//
// PROPÓSITO: Actualiza el contador de tiempo
//
void CGParticles::Update()
{
time += TIME_LAPSE;
}
//
// FUNCIÓN: CGParticles::randFloat()
//
// PROPÓSITO: Genera un float aleatorio entre 0.0 y 1.0
//
float CGParticles::randFloat()
{
return ((float)rand() / RAND_MAX);
}
|
La clase CGModel contiene en este caso el objeto
CGParticles en vez de una escena.
Para obtener el resultado que se muestra a continuación se ha
activado la opción de mezcla (para poder considerar la transparencia
de las partículas), la opción de point sprites y se ha
asignado un tamaño de punto adecuado. La creación del programa
gráfico se realiza en tres pasos: creación, configuración de
Transform Feedback y linkado.
#include "CGModel.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <GLFW/glfw3.h>
#include <iostream>
#include "CGCamera.h"
#include "CGParticles.h"
#include "resource.h"
//
// FUNCIÓN: CGModel::initialize(int, int)
//
// PROPÓSITO: Initializa el modelo 3D
//
void CGModel::initialize(int w, int h)
{
// Crea el programa gráfico para dibujar el fuego
particlesProgram = new CGShaderProgram(IDR_SHADER1, IDR_SHADER2, -1, -1, -1);
// particlesProgram = new CGShaderProgram("shaders/ParticlesVertexShader.glsl",
// "shaders/ParticlesFragmentShader.glsl",
// NULL, NULL, NULL);
const char* particlesVars[] = { "Position", "Velocity", "StartTime" };
particlesProgram->SetTransformFeedback(particlesVars, 3);
particlesProgram->Link();
if (particlesProgram->IsLinked() == GL_FALSE) return;
// Crea la cámara
camera = new CGCamera();
camera->SetPosition(0.0f, 5.0f, 30.0f);
// Crea las partículas
particles = new CGParticles(4000);
// Asigna el viewport y el clipping volume
resize(w, h);
// Opciones de dibujo
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glEnable(GL_BLEND);
glFrontFace(GL_CCW);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_POINT_SPRITE);
glPointSize(30.0f);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
//
// FUNCIÓN: CGModel::finalize()
//
// PROPÓSITO: Libera los recursos del modelo 3D
//
void CGModel::finalize()
{
delete camera;
delete particles;
delete particlesProgram;
}
//
// FUNCIÓN: CGModel::render()
//
// PROPÓSITO: Genera la imagen
//
void CGModel::render()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 view = camera->ViewMatrix();
particlesProgram->Use();
particles->Draw(particlesProgram, projection, view);
}
//
// FUNCIÓN: CGModel::update()
//
// PROPÓSITO: Anima la escena
//
void CGModel::update()
{
particles->Update();
camera->MoveFront();
}
|
El resultado de la aplicación gráfica es el siguiente

|
|
Project12c - Animación de un
fuego basado
en sistema de partículas con Transform Feedback
|
|
En este último proyecto se va a utilizar un sistema de partículas
para modelar un fuego de campamento. La leña del fuego se va a
generar por medio de un modelo 3D diseñado por el artista gráfico
Zelad y obtenido de la plataforma
TurboSquid. Sobre este modelo
vamos a dibujar una llamas generadas mediante un sistema de
partículas. En este caso la textura de cada partícula está formada
por valores anaranjados.
Para generar la aplicación se va a utilizar un modelo formado por
una escena, que incluye el objeto Campfire y la luz, un
conjunto de partículas y una cámara. La clase CGModel debe incluir también los
programas gráficos utilizados por la escena y el conjunto de
partículas. El fichero de cabecera de la clase es el siguiente:
class CGModel
{
public:
void initialize(int w, int h);
void finalize();
void render();
void update();
void key_pressed(int key);
void mouse_button(int button, int action);
void mouse_move(double xpos, double ypos);
void resize(int w, int h);
private:
CGShaderProgram* sceneProgram;
CGShaderProgram* particlesProgram;
CGScene* scene;
CGParticles* particles;
CGCamera* camera;
glm::mat4 projection;
};
|
El constructor del modelo debe inicializar los programas gráficos y
crear los objetos CGScene y CGParticles. El programa gráfico de la
escena es el habitual para representar objetos con texturas y con el
modelo de iluminación de Phong. El programa gráfico de las
partículas es el comentado en el apartado anterior. En este caso hay
que configurar la opción de Transform Feedback en este programa
gráfico.
//
// FUNCIÓN: CGModel::initialize(int, int)
//
// PROPÓSITO: Initializa el modelo 3D
//
void CGModel::initialize(int w, int h)
{
// Crea el programa gráfico para dibujar la escena
sceneProgram = new CGShaderProgram(IDR_SHADER1, IDR_SHADER2, -1, -1, -1);
// sceneProgram = new CGShaderProgram("shaders/VertexShader.glsl",
// "shaders/FragmentShader.glsl", NULL, NULL, NULL);
sceneProgram->Link();
if (sceneProgram->IsLinked() == GL_FALSE) return;
// Crea el programa gráfico para dibujar el fuego
particlesProgram = new CGShaderProgram(IDR_SHADER3, IDR_SHADER4, -1, -1, -1);
// particlesProgram = new CGShaderProgram("shaders/ParticlesVertexShader.glsl",
// "shaders/ParticlesFragmentShader.glsl", NULL, NULL, NULL);
const char* particlesVars[] = { "Position", "Velocity", "StartTime" };
particlesProgram->SetTransformFeedback(particlesVars, 3);
particlesProgram->Link();
if (particlesProgram->IsLinked() == GL_FALSE) return;
// Crea la cámara
camera = new CGCamera();
camera->SetPosition(0.0f, 5.0f, 30.0f);
// Crea la escena
scene = new CGScene();
// Crea el fuego
particles = new CGParticles(10000);
// Asigna el viewport y el clipping volume
resize(w, h);
// Opciones de dibujo
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glEnable(GL_BLEND);
glFrontFace(GL_CCW);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_POINT_SPRITE);
glPointSize(40.0f);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
//
// FUNCIÓN: CGModel::render()
//
// PROPÓSITO: Genera la imagen
//
void CGModel::render()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 view = camera->ViewMatrix();
sceneProgram->Use();
scene->Draw(sceneProgram, projection, view);
particlesProgram->Use();
particles->Draw(particlesProgram, projection, view);
}
...
|
La simulación del fuego requiere realizar algunas modificaciones en
la definición de la clase CGParticles. En este caso las
partículas deben tener una aceleración ligeramente positiva sobre el
eje Y de manera que tiendan a elevarse mientras se van difuminando.
La posición y velocidad inicial se ha programado de manera que las
partículas nazcan aproximadamente sobre la pila de leña y tengan una
cierta velocidad radial. Estas posiciones y velocidades iniciales se
generan de forma aleatoria formando un círculo respecto al centro de
la pila de leña. Las partículas se dibujan como point sprites.
El tamaño de los puntos se ha configurado para que se tenga en
cuenta la distancia a la que se observa el fuego.
//
// FUNCIÓN: CGParticles::CGParticles(n)
//
// PROPÓSITO: Crea el conjunto de partículas
//
CGParticles::CGParticles(int n)
{
time = 0.0f;
nParticles = n;
drawBuf = 1;
InitBuffers();
InitTexture("textures/fire.png");
}
//
// FUNCIÓN: CGParticles::InitBuffers()
//
// PROPÓSITO: Crea el conjunto de partículas
//
void CGParticles::InitBuffers()
{
GLfloat radius = 3.0f;
// Fill the first position and first velocity buffers with random values
GLfloat* firstVel = new GLfloat[nParticles * 3];
GLfloat* firstPos = new GLfloat[nParticles * 3];
for (int i = 0; i < nParticles * 3; i += 3) {
GLfloat x = 2.0f * randFloat() - 1.0f;
GLfloat z = 2.0f * randFloat() - 1.0f;
while (x * x + z * z > 1.0f)
{
x = 2.0f * randFloat() - 1.0f;
z = 2.0f * randFloat() - 1.0f;
}
firstPos[i] = radius * x;
firstPos[i + 1] = (radius +1.0f) * (1.0f - glm::sqrt(x*x+z*z)) + 1.0f;
firstPos[i + 2] = radius*z;
firstVel[i] = x / 5.0f;
firstVel[i + 1] = glm::mix(0.1f, 0.5f, randFloat());
firstVel[i + 2] = z / 5.0f;
}
GLfloat* start = new GLfloat[nParticles];
float time = 0.0f;
float rate = 0.0009f;
for (int i = 0; i < nParticles; i+=2) {
start[i] = time;
start[i+1] = time;
time += rate;
}
// Generate the buffers
glGenBuffers(2, posBuf); // position buffers
glGenBuffers(2, velBuf); // velocity buffers
glGenBuffers(2, startTime); // Start time buffers
glGenBuffers(1, &initPos); // Initial position buffer (only need one)
glGenBuffers(1, &initVel); // Initial velocity buffer (only need one)
// Allocate space for all buffers
int size = nParticles * 3 * sizeof(float);
glBindBuffer(GL_ARRAY_BUFFER, posBuf[0]);
glBufferData(GL_ARRAY_BUFFER, size, firstPos, GL_DYNAMIC_COPY);
glBindBuffer(GL_ARRAY_BUFFER, posBuf[1]);
glBufferData(GL_ARRAY_BUFFER, size, NULL, GL_DYNAMIC_COPY);
glBindBuffer(GL_ARRAY_BUFFER, velBuf[0]);
glBufferData(GL_ARRAY_BUFFER, size, firstVel, GL_DYNAMIC_COPY);
glBindBuffer(GL_ARRAY_BUFFER, velBuf[1]);
glBufferData(GL_ARRAY_BUFFER, size, NULL, GL_DYNAMIC_COPY);
glBindBuffer(GL_ARRAY_BUFFER, initPos);
glBufferData(GL_ARRAY_BUFFER, size, firstPos, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, initVel);
glBufferData(GL_ARRAY_BUFFER, size, firstVel, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, startTime[0]);
glBufferData(GL_ARRAY_BUFFER, nParticles * sizeof(float), start, GL_DYNAMIC_COPY);
glBindBuffer(GL_ARRAY_BUFFER, startTime[1]);
glBufferData(GL_ARRAY_BUFFER, nParticles * sizeof(float), NULL, GL_DYNAMIC_COPY);
delete[] firstPos;
delete[] firstVel;
delete[] start;
// Create vertex arrays for each set of buffers
glGenVertexArrays(2, particleArray);
// Set up particle array 0
glBindVertexArray(particleArray[0]);
glBindBuffer(GL_ARRAY_BUFFER, posBuf[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, velBuf[0]);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, startTime[0]);
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, initPos);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, initVel);
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(4);
// Set up particle array 1
glBindVertexArray(particleArray[1]);
glBindBuffer(GL_ARRAY_BUFFER, posBuf[1]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, velBuf[1]);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, startTime[1]);
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, initPos);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, initVel);
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(4);
glBindVertexArray(0);
// Setup the feedback objects
glGenTransformFeedbacks(2, feedback);
// Transform feedback 0
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, feedback[0]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, posBuf[0]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1, velBuf[0]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 2, startTime[0]);
// Transform feedback 1
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, feedback[1]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, posBuf[1]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1, velBuf[1]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 2, startTime[1]);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
}
//
// FUNCIÓN: CGParticles::Draw(CGShaderProgram* prog, glm::mat4 proj, glm::mat4 view)
//
// PROPÓSITO: Dibuja el conjunto de partículas
//
void CGParticles::Draw(CGShaderProgram* program, glm::mat4 proj, glm::mat4 view)
{
glm::vec4 dist = view * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
GLfloat d = glm::clamp(glm::abs(dist.z), 5.0f, 200.0f);
GLfloat f = (200.0f - d) / (200.0f - 5.0f);
GLfloat psize = 30.0f * f + 1.0f;
// Opciones de dibujo
glDisable(GL_DEPTH_TEST);
glEnable(GL_POINT_SPRITE);
glPointSize(psize);
// Asigna las variables uniformes
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureId);
program->SetUniformI("ParticleTex", 0);
program->SetUniformF("ParticleLifetime", 1.2f);
program->SetUniformVec3("Accel", glm::vec3(0.0f, 0.03f, 0.0f));
program->SetUniformMatrix4("MVP", proj*view);
program->SetUniformF("Time", time);
program->SetUniformF("H", TIME_LAPSE);
// Update pass
program->SetUniformSubroutine(GL_VERTEX_SHADER, "update");
glEnable(GL_RASTERIZER_DISCARD);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, feedback[drawBuf]);
glBeginTransformFeedback(GL_POINTS);
glBindVertexArray(particleArray[1 - drawBuf]);
glDrawArrays(GL_POINTS, 0, nParticles);
glEndTransformFeedback();
glDisable(GL_RASTERIZER_DISCARD);
// Render pass
program->SetUniformSubroutine(GL_VERTEX_SHADER,"render");
glBindVertexArray(particleArray[drawBuf]);
glDrawTransformFeedback(GL_POINTS, feedback[drawBuf]);
// Swap buffers
drawBuf = 1 - drawBuf;
// Reactiva el test de profundidad
glDisable(GL_POINT_SPRITE);
glEnable(GL_DEPTH_TEST);
}
//
// FUNCIÓN: CGParticles::Update()
//
// PROPÓSITO: Actualiza el contador de tiempo
//
void CGParticles::Update()
{
time += TIME_LAPSE;
}
...
|
El resultado de la aplicación gráfica es el siguiente

|
|