first render
[retroray] / src / rend.c
1 /*
2 RetroRay - integrated standalone vintage modeller/renderer
3 Copyright (C) 2023  John Tsiombikas <nuclear@mutantstargoat.com>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 #include "rend.h"
19 #include "app.h"
20 #include "geom.h"
21 #include "util.h"
22 #include "gfxutil.h"
23 #include "scene.h"
24
25 static int calc_light(const struct rayhit *hit, const struct light *lt,
26                 const cgm_vec3 *vdir, cgm_vec3 *dcol, cgm_vec3 *scol);
27
28 struct img_pixmap renderbuf;
29
30 int max_ray_depth;
31 cgm_vec3 ambient;
32
33 static int rx, ry, rwidth, rheight;
34 static int roffs;
35 static int xstep, ystep;
36 static int pan_x, pan_y;
37
38 static struct light def_light = {0, {0, 0, 0}, {1, 1, 1}};
39
40
41 int rend_init(void)
42 {
43         img_init(&renderbuf);
44
45         cgm_vcons(&ambient, 0.05, 0.05, 0.05);
46
47         rx = ry = rwidth = rheight = roffs = 0;
48         pan_x = pan_y = 0;
49
50         max_ray_depth = 6;
51         return 0;
52 }
53
54 void rend_destroy(void)
55 {
56         img_destroy(&renderbuf);
57 }
58
59 void rend_size(int xsz, int ysz)
60 {
61         if(xsz != renderbuf.width || ysz != renderbuf.height) {
62                 img_set_pixels(&renderbuf, xsz, ysz, IMG_FMT_RGBA32, 0);
63         }
64 }
65
66 void rend_pan(int xoffs, int yoffs)
67 {
68         pan_x = xoffs;
69         pan_y = yoffs;
70 }
71
72 void rend_begin(int x, int y, int w, int h)
73 {
74         int i;
75         uint32_t *ptr;
76
77         if(w == 0 || h == 0) {
78                 rx = ry = 0;
79                 rwidth = renderbuf.width;
80                 rheight = renderbuf.height;
81         } else {
82                 rx = x;
83                 ry = y;
84                 rwidth = w;
85                 rheight = h;
86         }
87         roffs = ry * renderbuf.width + rx;
88
89         xstep = rwidth;
90         ystep = rheight;
91
92         ptr = (uint32_t*)renderbuf.pixels + roffs;
93         for(i=0; i<rheight; i++) {
94                 memset(ptr, 0, rwidth * sizeof *ptr);
95                 ptr += renderbuf.width;
96         }
97 }
98
99 static void fillrect(uint32_t *fb, int x, int y, int w, int h, uint32_t c)
100 {
101         int i, j;
102
103         fb += y * renderbuf.width + x;
104         for(i=0; i<h; i++) {
105                 for(j=0; j<w; j++) {
106                         fb[j] = c;
107                 }
108                 fb += renderbuf.width;
109         }
110 }
111
112 int render(uint32_t *fb)
113 {
114         int i, j, w, h, offs, r, g, b;
115         uint32_t *dest, pcol;
116         cgm_vec3 color;
117         cgm_ray ray;
118
119         dest = (uint32_t*)renderbuf.pixels + roffs;
120         if(fb) {
121                 fb += roffs;
122         }
123
124         if(xstep < 1) xstep = 1;
125         if(ystep < 1) ystep = 1;
126
127         if(scn_num_lights(scn) == 0) {
128                 primray(&ray, renderbuf.width / 2, renderbuf.height / 2);
129                 def_light.pos = ray.origin;
130         }
131
132         for(i=0; i<rheight; i+=ystep) {
133                 h = ystep;
134                 if(i + h > rheight) h = rheight - i;
135
136                 for(j=0; j<rwidth; j+=xstep) {
137                         primray(&ray, rx + j + pan_x, ry + i + pan_y);
138                         ray_trace(&ray, max_ray_depth, &color);
139
140                         if(color.x > 1.0f) color.x = 1.0f;
141                         if(color.y > 1.0f) color.y = 1.0f;
142                         if(color.z > 1.0f) color.z = 1.0f;
143                         r = cround64(color.x * 255.0f);
144                         g = cround64(color.y * 255.0f);
145                         b = cround64(color.z * 255.0f);
146                         pcol = PACK_RGB32(r, g, b);
147
148                         offs = i * renderbuf.width + j;
149                         dest[offs] = pcol;
150
151                         if(fb) {
152                                 w = xstep;
153                                 if(j + w > rwidth) w = rwidth - j;
154
155                                 fillrect(fb, j, i, w, h, pcol);
156                         }
157                 }
158         }
159
160         xstep >>= 1;
161         ystep >>= 1;
162
163         if((xstep | ystep) >= 1) {
164                 return 1;
165         }
166         return 0;
167 }
168
169 int ray_trace(const cgm_ray *ray, int maxiter, cgm_vec3 *res)
170 {
171         struct rayhit hit;
172
173         if(!scn_intersect(scn, ray, &hit)) {
174                 *res = bgcolor(ray);
175                 return 0;
176         }
177
178         *res = shade(ray, &hit, maxiter);
179         return 1;
180 }
181
182 cgm_vec3 bgcolor(const cgm_ray *ray)
183 {
184         return cgm_vvec(0, 0, 0);
185 }
186
187 cgm_vec3 shade(const cgm_ray *ray, const struct rayhit *hit, int maxiter)
188 {
189         int i, num_lights;
190         cgm_vec3 color, dcol, scol, texel, vdir;
191         struct material *mtl;
192         struct light *lt;
193
194         dcol = ambient;
195         cgm_vcons(&scol, 0, 0, 0);
196
197         mtl = hit->obj->mtl;
198
199         vdir = ray->dir;
200         cgm_vneg(&vdir);
201         cgm_vnormalize(&vdir);
202
203         if(!(num_lights = scn_num_lights(scn))) {
204                 calc_light(hit, &def_light, &vdir, &dcol, &scol);
205         }
206         for(i=0; i<num_lights; i++) {
207                 lt = scn->lights[i];
208                 calc_light(hit, lt, &vdir, &dcol, &scol);
209         }
210
211         if(mtl->texmap) {
212                 texel = mtl->texmap->lookup(mtl->texmap, hit);
213                 cgm_vmul(&dcol, &texel);
214         }
215
216         color = dcol;
217         cgm_vadd(&color, &scol);
218         return color;
219 }
220
221 static int calc_light(const struct rayhit *hit, const struct light *lt,
222                 const cgm_vec3 *vdir, cgm_vec3 *dcol, cgm_vec3 *scol)
223 {
224         float ndotl, ndoth, spec;
225         cgm_vec3 ldir, hdir;
226         cgm_ray ray;
227         struct material *mtl = hit->obj->mtl;
228
229         ldir = lt->pos;
230         cgm_vsub(&ldir, &hit->pos);
231
232         ray.origin = hit->pos;
233         ray.dir = ldir;
234
235         if(scn_intersect(scn, &ray, 0)) {
236                 return 0;       /* in shadow */
237         }
238
239         cgm_vnormalize(&ldir);
240
241         hdir = *vdir;
242         cgm_vadd(&hdir, &ldir);
243         cgm_vnormalize(&hdir);
244
245         ndotl = cgm_vdot(&hit->norm, &ldir);
246         if(ndotl < 0.0f) ndotl = 0.0f;
247         ndoth = cgm_vdot(&hit->norm, &hdir);
248         if(ndoth < 0.0f) ndoth = 0.0f;
249
250         spec = pow(ndoth, mtl->shin);
251
252         dcol->x += mtl->kd.x * ndotl * lt->color.x;
253         dcol->y += mtl->kd.y * ndotl * lt->color.y;
254         dcol->z += mtl->kd.z * ndotl * lt->color.z;
255
256         scol->x += mtl->ks.x * spec * lt->color.x;
257         scol->y += mtl->ks.y * spec * lt->color.y;
258         scol->z += mtl->ks.z * spec * lt->color.z;
259
260         return 1;
261 }