ref: 239a319b41474a35e4c9c4b7c6ae3c6e0b0b7185
parent: dc597a2c65278119b7d11f83218b860c0c5da051
author: rodri <rgl@antares-labs.eu>
date: Thu Jun 6 13:35:09 EDT 2024
add cubemaps.
--- a/camera.c
+++ b/camera.c
@@ -8,6 +8,73 @@
#include "graphics.h"
#include "internal.h"
+/*
+ * references:
+ * - https://learnopengl.com/Advanced-OpenGL/Cubemaps
+ */
+static Point3
+skyboxvs(VSparams *sp)
+{
+ Point3 p;
+
+ addvattr(sp->v, "dir", VAPoint, &sp->v->p);
+ /* only rotate along with the camera */
+ p = sp->v->p; p.w = 0;
+ p = world2vcs(sp->su->camera, p); p.w = 1;
+ p = vcs2clip(sp->su->camera, p);
+ /* force the cube to always be on the far plane */
+ p.z = -p.w;
+ return p;
+}
+
+static Color
+skyboxfs(FSparams *sp)
+{
+ Vertexattr *va;
+ Color c;
+
+ va = getvattr(&sp->v, "dir");
+ c = cubemaptexture(sp->su->camera->scene->skybox, va->p, neartexsampler);
+ return c;
+}
+
+static Model *
+mkskyboxmodel(void)
+{
+ static Point3 axes[3] = {{0,1,0,0}, {1,0,0,0}, {0,0,1,0}};
+ static Point3 center = {0,0,0,1};
+ Model *m;
+ Primitive t[2];
+ Point3 p, v1, v2;
+ int i, j, k;
+
+ m = newmodel();
+ memset(t, 0, sizeof t);
+ t[0].type = t[1].type = PTriangle;
+
+ p = Vec3(-0.5,-0.5,0.5);
+ v1 = Vec3(1,0,0);
+ v2 = Vec3(0,1,0);
+ t[0].v[0].p = addpt3(center, p);
+ t[0].v[1].p = addpt3(center, addpt3(p, v1));
+ t[0].v[2].p = addpt3(center, addpt3(p, addpt3(v1, v2)));
+ t[1].v[0] = t[0].v[0];
+ t[1].v[1] = t[0].v[2];
+ t[1].v[2].p = addpt3(center, addpt3(p, v2));
+
+ for(i = 0; i < 6; i++){
+ for(j = 0; j < 2; j++)
+ for(k = 0; k < 3; k++)
+ if(i > 0)
+ t[j].v[k].p = qrotate(t[j].v[k].p, axes[i%3], PI/2);
+
+ m->prims = erealloc(m->prims, (m->nprims += 2)*sizeof(*m->prims));
+ m->prims[m->nprims-2] = t[0];
+ m->prims[m->nprims-1] = t[1];
+ }
+ return m;
+}
+
static void
updatestats(Camera *c, uvlong v)
{
@@ -91,6 +158,9 @@
void
shootcamera(Camera *c, Shadertab *s)
{
+ static Scene *skyboxscene;
+ static Shadertab skyboxshader = { nil, skyboxvs, skyboxfs };
+ Model *mdl;
Renderjob *job;
uvlong t0, t1;
@@ -97,7 +167,8 @@
job = emalloc(sizeof *job);
memset(job, 0, sizeof *job);
job->fb = c->vp->fbctl->getbb(c->vp->fbctl);
- job->scene = c->s;
+ job->camera = c;
+ job->scene = c->scene;
job->shaders = s;
job->donec = chancreate(sizeof(void*), 0);
@@ -105,6 +176,22 @@
t0 = nanosec();
sendp(c->rctl->c, job);
recvp(job->donec);
+ /*
+ * if the scene has a skybox, do another render pass,
+ * filling in the fragments left untouched by the z-buffer.
+ */
+ if(c->scene->skybox != nil){
+ if(skyboxscene == nil){
+ skyboxscene = newscene("skybox");
+ mdl = mkskyboxmodel();
+ skyboxscene->addent(skyboxscene, newentity(mdl));
+ }
+ skyboxscene->skybox = c->scene->skybox;
+ job->scene = skyboxscene;
+ job->shaders = &skyboxshader;
+ sendp(c->rctl->c, job);
+ recvp(job->donec);
+ }
t1 = nanosec();
c->vp->fbctl->swap(c->vp->fbctl);
--- a/clip.c
+++ b/clip.c
@@ -70,8 +70,7 @@
/*
* references:
- * - James F. Blinn, Martin E. Newell, “Clipping Using Homogeneous Coordinates”,
- * SIGGRAPH '78, pp. 245-251
+ * - “Clipping Using Homogeneous Coordinates”, James F. Blinn, Martin E. Newell, SIGGRAPH '78, pp. 245-251
* - https://cs418.cs.illinois.edu/website/text/clipping.html
* - https://github.com/aap/librw/blob/14dab85dcae6f3762fb2b1eda4d58d8e67541330/tools/playground/tl_tests.cpp#L522
*/
--- a/doc/libgraphics.ms
+++ b/doc/libgraphics.ms
@@ -152,3 +152,12 @@
.PE
.B "Figure 3" :
Raster task scheduling.
+.SH
+Frames of reference
+.PP
+Frames are right-handed throughout every stage.
+.PS
+.ps 7
+
+.ps 10
+.PE
--- a/graphics.h
+++ b/graphics.h
@@ -23,6 +23,7 @@
};
typedef struct Color Color;
+typedef struct Cubemap Cubemap;
typedef struct Vertexattr Vertexattr;
typedef struct Vertex Vertex;
typedef struct LightSource LightSource;
@@ -48,6 +49,12 @@
double r, g, b, a;
};
+struct Cubemap
+{
+ char *name;
+ Memimage *faces[6];
+};
+
/*
* a more general approach worth investigating.
* it could be made to handle types other than double.
@@ -124,7 +131,7 @@
{
Primitive *prims;
ulong nprims;
- Memimage *tex; /* texture map */
+ Memimage *tex; /* texture map (TODO get rid of it, use materials) */
Material *materials;
ulong nmaterials;
};
@@ -142,6 +149,7 @@
char *name;
Entity ents;
ulong nents;
+ Cubemap *skybox;
void (*addent)(Scene*, Entity*);
void (*delent)(Scene*, Entity*);
@@ -168,6 +176,7 @@
Framebuf *fb;
Memimage *frag;
Renderjob *job;
+ Camera *camera;
Entity *entity;
Primitive *eb, *ee;
@@ -199,6 +208,7 @@
Ref;
uvlong id;
Framebuf *fb;
+ Camera *camera;
Scene *scene;
Shadertab *shaders;
Channel *donec;
@@ -245,7 +255,7 @@
{
RFrame3; /* VCS */
Viewport *vp;
- Scene *s;
+ Scene *scene;
Renderer *rctl;
double fov; /* vertical FOV */
struct {
@@ -313,6 +323,9 @@
Color neartexsampler(Memimage*, Point2);
Color bilitexsampler(Memimage*, Point2);
Color texture(Memimage*, Point2, Color(*)(Memimage*, Point2));
+Cubemap *readcubemap(char*[6]);
+void freecubemap(Cubemap*);
+Color cubemaptexture(Cubemap*, Point3, Color(*)(Memimage*, Point2));
/* util */
double fmin(double, double);
--- a/render.c
+++ b/render.c
@@ -547,6 +547,7 @@
memset(params, 0, sizeof *params);
params->fb = job->fb;
params->job = job;
+ params->camera = job->camera;
params->entity = ent;
params->uni_time = time;
params->vshader = job->shaders->vshader;
--- a/scene.c
+++ b/scene.c
@@ -399,6 +399,7 @@
s->name = name == nil? nil: strdup(name);
s->ents.prev = s->ents.next = &s->ents;
s->nents = 0;
+ s->skybox = nil;
s->addent = scene_addent;
s->delent = scene_delent;
return s;
@@ -418,6 +419,7 @@
clearscene(Scene *s)
{
Entity *e, *ne;
+ int i;
for(e = s->ents.next; e != &s->ents; e = ne){
ne = e->next;
@@ -424,4 +426,6 @@
s->delent(s, e);
delentity(e);
}
+ if(s->skybox != nil)
+ freecubemap(s->skybox);
}
--- a/texture.c
+++ b/texture.c
@@ -8,6 +8,15 @@
#include "graphics.h"
#include "internal.h"
+enum {
+ CUBEMAP_FACE_LEFT, /* -x */
+ CUBEMAP_FACE_RIGHT, /* +x */
+ CUBEMAP_FACE_BOTTOM, /* -y */
+ CUBEMAP_FACE_TOP, /* +y */
+ CUBEMAP_FACE_FRONT, /* -z */
+ CUBEMAP_FACE_BACK, /* +z */
+};
+
/*
* uv-coords belong to the 1st quadrant (v grows bottom-up),
* hence the need to reverse the v coord.
@@ -107,4 +116,97 @@
texture(Memimage *i, Point2 uv, Color(*sampler)(Memimage*,Point2))
{
return sampler(i, uv);
+}
+
+/* cubemap sampling */
+
+Cubemap *
+readcubemap(char *paths[6])
+{
+ Cubemap *cm;
+ char **p;
+ int fd;
+
+ cm = emalloc(sizeof *cm);
+ memset(cm, 0, sizeof *cm);
+
+ for(p = paths; p < paths+6; p++){
+ assert(*p != nil);
+ fd = open(*p, OREAD);
+ if(fd < 0)
+ sysfatal("open: %r");
+ cm->faces[p-paths] = readmemimage(fd);
+ if(cm->faces[p-paths] == nil)
+ sysfatal("readmemimage: %r");
+ close(fd);
+ }
+ return cm;
+}
+
+void
+freecubemap(Cubemap *cm)
+{
+ int i;
+
+ for(i = 0; i < 6; i++)
+ freememimage(cm->faces[i]);
+ free(cm->name);
+ free(cm);
+}
+
+/*
+ * references:
+ * - https://github.com/zauonlok/renderer/blob/9ed5082f0eda453f0b2a0d5ec37cf5a60f0207f6/renderer/core/texture.c#L206
+ * - “Cubemap Texture Selection”, OpenGL ES 2.0 § 3.7.5, November 2010
+ */
+Color
+cubemaptexture(Cubemap *cm, Point3 d, Color(*sampler)(Memimage*,Point2))
+{
+ Point2 uv;
+ double ax, ay, az, ma, sc, tc;
+ int face;
+
+ ax = fabs(d.x);
+ ay = fabs(d.y);
+ az = fabs(d.z);
+
+ if(ax > ay && ax > az){
+ ma = ax;
+ if(d.x > 0){
+ face = CUBEMAP_FACE_RIGHT;
+ sc = -d.z;
+ tc = -d.y;
+ }else{
+ face = CUBEMAP_FACE_LEFT;
+ sc = d.z;
+ tc = -d.y;
+ }
+ }else if(ay > az){
+ ma = ay;
+ if(d.y > 0){
+ face = CUBEMAP_FACE_TOP;
+ sc = d.x;
+ tc = d.z;
+ }else{
+ face = CUBEMAP_FACE_BOTTOM;
+ sc = d.x;
+ tc = -d.z;
+ }
+ }else{
+ ma = az;
+ if(d.z > 0){
+ face = CUBEMAP_FACE_BACK;
+ sc = d.x;
+ tc = -d.y;
+ }else{
+ face = CUBEMAP_FACE_FRONT;
+ sc = -d.x;
+ tc = -d.y;
+ }
+ }
+
+ uv.x = (sc/ma + 1)/2;
+ uv.y = 1 - (tc/ma + 1)/2;
+ uv.w = 1;
+ return sampler(cm->faces[face], uv);
}