├── demo.png ├── readme.md └── simple_render.py /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldfog/simpleRender/75cc4afe258924a804758ece4daae86c02a3526e/demo.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Simple Render 2 | 3 | [![996.ICU](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu) 4 | 5 | ## Introduction 6 | 7 | This is a toy project for learning the theory of 3D render. I'm tring to 8 | add every necessary step into only one file. For simplifying and performance, all math operation are implemented by numpy and the window management are implemented by pyglet. 9 | 10 | ## Features 11 | 12 | 1. World, view, projection transform 13 | 2. Simple texture mapping 14 | 3. Backface culling 15 | 4. Z-buffer 16 | 4. Diffuse direction lighting 17 | 18 | ## Screen shoot 19 | ![demo.png](demo.png) 20 | 21 | ## Dependencies 22 | 23 | + numpy for fast math computation 24 | + pyglet for window management 25 | 26 | ## Note 27 | 28 | Because I have chosen Python as the main programming language. so there is a huge 29 | performance issue. 30 | -------------------------------------------------------------------------------- /simple_render.py: -------------------------------------------------------------------------------- 1 | import pyglet 2 | from pyglet import image 3 | import numpy as np 4 | import numba 5 | 6 | WINDOW_W = 800 7 | WINDOW_H = 600 8 | 9 | 10 | def vector(x): 11 | return np.array(x, dtype='float32') 12 | 13 | 14 | class Vertex: 15 | def __init__(self, pos=None, tex_coor=None, norm=None, rhw=None): 16 | self.pos = pos if pos is not None else np.zeros(4) 17 | self.tex_coor = tex_coor if tex_coor is not None else np.zeros(2) 18 | self.norm = norm if norm is not None else np.zeros(3) 19 | self.rhw = rhw if rhw else 0 20 | 21 | def copy(self): 22 | return Vertex(pos=self.pos.copy(), 23 | tex_coor=self.tex_coor.copy(), 24 | norm=self.norm.copy(), 25 | rhw=self.rhw) 26 | 27 | 28 | class Device: 29 | RENDER_STATE_WIREFRAME = 1 30 | RENDER_STATE_TEXTURE = 2 31 | 32 | def __init__(self, width, height): 33 | self.lights = dict(dir=[]) 34 | self._texture = None 35 | 36 | self.state = Device.RENDER_STATE_TEXTURE 37 | self._frame_buffer = np.zeros((height, width, 4), dtype='uint8') 38 | self._z_buffer = np.zeros((height, width), dtype='float') 39 | 40 | self.width = width 41 | self.height = height 42 | 43 | self._world_trans = np.eye(4) 44 | self._view_trans = np.eye(4) 45 | self._projection_trans = np.eye(4) 46 | self._trans = np.eye(4) 47 | self._norm_trans = np.zeros(3) 48 | self.set_perspective(np.pi * 0.5, float(self.width) / self.height, 1.0, 500) 49 | 50 | def set_texture(self, texture): 51 | self._texture = texture 52 | 53 | @staticmethod 54 | def make_transform(scale=None, rotate=None, translate=None): 55 | """ 56 | scale: (scalex, scaley, scalez) 57 | rotate: (x, y, z, theta). indicate the axis rotated by, 58 | theta is the degree of rotation 59 | translate: (translatex, translatey, translatez) 60 | """ 61 | res = np.eye(4) 62 | 63 | if scale: 64 | tmp_matrix = np.eye(4) 65 | tmp_matrix[0, 0] = scale[0] 66 | tmp_matrix[1, 1] = scale[1] 67 | tmp_matrix[2, 2] = scale[2] 68 | res = np.dot(res, tmp_matrix) 69 | 70 | if rotate: 71 | tmp_matrix = np.eye(4) 72 | x, y, z = Device._normalize(vector(rotate[:3])) 73 | theta = rotate[3] 74 | 75 | cos = np.cos(theta) 76 | one_sub_cos = 1 - cos 77 | sin = np.sin(theta) 78 | tmp_matrix[0, 0] = cos + one_sub_cos * x ** 2 79 | tmp_matrix[0, 1] = one_sub_cos * x * y - sin * z 80 | tmp_matrix[0, 2] = one_sub_cos * x * z + sin * y 81 | tmp_matrix[1, 0] = one_sub_cos * y * x + sin * z 82 | tmp_matrix[1, 1] = cos + one_sub_cos * y ** 2 83 | tmp_matrix[1, 2] = one_sub_cos * y * z - sin * x 84 | tmp_matrix[2, 0] = one_sub_cos * z * x - sin * y 85 | tmp_matrix[2, 1] = one_sub_cos * z * y + sin * x 86 | tmp_matrix[2, 2] = cos + one_sub_cos * z ** 2 87 | res = np.dot(res, tmp_matrix) 88 | 89 | if translate: 90 | tmp_matrix = np.eye(4) 91 | tmp_matrix[0, 3] = translate[0] 92 | tmp_matrix[1, 3] = translate[1] 93 | tmp_matrix[2, 3] = translate[2] 94 | res = np.dot(res, tmp_matrix) 95 | 96 | return res 97 | 98 | @staticmethod 99 | def _normalize(vec): 100 | n = np.linalg.norm(vec) 101 | return vec / n if n != 0 else vec 102 | 103 | def set_camera(self, eye, at, up): 104 | 105 | z = Device._normalize((at - eye)[:3]) 106 | x = Device._normalize(np.cross(up[:3], z)) 107 | y = Device._normalize(np.cross(z, x)) 108 | 109 | tmp_matrix = np.eye(4) 110 | tmp_matrix[0, :3] = x 111 | tmp_matrix[1, :3] = y 112 | tmp_matrix[2, :3] = z 113 | tmp_matrix[3, 0] = -np.dot(x, eye[:3]) 114 | tmp_matrix[3, 1] = -np.dot(y, eye[:3]) 115 | tmp_matrix[3, 2] = -np.dot(z, eye[:3]) 116 | 117 | self._view_trans = tmp_matrix 118 | self.update_transform() 119 | 120 | def set_perspective(self, fov, aspect, zn, zf): 121 | tmp_matrix = np.zeros((4, 4)) 122 | cot = 1. / np.tan(fov / 2.) 123 | tmp_matrix[0, 0] = cot / aspect 124 | tmp_matrix[1, 1] = cot 125 | tmp_matrix[2, 2] = zf / (zf - zn) 126 | tmp_matrix[2, 3] = 1 127 | tmp_matrix[3, 2] = zf * zn / (zn - zf) 128 | 129 | self._projection_trans = tmp_matrix 130 | self.update_transform() 131 | 132 | def set_world_trans(self, transform): 133 | self._world_trans = transform 134 | self.update_transform() 135 | 136 | def update_transform(self): 137 | tmp_trans = np.dot(self._world_trans, self._view_trans) 138 | self._trans = np.dot(tmp_trans, self._projection_trans) 139 | self._norm_trans = np.linalg.inv(self._world_trans[:3, :3]).T 140 | 141 | def transform(self, v): 142 | # transform 143 | transformed_v = np.dot(v, self._trans) 144 | w = transformed_v[3] 145 | 146 | # homogenize 147 | transformed_v /= transformed_v[3] 148 | transformed_v[0] = (transformed_v[0] + 1) * self.width * 0.5 149 | transformed_v[1] = (1 - transformed_v[1]) * self.height * 0.5 150 | transformed_v[3] = w 151 | 152 | return transformed_v 153 | 154 | def transform_norm(self, v): 155 | return np.dot(v, self._norm_trans) 156 | 157 | def clear_frame_buffer(self, color=vector([0, 0, 0, 255])): 158 | self._frame_buffer[..., 0] = color[0] 159 | self._frame_buffer[..., 1] = color[1] 160 | self._frame_buffer[..., 2] = color[2] 161 | self._frame_buffer[..., 3] = color[3] 162 | 163 | def clear_z_buffer(self): 164 | self._z_buffer[...] = 0 165 | 166 | def get_frame_buffer_str(self): 167 | return self._frame_buffer.tostring() 168 | 169 | def draw_line(self, x0, y0, x1, y1, color=vector([255, 255, 255, 255])): 170 | """ 171 | Bresenham line drawing 172 | x0, y0, x1, y1 must be integer 173 | """ 174 | steep = np.abs(y1 - y0) > np.abs(x1 - x0) 175 | if steep: 176 | x0, y0 = y0, x0 177 | x1, y1 = y1, x1 178 | if x0 > x1: 179 | x0, x1 = x1, x0 180 | y0, y1 = y1, y0 181 | delta_x = x1 - x0 182 | delta_y = np.abs(y1 - y0) 183 | error = delta_x / 2 184 | 185 | y = y0 186 | if y0 < y1: 187 | y_step = 1 188 | else: 189 | y_step = -1 190 | 191 | for x in xrange(x0, x1): 192 | if steep: 193 | self._frame_buffer[y, x, :] = color 194 | else: 195 | self._frame_buffer[x, y, :] = color 196 | error = error - delta_y 197 | if error < 0: 198 | y += y_step 199 | error += delta_x 200 | 201 | @staticmethod 202 | def _interp(x1, x2, t): 203 | return (x2 - x1) * t + x1 204 | 205 | @staticmethod 206 | def _vertex_init_rhw(v): 207 | rhw = 1.0 / v.pos[3] 208 | v.rhw = rhw 209 | v.tex_coor *= rhw 210 | 211 | @staticmethod 212 | def _vertex_interp(x1, x2, t): 213 | res = Vertex() 214 | res.pos = Device._interp(x1.pos, x2.pos, t) 215 | res.pos[3] = 1 216 | res.tex_coor = Device._interp(x1.tex_coor, x2.tex_coor, t) 217 | res.norm = Device._interp(x1.norm, x2.norm, t) 218 | res.rhw = Device._interp(x1.rhw, x2.rhw, t) 219 | return res 220 | 221 | @staticmethod 222 | def _trapezoid_triangle(v1, v2, v3): 223 | """ 224 | v1, v2, v3: Vertex obj. 225 | ret list of trapezoid 0~2 226 | """ 227 | # sort by pos[1] 228 | if v1.pos[1] < v2.pos[1]: 229 | v1, v2 = v2, v1 230 | if v1.pos[1] < v3.pos[1]: 231 | v1, v3 = v3, v1 232 | if v2.pos[1] < v3.pos[1]: 233 | v2, v3 = v3, v2 234 | 235 | # collinear 236 | if v1.pos[1] == v2.pos[1] and v2.pos[1] == v3.pos[1]: 237 | return [] 238 | if v1.pos[0] == v2.pos[0] and v2.pos[0] == v3.pos[0]: 239 | return [] 240 | 241 | # triangle down 242 | if v1.pos[1] - v2.pos[1] < 0.5: 243 | if v1.pos[0] > v2.pos[0]: 244 | v1, v2 = v2, v1 245 | return ((v1, v3), (v2, v3)), 246 | # triangle up 247 | if v2.pos[1] - v3.pos[1] < 0.5: 248 | if v2.pos[0] > v3.pos[0]: 249 | v2, v3 = v3, v2 250 | return ((v1, v2), (v1, v3)), 251 | 252 | t = (v2.pos[1] - v3.pos[1]) / (v1.pos[1] - v3.pos[1]) 253 | 254 | middle = Device._vertex_interp(v3, v1, t) 255 | 256 | if middle.pos[0] < v2.pos[0]: # middle on the left 257 | return (((v1, middle), (v1, v2)), # top tri - left edge, right edge 258 | ((middle, v3), (v2, v3))) # bottom tri - left edge, right edge 259 | else: 260 | return (((v1, v2), (v1, middle)), # top tri - left edge, right edge 261 | ((v2, v3), (middle, v3))) # bottom tri - left edge, right edge 262 | 263 | @staticmethod 264 | def _texture_readline(tex, start, end, sample_nums, rhw): 265 | """ 266 | Get a line of pixel on tex from start to end 267 | :param tex: the texture 268 | :param start: start vertex location 269 | :param end: end vertex location 270 | :param sample_nums: how many value will be sampled in this line 271 | :param rhw: rhw 272 | :return: the pixel value in an np.ndarray 273 | """ 274 | h, w, c = tex.shape 275 | h -= 1 276 | w -= 1 277 | 278 | start_tex = start.tex_coor[0] * w, start.tex_coor[1] * h 279 | end_tex = end.tex_coor[0] * w, end.tex_coor[1] * h 280 | 281 | if sample_nums != 0: 282 | x = (np.linspace(start_tex[0], end_tex[0], sample_nums) / rhw + 0.5).astype(int) 283 | y = (np.linspace(start_tex[1], end_tex[1], sample_nums) / rhw + 0.5).astype(int) 284 | 285 | return tex[y, x] 286 | else: 287 | return tex[start_tex[1], start_tex[0]] 288 | 289 | def _draw_scan_line(self, trapezoid): 290 | """ 291 | Draw scan line in trapezoid 292 | :param trapezoid: a trapezoid, a tuple which returned by _trapezoid_triangle 293 | :return: void 294 | """ 295 | left_edge = trapezoid[0] 296 | right_edge = trapezoid[1] 297 | 298 | bottom = int(left_edge[1].pos[1] + 0.5) 299 | top = int(left_edge[0].pos[1] + 0.5) 300 | 301 | for cur_y in xrange(bottom, top): 302 | 303 | t = float(cur_y - bottom) / (top - bottom) 304 | 305 | start = Device._vertex_interp(left_edge[1], left_edge[0], t) 306 | end = Device._vertex_interp(right_edge[1], right_edge[0], t) 307 | 308 | if cur_y < 0: continue 309 | if cur_y >= self.height: break 310 | 311 | l = int(start.pos[0] + 0.5) 312 | r = int(end.pos[0] + 0.5) 313 | sample_nums = r - l + 1 314 | 315 | # clip 316 | if l < 0 < r: 317 | l = 0 318 | elif l <= r < 0: 319 | continue 320 | 321 | if r >= self.width > l: 322 | r = self.width - 1 323 | elif r >= l > self.width: 324 | continue 325 | 326 | z_buffer = self._z_buffer[cur_y, l:r + 1] 327 | frame_buffer = self._frame_buffer[cur_y, l:r + 1] 328 | rhw = np.linspace(start.rhw, end.rhw, sample_nums) 329 | norm = np.vstack((np.linspace(start.norm[0], end.norm[0], sample_nums), 330 | np.linspace(start.norm[1], end.norm[1], sample_nums), 331 | np.linspace(start.norm[2], end.norm[2], sample_nums))).T 332 | 333 | tex_line = Device._texture_readline(self._texture, start, end, sample_nums, rhw) 334 | 335 | # clip 336 | clip_mask = np.arange(l, r + 1) 337 | clip_mask = (clip_mask >= 0) & (clip_mask < self.width) 338 | rhw = rhw[clip_mask] 339 | norm = norm[clip_mask, :] 340 | tex_line = tex_line[clip_mask, :] 341 | 342 | for light in self.lights['dir']: 343 | light_dir = Device._normalize(light['dir']) 344 | diffuse = np.maximum(np.atleast_2d(np.dot(norm, -light_dir)).T, 0) * light['diffuse'] 345 | ambient = light['ambient'] 346 | tex_line[:, :3] = np.minimum((diffuse + ambient) * tex_line[:, :3], 255) 347 | 348 | mask = z_buffer <= rhw 349 | frame_buffer[mask] = tex_line[mask] 350 | z_buffer[mask] = rhw[mask] 351 | 352 | @staticmethod 353 | def _is_backface(v1, v2, v3): 354 | """ 355 | Determine if a triangle is clockwise or not. The algorithm is compute the signed area of triangle 356 | """ 357 | m = np.vstack((v1.pos, v2.pos, v3.pos, v1.pos)) 358 | return np.linalg.det(m[:2, :2]) + np.linalg.det(m[1:3, :2]) + np.linalg.det(m[2:4, :2]) >= 0 359 | 360 | def add_dir_light(self, direction, ambient, diffuse): 361 | self.lights['dir'].append(dict(dir=direction, ambient=ambient, diffuse=diffuse)) 362 | 363 | def draw_primitive(self, v1, v2, v3): 364 | 365 | p1 = v1.copy() 366 | p2 = v2.copy() 367 | p3 = v3.copy() 368 | 369 | p1.pos = device.transform(v1.pos) 370 | p2.pos = device.transform(v2.pos) 371 | p3.pos = device.transform(v3.pos) 372 | 373 | p1.norm = device.transform_norm(v1.norm) 374 | p2.norm = device.transform_norm(v2.norm) 375 | p3.norm = device.transform_norm(v3.norm) 376 | 377 | # backface culling 378 | if self._is_backface(p1, p2, p3): 379 | return 380 | 381 | if self.state == Device.RENDER_STATE_WIREFRAME: 382 | self.draw_line(p1.pos[1].astype(int), p1.pos[0].astype(int), 383 | p2.pos[1].astype(int), p2.pos[0].astype(int)) 384 | self.draw_line(p2.pos[1].astype(int), p2.pos[0].astype(int), 385 | p3.pos[1].astype(int), p3.pos[0].astype(int)) 386 | self.draw_line(p3.pos[1].astype(int), p3.pos[0].astype(int), 387 | p1.pos[1].astype(int), p1.pos[0].astype(int)) 388 | elif self.state == Device.RENDER_STATE_TEXTURE: 389 | self._vertex_init_rhw(p1) 390 | self._vertex_init_rhw(p2) 391 | self._vertex_init_rhw(p3) 392 | 393 | trapezoids = self._trapezoid_triangle(p1, p2, p3) 394 | for trap in trapezoids: 395 | self._draw_scan_line(trap) 396 | else: 397 | raise Exception("Invalid Render state %s" % self.state) 398 | 399 | def draw_quad(self, v1, v2, v3, v4): 400 | self.draw_primitive(v1, v2, v3) 401 | self.draw_primitive(v3, v4, v1) 402 | 403 | def draw_mesh(self, vertices, indices): 404 | for i in indices: 405 | if len(i) == 3: 406 | self.draw_primitive(vertices[i[0]], vertices[i[1]], vertices[i[2]]) 407 | else: 408 | self.draw_quad(vertices[i[0]], vertices[i[1]], vertices[i[2]], vertices[i[3]]) 409 | 410 | 411 | if __name__ == '__main__': 412 | game_window = pyglet.window.Window(WINDOW_W, WINDOW_H) 413 | device = Device(WINDOW_W, WINDOW_H) 414 | 415 | # define the mesh of box 416 | 417 | mesh = [ 418 | Vertex(pos=vector([-0.5, -0.5, -0.5, 1]), norm=vector([0.0, 0.0, -1.0]), tex_coor=vector([0.0, 0.0]), rhw=1), 419 | Vertex(pos=vector([0.5, -0.5, -0.5, 1]), norm=vector([0.0, 0.0, -1.0]), tex_coor=vector([1.0, 0.0]), rhw=1), 420 | Vertex(pos=vector([0.5, 0.5, -0.5, 1]), norm=vector([0.0, 0.0, -1.0]), tex_coor=vector([1.0, 1.0]), rhw=1), 421 | Vertex(pos=vector([0.5, 0.5, -0.5, 1]), norm=vector([0.0, 0.0, -1.0]), tex_coor=vector([1.0, 1.0]), rhw=1), 422 | Vertex(pos=vector([-0.5, 0.5, -0.5, 1]), norm=vector([0.0, 0.0, -1.0]), tex_coor=vector([0.0, 1.0]), rhw=1), 423 | Vertex(pos=vector([-0.5, -0.5, -0.5, 1]), norm=vector([0.0, 0.0, -1.0]), tex_coor=vector([0.0, 0.0]), rhw=1), 424 | 425 | Vertex(pos=vector([-0.5, -0.5, 0.5, 1]), norm=vector([0.0, 0.0, 1.0]), tex_coor=vector([0.0, 0.0]), rhw=1), 426 | Vertex(pos=vector([0.5, -0.5, 0.5, 1]), norm=vector([0.0, 0.0, 1.0]), tex_coor=vector([1.0, 0.0]), rhw=1), 427 | Vertex(pos=vector([0.5, 0.5, 0.5, 1]), norm=vector([0.0, 0.0, 1.0]), tex_coor=vector([1.0, 1.0]), rhw=1), 428 | Vertex(pos=vector([0.5, 0.5, 0.5, 1]), norm=vector([0.0, 0.0, 1.0]), tex_coor=vector([1.0, 1.0]), rhw=1), 429 | Vertex(pos=vector([-0.5, 0.5, 0.5, 1]), norm=vector([0.0, 0.0, 1.0]), tex_coor=vector([0.0, 1.0]), rhw=1), 430 | Vertex(pos=vector([-0.5, -0.5, 0.5, 1]), norm=vector([0.0, 0.0, 1.0]), tex_coor=vector([0.0, 0.0]), rhw=1), 431 | 432 | Vertex(pos=vector([-0.5, 0.5, 0.5, 1]), norm=vector([-1.0, 0.0, 0.0]), tex_coor=vector([1.0, 0.0]), rhw=1), 433 | Vertex(pos=vector([-0.5, 0.5, -0.5, 1]), norm=vector([-1.0, 0.0, 0.0]), tex_coor=vector([1.0, 1.0]), rhw=1), 434 | Vertex(pos=vector([-0.5, -0.5, -0.5, 1]), norm=vector([-1.0, 0.0, 0.0]), tex_coor=vector([0.0, 1.0]), rhw=1), 435 | Vertex(pos=vector([-0.5, -0.5, -0.5, 1]), norm=vector([-1.0, 0.0, 0.0]), tex_coor=vector([0.0, 1.0]), rhw=1), 436 | Vertex(pos=vector([-0.5, -0.5, 0.5, 1]), norm=vector([-1.0, 0.0, 0.0]), tex_coor=vector([0.0, 0.0]), rhw=1), 437 | Vertex(pos=vector([-0.5, 0.5, 0.5, 1]), norm=vector([-1.0, 0.0, 0.0]), tex_coor=vector([1.0, 0.0]), rhw=1), 438 | 439 | Vertex(pos=vector([0.5, 0.5, 0.5, 1]), norm=vector([1.0, 0.0, 0.0]), tex_coor=vector([1.0, 0.0]), rhw=1), 440 | Vertex(pos=vector([0.5, 0.5, -0.5, 1]), norm=vector([1.0, 0.0, 0.0]), tex_coor=vector([1.0, 1.0]), rhw=1), 441 | Vertex(pos=vector([0.5, -0.5, -0.5, 1]), norm=vector([1.0, 0.0, 0.0]), tex_coor=vector([0.0, 1.0]), rhw=1), 442 | Vertex(pos=vector([0.5, -0.5, -0.5, 1]), norm=vector([1.0, 0.0, 0.0]), tex_coor=vector([0.0, 1.0]), rhw=1), 443 | Vertex(pos=vector([0.5, -0.5, 0.5, 1]), norm=vector([1.0, 0.0, 0.0]), tex_coor=vector([0.0, 0.0]), rhw=1), 444 | Vertex(pos=vector([0.5, 0.5, 0.5, 1]), norm=vector([1.0, 0.0, 0.0]), tex_coor=vector([1.0, 0.0]), rhw=1), 445 | 446 | Vertex(pos=vector([-0.5, -0.5, -0.5, 1]), norm=vector([0.0, -1.0, 0.0]), tex_coor=vector([0.0, 1.0]), rhw=1), 447 | Vertex(pos=vector([0.5, -0.5, -0.5, 1]), norm=vector([0.0, -1.0, 0.0]), tex_coor=vector([1.0, 1.0]), rhw=1), 448 | Vertex(pos=vector([0.5, -0.5, 0.5, 1]), norm=vector([0.0, -1.0, 0.0]), tex_coor=vector([1.0, 0.0]), rhw=1), 449 | Vertex(pos=vector([0.5, -0.5, 0.5, 1]), norm=vector([0.0, -1.0, 0.0]), tex_coor=vector([1.0, 0.0]), rhw=1), 450 | Vertex(pos=vector([-0.5, -0.5, 0.5, 1]), norm=vector([0.0, -1.0, 0.0]), tex_coor=vector([0.0, 0.0]), rhw=1), 451 | Vertex(pos=vector([-0.5, -0.5, -0.5, 1]), norm=vector([0.0, -1.0, 0.0]), tex_coor=vector([0.0, 1.0]), rhw=1), 452 | 453 | Vertex(pos=vector([-0.5, 0.5, -0.5, 1]), norm=vector([0.0, 1.0, 0.0]), tex_coor=vector([0.0, 1.0]), rhw=1), 454 | Vertex(pos=vector([0.5, 0.5, -0.5, 1]), norm=vector([0.0, 1.0, 0.0]), tex_coor=vector([1.0, 1.0]), rhw=1), 455 | Vertex(pos=vector([0.5, 0.5, 0.5, 1]), norm=vector([0.0, 1.0, 0.0]), tex_coor=vector([1.0, 0.0]), rhw=1), 456 | Vertex(pos=vector([0.5, 0.5, 0.5, 1]), norm=vector([0.0, 1.0, 0.0]), tex_coor=vector([1.0, 0.0]), rhw=1), 457 | Vertex(pos=vector([-0.5, 0.5, 0.5, 1]), norm=vector([0.0, 1.0, 0.0]), tex_coor=vector([0.0, 0.0]), rhw=1), 458 | Vertex(pos=vector([-0.5, 0.5, -0.5, 1]), norm=vector([0.0, 1.0, 0.0]), tex_coor=vector([0.0, 1.]), rhw=1)] 459 | 460 | indices = [ 461 | [0, 1, 2], 462 | [3, 4, 5], 463 | [8, 7, 6], 464 | [11, 10, 9], 465 | [14, 13, 12], 466 | [17, 16, 15], 467 | [18, 19, 20], 468 | [21, 22, 23], 469 | [26, 25, 24], 470 | [29, 28, 27], 471 | [30, 31, 32], 472 | [33, 34, 35] 473 | ] 474 | 475 | device.set_camera(eye=vector([0, 0, -3, 1]), 476 | at=vector([0.23, 0, 0, 1]), 477 | up=vector([0, 1, 0, 1])) 478 | 479 | # the rotate degree 480 | rotate_degree = 1 481 | 482 | frame = image.create(device.width, device.height) 483 | fps_display = pyglet.clock.ClockDisplay() 484 | 485 | # produce texture, chess board 486 | texture = np.ones((256, 256, 4), dtype='uint8') * 255 487 | grid_size = 32 488 | for i in range(grid_size): 489 | for j in [j * 2 for j in range(grid_size / 2)]: 490 | texture[i * grid_size:i * grid_size + grid_size, 491 | (j + (i % 2)) * grid_size:(j + (i % 2)) * grid_size + grid_size, :] = vector([1, 128, 255, 255]) 492 | 493 | device.set_texture(texture) 494 | device.add_dir_light(vector([0, 0, 3]), 495 | vector([0.05, 0.05, 0.05]), 496 | vector([0.7, 0.7, 0.7])) 497 | 498 | 499 | @game_window.event 500 | def on_draw(): 501 | game_window.clear() 502 | device.clear_frame_buffer(vector([128, 33, 78, 255])) 503 | device.clear_z_buffer() 504 | 505 | global rotate_degree 506 | trans = device.make_transform(rotate=(1, 1, 1, rotate_degree / np.pi * 180)) 507 | device.set_world_trans(trans) 508 | rotate_degree += 0.0005 509 | rotate_degree %= 180 510 | 511 | # draw box 512 | device.draw_mesh(mesh, indices) 513 | 514 | frame.set_data( 515 | 'RGBA', device.width * 4, device.get_frame_buffer_str()) 516 | frame.blit(0, 0) 517 | # fps_display.draw() 518 | 519 | 520 | pyglet.clock.schedule_interval(lambda dt: None, 1 / 60.0) 521 | 522 | pyglet.app.run() 523 | --------------------------------------------------------------------------------