456 lines
17 KiB
C
456 lines
17 KiB
C
/*!\file rasterize.c
|
|
* \brief ensemble de fonctions de rasterization "DIY". S'occupe
|
|
* principalement que du cas du triangle.
|
|
*
|
|
* CE CODE A EU COMME POINT DE DÉPART CE QUI A ÉTÉ FAIT
|
|
* EN COURS, IL EST COMPLÉTÉ PAR L'ENSEIGNANT POUR ÊTRE
|
|
* FONCTIONNEL MAIS IL DES CHOSES À AMÉLIORER.
|
|
*
|
|
* \author Farès BELHADJ, amsi@up8.edu
|
|
* \date November 16, 2021.
|
|
*/
|
|
|
|
#include "rasterize.h"
|
|
#include <assert.h>
|
|
|
|
/* bloc de fonctions locales (static) */
|
|
static inline void fill_triangle(surface_t * s, triangle_t * t);
|
|
static inline void abscisses(surface_t * s, vertex_t * p0, vertex_t * p1, vertex_t * absc, int replace);
|
|
static inline void horizontal_line(surface_t * s, vertex_t * vG, vertex_t * vD);
|
|
static inline void shading_none(surface_t * s, GLuint * pcolor, vertex_t * v);
|
|
static inline void shading_only_tex(surface_t * s, GLuint * pcolor, vertex_t * v);
|
|
static inline void shading_only_color_CM(surface_t * s, GLuint * pcolor, vertex_t * v);
|
|
static inline void shading_only_color(surface_t * s, GLuint * pcolor, vertex_t * v);
|
|
static inline void shading_all_CM(surface_t * s, GLuint * pcolor, vertex_t * v);
|
|
static inline void shading_all(surface_t * s, GLuint * pcolor, vertex_t * v);
|
|
static inline void interpolate(vertex_t * r, vertex_t * a, vertex_t * b, float fa, float fb, int s, int e);
|
|
static inline void metainterpolate_none(vertex_t * r, vertex_t * a, vertex_t * b, float fa, float fb);
|
|
static inline void metainterpolate_only_tex(vertex_t * r, vertex_t * a, vertex_t * b, float fa, float fb);
|
|
static inline void metainterpolate_only_color(vertex_t * r, vertex_t * a, vertex_t * b, float fa, float fb);
|
|
static inline void metainterpolate_all(vertex_t * r, vertex_t * a, vertex_t * b, float fa, float fb);
|
|
static inline GLuint rgba(GLubyte r, GLubyte g, GLubyte b, GLubyte a);
|
|
static inline GLubyte red(GLuint c);
|
|
static inline GLubyte green(GLuint c);
|
|
static inline GLubyte blue(GLuint c);
|
|
static inline GLubyte alpha(GLuint c);
|
|
static void pquit(void);
|
|
|
|
/*!\brief la texture courante à utiliser en cas de mapping de texture */
|
|
static GLuint * _tex = NULL;
|
|
/*!\brief la largeur de la texture courante à utiliser en cas de
|
|
* mapping de texture */
|
|
static GLuint _texW = 0;
|
|
/*!\brief la hauteur de la texture courante à utiliser en cas de
|
|
* mapping de texture */
|
|
static GLuint _texH = 0;
|
|
/*!\brief un buffer de depth pour faire le z-test */
|
|
static float * _depth = NULL;
|
|
/*!\brief flag pour savoir s'il faut ou non corriger l'interpolation
|
|
* par rapport à la profondeur en cas de projection en
|
|
* perspective */
|
|
static int _perpective_correction = 0;
|
|
|
|
/*!\brief transforme et rastérise l'ensemble des triangles de la
|
|
* surface. */
|
|
void transform_n_rasterize(surface_t * s, float * model_view_matrix, float * projection_matrix) {
|
|
int i;
|
|
/* la première fois allouer le depth buffer */
|
|
if(_depth == NULL) {
|
|
_depth = calloc(gl4dpGetWidth() * gl4dpGetHeight(), sizeof *_depth);
|
|
assert(_depth);
|
|
atexit(pquit);
|
|
}
|
|
/* si projection_matrix[15] est à 1, c'est une projection orthogonale, pas
|
|
* besoin de correction de perspective */
|
|
_perpective_correction = projection_matrix[15] == 1.0f ? 0 : 1;
|
|
/* le viewport est fixe ; \todo peut devenir paramétrable ... */
|
|
float viewport[] = { 0.0f, 0.0f, (float)gl4dpGetWidth(), (float)gl4dpGetHeight() };
|
|
stransform(s, model_view_matrix, projection_matrix, viewport);
|
|
/* mettre en place la texture qui sera utilisée pour mapper la surface */
|
|
if(s->options & SO_USE_TEXTURE)
|
|
set_texture(s->tex_id);
|
|
for(i = 0; i < s->n; ++i) {
|
|
/* si le triangle est déclaré CULL (par exemple en backface), le rejeter */
|
|
if(s->t[i].state & PS_CULL ) continue;
|
|
/* on rejette aussi les triangles complètement out */
|
|
if(s->t[i].state & PS_TOTALLY_OUT) continue;
|
|
/* "hack" pas terrible permettant de rejeter les triangles
|
|
* partiellement out dont au moins un sommet est TOO_FAR (trop
|
|
* éloigné). Voir le fichier transformations.c pour voir comment
|
|
* améliorer ce traitement. */
|
|
if( s->t[i].state & PS_PARTIALLY_OUT &&
|
|
( (s->t[i].v[0].state & PS_TOO_FAR) ||
|
|
(s->t[i].v[1].state & PS_TOO_FAR) ||
|
|
(s->t[i].v[2].state & PS_TOO_FAR) )
|
|
)
|
|
continue;
|
|
fill_triangle(s, &(s->t[i]));
|
|
}
|
|
}
|
|
|
|
/*!\brief effacer le buffer de profondeur (à chaque frame) pour
|
|
* réaliser le z-test */
|
|
void clear_depth_map(void) {
|
|
if(_depth) {
|
|
memset(_depth, 0, gl4dpGetWidth() * gl4dpGetHeight() * sizeof *_depth);
|
|
}
|
|
}
|
|
|
|
/*!\brief met en place une texture pour être mappée sur la surface en cours */
|
|
void set_texture(GLuint screen) {
|
|
GLuint old_id = gl4dpGetTextureId(); /* au cas où */
|
|
gl4dpSetScreen(screen);
|
|
_tex = gl4dpGetPixels();
|
|
_texW = gl4dpGetWidth();
|
|
_texH = gl4dpGetHeight();
|
|
if(old_id)
|
|
gl4dpSetScreen(old_id);
|
|
}
|
|
|
|
|
|
/*!\brief met à jour la fonction d'interpolation et de coloriage
|
|
* (shadingfunc) de la surface en fonction de ses options */
|
|
void updatesfuncs(surface_t * s) {
|
|
int t;
|
|
if(s->options & SO_USE_TEXTURE) {
|
|
s->interpolatefunc = (t = s->options & SO_COLOR_MATERIAL) ? metainterpolate_all : metainterpolate_only_tex;
|
|
s->shadingfunc = (s->options & SO_USE_COLOR) ? (t ? shading_all_CM : shading_all) : shading_only_tex;
|
|
} else {
|
|
s->interpolatefunc = (t = s->options & SO_COLOR_MATERIAL) ? metainterpolate_only_color : metainterpolate_none;
|
|
s->shadingfunc = (s->options & SO_USE_COLOR) ? (t ? shading_only_color_CM : shading_only_color) : shading_none;;
|
|
}
|
|
}
|
|
|
|
/*!\brief fonction principale de ce fichier, elle dessine un triangle
|
|
* rempli à l'écran en calculant l'ensemble des gradients
|
|
* (interpolations bilinaires des attributs du sommet).
|
|
*/
|
|
inline void fill_triangle(surface_t * s, triangle_t * t) {
|
|
vertex_t * aG = NULL, * aD = NULL;
|
|
int bas, median, haut, n, signe, i, h = gl4dpGetHeight();
|
|
if(t->v[0].y < t->v[1].y) {
|
|
if(t->v[0].y < t->v[2].y) {
|
|
bas = 0;
|
|
if(t->v[1].y < t->v[2].y) {
|
|
median = 1;
|
|
haut = 2;
|
|
} else {
|
|
median = 2;
|
|
haut = 1;
|
|
}
|
|
} else {
|
|
bas = 2;
|
|
median = 0;
|
|
haut = 1;
|
|
}
|
|
} else { /* p0 au dessus de p1 */
|
|
if(t->v[1].y < t->v[2].y) {
|
|
bas = 1;
|
|
if(t->v[0].y < t->v[2].y) {
|
|
median = 0;
|
|
haut = 2;
|
|
} else {
|
|
median = 2;
|
|
haut = 0;
|
|
}
|
|
} else {
|
|
bas = 2;
|
|
median = 1;
|
|
haut = 0;
|
|
}
|
|
}
|
|
n = t->v[haut].y - t->v[bas].y + 1;
|
|
aG = malloc(n * sizeof *aG);
|
|
assert(aG);
|
|
aD = malloc(n * sizeof *aD);
|
|
assert(aD);
|
|
/* est-ce que Pm est à gauche (+) ou à droite (-) de la droite (Pb->Ph) ? */
|
|
/* idée TODO?, un produit vectoriel pourrait s'avérer mieux */
|
|
if(t->v[haut].x == t->v[bas].x || t->v[haut].y == t->v[bas].y) {
|
|
/* eq de la droite x = t->v[haut].x; ou y = t->v[haut].y; */
|
|
signe = (t->v[median].x > t->v[haut].x) ? -1 : 1;
|
|
} else {
|
|
/* eq ax + y + c = 0 */
|
|
float a, c, x;
|
|
a = (t->v[haut].y - t->v[bas].y) / (float)(t->v[bas].x - t->v[haut].x);
|
|
c = -a * t->v[haut].x - t->v[haut].y;
|
|
/* on trouve le x sur la droite au même y que le median et on compare */
|
|
x = -(c + t->v[median].y) / a;
|
|
signe = (t->v[median].x >= x) ? -1 : 1;
|
|
}
|
|
if(signe < 0) { /* aG reçoit Ph->Pb, et aD reçoit Ph->Pm puis Pm vers Pb */
|
|
abscisses(s, &(t->v[haut]), &(t->v[bas]), aG, 1);
|
|
abscisses(s, &(t->v[haut]), &(t->v[median]), aD, 1);
|
|
abscisses(s, &(t->v[median]), &(t->v[bas]), &aD[t->v[haut].y - t->v[median].y], 0);
|
|
} else { /* aG reçoit Ph->Pm puis Pm vers Pb, et aD reçoit Ph->Pb */
|
|
abscisses(s, &(t->v[haut]), &(t->v[bas]), aD, 1);
|
|
abscisses(s, &(t->v[haut]), &(t->v[median]), aG, 1);
|
|
abscisses(s, &(t->v[median]), &(t->v[bas]), &aG[t->v[haut].y - t->v[median].y], 0);
|
|
}
|
|
for(i = 0; i < n; ++i) {
|
|
if( aG[i].y >= 0 && aG[i].y < h &&
|
|
( (aG[i].z >= 0 && aG[i].z <= 1) || (aD[i].z >= 0 && aD[i].z <= 1) ) )
|
|
horizontal_line(s, &aG[i], &aD[i]);
|
|
}
|
|
free(aG);
|
|
free(aD);
|
|
}
|
|
|
|
/*!\brief utilise Br'65 pour determiner les abscisses des segments du
|
|
* triangle à remplir (par \a horizontal_line).
|
|
*/
|
|
inline void abscisses(surface_t * s, vertex_t * p0, vertex_t * p1, vertex_t * absc, int replace) {
|
|
int u = p1->x - p0->x, v = p1->y - p0->y, pasX = u < 0 ? -1 : 1, pasY = v < 0 ? -1 : 1;
|
|
float dmax = sqrtf(u * u + v * v), p;
|
|
u = abs(u); v = abs(v);
|
|
if(u > v) { // 1er octan
|
|
if(replace) {
|
|
int objX = (u + 1) * pasX;
|
|
int delta = u - 2 * v, incH = -2 * v, incO = 2 * u - 2 * v;
|
|
for (int x = 0, y = 0, k = 0; x != objX; x += pasX) {
|
|
absc[k].x = x + p0->x;
|
|
absc[k].y = y + p0->y;
|
|
p = sqrtf(x * x + y * y) / dmax;
|
|
s->interpolatefunc(&absc[k], p0, p1, 1.0f - p, p);
|
|
if(delta < 0) {
|
|
++k;
|
|
y += pasY;
|
|
delta += incO;
|
|
} else
|
|
delta += incH;
|
|
}
|
|
} else {
|
|
int objX = (u + 1) * pasX;
|
|
int delta = u - 2 * v, incH = -2 * v, incO = 2 * u - 2 * v;
|
|
for (int x = 0, y = 0, k = 0, done = 0; x != objX; x += pasX) {
|
|
if(!done) {
|
|
absc[k].x = x + p0->x;
|
|
absc[k].y = y + p0->y;
|
|
p = sqrtf(x * x + y * y) / dmax;
|
|
s->interpolatefunc(&absc[k], p0, p1, 1.0f - p, p);
|
|
done = 1;
|
|
}
|
|
if(delta < 0) {
|
|
++k;
|
|
done = 0;
|
|
y += pasY;
|
|
delta += incO;
|
|
} else
|
|
delta += incH;
|
|
}
|
|
}
|
|
} else { // 2eme octan
|
|
int objY = (v + 1) * pasY;
|
|
int delta = v - 2 * u, incH = -2 * u, incO = 2 * v - 2 * u;
|
|
for (int x = 0, y = 0, k = 0; y != objY; y += pasY) {
|
|
absc[k].x = x + p0->x;
|
|
absc[k].y = y + p0->y;
|
|
p = sqrtf(x * x + y * y) / dmax;
|
|
s->interpolatefunc(&absc[k], p0, p1, 1.0f - p, p);
|
|
++k;
|
|
if(delta < 0) {
|
|
x += pasX;
|
|
delta += incO;
|
|
} else
|
|
delta += incH;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!\brief remplissage par droite horizontale entre deux abscisses */
|
|
inline void horizontal_line(surface_t * s, vertex_t * vG, vertex_t * vD) {
|
|
int w = gl4dpGetWidth(), x, yw = vG->y * w;
|
|
GLuint * image = gl4dpGetPixels();
|
|
float dmax = vD->x - vG->x, p, deltap;
|
|
vertex_t v;
|
|
/* il reste d'autres optims possibles */
|
|
for(x = vG->x, p = 0.0f, deltap = 1.0f / dmax; x <= vD->x; ++x, p += deltap)
|
|
if(x >= 0 && x < w) {
|
|
s->interpolatefunc(&v, vG, vD, 1.0f - p, p);
|
|
if(v.z < 0 || v.z > 1 || v.z < _depth[yw + x]) { continue; }
|
|
s->shadingfunc(s, &image[yw + x], &v);
|
|
_depth[yw + x] = v.z;
|
|
}
|
|
}
|
|
/*!\brief aucune couleur n'est inscrite */
|
|
inline void shading_none(surface_t * s, GLuint * pcolor, vertex_t * v) {
|
|
//vide pour l'instant, à prévoir le z-buffer
|
|
}
|
|
|
|
/*!\brief la couleur du pixel est tirée uniquement de la texture */
|
|
inline void shading_only_tex(surface_t * s, GLuint * pcolor, vertex_t * v) {
|
|
int xt, yt, ct;
|
|
GLubyte r, g, b, a;
|
|
xt = (int)(v->texCoord.x * (_texW - EPSILON));
|
|
if(xt < 0) {
|
|
xt = xt % (-_texW);
|
|
while(xt < 0) xt += _texW;
|
|
} else
|
|
xt = xt % _texW;
|
|
yt = (int)(v->texCoord.y * (_texH - EPSILON));
|
|
if(yt < 0) {
|
|
yt = yt % (-_texH);
|
|
while(yt < 0) yt += _texH;
|
|
} else
|
|
yt = yt % _texH;
|
|
ct = yt * _texW + xt;
|
|
*pcolor = _tex[yt * _texW + xt];
|
|
r = (GLubyte)( red(_tex[ct]) * v->li);
|
|
g = (GLubyte)(green(_tex[ct]) * v->li);
|
|
b = (GLubyte)( blue(_tex[ct]) * v->li);
|
|
a = (GLubyte) alpha(_tex[ct]);
|
|
*pcolor = rgba(r, g, b, a);
|
|
}
|
|
|
|
/*!\brief la couleur du pixel est tirée de la couleur interpolée */
|
|
inline void shading_only_color_CM(surface_t * s, GLuint * pcolor, vertex_t * v) {
|
|
GLubyte r, g, b, a;
|
|
r = (GLubyte)(v->li * v->icolor.x * (255 + EPSILON));
|
|
g = (GLubyte)(v->li * v->icolor.y * (255 + EPSILON));
|
|
b = (GLubyte)(v->li * v->icolor.z * (255 + EPSILON));
|
|
a = (GLubyte)(v->icolor.w * (255 + EPSILON));
|
|
*pcolor = rgba(r, g, b, a);
|
|
}
|
|
|
|
/*!\brief la couleur du pixel est tirée de la couleur diffuse de la
|
|
* surface */
|
|
inline void shading_only_color(surface_t * s, GLuint * pcolor, vertex_t * v) {
|
|
GLubyte r, g, b, a;
|
|
r = (GLubyte)(v->li * s->dcolor.x * (255 + EPSILON));
|
|
g = (GLubyte)(v->li * s->dcolor.y * (255 + EPSILON));
|
|
b = (GLubyte)(v->li * s->dcolor.z * (255 + EPSILON));
|
|
a = (GLubyte)(s->dcolor.w * (255 + EPSILON));
|
|
*pcolor = rgba(r, g, b, a);
|
|
}
|
|
|
|
/*!\brief la couleur du pixel est le produit de la couleur interpolée
|
|
* et de la texture */
|
|
inline void shading_all_CM(surface_t * s, GLuint * pcolor, vertex_t * v) {
|
|
GLubyte r, g, b, a;
|
|
int xt, yt, ct;
|
|
xt = (int)(v->texCoord.x * (_texW - EPSILON));
|
|
if(xt < 0) {
|
|
xt = xt % (-_texW);
|
|
while(xt < 0) xt += _texW;
|
|
} else
|
|
xt = xt % _texW;
|
|
yt = (int)(v->texCoord.y * (_texH - EPSILON));
|
|
if(yt < 0) {
|
|
yt = yt % (-_texH);
|
|
while(yt < 0) yt += _texH;
|
|
} else
|
|
yt = yt % _texH;
|
|
ct = yt * _texW + xt;
|
|
r = (GLubyte)(( red(_tex[ct]) + EPSILON) * v->li * v->icolor.x);
|
|
g = (GLubyte)((green(_tex[ct]) + EPSILON) * v->li * v->icolor.y);
|
|
b = (GLubyte)(( blue(_tex[ct]) + EPSILON) * v->li * v->icolor.z);
|
|
a = (GLubyte)((alpha(_tex[ct]) + EPSILON) * v->icolor.w);
|
|
*pcolor = rgba(r, g, b, a);
|
|
}
|
|
|
|
/*!\brief la couleur du pixel est le produit de la couleur diffuse
|
|
* de la surface et de la texture */
|
|
inline void shading_all(surface_t * s, GLuint * pcolor, vertex_t * v) {
|
|
GLubyte r, g, b, a;
|
|
int xt, yt, ct;
|
|
xt = (int)(v->texCoord.x * (_texW - EPSILON));
|
|
if(xt < 0) {
|
|
xt = xt % (-_texW);
|
|
while(xt < 0) xt += _texW;
|
|
} else
|
|
xt = xt % _texW;
|
|
yt = (int)(v->texCoord.y * (_texH - EPSILON));
|
|
if(yt < 0) {
|
|
yt = yt % (-_texH);
|
|
while(yt < 0) yt += _texH;
|
|
} else
|
|
yt = yt % _texH;
|
|
ct = yt * _texW + xt;
|
|
r = (GLubyte)(( red(_tex[ct]) + EPSILON) * v->li * s->dcolor.x);
|
|
g = (GLubyte)((green(_tex[ct]) + EPSILON) * v->li * s->dcolor.y);
|
|
b = (GLubyte)(( blue(_tex[ct]) + EPSILON) * v->li * s->dcolor.z);
|
|
a = (GLubyte)((alpha(_tex[ct]) + EPSILON) * s->dcolor.w);
|
|
*pcolor = rgba(r, g, b, a);
|
|
}
|
|
|
|
/*!\brief interpolation de plusieurs floattants (entre \a s et \a e)
|
|
* de la structure vertex_t en utilisant \a a et \a b, les
|
|
* facteurs \a fa et \a fb, le tout dans \a r
|
|
* \todo un pointeur de fonction pour éviter un test s'il faut
|
|
* un _perpective_correction != 0 ??? */
|
|
inline void interpolate(vertex_t * r, vertex_t * a, vertex_t * b, float fa, float fb, int s, int e) {
|
|
int i;
|
|
float * pr = (float *)&(r->texCoord);
|
|
float * pa = (float *)&(a->texCoord);
|
|
float * pb = (float *)&(b->texCoord);
|
|
/* Correction de l'interpolation par rapport à la perspective, le z
|
|
* joue un rôle dans les distances, il est nécessaire de le
|
|
* réintégrer en modifiant les facteurs de proportion.
|
|
* lien utile : https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/perspective-correct-interpolation-vertex-attributes
|
|
*/
|
|
if(_perpective_correction) {
|
|
float z = 1.0f / (fa / a->zmod + fb / b->zmod);
|
|
if(e == 8) { /* attention il faut que cet indice colle avec la position de la proriété z à partir de l'adresse texCoord */
|
|
pr[e] = fa * pa[e] + fb * pb[e];
|
|
e = 7;
|
|
}
|
|
fa = z * fa / a->zmod; fb = z * fb / b->zmod;
|
|
}
|
|
for(i = s; i <= e; ++i)
|
|
pr[i] = fa * pa[i] + fb * pb[i];
|
|
}
|
|
|
|
/*!\brief meta-fonction pour appeler \a interpolate, demande
|
|
* uniquement l'interpolation des z */
|
|
inline void metainterpolate_none(vertex_t * r, vertex_t * a, vertex_t * b, float fa, float fb) {
|
|
interpolate(r, a, b, fa, fb, 6, 8);
|
|
}
|
|
|
|
/*!\brief meta-fonction pour appeler \a interpolate, demande
|
|
* uniquement l'interpolation des coord. de texture et les z */
|
|
inline void metainterpolate_only_tex(vertex_t * r, vertex_t * a, vertex_t * b, float fa, float fb) {
|
|
interpolate(r, a, b, fa, fb, 0, 1);
|
|
interpolate(r, a, b, fa, fb, 6, 8);
|
|
}
|
|
|
|
/*!\brief meta-fonction pour appeler \a interpolate, demande
|
|
* uniquement l'interpolation des couleurs et les z */
|
|
inline void metainterpolate_only_color(vertex_t * r, vertex_t * a, vertex_t * b, float fa, float fb) {
|
|
interpolate(r, a, b, fa, fb, 2, 8);
|
|
}
|
|
|
|
/*!\brief meta-fonction pour appeler \a interpolate, demande
|
|
* l'interpolation de l'ensemble des attributs */
|
|
inline void metainterpolate_all(vertex_t * r, vertex_t * a, vertex_t * b, float fa, float fb) {
|
|
interpolate(r, a, b, fa, fb, 0, 8);
|
|
}
|
|
|
|
GLuint rgba(GLubyte r, GLubyte g, GLubyte b, GLubyte a) {
|
|
return RGBA(r, g, b, a);
|
|
}
|
|
|
|
GLubyte red(GLuint c) {
|
|
return RED(c);
|
|
}
|
|
|
|
GLubyte green(GLuint c) {
|
|
return GREEN(c);
|
|
}
|
|
|
|
GLubyte blue(GLuint c) {
|
|
return BLUE(c);
|
|
}
|
|
|
|
GLubyte alpha(GLuint c) {
|
|
return ALPHA(c);
|
|
}
|
|
|
|
|
|
/*!\brief au moment de quitter le programme désallouer la mémoire
|
|
* utilisée pour _depth */
|
|
void pquit(void) {
|
|
if(_depth) {
|
|
free(_depth);
|
|
_depth = NULL;
|
|
}
|
|
}
|