From c4270d4a2e44558a6718ee2bb2cc5b8f0dc05809 Mon Sep 17 00:00:00 2001 From: BlubbFish Date: Thu, 9 Mar 2017 21:17:55 +0000 Subject: [PATCH] Some stuff --- LICENSE.txt | 22 + Makefile | 41 ++ README.txt | 58 ++ RUN_SCENE_CORNELL_128SIZE.bat | 1 + RUN_SCENE_CORNELL_32SIZE.bat | 1 + RUN_SCENE_CORNELL_64SIZE.bat | 1 + RUN_SCENE_CORNELL_64SIZE_DL.bat | 1 + RUN_SCENE_SIMPLE_64SIZE.bat | 1 + SmallptGPU.exe | Bin 0 -> 24064 bytes camera.h | 37 ++ displayfunc.c | 431 ++++++++++++++ displayfunc.h | 53 ++ geom.h | 50 ++ geomfunc.h | 488 ++++++++++++++++ glut32.dll | Bin 0 -> 237568 bytes preprocessed_rendering_kernel.cl | 586 +++++++++++++++++++ preprocessed_rendering_kernel_dl.cl | 586 +++++++++++++++++++ rendering_kernel.cl | 97 ++++ rendering_kernel_dl.cl | 97 ++++ scene.h | 53 ++ scene_build_complex.pl | 60 ++ scenes/caustic.scn | 5 + scenes/caustic3.scn | 7 + scenes/complex.scn | 785 +++++++++++++++++++++++++ scenes/cornell.scn | 11 + scenes/cornell_large.scn | 11 + scenes/simple.scn | 7 + simplernd.h | 53 ++ smallptCPU | Bin 0 -> 39244 bytes smallptCPU.c | 175 ++++++ smallptGPU | Bin 0 -> 40909 bytes smallptGPU.c | 862 ++++++++++++++++++++++++++++ vec.h | 66 +++ 33 files changed, 4646 insertions(+) create mode 100644 LICENSE.txt create mode 100644 Makefile create mode 100644 README.txt create mode 100644 RUN_SCENE_CORNELL_128SIZE.bat create mode 100644 RUN_SCENE_CORNELL_32SIZE.bat create mode 100644 RUN_SCENE_CORNELL_64SIZE.bat create mode 100644 RUN_SCENE_CORNELL_64SIZE_DL.bat create mode 100644 RUN_SCENE_SIMPLE_64SIZE.bat create mode 100644 SmallptGPU.exe create mode 100644 camera.h create mode 100644 displayfunc.c create mode 100644 displayfunc.h create mode 100644 geom.h create mode 100644 geomfunc.h create mode 100644 glut32.dll create mode 100644 preprocessed_rendering_kernel.cl create mode 100644 preprocessed_rendering_kernel_dl.cl create mode 100644 rendering_kernel.cl create mode 100644 rendering_kernel_dl.cl create mode 100644 scene.h create mode 100644 scene_build_complex.pl create mode 100644 scenes/caustic.scn create mode 100644 scenes/caustic3.scn create mode 100644 scenes/complex.scn create mode 100644 scenes/cornell.scn create mode 100644 scenes/cornell_large.scn create mode 100644 scenes/simple.scn create mode 100644 simplernd.h create mode 100644 smallptCPU create mode 100644 smallptCPU.c create mode 100644 smallptGPU create mode 100644 smallptGPU.c create mode 100644 vec.h diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..1ec2f2a --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +LICENSE + +Copyright (c) 2009 David Bucciarelli (davibu@interfree.it) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..feb0401 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +# +# smallptGPU & smallptCPU Makefile +# + +ATISTREAMSDKROOT=/home/david/src/ati-stream-sdk-v2.0-lnx64 + +CC=gcc +CCFLAGS=-O3 -msse2 -mfpmath=sse -ftree-vectorize -funroll-loops -Wall \ + -I$(ATISTREAMSDKROOT)/include -L$(ATISTREAMSDKROOT)/lib/x86_64 -lglut -lOpenCL +# Jens's patch for MacOS, comment the 2 lines above and un-comment the lines below +#CCFLAGS=-O3 -ftree-vectorize -msse -msse2 -msse3 -mssse3 -fvariable-expansion-in-unroller \ +# -cl-fast-relaxed-math -cl-mad-enable -Wall -framework OpenCL -framework OpenGl -framework Glut + +default: all + +all: Makefile smallptCPU smallptGPU preprocessed_kernels + +smallptCPU: smallptCPU.c displayfunc.c Makefile vec.h camera.h geom.h displayfunc.h simplernd.h scene.h geomfunc.h + $(CC) $(CCFLAGS) -DSMALLPT_CPU -o smallptCPU smallptCPU.c displayfunc.c + +smallptGPU: smallptGPU.c displayfunc.c Makefile vec.h camera.h geom.h displayfunc.h simplernd.h scene.h geomfunc.h + $(CC) $(CCFLAGS) -DSMALLPT_GPU -o smallptGPU smallptGPU.c displayfunc.c + +clean: + rm -rf smallptCPU smallptGPU image.ppm SmallptGPU-v1.6 smallptgpu-v1.6.tgz preprocessed_rendering_kernel.cl + +preprocessed_kernels: + cpp preprocessed_rendering_kernel.cl + cpp preprocessed_rendering_kernel_dl.cl + +tgz: clean all + mkdir SmallptGPU-v1.6 + cp -r smallptCPU smallptGPU scenes LICENSE.txt Makefile README.txt \ + *.pl \ + *.c \ + *.h \ + *.cl \ + *.bat \ + SmallptGPU.exe glut32.dll SmallptGPU-v1.6 + tar zcvf smallptgpu-v1.6.tgz SmallptGPU-v1.6 + rm -rf SmallptGPU-v1.6 diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..9691e36 --- /dev/null +++ b/README.txt @@ -0,0 +1,58 @@ +SmallptCPU vs. SmallptGPU +========================= + +SmallptGPU is a small and simple demo written in OpenCL in order to test the +performance of this new standard. It is based on Kevin Beason's Smallpt available +at http://www.kevinbeason.com/smallpt/ +SmallptGPU has been written using the ATI OpenCL SDK 2.0 on Linux but it +should work on any platform/implementation. + +glut32.dll has been downloaded from Nate Robins's http://www.xmission.com/~nate/glut.html + + +How to compile +============== + +Just edit the Makefile and use an appropriate value for ATISTREAMSDKROOT. + + +Key bindings +============ + +'p' - save image.ppm +ESC - exit +Arrow keys - rotate camera left/right/up/down +'a' and 'd' - move camera left and right +'w' and 's' - move camera forward and backward +'r' and 'f' - move camera up and down +PageUp and PageDown - move camera target up and down +' ' - refresh the window +'+' and '-' - to select next/previous object +'2', '3', '4', '5', '6', '8', '9' - to move selected object + +History +======= + +V1.6 - Thanks to Jens and all the discussion at http://www.luxrender.net/forum/viewtopic.php?f=21&t=2947&start=240#p29397 +now SmallptGPU works fine with MacOS and NVIDIA cards. A bug in the Apple's OpenCL +compiler has been found (http://www.khronos.org/message_boards/viewtopic.php?f=37&t=2148) +and a workaround has been applied to SmallptGPU. Added a new kernel with +direct lighting surface integrator (very fast indeed). + +V1.5 - Thanks to discussion at http://forum.beyond3d.com/showthread.php?t=55913 +the perfomances on NVIDA GPUs have been improved. They are not yet where they should +be but are lot better now. + +V1.4 - Updated for ATI SDK 2.0, fixed a problem in object selection + +V1.3 - Jens's patch for MacOS, added on-screen help, fixed performance +estimation, removed movie recording, added on-screen help, added Windows binaries + +V1.2 - Indirect diffuse path can be now disabled/enabled (available only +on CPU version because a bug of ATI's compiler), optimized buffers +reallocation, added keys to select/move objects + +V1.1 - Fixed few portability problems, added support to save movie, fixed a +problem in window resize code + +V1.0 - First release diff --git a/RUN_SCENE_CORNELL_128SIZE.bat b/RUN_SCENE_CORNELL_128SIZE.bat new file mode 100644 index 0000000..a78c928 --- /dev/null +++ b/RUN_SCENE_CORNELL_128SIZE.bat @@ -0,0 +1 @@ +smallptGPU.exe 1 128 rendering_kernel.cl 640 480 scenes\cornell.scn diff --git a/RUN_SCENE_CORNELL_32SIZE.bat b/RUN_SCENE_CORNELL_32SIZE.bat new file mode 100644 index 0000000..0a7a1e8 --- /dev/null +++ b/RUN_SCENE_CORNELL_32SIZE.bat @@ -0,0 +1 @@ +smallptGPU.exe 1 32 rendering_kernel.cl 640 480 scenes\cornell.scn diff --git a/RUN_SCENE_CORNELL_64SIZE.bat b/RUN_SCENE_CORNELL_64SIZE.bat new file mode 100644 index 0000000..5b091f1 --- /dev/null +++ b/RUN_SCENE_CORNELL_64SIZE.bat @@ -0,0 +1 @@ +smallptGPU.exe 1 64 rendering_kernel.cl 640 480 scenes\cornell.scn diff --git a/RUN_SCENE_CORNELL_64SIZE_DL.bat b/RUN_SCENE_CORNELL_64SIZE_DL.bat new file mode 100644 index 0000000..e648389 --- /dev/null +++ b/RUN_SCENE_CORNELL_64SIZE_DL.bat @@ -0,0 +1 @@ +smallptGPU.exe 1 64 rendering_kernel_dl.cl 640 480 scenes\cornell.scn diff --git a/RUN_SCENE_SIMPLE_64SIZE.bat b/RUN_SCENE_SIMPLE_64SIZE.bat new file mode 100644 index 0000000..a933a2d --- /dev/null +++ b/RUN_SCENE_SIMPLE_64SIZE.bat @@ -0,0 +1 @@ +smallptGPU.exe 1 64 rendering_kernel.cl 640 480 scenes\simple.scn diff --git a/SmallptGPU.exe b/SmallptGPU.exe new file mode 100644 index 0000000000000000000000000000000000000000..442100b07e52ec1ff251b4b0a914a52f5d65e67c GIT binary patch literal 24064 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 +#include +#include + +#ifdef WIN32 +#define _USE_MATH_DEFINES +#endif +#include + +#if defined(__linux__) || defined(__APPLE__) +#include +#elif defined (WIN32) +#include +#else + Unsupported Platform !!! +#endif + +#include "camera.h" +#include "geom.h" +#include "displayfunc.h" + +extern void ReInit(const int); +extern void ReInitScene(); +extern void UpdateRendering(); +extern void UpdateCamera(); + +extern Camera camera; +extern Sphere *spheres; +extern unsigned int sphereCount; + +int amiSmallptCPU; + +int width = 640; +int height = 480; +unsigned int *pixels; +char captionBuffer[256]; + +static int printHelp = 1; +static int currentSphere; + +double WallClockTime() { +#if defined(__linux__) || defined(__APPLE__) + struct timeval t; + gettimeofday(&t, NULL); + + return t.tv_sec + t.tv_usec / 1000000.0; +#elif defined (WIN32) + return GetTickCount() / 1000.0; +#else + Unsupported Platform !!! +#endif +} + +static void PrintString(void *font, const char *string) { + int len, i; + + len = (int)strlen(string); + for (i = 0; i < len; i++) + glutBitmapCharacter(font, string[i]); +} + +static void PrintHelp() { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(0.f, 0.f, 0.5f, 0.5f); + glRecti(40, 40, 600, 440); + + glColor3f(1.f, 1.f, 1.f); + glRasterPos2i(300, 420); + PrintString(GLUT_BITMAP_HELVETICA_18, "Help"); + + glRasterPos2i(60, 390); + PrintString(GLUT_BITMAP_HELVETICA_18, "h - toggle Help"); + glRasterPos2i(60, 360); + PrintString(GLUT_BITMAP_HELVETICA_18, "arrow Keys - rotate camera left/right/up/down"); + glRasterPos2i(60, 330); + PrintString(GLUT_BITMAP_HELVETICA_18, "a and d - move camera left and right"); + glRasterPos2i(60, 300); + PrintString(GLUT_BITMAP_HELVETICA_18, "w and s - move camera forward and backward"); + glRasterPos2i(60, 270); + PrintString(GLUT_BITMAP_HELVETICA_18, "r and f - move camera up and down"); + glRasterPos2i(60, 240); + PrintString(GLUT_BITMAP_HELVETICA_18, "PageUp and PageDown - move camera target up and down"); + glRasterPos2i(60, 210); + PrintString(GLUT_BITMAP_HELVETICA_18, "+ and - - to select next/previous object"); + glRasterPos2i(60, 180); + PrintString(GLUT_BITMAP_HELVETICA_18, "2, 3, 4, 5, 6, 8, 9 - to move selected object"); + + glDisable(GL_BLEND); +} + +void ReadScene(char *fileName) { + fprintf(stderr, "Reading scene: %s\n", fileName); + + FILE *f = fopen(fileName, "r"); + if (!f) { + fprintf(stderr, "Failed to open file: %s\n", fileName); + exit(-1); + } + + /* Read the camera position */ + int c = fscanf(f,"camera %f %f %f %f %f %f\n", + &camera.orig.x, &camera.orig.y, &camera.orig.z, + &camera.target.x, &camera.target.y, &camera.target.z); + if (c != 6) { + fprintf(stderr, "Failed to read 6 camera parameters: %d\n", c); + exit(-1); + } + + /* Read the sphere count */ + c = fscanf(f,"size %u\n", &sphereCount); + if (c != 1) { + fprintf(stderr, "Failed to read sphere count: %d\n", c); + exit(-1); + } + fprintf(stderr, "Scene size: %d\n", sphereCount); + + /* Read all spheres */ + spheres = (Sphere *)malloc(sizeof(Sphere) * sphereCount); + unsigned int i; + for (i = 0; i < sphereCount; i++) { + Sphere *s = &spheres[i]; + int mat; + int c = fscanf(f,"sphere %f %f %f %f %f %f %f %f %f %f %d\n", + &s->rad, + &s->p.x, &s->p.y, &s->p.z, + &s->e.x, &s->e.y, &s->e.z, + &s->c.x, &s->c.y, &s->c.z, + &mat); + switch (mat) { + case 0: + s->refl = DIFF; + break; + case 1: + s->refl = SPEC; + break; + case 2: + s->refl = REFR; + break; + default: + fprintf(stderr, "Failed to read material type for sphere #%d: %d\n", i, mat); + exit(-1); + break; + } + if (c != 11) { + fprintf(stderr, "Failed to read sphere #%d: %d\n", i, c); + exit(-1); + } + } + + fclose(f); +} + +void UpdateCamera() { + vsub(camera.dir, camera.target, camera.orig); + vnorm(camera.dir); + + const Vec up = {0.f, 1.f, 0.f}; + const float fov = (M_PI / 180.f) * 45.f; + vxcross(camera.x, camera.dir, up); + vnorm(camera.x); + vsmul(camera.x, width * fov / height, camera.x); + + vxcross(camera.y, camera.x, camera.dir); + vnorm(camera.y); + vsmul(camera.y, fov, camera.y); +} + +void idleFunc(void) { + UpdateRendering(); + + glutPostRedisplay(); +} + +void displayFunc(void) { + glClear(GL_COLOR_BUFFER_BIT); + glRasterPos2i(0, 0); + glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Title + glColor3f(1.f, 1.f, 1.f); + glRasterPos2i(4, height - 16); + if (amiSmallptCPU) + PrintString(GLUT_BITMAP_HELVETICA_18, "SmallptCPU v1.6 (Written by David Bucciarelli)"); + else + PrintString(GLUT_BITMAP_HELVETICA_18, "SmallptGPU v1.6 (Written by David Bucciarelli)"); + + // Caption line 0 + glColor3f(1.f, 1.f, 1.f); + glRasterPos2i(4, 10); + PrintString(GLUT_BITMAP_HELVETICA_18, captionBuffer); + + if (printHelp) { + glPushMatrix(); + glLoadIdentity(); + glOrtho(-0.5, 639.5, -0.5, 479.5, -1.0, 1.0); + + PrintHelp(); + + glPopMatrix(); + } + + glutSwapBuffers(); +} + +void reshapeFunc(int newWidth, int newHeight) { + width = newWidth; + height = newHeight; + + glViewport(0, 0, width, height); + glLoadIdentity(); + glOrtho(0.f, width - 1.f, 0.f, height - 1.f, -1.f, 1.f); + + ReInit(1); + + glutPostRedisplay(); +} + +#define MOVE_STEP 10.0f +#define ROTATE_STEP (2.f * M_PI / 180.f) +void keyFunc(unsigned char key, int x, int y) { + switch (key) { + case 'p': { + FILE *f = fopen("image.ppm", "w"); // Write image to PPM file. + if (!f) { + fprintf(stderr, "Failed to open image file: image.ppm\n"); + } else { + fprintf(f, "P3\n%d %d\n%d\n", width, height, 255); + + int x, y; + for (y = height - 1; y >= 0; --y) { + unsigned char *p = (unsigned char *)(&pixels[y * width]); + for (x = 0; x < width; ++x, p += 4) + fprintf(f, "%d %d %d ", p[0], p[1], p[2]); + } + + fclose(f); + } + break; + } + case 27: /* Escape key */ + fprintf(stderr, "Done.\n"); + exit(0); + break; + case ' ': /* Refresh display */ + ReInit(1); + break; + case 'a': { + Vec dir = camera.x; + vnorm(dir); + vsmul(dir, -MOVE_STEP, dir); + vadd(camera.orig, camera.orig, dir); + vadd(camera.target, camera.target, dir); + ReInit(0); + break; + } + case 'd': { + Vec dir = camera.x; + vnorm(dir); + vsmul(dir, MOVE_STEP, dir); + vadd(camera.orig, camera.orig, dir); + vadd(camera.target, camera.target, dir); + ReInit(0); + break; + } + case 'w': { + Vec dir = camera.dir; + vsmul(dir, MOVE_STEP, dir); + vadd(camera.orig, camera.orig, dir); + vadd(camera.target, camera.target, dir); + ReInit(0); + break; + } + case 's': { + Vec dir = camera.dir; + vsmul(dir, -MOVE_STEP, dir); + vadd(camera.orig, camera.orig, dir); + vadd(camera.target, camera.target, dir); + ReInit(0); + break; + } + case 'r': + camera.orig.y += MOVE_STEP; + camera.target.y += MOVE_STEP; + ReInit(0); + break; + case 'f': + camera.orig.y -= MOVE_STEP; + camera.target.y -= MOVE_STEP; + ReInit(0); + break; + case '+': + currentSphere = (currentSphere + 1) % sphereCount; + fprintf(stderr, "Selected sphere %d (%f %f %f)\n", currentSphere, + spheres[currentSphere].p.x, spheres[currentSphere].p.y, spheres[currentSphere].p.z); + ReInitScene(); + break; + case '-': + currentSphere = (currentSphere + (sphereCount - 1)) % sphereCount; + fprintf(stderr, "Selected sphere %d (%f %f %f)\n", currentSphere, + spheres[currentSphere].p.x, spheres[currentSphere].p.y, spheres[currentSphere].p.z); + ReInitScene(); + break; + case '4': + spheres[currentSphere].p.x -= 0.5f * MOVE_STEP; + ReInitScene(); + break; + case '6': + spheres[currentSphere].p.x += 0.5f * MOVE_STEP; + ReInitScene(); + break; + case '8': + spheres[currentSphere].p.z -= 0.5f * MOVE_STEP; + ReInitScene(); + break; + case '2': + spheres[currentSphere].p.z += 0.5f * MOVE_STEP; + ReInitScene(); + break; + case '9': + spheres[currentSphere].p.y += 0.5f * MOVE_STEP; + ReInitScene(); + break; + case '3': + spheres[currentSphere].p.y -= 0.5f * MOVE_STEP; + ReInitScene(); + break; + case 'h': + printHelp = (!printHelp); + break; + default: + break; + } +} + +void specialFunc(int key, int x, int y) { + switch (key) { + case GLUT_KEY_UP: { + Vec t = camera.target; + vsub(t, t, camera.orig); + t.y = t.y * cos(-ROTATE_STEP) + t.z * sin(-ROTATE_STEP); + t.z = -t.y * sin(-ROTATE_STEP) + t.z * cos(-ROTATE_STEP); + vadd(t, t, camera.orig); + camera.target = t; + ReInit(0); + break; + } + case GLUT_KEY_DOWN: { + Vec t = camera.target; + vsub(t, t, camera.orig); + t.y = t.y * cos(ROTATE_STEP) + t.z * sin(ROTATE_STEP); + t.z = -t.y * sin(ROTATE_STEP) + t.z * cos(ROTATE_STEP); + vadd(t, t, camera.orig); + camera.target = t; + ReInit(0); + break; + } + case GLUT_KEY_LEFT: { + Vec t = camera.target; + vsub(t, t, camera.orig); + t.x = t.x * cos(-ROTATE_STEP) - t.z * sin(-ROTATE_STEP); + t.z = t.x * sin(-ROTATE_STEP) + t.z * cos(-ROTATE_STEP); + vadd(t, t, camera.orig); + camera.target = t; + ReInit(0); + break; + } + case GLUT_KEY_RIGHT: { + Vec t = camera.target; + vsub(t, t, camera.orig); + t.x = t.x * cos(ROTATE_STEP) - t.z * sin(ROTATE_STEP); + t.z = t.x * sin(ROTATE_STEP) + t.z * cos(ROTATE_STEP); + vadd(t, t, camera.orig); + camera.target = t; + ReInit(0); + break; + } + case GLUT_KEY_PAGE_UP: + camera.target.y += MOVE_STEP; + ReInit(0); + break; + case GLUT_KEY_PAGE_DOWN: + camera.target.y -= MOVE_STEP; + ReInit(0); + break; + default: + break; + } +} + +void InitGlut(int argc, char *argv[], char *windowTittle) { + glutInitWindowSize(width, height); + glutInitWindowPosition(0,0); + glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); + glutInit(&argc, argv); + + glutCreateWindow(windowTittle); + + glutReshapeFunc(reshapeFunc); + glutKeyboardFunc(keyFunc); + glutSpecialFunc(specialFunc); + glutDisplayFunc(displayFunc); + glutIdleFunc(idleFunc); + + glViewport(0, 0, width, height); + glLoadIdentity(); + glOrtho(0.f, width - 1.f, 0.f, height - 1.f, -1.f, 1.f); +} diff --git a/displayfunc.h b/displayfunc.h new file mode 100644 index 0000000..f9f32d4 --- /dev/null +++ b/displayfunc.h @@ -0,0 +1,53 @@ +/* +Copyright (c) 2009 David Bucciarelli (davibu@interfree.it) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef _DISPLAYFUNC_H +#define _DISPLAYFUNC_H + +#include + +// Jens's patch for MacOS +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "vec.h" + +extern int width; +extern int height; +extern unsigned int *pixels; +extern unsigned int renderingFlags; +extern char captionBuffer[256]; + +extern int amiSmallptCPU; + +extern void InitGlut(int argc, char *argv[], char *windowTittle); +extern double WallClockTime(); + +extern void ReadScene(char *); +extern void UpdateCamera(); + +#endif /* _DISPLAYFUNC_H */ + diff --git a/geom.h b/geom.h new file mode 100644 index 0000000..91a7d24 --- /dev/null +++ b/geom.h @@ -0,0 +1,50 @@ +/* +Copyright (c) 2009 David Bucciarelli (davibu@interfree.it) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef _GEOM_H +#define _GEOM_H + +#include "vec.h" + +#define EPSILON 0.01f +#define FLOAT_PI 3.14159265358979323846f + +typedef struct { + Vec o, d; +} Ray; + +#define rinit(r, a, b) { vassign((r).o, a); vassign((r).d, b); } +#define rassign(a, b) { vassign((a).o, (b).o); vassign((a).d, (b).d); } + +enum Refl { + DIFF, SPEC, REFR +}; /* material types, used in radiance() */ + +typedef struct { + float rad; /* radius */ + Vec p, e, c; /* position, emission, color */ + enum Refl refl; /* reflection type (DIFFuse, SPECular, REFRactive) */ +} Sphere; + +#endif /* _GEOM_H */ + diff --git a/geomfunc.h b/geomfunc.h new file mode 100644 index 0000000..6f2fde3 --- /dev/null +++ b/geomfunc.h @@ -0,0 +1,488 @@ +/* +Copyright (c) 2009 David Bucciarelli (davibu@interfree.it) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef _GEOMFUNC_H +#define _GEOMFUNC_H + +#include "geom.h" +#include "simplernd.h" + +#ifndef SMALLPT_GPU + +static float SphereIntersect( +#ifdef GPU_KERNEL +OCL_CONSTANT_BUFFER +#endif + const Sphere *s, + const Ray *r) { /* returns distance, 0 if nohit */ + Vec op; /* Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0 */ + vsub(op, s->p, r->o); + + float b = vdot(op, r->d); + float det = b * b - vdot(op, op) + s->rad * s->rad; + if (det < 0.f) + return 0.f; + else + det = sqrt(det); + + float t = b - det; + if (t > EPSILON) + return t; + else { + t = b + det; + + if (t > EPSILON) + return t; + else + return 0.f; + } +} + +static void UniformSampleSphere(const float u1, const float u2, Vec *v) { + const float zz = 1.f - 2.f * u1; + const float r = sqrt(max(0.f, 1.f - zz * zz)); + const float phi = 2.f * FLOAT_PI * u2; + const float xx = r * cos(phi); + const float yy = r * sin(phi); + + vinit(*v, xx, yy, zz); +} + +static int Intersect( +#ifdef GPU_KERNEL +OCL_CONSTANT_BUFFER +#endif + const Sphere *spheres, + const unsigned int sphereCount, + const Ray *r, + float *t, + unsigned int *id) { + float inf = (*t) = 1e20f; + + unsigned int i = sphereCount; + for (; i--;) { + const float d = SphereIntersect(&spheres[i], r); + if ((d != 0.f) && (d < *t)) { + *t = d; + *id = i; + } + } + + return (*t < inf); +} + +static int IntersectP( +#ifdef GPU_KERNEL +OCL_CONSTANT_BUFFER +#endif + const Sphere *spheres, + const unsigned int sphereCount, + const Ray *r, + const float maxt) { + unsigned int i = sphereCount; + for (; i--;) { + const float d = SphereIntersect(&spheres[i], r); + if ((d != 0.f) && (d < maxt)) + return 1; + } + + return 0; +} + +static void SampleLights( +#ifdef GPU_KERNEL +OCL_CONSTANT_BUFFER +#endif + const Sphere *spheres, + const unsigned int sphereCount, + unsigned int *seed0, unsigned int *seed1, + const Vec *hitPoint, + const Vec *normal, + Vec *result) { + vclr(*result); + + /* For each light */ + unsigned int i; + for (i = 0; i < sphereCount; i++) { +#ifdef GPU_KERNEL +OCL_CONSTANT_BUFFER +#endif + const Sphere *light = &spheres[i]; + if (!viszero(light->e)) { + /* It is a light source */ + Ray shadowRay; + shadowRay.o = *hitPoint; + + /* Choose a point over the light source */ + Vec unitSpherePoint; + UniformSampleSphere(GetRandom(seed0, seed1), GetRandom(seed0, seed1), &unitSpherePoint); + Vec spherePoint; + vsmul(spherePoint, light->rad, unitSpherePoint); + vadd(spherePoint, spherePoint, light->p); + + /* Build the shadow ray direction */ + vsub(shadowRay.d, spherePoint, *hitPoint); + const float len = sqrt(vdot(shadowRay.d, shadowRay.d)); + vsmul(shadowRay.d, 1.f / len, shadowRay.d); + + float wo = vdot(shadowRay.d, unitSpherePoint); + if (wo > 0.f) { + /* It is on the other half of the sphere */ + continue; + } else + wo = -wo; + + /* Check if the light is visible */ + const float wi = vdot(shadowRay.d, *normal); + if ((wi > 0.f) && (!IntersectP(spheres, sphereCount, &shadowRay, len - EPSILON))) { + Vec c; vassign(c, light->e); + const float s = (4.f * FLOAT_PI * light->rad * light->rad) * wi * wo / (len *len); + vsmul(c, s, c); + vadd(*result, *result, c); + } + } + } +} + +static void RadiancePathTracing( +#ifdef GPU_KERNEL +OCL_CONSTANT_BUFFER +#endif + const Sphere *spheres, + const unsigned int sphereCount, + const Ray *startRay, + unsigned int *seed0, unsigned int *seed1, + Vec *result) { + Ray currentRay; rassign(currentRay, *startRay); + Vec rad; vinit(rad, 0.f, 0.f, 0.f); + Vec throughput; vinit(throughput, 1.f, 1.f, 1.f); + + unsigned int depth = 0; + int specularBounce = 1; + for (;; ++depth) { + // Removed Russian Roulette in order to improve execution on SIMT + if (depth > 6) { + *result = rad; + return; + } + + float t; /* distance to intersection */ + unsigned int id = 0; /* id of intersected object */ + if (!Intersect(spheres, sphereCount, ¤tRay, &t, &id)) { + *result = rad; /* if miss, return */ + return; + } + +#ifdef GPU_KERNEL +OCL_CONSTANT_BUFFER +#endif + const Sphere *obj = &spheres[id]; /* the hit object */ + + Vec hitPoint; + vsmul(hitPoint, t, currentRay.d); + vadd(hitPoint, currentRay.o, hitPoint); + + Vec normal; + vsub(normal, hitPoint, obj->p); + vnorm(normal); + + const float dp = vdot(normal, currentRay.d); + + Vec nl; + // SIMT optimization + const float invSignDP = -1.f * sign(dp); + vsmul(nl, invSignDP, normal); + + /* Add emitted light */ + Vec eCol; vassign(eCol, obj->e); + if (!viszero(eCol)) { + if (specularBounce) { + vsmul(eCol, fabs(dp), eCol); + vmul(eCol, throughput, eCol); + vadd(rad, rad, eCol); + } + + *result = rad; + return; + } + + if (obj->refl == DIFF) { /* Ideal DIFFUSE reflection */ + specularBounce = 0; + vmul(throughput, throughput, obj->c); + + /* Direct lighting component */ + + Vec Ld; + SampleLights(spheres, sphereCount, seed0, seed1, &hitPoint, &nl, &Ld); + vmul(Ld, throughput, Ld); + vadd(rad, rad, Ld); + + /* Diffuse component */ + + float r1 = 2.f * FLOAT_PI * GetRandom(seed0, seed1); + float r2 = GetRandom(seed0, seed1); + float r2s = sqrt(r2); + + Vec w; vassign(w, nl); + + Vec u, a; + if (fabs(w.x) > .1f) { + vinit(a, 0.f, 1.f, 0.f); + } else { + vinit(a, 1.f, 0.f, 0.f); + } + vxcross(u, a, w); + vnorm(u); + + Vec v; + vxcross(v, w, u); + + Vec newDir; + vsmul(u, cos(r1) * r2s, u); + vsmul(v, sin(r1) * r2s, v); + vadd(newDir, u, v); + vsmul(w, sqrt(1 - r2), w); + vadd(newDir, newDir, w); + + currentRay.o = hitPoint; + currentRay.d = newDir; + continue; + } else if (obj->refl == SPEC) { /* Ideal SPECULAR reflection */ + specularBounce = 1; + + Vec newDir; + vsmul(newDir, 2.f * vdot(normal, currentRay.d), normal); + vsub(newDir, currentRay.d, newDir); + + vmul(throughput, throughput, obj->c); + + rinit(currentRay, hitPoint, newDir); + continue; + } else { + specularBounce = 1; + + Vec newDir; + vsmul(newDir, 2.f * vdot(normal, currentRay.d), normal); + vsub(newDir, currentRay.d, newDir); + + Ray reflRay; rinit(reflRay, hitPoint, newDir); /* Ideal dielectric REFRACTION */ + int into = (vdot(normal, nl) > 0); /* Ray from outside going in? */ + + float nc = 1.f; + float nt = 1.5f; + float nnt = into ? nc / nt : nt / nc; + float ddn = vdot(currentRay.d, nl); + float cos2t = 1.f - nnt * nnt * (1.f - ddn * ddn); + + if (cos2t < 0.f) { /* Total internal reflection */ + vmul(throughput, throughput, obj->c); + + rassign(currentRay, reflRay); + continue; + } + + float kk = (into ? 1 : -1) * (ddn * nnt + sqrt(cos2t)); + Vec nkk; + vsmul(nkk, kk, normal); + Vec transDir; + vsmul(transDir, nnt, currentRay.d); + vsub(transDir, transDir, nkk); + vnorm(transDir); + + float a = nt - nc; + float b = nt + nc; + float R0 = a * a / (b * b); + float c = 1 - (into ? -ddn : vdot(transDir, normal)); + + float Re = R0 + (1 - R0) * c * c * c * c*c; + float Tr = 1.f - Re; + float P = .25f + .5f * Re; + float RP = Re / P; + float TP = Tr / (1.f - P); + + if (GetRandom(seed0, seed1) < P) { /* R.R. */ + vsmul(throughput, RP, throughput); + vmul(throughput, throughput, obj->c); + + rassign(currentRay, reflRay); + continue; + } else { + vsmul(throughput, TP, throughput); + vmul(throughput, throughput, obj->c); + + rinit(currentRay, hitPoint, transDir); + continue; + } + } + } +} + +static void RadianceDirectLighting( +#ifdef GPU_KERNEL +OCL_CONSTANT_BUFFER +#endif + const Sphere *spheres, + const unsigned int sphereCount, + const Ray *startRay, + unsigned int *seed0, unsigned int *seed1, + Vec *result) { + Ray currentRay; rassign(currentRay, *startRay); + Vec rad; vinit(rad, 0.f, 0.f, 0.f); + Vec throughput; vinit(throughput, 1.f, 1.f, 1.f); + + unsigned int depth = 0; + int specularBounce = 1; + for (;; ++depth) { + // Removed Russian Roulette in order to improve execution on SIMT + if (depth > 6) { + *result = rad; + return; + } + + float t; /* distance to intersection */ + unsigned int id = 0; /* id of intersected object */ + if (!Intersect(spheres, sphereCount, ¤tRay, &t, &id)) { + *result = rad; /* if miss, return */ + return; + } + +#ifdef GPU_KERNEL +OCL_CONSTANT_BUFFER +#endif + const Sphere *obj = &spheres[id]; /* the hit object */ + + Vec hitPoint; + vsmul(hitPoint, t, currentRay.d); + vadd(hitPoint, currentRay.o, hitPoint); + + Vec normal; + vsub(normal, hitPoint, obj->p); + vnorm(normal); + + const float dp = vdot(normal, currentRay.d); + + Vec nl; + // SIMT optimization + const float invSignDP = -1.f * sign(dp); + vsmul(nl, invSignDP, normal); + + /* Add emitted light */ + Vec eCol; vassign(eCol, obj->e); + if (!viszero(eCol)) { + if (specularBounce) { + vsmul(eCol, fabs(dp), eCol); + vmul(eCol, throughput, eCol); + vadd(rad, rad, eCol); + } + + *result = rad; + return; + } + + if (obj->refl == DIFF) { /* Ideal DIFFUSE reflection */ + specularBounce = 0; + vmul(throughput, throughput, obj->c); + + /* Direct lighting component */ + + Vec Ld; + SampleLights(spheres, sphereCount, seed0, seed1, &hitPoint, &nl, &Ld); + vmul(Ld, throughput, Ld); + vadd(rad, rad, Ld); + + *result = rad; + return; + } else if (obj->refl == SPEC) { /* Ideal SPECULAR reflection */ + specularBounce = 1; + + Vec newDir; + vsmul(newDir, 2.f * vdot(normal, currentRay.d), normal); + vsub(newDir, currentRay.d, newDir); + + vmul(throughput, throughput, obj->c); + + rinit(currentRay, hitPoint, newDir); + continue; + } else { + specularBounce = 1; + + Vec newDir; + vsmul(newDir, 2.f * vdot(normal, currentRay.d), normal); + vsub(newDir, currentRay.d, newDir); + + Ray reflRay; rinit(reflRay, hitPoint, newDir); /* Ideal dielectric REFRACTION */ + int into = (vdot(normal, nl) > 0); /* Ray from outside going in? */ + + float nc = 1.f; + float nt = 1.5f; + float nnt = into ? nc / nt : nt / nc; + float ddn = vdot(currentRay.d, nl); + float cos2t = 1.f - nnt * nnt * (1.f - ddn * ddn); + + if (cos2t < 0.f) { /* Total internal reflection */ + vmul(throughput, throughput, obj->c); + + rassign(currentRay, reflRay); + continue; + } + + float kk = (into ? 1 : -1) * (ddn * nnt + sqrt(cos2t)); + Vec nkk; + vsmul(nkk, kk, normal); + Vec transDir; + vsmul(transDir, nnt, currentRay.d); + vsub(transDir, transDir, nkk); + vnorm(transDir); + + float a = nt - nc; + float b = nt + nc; + float R0 = a * a / (b * b); + float c = 1 - (into ? -ddn : vdot(transDir, normal)); + + float Re = R0 + (1 - R0) * c * c * c * c*c; + float Tr = 1.f - Re; + float P = .25f + .5f * Re; + float RP = Re / P; + float TP = Tr / (1.f - P); + + if (GetRandom(seed0, seed1) < P) { /* R.R. */ + vsmul(throughput, RP, throughput); + vmul(throughput, throughput, obj->c); + + rassign(currentRay, reflRay); + continue; + } else { + vsmul(throughput, TP, throughput); + vmul(throughput, throughput, obj->c); + + rinit(currentRay, hitPoint, transDir); + continue; + } + } + } +} + +#endif + +#endif /* _GEOMFUNC_H */ + diff --git a/glut32.dll b/glut32.dll new file mode 100644 index 0000000000000000000000000000000000000000..106646ff710941b60525ad3ee9bd68dc2a27b667 GIT binary patch literal 237568 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 literal 0 HcmV?d00001 diff --git a/preprocessed_rendering_kernel.cl b/preprocessed_rendering_kernel.cl new file mode 100644 index 0000000..74fb105 --- /dev/null +++ b/preprocessed_rendering_kernel.cl @@ -0,0 +1,586 @@ +# 1 "" +# 1 "" +# 1 "" +# 1 "" +# 26 "" +# 1 "camera.h" 1 +# 27 "camera.h" +# 1 "vec.h" 1 +# 27 "vec.h" +typedef struct { + float x, y, z; +} Vec; +# 28 "camera.h" 2 + +typedef struct { + + Vec orig, target; + + Vec dir, x, y; +} Camera; +# 27 "" 2 +# 1 "geomfunc.h" 1 +# 27 "geomfunc.h" +# 1 "geom.h" 1 +# 32 "geom.h" +typedef struct { + Vec o, d; +} Ray; + + + + +enum Refl { + DIFF, SPEC, REFR +}; + +typedef struct { + float rad; + Vec p, e, c; + enum Refl refl; +} Sphere; +# 28 "geomfunc.h" 2 +# 1 "simplernd.h" 1 +# 34 "simplernd.h" +static float GetRandom(unsigned int *seed0, unsigned int *seed1) { + *seed0 = 36969 * ((*seed0) & 65535) + ((*seed0) >> 16); + *seed1 = 18000 * ((*seed1) & 65535) + ((*seed1) >> 16); + + unsigned int ires = ((*seed0) << 16) + (*seed1); + + + union { + float f; + unsigned int ui; + } res; + res.ui = (ires & 0x007fffff) | 0x40000000; + + return (res.f - 2.f) / 2.f; +} +# 29 "geomfunc.h" 2 + + + +static float SphereIntersect( + +__constant + + const Sphere *s, + const Ray *r) { + Vec op; + { (op).x = (s->p).x - (r->o).x; (op).y = (s->p).y - (r->o).y; (op).z = (s->p).z - (r->o).z; }; + + float b = ((op).x * (r->d).x + (op).y * (r->d).y + (op).z * (r->d).z); + float det = b * b - ((op).x * (op).x + (op).y * (op).y + (op).z * (op).z) + s->rad * s->rad; + if (det < 0.f) + return 0.f; + else + det = sqrt(det); + + float t = b - det; + if (t > 0.01f) + return t; + else { + t = b + det; + + if (t > 0.01f) + return t; + else + return 0.f; + } +} + +static void UniformSampleSphere(const float u1, const float u2, Vec *v) { + const float zz = 1.f - 2.f * u1; + const float r = sqrt(max(0.f, 1.f - zz * zz)); + const float phi = 2.f * 3.14159265358979323846f * u2; + const float xx = r * cos(phi); + const float yy = r * sin(phi); + + { (*v).x = xx; (*v).y = yy; (*v).z = zz; }; +} + +static int Intersect( + +__constant + + const Sphere *spheres, + const unsigned int sphereCount, + const Ray *r, + float *t, + unsigned int *id) { + float inf = (*t) = 1e20f; + + unsigned int i = sphereCount; + for (; i--;) { + const float d = SphereIntersect(&spheres[i], r); + if ((d != 0.f) && (d < *t)) { + *t = d; + *id = i; + } + } + + return (*t < inf); +} + +static int IntersectP( + +__constant + + const Sphere *spheres, + const unsigned int sphereCount, + const Ray *r, + const float maxt) { + unsigned int i = sphereCount; + for (; i--;) { + const float d = SphereIntersect(&spheres[i], r); + if ((d != 0.f) && (d < maxt)) + return 1; + } + + return 0; +} + +static void SampleLights( + +__constant + + const Sphere *spheres, + const unsigned int sphereCount, + unsigned int *seed0, unsigned int *seed1, + const Vec *hitPoint, + const Vec *normal, + Vec *result) { + { (*result).x = 0.f; (*result).y = 0.f; (*result).z = 0.f; }; + + + unsigned int i; + for (i = 0; i < sphereCount; i++) { + +__constant + + const Sphere *light = &spheres[i]; + if (!(((light->e).x == 0.f) && ((light->e).x == 0.f) && ((light->e).z == 0.f))) { + + Ray shadowRay; + shadowRay.o = *hitPoint; + + + Vec unitSpherePoint; + UniformSampleSphere(GetRandom(seed0, seed1), GetRandom(seed0, seed1), &unitSpherePoint); + Vec spherePoint; + { float k = (light->rad); { (spherePoint).x = k * (unitSpherePoint).x; (spherePoint).y = k * (unitSpherePoint).y; (spherePoint).z = k * (unitSpherePoint).z; } }; + { (spherePoint).x = (spherePoint).x + (light->p).x; (spherePoint).y = (spherePoint).y + (light->p).y; (spherePoint).z = (spherePoint).z + (light->p).z; }; + + + { (shadowRay.d).x = (spherePoint).x - (*hitPoint).x; (shadowRay.d).y = (spherePoint).y - (*hitPoint).y; (shadowRay.d).z = (spherePoint).z - (*hitPoint).z; }; + const float len = sqrt(((shadowRay.d).x * (shadowRay.d).x + (shadowRay.d).y * (shadowRay.d).y + (shadowRay.d).z * (shadowRay.d).z)); + { float k = (1.f / len); { (shadowRay.d).x = k * (shadowRay.d).x; (shadowRay.d).y = k * (shadowRay.d).y; (shadowRay.d).z = k * (shadowRay.d).z; } }; + + float wo = ((shadowRay.d).x * (unitSpherePoint).x + (shadowRay.d).y * (unitSpherePoint).y + (shadowRay.d).z * (unitSpherePoint).z); + if (wo > 0.f) { + + continue; + } else + wo = -wo; + + + const float wi = ((shadowRay.d).x * (*normal).x + (shadowRay.d).y * (*normal).y + (shadowRay.d).z * (*normal).z); + if ((wi > 0.f) && (!IntersectP(spheres, sphereCount, &shadowRay, len - 0.01f))) { + Vec c; { (c).x = (light->e).x; (c).y = (light->e).y; (c).z = (light->e).z; }; + const float s = (4.f * 3.14159265358979323846f * light->rad * light->rad) * wi * wo / (len *len); + { float k = (s); { (c).x = k * (c).x; (c).y = k * (c).y; (c).z = k * (c).z; } }; + { (*result).x = (*result).x + (c).x; (*result).y = (*result).y + (c).y; (*result).z = (*result).z + (c).z; }; + } + } + } +} + +static void RadiancePathTracing( + +__constant + + const Sphere *spheres, + const unsigned int sphereCount, + const Ray *startRay, + unsigned int *seed0, unsigned int *seed1, + Vec *result) { + Ray currentRay; { { ((currentRay).o).x = ((*startRay).o).x; ((currentRay).o).y = ((*startRay).o).y; ((currentRay).o).z = ((*startRay).o).z; }; { ((currentRay).d).x = ((*startRay).d).x; ((currentRay).d).y = ((*startRay).d).y; ((currentRay).d).z = ((*startRay).d).z; }; }; + Vec rad; { (rad).x = 0.f; (rad).y = 0.f; (rad).z = 0.f; }; + Vec throughput; { (throughput).x = 1.f; (throughput).y = 1.f; (throughput).z = 1.f; }; + + unsigned int depth = 0; + int specularBounce = 1; + for (;; ++depth) { + + if (depth > 6) { + *result = rad; + return; + } + + float t; + unsigned int id = 0; + if (!Intersect(spheres, sphereCount, ¤tRay, &t, &id)) { + *result = rad; + return; + } + + +__constant + + const Sphere *obj = &spheres[id]; + + Vec hitPoint; + { float k = (t); { (hitPoint).x = k * (currentRay.d).x; (hitPoint).y = k * (currentRay.d).y; (hitPoint).z = k * (currentRay.d).z; } }; + { (hitPoint).x = (currentRay.o).x + (hitPoint).x; (hitPoint).y = (currentRay.o).y + (hitPoint).y; (hitPoint).z = (currentRay.o).z + (hitPoint).z; }; + + Vec normal; + { (normal).x = (hitPoint).x - (obj->p).x; (normal).y = (hitPoint).y - (obj->p).y; (normal).z = (hitPoint).z - (obj->p).z; }; + { float l = 1.f / sqrt(((normal).x * (normal).x + (normal).y * (normal).y + (normal).z * (normal).z)); { float k = (l); { (normal).x = k * (normal).x; (normal).y = k * (normal).y; (normal).z = k * (normal).z; } }; }; + + const float dp = ((normal).x * (currentRay.d).x + (normal).y * (currentRay.d).y + (normal).z * (currentRay.d).z); + + Vec nl; + + const float invSignDP = -1.f * sign(dp); + { float k = (invSignDP); { (nl).x = k * (normal).x; (nl).y = k * (normal).y; (nl).z = k * (normal).z; } }; + + + Vec eCol; { (eCol).x = (obj->e).x; (eCol).y = (obj->e).y; (eCol).z = (obj->e).z; }; + if (!(((eCol).x == 0.f) && ((eCol).x == 0.f) && ((eCol).z == 0.f))) { + if (specularBounce) { + { float k = (fabs(dp)); { (eCol).x = k * (eCol).x; (eCol).y = k * (eCol).y; (eCol).z = k * (eCol).z; } }; + { (eCol).x = (throughput).x * (eCol).x; (eCol).y = (throughput).y * (eCol).y; (eCol).z = (throughput).z * (eCol).z; }; + { (rad).x = (rad).x + (eCol).x; (rad).y = (rad).y + (eCol).y; (rad).z = (rad).z + (eCol).z; }; + } + + *result = rad; + return; + } + + if (obj->refl == DIFF) { + specularBounce = 0; + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + + + Vec Ld; + SampleLights(spheres, sphereCount, seed0, seed1, &hitPoint, &nl, &Ld); + { (Ld).x = (throughput).x * (Ld).x; (Ld).y = (throughput).y * (Ld).y; (Ld).z = (throughput).z * (Ld).z; }; + { (rad).x = (rad).x + (Ld).x; (rad).y = (rad).y + (Ld).y; (rad).z = (rad).z + (Ld).z; }; + + + + float r1 = 2.f * 3.14159265358979323846f * GetRandom(seed0, seed1); + float r2 = GetRandom(seed0, seed1); + float r2s = sqrt(r2); + + Vec w; { (w).x = (nl).x; (w).y = (nl).y; (w).z = (nl).z; }; + + Vec u, a; + if (fabs(w.x) > .1f) { + { (a).x = 0.f; (a).y = 1.f; (a).z = 0.f; }; + } else { + { (a).x = 1.f; (a).y = 0.f; (a).z = 0.f; }; + } + { (u).x = (a).y * (w).z - (a).z * (w).y; (u).y = (a).z * (w).x - (a).x * (w).z; (u).z = (a).x * (w).y - (a).y * (w).x; }; + { float l = 1.f / sqrt(((u).x * (u).x + (u).y * (u).y + (u).z * (u).z)); { float k = (l); { (u).x = k * (u).x; (u).y = k * (u).y; (u).z = k * (u).z; } }; }; + + Vec v; + { (v).x = (w).y * (u).z - (w).z * (u).y; (v).y = (w).z * (u).x - (w).x * (u).z; (v).z = (w).x * (u).y - (w).y * (u).x; }; + + Vec newDir; + { float k = (cos(r1) * r2s); { (u).x = k * (u).x; (u).y = k * (u).y; (u).z = k * (u).z; } }; + { float k = (sin(r1) * r2s); { (v).x = k * (v).x; (v).y = k * (v).y; (v).z = k * (v).z; } }; + { (newDir).x = (u).x + (v).x; (newDir).y = (u).y + (v).y; (newDir).z = (u).z + (v).z; }; + { float k = (sqrt(1 - r2)); { (w).x = k * (w).x; (w).y = k * (w).y; (w).z = k * (w).z; } }; + { (newDir).x = (newDir).x + (w).x; (newDir).y = (newDir).y + (w).y; (newDir).z = (newDir).z + (w).z; }; + + currentRay.o = hitPoint; + currentRay.d = newDir; + continue; + } else if (obj->refl == SPEC) { + specularBounce = 1; + + Vec newDir; + { float k = (2.f * ((normal).x * (currentRay.d).x + (normal).y * (currentRay.d).y + (normal).z * (currentRay.d).z)); { (newDir).x = k * (normal).x; (newDir).y = k * (normal).y; (newDir).z = k * (normal).z; } }; + { (newDir).x = (currentRay.d).x - (newDir).x; (newDir).y = (currentRay.d).y - (newDir).y; (newDir).z = (currentRay.d).z - (newDir).z; }; + + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = (hitPoint).x; ((currentRay).o).y = (hitPoint).y; ((currentRay).o).z = (hitPoint).z; }; { ((currentRay).d).x = (newDir).x; ((currentRay).d).y = (newDir).y; ((currentRay).d).z = (newDir).z; }; }; + continue; + } else { + specularBounce = 1; + + Vec newDir; + { float k = (2.f * ((normal).x * (currentRay.d).x + (normal).y * (currentRay.d).y + (normal).z * (currentRay.d).z)); { (newDir).x = k * (normal).x; (newDir).y = k * (normal).y; (newDir).z = k * (normal).z; } }; + { (newDir).x = (currentRay.d).x - (newDir).x; (newDir).y = (currentRay.d).y - (newDir).y; (newDir).z = (currentRay.d).z - (newDir).z; }; + + Ray reflRay; { { ((reflRay).o).x = (hitPoint).x; ((reflRay).o).y = (hitPoint).y; ((reflRay).o).z = (hitPoint).z; }; { ((reflRay).d).x = (newDir).x; ((reflRay).d).y = (newDir).y; ((reflRay).d).z = (newDir).z; }; }; + int into = (((normal).x * (nl).x + (normal).y * (nl).y + (normal).z * (nl).z) > 0); + + float nc = 1.f; + float nt = 1.5f; + float nnt = into ? nc / nt : nt / nc; + float ddn = ((currentRay.d).x * (nl).x + (currentRay.d).y * (nl).y + (currentRay.d).z * (nl).z); + float cos2t = 1.f - nnt * nnt * (1.f - ddn * ddn); + + if (cos2t < 0.f) { + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = ((reflRay).o).x; ((currentRay).o).y = ((reflRay).o).y; ((currentRay).o).z = ((reflRay).o).z; }; { ((currentRay).d).x = ((reflRay).d).x; ((currentRay).d).y = ((reflRay).d).y; ((currentRay).d).z = ((reflRay).d).z; }; }; + continue; + } + + float kk = (into ? 1 : -1) * (ddn * nnt + sqrt(cos2t)); + Vec nkk; + { float k = (kk); { (nkk).x = k * (normal).x; (nkk).y = k * (normal).y; (nkk).z = k * (normal).z; } }; + Vec transDir; + { float k = (nnt); { (transDir).x = k * (currentRay.d).x; (transDir).y = k * (currentRay.d).y; (transDir).z = k * (currentRay.d).z; } }; + { (transDir).x = (transDir).x - (nkk).x; (transDir).y = (transDir).y - (nkk).y; (transDir).z = (transDir).z - (nkk).z; }; + { float l = 1.f / sqrt(((transDir).x * (transDir).x + (transDir).y * (transDir).y + (transDir).z * (transDir).z)); { float k = (l); { (transDir).x = k * (transDir).x; (transDir).y = k * (transDir).y; (transDir).z = k * (transDir).z; } }; }; + + float a = nt - nc; + float b = nt + nc; + float R0 = a * a / (b * b); + float c = 1 - (into ? -ddn : ((transDir).x * (normal).x + (transDir).y * (normal).y + (transDir).z * (normal).z)); + + float Re = R0 + (1 - R0) * c * c * c * c*c; + float Tr = 1.f - Re; + float P = .25f + .5f * Re; + float RP = Re / P; + float TP = Tr / (1.f - P); + + if (GetRandom(seed0, seed1) < P) { + { float k = (RP); { (throughput).x = k * (throughput).x; (throughput).y = k * (throughput).y; (throughput).z = k * (throughput).z; } }; + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = ((reflRay).o).x; ((currentRay).o).y = ((reflRay).o).y; ((currentRay).o).z = ((reflRay).o).z; }; { ((currentRay).d).x = ((reflRay).d).x; ((currentRay).d).y = ((reflRay).d).y; ((currentRay).d).z = ((reflRay).d).z; }; }; + continue; + } else { + { float k = (TP); { (throughput).x = k * (throughput).x; (throughput).y = k * (throughput).y; (throughput).z = k * (throughput).z; } }; + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = (hitPoint).x; ((currentRay).o).y = (hitPoint).y; ((currentRay).o).z = (hitPoint).z; }; { ((currentRay).d).x = (transDir).x; ((currentRay).d).y = (transDir).y; ((currentRay).d).z = (transDir).z; }; }; + continue; + } + } + } +} + +static void RadianceDirectLighting( + +__constant + + const Sphere *spheres, + const unsigned int sphereCount, + const Ray *startRay, + unsigned int *seed0, unsigned int *seed1, + Vec *result) { + Ray currentRay; { { ((currentRay).o).x = ((*startRay).o).x; ((currentRay).o).y = ((*startRay).o).y; ((currentRay).o).z = ((*startRay).o).z; }; { ((currentRay).d).x = ((*startRay).d).x; ((currentRay).d).y = ((*startRay).d).y; ((currentRay).d).z = ((*startRay).d).z; }; }; + Vec rad; { (rad).x = 0.f; (rad).y = 0.f; (rad).z = 0.f; }; + Vec throughput; { (throughput).x = 1.f; (throughput).y = 1.f; (throughput).z = 1.f; }; + + unsigned int depth = 0; + int specularBounce = 1; + for (;; ++depth) { + + if (depth > 6) { + *result = rad; + return; + } + + float t; + unsigned int id = 0; + if (!Intersect(spheres, sphereCount, ¤tRay, &t, &id)) { + *result = rad; + return; + } + + +__constant + + const Sphere *obj = &spheres[id]; + + Vec hitPoint; + { float k = (t); { (hitPoint).x = k * (currentRay.d).x; (hitPoint).y = k * (currentRay.d).y; (hitPoint).z = k * (currentRay.d).z; } }; + { (hitPoint).x = (currentRay.o).x + (hitPoint).x; (hitPoint).y = (currentRay.o).y + (hitPoint).y; (hitPoint).z = (currentRay.o).z + (hitPoint).z; }; + + Vec normal; + { (normal).x = (hitPoint).x - (obj->p).x; (normal).y = (hitPoint).y - (obj->p).y; (normal).z = (hitPoint).z - (obj->p).z; }; + { float l = 1.f / sqrt(((normal).x * (normal).x + (normal).y * (normal).y + (normal).z * (normal).z)); { float k = (l); { (normal).x = k * (normal).x; (normal).y = k * (normal).y; (normal).z = k * (normal).z; } }; }; + + const float dp = ((normal).x * (currentRay.d).x + (normal).y * (currentRay.d).y + (normal).z * (currentRay.d).z); + + Vec nl; + + const float invSignDP = -1.f * sign(dp); + { float k = (invSignDP); { (nl).x = k * (normal).x; (nl).y = k * (normal).y; (nl).z = k * (normal).z; } }; + + + Vec eCol; { (eCol).x = (obj->e).x; (eCol).y = (obj->e).y; (eCol).z = (obj->e).z; }; + if (!(((eCol).x == 0.f) && ((eCol).x == 0.f) && ((eCol).z == 0.f))) { + if (specularBounce) { + { float k = (fabs(dp)); { (eCol).x = k * (eCol).x; (eCol).y = k * (eCol).y; (eCol).z = k * (eCol).z; } }; + { (eCol).x = (throughput).x * (eCol).x; (eCol).y = (throughput).y * (eCol).y; (eCol).z = (throughput).z * (eCol).z; }; + { (rad).x = (rad).x + (eCol).x; (rad).y = (rad).y + (eCol).y; (rad).z = (rad).z + (eCol).z; }; + } + + *result = rad; + return; + } + + if (obj->refl == DIFF) { + specularBounce = 0; + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + + + Vec Ld; + SampleLights(spheres, sphereCount, seed0, seed1, &hitPoint, &nl, &Ld); + { (Ld).x = (throughput).x * (Ld).x; (Ld).y = (throughput).y * (Ld).y; (Ld).z = (throughput).z * (Ld).z; }; + { (rad).x = (rad).x + (Ld).x; (rad).y = (rad).y + (Ld).y; (rad).z = (rad).z + (Ld).z; }; + + *result = rad; + return; + } else if (obj->refl == SPEC) { + specularBounce = 1; + + Vec newDir; + { float k = (2.f * ((normal).x * (currentRay.d).x + (normal).y * (currentRay.d).y + (normal).z * (currentRay.d).z)); { (newDir).x = k * (normal).x; (newDir).y = k * (normal).y; (newDir).z = k * (normal).z; } }; + { (newDir).x = (currentRay.d).x - (newDir).x; (newDir).y = (currentRay.d).y - (newDir).y; (newDir).z = (currentRay.d).z - (newDir).z; }; + + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = (hitPoint).x; ((currentRay).o).y = (hitPoint).y; ((currentRay).o).z = (hitPoint).z; }; { ((currentRay).d).x = (newDir).x; ((currentRay).d).y = (newDir).y; ((currentRay).d).z = (newDir).z; }; }; + continue; + } else { + specularBounce = 1; + + Vec newDir; + { float k = (2.f * ((normal).x * (currentRay.d).x + (normal).y * (currentRay.d).y + (normal).z * (currentRay.d).z)); { (newDir).x = k * (normal).x; (newDir).y = k * (normal).y; (newDir).z = k * (normal).z; } }; + { (newDir).x = (currentRay.d).x - (newDir).x; (newDir).y = (currentRay.d).y - (newDir).y; (newDir).z = (currentRay.d).z - (newDir).z; }; + + Ray reflRay; { { ((reflRay).o).x = (hitPoint).x; ((reflRay).o).y = (hitPoint).y; ((reflRay).o).z = (hitPoint).z; }; { ((reflRay).d).x = (newDir).x; ((reflRay).d).y = (newDir).y; ((reflRay).d).z = (newDir).z; }; }; + int into = (((normal).x * (nl).x + (normal).y * (nl).y + (normal).z * (nl).z) > 0); + + float nc = 1.f; + float nt = 1.5f; + float nnt = into ? nc / nt : nt / nc; + float ddn = ((currentRay.d).x * (nl).x + (currentRay.d).y * (nl).y + (currentRay.d).z * (nl).z); + float cos2t = 1.f - nnt * nnt * (1.f - ddn * ddn); + + if (cos2t < 0.f) { + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = ((reflRay).o).x; ((currentRay).o).y = ((reflRay).o).y; ((currentRay).o).z = ((reflRay).o).z; }; { ((currentRay).d).x = ((reflRay).d).x; ((currentRay).d).y = ((reflRay).d).y; ((currentRay).d).z = ((reflRay).d).z; }; }; + continue; + } + + float kk = (into ? 1 : -1) * (ddn * nnt + sqrt(cos2t)); + Vec nkk; + { float k = (kk); { (nkk).x = k * (normal).x; (nkk).y = k * (normal).y; (nkk).z = k * (normal).z; } }; + Vec transDir; + { float k = (nnt); { (transDir).x = k * (currentRay.d).x; (transDir).y = k * (currentRay.d).y; (transDir).z = k * (currentRay.d).z; } }; + { (transDir).x = (transDir).x - (nkk).x; (transDir).y = (transDir).y - (nkk).y; (transDir).z = (transDir).z - (nkk).z; }; + { float l = 1.f / sqrt(((transDir).x * (transDir).x + (transDir).y * (transDir).y + (transDir).z * (transDir).z)); { float k = (l); { (transDir).x = k * (transDir).x; (transDir).y = k * (transDir).y; (transDir).z = k * (transDir).z; } }; }; + + float a = nt - nc; + float b = nt + nc; + float R0 = a * a / (b * b); + float c = 1 - (into ? -ddn : ((transDir).x * (normal).x + (transDir).y * (normal).y + (transDir).z * (normal).z)); + + float Re = R0 + (1 - R0) * c * c * c * c*c; + float Tr = 1.f - Re; + float P = .25f + .5f * Re; + float RP = Re / P; + float TP = Tr / (1.f - P); + + if (GetRandom(seed0, seed1) < P) { + { float k = (RP); { (throughput).x = k * (throughput).x; (throughput).y = k * (throughput).y; (throughput).z = k * (throughput).z; } }; + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = ((reflRay).o).x; ((currentRay).o).y = ((reflRay).o).y; ((currentRay).o).z = ((reflRay).o).z; }; { ((currentRay).d).x = ((reflRay).d).x; ((currentRay).d).y = ((reflRay).d).y; ((currentRay).d).z = ((reflRay).d).z; }; }; + continue; + } else { + { float k = (TP); { (throughput).x = k * (throughput).x; (throughput).y = k * (throughput).y; (throughput).z = k * (throughput).z; } }; + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = (hitPoint).x; ((currentRay).o).y = (hitPoint).y; ((currentRay).o).z = (hitPoint).z; }; { ((currentRay).d).x = (transDir).x; ((currentRay).d).y = (transDir).y; ((currentRay).d).z = (transDir).z; }; }; + continue; + } + } + } +} +# 28 "" 2 + +static void GenerateCameraRay(__constant Camera *camera, + unsigned int *seed0, unsigned int *seed1, + const int width, const int height, const int x, const int y, Ray *ray) { + const float invWidth = 1.f / width; + const float invHeight = 1.f / height; + const float r1 = GetRandom(seed0, seed1) - .5f; + const float r2 = GetRandom(seed0, seed1) - .5f; + const float kcx = (x + r1) * invWidth - .5f; + const float kcy = (y + r2) * invHeight - .5f; + + Vec rdir; + { (rdir).x = camera->x.x * kcx + camera->y.x * kcy + camera->dir.x; (rdir).y = camera->x.y * kcx + camera->y.y * kcy + camera->dir.y; (rdir).z = camera->x.z * kcx + camera->y.z * kcy + camera->dir.z; }; + + + + + Vec rorig; + { float k = (0.1f); { (rorig).x = k * (rdir).x; (rorig).y = k * (rdir).y; (rorig).z = k * (rdir).z; } }; + { (rorig).x = (rorig).x + (camera->orig).x; (rorig).y = (rorig).y + (camera->orig).y; (rorig).z = (rorig).z + (camera->orig).z; } + + { float l = 1.f / sqrt(((rdir).x * (rdir).x + (rdir).y * (rdir).y + (rdir).z * (rdir).z)); { float k = (l); { (rdir).x = k * (rdir).x; (rdir).y = k * (rdir).y; (rdir).z = k * (rdir).z; } }; }; + { { ((*ray).o).x = (rorig).x; ((*ray).o).y = (rorig).y; ((*ray).o).z = (rorig).z; }; { ((*ray).d).x = (rdir).x; ((*ray).d).y = (rdir).y; ((*ray).d).z = (rdir).z; }; }; +} + +__kernel void RadianceGPU( + __global Vec *colors, __global unsigned int *seedsInput, + __constant Sphere *sphere, __constant Camera *camera, + const unsigned int sphereCount, + const int width, const int height, + const int currentSample, + __global int *pixels) { + const int gid = get_global_id(0); + const int gid2 = 2 * gid; + const int x = gid % width; + const int y = gid / width; + + + if (y >= height) + return; + + + unsigned int seed0 = seedsInput[gid2]; + unsigned int seed1 = seedsInput[gid2 + 1]; + + Ray ray; + GenerateCameraRay(camera, &seed0, &seed1, width, height, x, y, &ray); + + Vec r; + RadiancePathTracing(sphere, sphereCount, &ray, &seed0, &seed1, &r); + + const int i = (height - y - 1) * width + x; + if (currentSample == 0) { + + { (colors[i]).x = (r).x; (colors[i]).y = (r).y; (colors[i]).z = (r).z; }; + } else { + const float k1 = currentSample; + const float k2 = 1.f / (currentSample + 1.f); + colors[i].x = (colors[i].x * k1 + r.x) * k2; + colors[i].y = (colors[i].y * k1 + r.y) * k2; + colors[i].z = (colors[i].z * k1 + r.z) * k2; + } + + pixels[y * width + x] = ((int)(pow(clamp(colors[i].x, 0.f, 1.f), 1.f / 2.2f) * 255.f + .5f)) | + (((int)(pow(clamp(colors[i].y, 0.f, 1.f), 1.f / 2.2f) * 255.f + .5f)) << 8) | + (((int)(pow(clamp(colors[i].z, 0.f, 1.f), 1.f / 2.2f) * 255.f + .5f)) << 16); + + seedsInput[gid2] = seed0; + seedsInput[gid2 + 1] = seed1; +} diff --git a/preprocessed_rendering_kernel_dl.cl b/preprocessed_rendering_kernel_dl.cl new file mode 100644 index 0000000..6c85a31 --- /dev/null +++ b/preprocessed_rendering_kernel_dl.cl @@ -0,0 +1,586 @@ +# 1 "" +# 1 "" +# 1 "" +# 1 "" +# 26 "" +# 1 "camera.h" 1 +# 27 "camera.h" +# 1 "vec.h" 1 +# 27 "vec.h" +typedef struct { + float x, y, z; +} Vec; +# 28 "camera.h" 2 + +typedef struct { + + Vec orig, target; + + Vec dir, x, y; +} Camera; +# 27 "" 2 +# 1 "geomfunc.h" 1 +# 27 "geomfunc.h" +# 1 "geom.h" 1 +# 32 "geom.h" +typedef struct { + Vec o, d; +} Ray; + + + + +enum Refl { + DIFF, SPEC, REFR +}; + +typedef struct { + float rad; + Vec p, e, c; + enum Refl refl; +} Sphere; +# 28 "geomfunc.h" 2 +# 1 "simplernd.h" 1 +# 34 "simplernd.h" +static float GetRandom(unsigned int *seed0, unsigned int *seed1) { + *seed0 = 36969 * ((*seed0) & 65535) + ((*seed0) >> 16); + *seed1 = 18000 * ((*seed1) & 65535) + ((*seed1) >> 16); + + unsigned int ires = ((*seed0) << 16) + (*seed1); + + + union { + float f; + unsigned int ui; + } res; + res.ui = (ires & 0x007fffff) | 0x40000000; + + return (res.f - 2.f) / 2.f; +} +# 29 "geomfunc.h" 2 + + + +static float SphereIntersect( + +__constant + + const Sphere *s, + const Ray *r) { + Vec op; + { (op).x = (s->p).x - (r->o).x; (op).y = (s->p).y - (r->o).y; (op).z = (s->p).z - (r->o).z; }; + + float b = ((op).x * (r->d).x + (op).y * (r->d).y + (op).z * (r->d).z); + float det = b * b - ((op).x * (op).x + (op).y * (op).y + (op).z * (op).z) + s->rad * s->rad; + if (det < 0.f) + return 0.f; + else + det = sqrt(det); + + float t = b - det; + if (t > 0.01f) + return t; + else { + t = b + det; + + if (t > 0.01f) + return t; + else + return 0.f; + } +} + +static void UniformSampleSphere(const float u1, const float u2, Vec *v) { + const float zz = 1.f - 2.f * u1; + const float r = sqrt(max(0.f, 1.f - zz * zz)); + const float phi = 2.f * 3.14159265358979323846f * u2; + const float xx = r * cos(phi); + const float yy = r * sin(phi); + + { (*v).x = xx; (*v).y = yy; (*v).z = zz; }; +} + +static int Intersect( + +__constant + + const Sphere *spheres, + const unsigned int sphereCount, + const Ray *r, + float *t, + unsigned int *id) { + float inf = (*t) = 1e20f; + + unsigned int i = sphereCount; + for (; i--;) { + const float d = SphereIntersect(&spheres[i], r); + if ((d != 0.f) && (d < *t)) { + *t = d; + *id = i; + } + } + + return (*t < inf); +} + +static int IntersectP( + +__constant + + const Sphere *spheres, + const unsigned int sphereCount, + const Ray *r, + const float maxt) { + unsigned int i = sphereCount; + for (; i--;) { + const float d = SphereIntersect(&spheres[i], r); + if ((d != 0.f) && (d < maxt)) + return 1; + } + + return 0; +} + +static void SampleLights( + +__constant + + const Sphere *spheres, + const unsigned int sphereCount, + unsigned int *seed0, unsigned int *seed1, + const Vec *hitPoint, + const Vec *normal, + Vec *result) { + { (*result).x = 0.f; (*result).y = 0.f; (*result).z = 0.f; }; + + + unsigned int i; + for (i = 0; i < sphereCount; i++) { + +__constant + + const Sphere *light = &spheres[i]; + if (!(((light->e).x == 0.f) && ((light->e).x == 0.f) && ((light->e).z == 0.f))) { + + Ray shadowRay; + shadowRay.o = *hitPoint; + + + Vec unitSpherePoint; + UniformSampleSphere(GetRandom(seed0, seed1), GetRandom(seed0, seed1), &unitSpherePoint); + Vec spherePoint; + { float k = (light->rad); { (spherePoint).x = k * (unitSpherePoint).x; (spherePoint).y = k * (unitSpherePoint).y; (spherePoint).z = k * (unitSpherePoint).z; } }; + { (spherePoint).x = (spherePoint).x + (light->p).x; (spherePoint).y = (spherePoint).y + (light->p).y; (spherePoint).z = (spherePoint).z + (light->p).z; }; + + + { (shadowRay.d).x = (spherePoint).x - (*hitPoint).x; (shadowRay.d).y = (spherePoint).y - (*hitPoint).y; (shadowRay.d).z = (spherePoint).z - (*hitPoint).z; }; + const float len = sqrt(((shadowRay.d).x * (shadowRay.d).x + (shadowRay.d).y * (shadowRay.d).y + (shadowRay.d).z * (shadowRay.d).z)); + { float k = (1.f / len); { (shadowRay.d).x = k * (shadowRay.d).x; (shadowRay.d).y = k * (shadowRay.d).y; (shadowRay.d).z = k * (shadowRay.d).z; } }; + + float wo = ((shadowRay.d).x * (unitSpherePoint).x + (shadowRay.d).y * (unitSpherePoint).y + (shadowRay.d).z * (unitSpherePoint).z); + if (wo > 0.f) { + + continue; + } else + wo = -wo; + + + const float wi = ((shadowRay.d).x * (*normal).x + (shadowRay.d).y * (*normal).y + (shadowRay.d).z * (*normal).z); + if ((wi > 0.f) && (!IntersectP(spheres, sphereCount, &shadowRay, len - 0.01f))) { + Vec c; { (c).x = (light->e).x; (c).y = (light->e).y; (c).z = (light->e).z; }; + const float s = (4.f * 3.14159265358979323846f * light->rad * light->rad) * wi * wo / (len *len); + { float k = (s); { (c).x = k * (c).x; (c).y = k * (c).y; (c).z = k * (c).z; } }; + { (*result).x = (*result).x + (c).x; (*result).y = (*result).y + (c).y; (*result).z = (*result).z + (c).z; }; + } + } + } +} + +static void RadiancePathTracing( + +__constant + + const Sphere *spheres, + const unsigned int sphereCount, + const Ray *startRay, + unsigned int *seed0, unsigned int *seed1, + Vec *result) { + Ray currentRay; { { ((currentRay).o).x = ((*startRay).o).x; ((currentRay).o).y = ((*startRay).o).y; ((currentRay).o).z = ((*startRay).o).z; }; { ((currentRay).d).x = ((*startRay).d).x; ((currentRay).d).y = ((*startRay).d).y; ((currentRay).d).z = ((*startRay).d).z; }; }; + Vec rad; { (rad).x = 0.f; (rad).y = 0.f; (rad).z = 0.f; }; + Vec throughput; { (throughput).x = 1.f; (throughput).y = 1.f; (throughput).z = 1.f; }; + + unsigned int depth = 0; + int specularBounce = 1; + for (;; ++depth) { + + if (depth > 6) { + *result = rad; + return; + } + + float t; + unsigned int id = 0; + if (!Intersect(spheres, sphereCount, ¤tRay, &t, &id)) { + *result = rad; + return; + } + + +__constant + + const Sphere *obj = &spheres[id]; + + Vec hitPoint; + { float k = (t); { (hitPoint).x = k * (currentRay.d).x; (hitPoint).y = k * (currentRay.d).y; (hitPoint).z = k * (currentRay.d).z; } }; + { (hitPoint).x = (currentRay.o).x + (hitPoint).x; (hitPoint).y = (currentRay.o).y + (hitPoint).y; (hitPoint).z = (currentRay.o).z + (hitPoint).z; }; + + Vec normal; + { (normal).x = (hitPoint).x - (obj->p).x; (normal).y = (hitPoint).y - (obj->p).y; (normal).z = (hitPoint).z - (obj->p).z; }; + { float l = 1.f / sqrt(((normal).x * (normal).x + (normal).y * (normal).y + (normal).z * (normal).z)); { float k = (l); { (normal).x = k * (normal).x; (normal).y = k * (normal).y; (normal).z = k * (normal).z; } }; }; + + const float dp = ((normal).x * (currentRay.d).x + (normal).y * (currentRay.d).y + (normal).z * (currentRay.d).z); + + Vec nl; + + const float invSignDP = -1.f * sign(dp); + { float k = (invSignDP); { (nl).x = k * (normal).x; (nl).y = k * (normal).y; (nl).z = k * (normal).z; } }; + + + Vec eCol; { (eCol).x = (obj->e).x; (eCol).y = (obj->e).y; (eCol).z = (obj->e).z; }; + if (!(((eCol).x == 0.f) && ((eCol).x == 0.f) && ((eCol).z == 0.f))) { + if (specularBounce) { + { float k = (fabs(dp)); { (eCol).x = k * (eCol).x; (eCol).y = k * (eCol).y; (eCol).z = k * (eCol).z; } }; + { (eCol).x = (throughput).x * (eCol).x; (eCol).y = (throughput).y * (eCol).y; (eCol).z = (throughput).z * (eCol).z; }; + { (rad).x = (rad).x + (eCol).x; (rad).y = (rad).y + (eCol).y; (rad).z = (rad).z + (eCol).z; }; + } + + *result = rad; + return; + } + + if (obj->refl == DIFF) { + specularBounce = 0; + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + + + Vec Ld; + SampleLights(spheres, sphereCount, seed0, seed1, &hitPoint, &nl, &Ld); + { (Ld).x = (throughput).x * (Ld).x; (Ld).y = (throughput).y * (Ld).y; (Ld).z = (throughput).z * (Ld).z; }; + { (rad).x = (rad).x + (Ld).x; (rad).y = (rad).y + (Ld).y; (rad).z = (rad).z + (Ld).z; }; + + + + float r1 = 2.f * 3.14159265358979323846f * GetRandom(seed0, seed1); + float r2 = GetRandom(seed0, seed1); + float r2s = sqrt(r2); + + Vec w; { (w).x = (nl).x; (w).y = (nl).y; (w).z = (nl).z; }; + + Vec u, a; + if (fabs(w.x) > .1f) { + { (a).x = 0.f; (a).y = 1.f; (a).z = 0.f; }; + } else { + { (a).x = 1.f; (a).y = 0.f; (a).z = 0.f; }; + } + { (u).x = (a).y * (w).z - (a).z * (w).y; (u).y = (a).z * (w).x - (a).x * (w).z; (u).z = (a).x * (w).y - (a).y * (w).x; }; + { float l = 1.f / sqrt(((u).x * (u).x + (u).y * (u).y + (u).z * (u).z)); { float k = (l); { (u).x = k * (u).x; (u).y = k * (u).y; (u).z = k * (u).z; } }; }; + + Vec v; + { (v).x = (w).y * (u).z - (w).z * (u).y; (v).y = (w).z * (u).x - (w).x * (u).z; (v).z = (w).x * (u).y - (w).y * (u).x; }; + + Vec newDir; + { float k = (cos(r1) * r2s); { (u).x = k * (u).x; (u).y = k * (u).y; (u).z = k * (u).z; } }; + { float k = (sin(r1) * r2s); { (v).x = k * (v).x; (v).y = k * (v).y; (v).z = k * (v).z; } }; + { (newDir).x = (u).x + (v).x; (newDir).y = (u).y + (v).y; (newDir).z = (u).z + (v).z; }; + { float k = (sqrt(1 - r2)); { (w).x = k * (w).x; (w).y = k * (w).y; (w).z = k * (w).z; } }; + { (newDir).x = (newDir).x + (w).x; (newDir).y = (newDir).y + (w).y; (newDir).z = (newDir).z + (w).z; }; + + currentRay.o = hitPoint; + currentRay.d = newDir; + continue; + } else if (obj->refl == SPEC) { + specularBounce = 1; + + Vec newDir; + { float k = (2.f * ((normal).x * (currentRay.d).x + (normal).y * (currentRay.d).y + (normal).z * (currentRay.d).z)); { (newDir).x = k * (normal).x; (newDir).y = k * (normal).y; (newDir).z = k * (normal).z; } }; + { (newDir).x = (currentRay.d).x - (newDir).x; (newDir).y = (currentRay.d).y - (newDir).y; (newDir).z = (currentRay.d).z - (newDir).z; }; + + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = (hitPoint).x; ((currentRay).o).y = (hitPoint).y; ((currentRay).o).z = (hitPoint).z; }; { ((currentRay).d).x = (newDir).x; ((currentRay).d).y = (newDir).y; ((currentRay).d).z = (newDir).z; }; }; + continue; + } else { + specularBounce = 1; + + Vec newDir; + { float k = (2.f * ((normal).x * (currentRay.d).x + (normal).y * (currentRay.d).y + (normal).z * (currentRay.d).z)); { (newDir).x = k * (normal).x; (newDir).y = k * (normal).y; (newDir).z = k * (normal).z; } }; + { (newDir).x = (currentRay.d).x - (newDir).x; (newDir).y = (currentRay.d).y - (newDir).y; (newDir).z = (currentRay.d).z - (newDir).z; }; + + Ray reflRay; { { ((reflRay).o).x = (hitPoint).x; ((reflRay).o).y = (hitPoint).y; ((reflRay).o).z = (hitPoint).z; }; { ((reflRay).d).x = (newDir).x; ((reflRay).d).y = (newDir).y; ((reflRay).d).z = (newDir).z; }; }; + int into = (((normal).x * (nl).x + (normal).y * (nl).y + (normal).z * (nl).z) > 0); + + float nc = 1.f; + float nt = 1.5f; + float nnt = into ? nc / nt : nt / nc; + float ddn = ((currentRay.d).x * (nl).x + (currentRay.d).y * (nl).y + (currentRay.d).z * (nl).z); + float cos2t = 1.f - nnt * nnt * (1.f - ddn * ddn); + + if (cos2t < 0.f) { + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = ((reflRay).o).x; ((currentRay).o).y = ((reflRay).o).y; ((currentRay).o).z = ((reflRay).o).z; }; { ((currentRay).d).x = ((reflRay).d).x; ((currentRay).d).y = ((reflRay).d).y; ((currentRay).d).z = ((reflRay).d).z; }; }; + continue; + } + + float kk = (into ? 1 : -1) * (ddn * nnt + sqrt(cos2t)); + Vec nkk; + { float k = (kk); { (nkk).x = k * (normal).x; (nkk).y = k * (normal).y; (nkk).z = k * (normal).z; } }; + Vec transDir; + { float k = (nnt); { (transDir).x = k * (currentRay.d).x; (transDir).y = k * (currentRay.d).y; (transDir).z = k * (currentRay.d).z; } }; + { (transDir).x = (transDir).x - (nkk).x; (transDir).y = (transDir).y - (nkk).y; (transDir).z = (transDir).z - (nkk).z; }; + { float l = 1.f / sqrt(((transDir).x * (transDir).x + (transDir).y * (transDir).y + (transDir).z * (transDir).z)); { float k = (l); { (transDir).x = k * (transDir).x; (transDir).y = k * (transDir).y; (transDir).z = k * (transDir).z; } }; }; + + float a = nt - nc; + float b = nt + nc; + float R0 = a * a / (b * b); + float c = 1 - (into ? -ddn : ((transDir).x * (normal).x + (transDir).y * (normal).y + (transDir).z * (normal).z)); + + float Re = R0 + (1 - R0) * c * c * c * c*c; + float Tr = 1.f - Re; + float P = .25f + .5f * Re; + float RP = Re / P; + float TP = Tr / (1.f - P); + + if (GetRandom(seed0, seed1) < P) { + { float k = (RP); { (throughput).x = k * (throughput).x; (throughput).y = k * (throughput).y; (throughput).z = k * (throughput).z; } }; + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = ((reflRay).o).x; ((currentRay).o).y = ((reflRay).o).y; ((currentRay).o).z = ((reflRay).o).z; }; { ((currentRay).d).x = ((reflRay).d).x; ((currentRay).d).y = ((reflRay).d).y; ((currentRay).d).z = ((reflRay).d).z; }; }; + continue; + } else { + { float k = (TP); { (throughput).x = k * (throughput).x; (throughput).y = k * (throughput).y; (throughput).z = k * (throughput).z; } }; + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = (hitPoint).x; ((currentRay).o).y = (hitPoint).y; ((currentRay).o).z = (hitPoint).z; }; { ((currentRay).d).x = (transDir).x; ((currentRay).d).y = (transDir).y; ((currentRay).d).z = (transDir).z; }; }; + continue; + } + } + } +} + +static void RadianceDirectLighting( + +__constant + + const Sphere *spheres, + const unsigned int sphereCount, + const Ray *startRay, + unsigned int *seed0, unsigned int *seed1, + Vec *result) { + Ray currentRay; { { ((currentRay).o).x = ((*startRay).o).x; ((currentRay).o).y = ((*startRay).o).y; ((currentRay).o).z = ((*startRay).o).z; }; { ((currentRay).d).x = ((*startRay).d).x; ((currentRay).d).y = ((*startRay).d).y; ((currentRay).d).z = ((*startRay).d).z; }; }; + Vec rad; { (rad).x = 0.f; (rad).y = 0.f; (rad).z = 0.f; }; + Vec throughput; { (throughput).x = 1.f; (throughput).y = 1.f; (throughput).z = 1.f; }; + + unsigned int depth = 0; + int specularBounce = 1; + for (;; ++depth) { + + if (depth > 6) { + *result = rad; + return; + } + + float t; + unsigned int id = 0; + if (!Intersect(spheres, sphereCount, ¤tRay, &t, &id)) { + *result = rad; + return; + } + + +__constant + + const Sphere *obj = &spheres[id]; + + Vec hitPoint; + { float k = (t); { (hitPoint).x = k * (currentRay.d).x; (hitPoint).y = k * (currentRay.d).y; (hitPoint).z = k * (currentRay.d).z; } }; + { (hitPoint).x = (currentRay.o).x + (hitPoint).x; (hitPoint).y = (currentRay.o).y + (hitPoint).y; (hitPoint).z = (currentRay.o).z + (hitPoint).z; }; + + Vec normal; + { (normal).x = (hitPoint).x - (obj->p).x; (normal).y = (hitPoint).y - (obj->p).y; (normal).z = (hitPoint).z - (obj->p).z; }; + { float l = 1.f / sqrt(((normal).x * (normal).x + (normal).y * (normal).y + (normal).z * (normal).z)); { float k = (l); { (normal).x = k * (normal).x; (normal).y = k * (normal).y; (normal).z = k * (normal).z; } }; }; + + const float dp = ((normal).x * (currentRay.d).x + (normal).y * (currentRay.d).y + (normal).z * (currentRay.d).z); + + Vec nl; + + const float invSignDP = -1.f * sign(dp); + { float k = (invSignDP); { (nl).x = k * (normal).x; (nl).y = k * (normal).y; (nl).z = k * (normal).z; } }; + + + Vec eCol; { (eCol).x = (obj->e).x; (eCol).y = (obj->e).y; (eCol).z = (obj->e).z; }; + if (!(((eCol).x == 0.f) && ((eCol).x == 0.f) && ((eCol).z == 0.f))) { + if (specularBounce) { + { float k = (fabs(dp)); { (eCol).x = k * (eCol).x; (eCol).y = k * (eCol).y; (eCol).z = k * (eCol).z; } }; + { (eCol).x = (throughput).x * (eCol).x; (eCol).y = (throughput).y * (eCol).y; (eCol).z = (throughput).z * (eCol).z; }; + { (rad).x = (rad).x + (eCol).x; (rad).y = (rad).y + (eCol).y; (rad).z = (rad).z + (eCol).z; }; + } + + *result = rad; + return; + } + + if (obj->refl == DIFF) { + specularBounce = 0; + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + + + Vec Ld; + SampleLights(spheres, sphereCount, seed0, seed1, &hitPoint, &nl, &Ld); + { (Ld).x = (throughput).x * (Ld).x; (Ld).y = (throughput).y * (Ld).y; (Ld).z = (throughput).z * (Ld).z; }; + { (rad).x = (rad).x + (Ld).x; (rad).y = (rad).y + (Ld).y; (rad).z = (rad).z + (Ld).z; }; + + *result = rad; + return; + } else if (obj->refl == SPEC) { + specularBounce = 1; + + Vec newDir; + { float k = (2.f * ((normal).x * (currentRay.d).x + (normal).y * (currentRay.d).y + (normal).z * (currentRay.d).z)); { (newDir).x = k * (normal).x; (newDir).y = k * (normal).y; (newDir).z = k * (normal).z; } }; + { (newDir).x = (currentRay.d).x - (newDir).x; (newDir).y = (currentRay.d).y - (newDir).y; (newDir).z = (currentRay.d).z - (newDir).z; }; + + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = (hitPoint).x; ((currentRay).o).y = (hitPoint).y; ((currentRay).o).z = (hitPoint).z; }; { ((currentRay).d).x = (newDir).x; ((currentRay).d).y = (newDir).y; ((currentRay).d).z = (newDir).z; }; }; + continue; + } else { + specularBounce = 1; + + Vec newDir; + { float k = (2.f * ((normal).x * (currentRay.d).x + (normal).y * (currentRay.d).y + (normal).z * (currentRay.d).z)); { (newDir).x = k * (normal).x; (newDir).y = k * (normal).y; (newDir).z = k * (normal).z; } }; + { (newDir).x = (currentRay.d).x - (newDir).x; (newDir).y = (currentRay.d).y - (newDir).y; (newDir).z = (currentRay.d).z - (newDir).z; }; + + Ray reflRay; { { ((reflRay).o).x = (hitPoint).x; ((reflRay).o).y = (hitPoint).y; ((reflRay).o).z = (hitPoint).z; }; { ((reflRay).d).x = (newDir).x; ((reflRay).d).y = (newDir).y; ((reflRay).d).z = (newDir).z; }; }; + int into = (((normal).x * (nl).x + (normal).y * (nl).y + (normal).z * (nl).z) > 0); + + float nc = 1.f; + float nt = 1.5f; + float nnt = into ? nc / nt : nt / nc; + float ddn = ((currentRay.d).x * (nl).x + (currentRay.d).y * (nl).y + (currentRay.d).z * (nl).z); + float cos2t = 1.f - nnt * nnt * (1.f - ddn * ddn); + + if (cos2t < 0.f) { + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = ((reflRay).o).x; ((currentRay).o).y = ((reflRay).o).y; ((currentRay).o).z = ((reflRay).o).z; }; { ((currentRay).d).x = ((reflRay).d).x; ((currentRay).d).y = ((reflRay).d).y; ((currentRay).d).z = ((reflRay).d).z; }; }; + continue; + } + + float kk = (into ? 1 : -1) * (ddn * nnt + sqrt(cos2t)); + Vec nkk; + { float k = (kk); { (nkk).x = k * (normal).x; (nkk).y = k * (normal).y; (nkk).z = k * (normal).z; } }; + Vec transDir; + { float k = (nnt); { (transDir).x = k * (currentRay.d).x; (transDir).y = k * (currentRay.d).y; (transDir).z = k * (currentRay.d).z; } }; + { (transDir).x = (transDir).x - (nkk).x; (transDir).y = (transDir).y - (nkk).y; (transDir).z = (transDir).z - (nkk).z; }; + { float l = 1.f / sqrt(((transDir).x * (transDir).x + (transDir).y * (transDir).y + (transDir).z * (transDir).z)); { float k = (l); { (transDir).x = k * (transDir).x; (transDir).y = k * (transDir).y; (transDir).z = k * (transDir).z; } }; }; + + float a = nt - nc; + float b = nt + nc; + float R0 = a * a / (b * b); + float c = 1 - (into ? -ddn : ((transDir).x * (normal).x + (transDir).y * (normal).y + (transDir).z * (normal).z)); + + float Re = R0 + (1 - R0) * c * c * c * c*c; + float Tr = 1.f - Re; + float P = .25f + .5f * Re; + float RP = Re / P; + float TP = Tr / (1.f - P); + + if (GetRandom(seed0, seed1) < P) { + { float k = (RP); { (throughput).x = k * (throughput).x; (throughput).y = k * (throughput).y; (throughput).z = k * (throughput).z; } }; + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = ((reflRay).o).x; ((currentRay).o).y = ((reflRay).o).y; ((currentRay).o).z = ((reflRay).o).z; }; { ((currentRay).d).x = ((reflRay).d).x; ((currentRay).d).y = ((reflRay).d).y; ((currentRay).d).z = ((reflRay).d).z; }; }; + continue; + } else { + { float k = (TP); { (throughput).x = k * (throughput).x; (throughput).y = k * (throughput).y; (throughput).z = k * (throughput).z; } }; + { (throughput).x = (throughput).x * (obj->c).x; (throughput).y = (throughput).y * (obj->c).y; (throughput).z = (throughput).z * (obj->c).z; }; + + { { ((currentRay).o).x = (hitPoint).x; ((currentRay).o).y = (hitPoint).y; ((currentRay).o).z = (hitPoint).z; }; { ((currentRay).d).x = (transDir).x; ((currentRay).d).y = (transDir).y; ((currentRay).d).z = (transDir).z; }; }; + continue; + } + } + } +} +# 28 "" 2 + +static void GenerateCameraRay(__constant Camera *camera, + unsigned int *seed0, unsigned int *seed1, + const int width, const int height, const int x, const int y, Ray *ray) { + const float invWidth = 1.f / width; + const float invHeight = 1.f / height; + const float r1 = GetRandom(seed0, seed1) - .5f; + const float r2 = GetRandom(seed0, seed1) - .5f; + const float kcx = (x + r1) * invWidth - .5f; + const float kcy = (y + r2) * invHeight - .5f; + + Vec rdir; + { (rdir).x = camera->x.x * kcx + camera->y.x * kcy + camera->dir.x; (rdir).y = camera->x.y * kcx + camera->y.y * kcy + camera->dir.y; (rdir).z = camera->x.z * kcx + camera->y.z * kcy + camera->dir.z; }; + + + + + Vec rorig; + { float k = (0.1f); { (rorig).x = k * (rdir).x; (rorig).y = k * (rdir).y; (rorig).z = k * (rdir).z; } }; + { (rorig).x = (rorig).x + (camera->orig).x; (rorig).y = (rorig).y + (camera->orig).y; (rorig).z = (rorig).z + (camera->orig).z; } + + { float l = 1.f / sqrt(((rdir).x * (rdir).x + (rdir).y * (rdir).y + (rdir).z * (rdir).z)); { float k = (l); { (rdir).x = k * (rdir).x; (rdir).y = k * (rdir).y; (rdir).z = k * (rdir).z; } }; }; + { { ((*ray).o).x = (rorig).x; ((*ray).o).y = (rorig).y; ((*ray).o).z = (rorig).z; }; { ((*ray).d).x = (rdir).x; ((*ray).d).y = (rdir).y; ((*ray).d).z = (rdir).z; }; }; +} + +__kernel void RadianceGPU( + __global Vec *colors, __global unsigned int *seedsInput, + __constant Sphere *sphere, __constant Camera *camera, + const unsigned int sphereCount, + const int width, const int height, + const int currentSample, + __global int *pixels) { + const int gid = get_global_id(0); + const int gid2 = 2 * gid; + const int x = gid % width; + const int y = gid / width; + + + if (y >= height) + return; + + + unsigned int seed0 = seedsInput[gid2]; + unsigned int seed1 = seedsInput[gid2 + 1]; + + Ray ray; + GenerateCameraRay(camera, &seed0, &seed1, width, height, x, y, &ray); + + Vec r; + RadianceDirectLighting(sphere, sphereCount, &ray, &seed0, &seed1, &r); + + const int i = (height - y - 1) * width + x; + if (currentSample == 0) { + + { (colors[i]).x = (r).x; (colors[i]).y = (r).y; (colors[i]).z = (r).z; }; + } else { + const float k1 = currentSample; + const float k2 = 1.f / (currentSample + 1.f); + colors[i].x = (colors[i].x * k1 + r.x) * k2; + colors[i].y = (colors[i].y * k1 + r.y) * k2; + colors[i].z = (colors[i].z * k1 + r.z) * k2; + } + + pixels[y * width + x] = ((int)(pow(clamp(colors[i].x, 0.f, 1.f), 1.f / 2.2f) * 255.f + .5f)) | + (((int)(pow(clamp(colors[i].y, 0.f, 1.f), 1.f / 2.2f) * 255.f + .5f)) << 8) | + (((int)(pow(clamp(colors[i].z, 0.f, 1.f), 1.f / 2.2f) * 255.f + .5f)) << 16); + + seedsInput[gid2] = seed0; + seedsInput[gid2 + 1] = seed1; +} diff --git a/rendering_kernel.cl b/rendering_kernel.cl new file mode 100644 index 0000000..593b321 --- /dev/null +++ b/rendering_kernel.cl @@ -0,0 +1,97 @@ +/* +Copyright (c) 2009 David Bucciarelli (davibu@interfree.it) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#define GPU_KERNEL + +#include "camera.h" +#include "geomfunc.h" + +static void GenerateCameraRay(OCL_CONSTANT_BUFFER Camera *camera, + unsigned int *seed0, unsigned int *seed1, + const int width, const int height, const int x, const int y, Ray *ray) { + const float invWidth = 1.f / width; + const float invHeight = 1.f / height; + const float r1 = GetRandom(seed0, seed1) - .5f; + const float r2 = GetRandom(seed0, seed1) - .5f; + const float kcx = (x + r1) * invWidth - .5f; + const float kcy = (y + r2) * invHeight - .5f; + + Vec rdir; + vinit(rdir, + camera->x.x * kcx + camera->y.x * kcy + camera->dir.x, + camera->x.y * kcx + camera->y.y * kcy + camera->dir.y, + camera->x.z * kcx + camera->y.z * kcy + camera->dir.z); + + Vec rorig; + vsmul(rorig, 0.1f, rdir); + vadd(rorig, rorig, camera->orig) + + vnorm(rdir); + rinit(*ray, rorig, rdir); +} + +__kernel void RadianceGPU( + __global Vec *colors, __global unsigned int *seedsInput, + OCL_CONSTANT_BUFFER Sphere *sphere, OCL_CONSTANT_BUFFER Camera *camera, + const unsigned int sphereCount, + const int width, const int height, + const int currentSample, + __global int *pixels) { + const int gid = get_global_id(0); + const int gid2 = 2 * gid; + const int x = gid % width; + const int y = gid / width; + + /* Check if we have to do something */ + if (y >= height) + return; + + /* LordCRC: move seed to local store */ + unsigned int seed0 = seedsInput[gid2]; + unsigned int seed1 = seedsInput[gid2 + 1]; + + Ray ray; + GenerateCameraRay(camera, &seed0, &seed1, width, height, x, y, &ray); + + Vec r; + RadiancePathTracing(sphere, sphereCount, &ray, &seed0, &seed1, &r); + + const int i = (height - y - 1) * width + x; + if (currentSample == 0) { + // Jens's patch for MacOS + vassign(colors[i], r); + } else { + const float k1 = currentSample; + const float k2 = 1.f / (currentSample + 1.f); + colors[i].x = (colors[i].x * k1 + r.x) * k2; + colors[i].y = (colors[i].y * k1 + r.y) * k2; + colors[i].z = (colors[i].z * k1 + r.z) * k2; + } + + pixels[y * width + x] = toInt(colors[i].x) | + (toInt(colors[i].y) << 8) | + (toInt(colors[i].z) << 16); + + seedsInput[gid2] = seed0; + seedsInput[gid2 + 1] = seed1; +} diff --git a/rendering_kernel_dl.cl b/rendering_kernel_dl.cl new file mode 100644 index 0000000..c4c3a04 --- /dev/null +++ b/rendering_kernel_dl.cl @@ -0,0 +1,97 @@ +/* +Copyright (c) 2009 David Bucciarelli (davibu@interfree.it) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#define GPU_KERNEL + +#include "camera.h" +#include "geomfunc.h" + +static void GenerateCameraRay(OCL_CONSTANT_BUFFER Camera *camera, + unsigned int *seed0, unsigned int *seed1, + const int width, const int height, const int x, const int y, Ray *ray) { + const float invWidth = 1.f / width; + const float invHeight = 1.f / height; + const float r1 = GetRandom(seed0, seed1) - .5f; + const float r2 = GetRandom(seed0, seed1) - .5f; + const float kcx = (x + r1) * invWidth - .5f; + const float kcy = (y + r2) * invHeight - .5f; + + Vec rdir; + vinit(rdir, + camera->x.x * kcx + camera->y.x * kcy + camera->dir.x, + camera->x.y * kcx + camera->y.y * kcy + camera->dir.y, + camera->x.z * kcx + camera->y.z * kcy + camera->dir.z); + + Vec rorig; + vsmul(rorig, 0.1f, rdir); + vadd(rorig, rorig, camera->orig) + + vnorm(rdir); + rinit(*ray, rorig, rdir); +} + +__kernel void RadianceGPU( + __global Vec *colors, __global unsigned int *seedsInput, + OCL_CONSTANT_BUFFER Sphere *sphere, OCL_CONSTANT_BUFFER Camera *camera, + const unsigned int sphereCount, + const int width, const int height, + const int currentSample, + __global int *pixels) { + const int gid = get_global_id(0); + const int gid2 = 2 * gid; + const int x = gid % width; + const int y = gid / width; + + /* Check if we have to do something */ + if (y >= height) + return; + + /* LordCRC: move seed to local store */ + unsigned int seed0 = seedsInput[gid2]; + unsigned int seed1 = seedsInput[gid2 + 1]; + + Ray ray; + GenerateCameraRay(camera, &seed0, &seed1, width, height, x, y, &ray); + + Vec r; + RadianceDirectLighting(sphere, sphereCount, &ray, &seed0, &seed1, &r); + + const int i = (height - y - 1) * width + x; + if (currentSample == 0) { + // Jens's patch for MacOS + vassign(colors[i], r); + } else { + const float k1 = currentSample; + const float k2 = 1.f / (currentSample + 1.f); + colors[i].x = (colors[i].x * k1 + r.x) * k2; + colors[i].y = (colors[i].y * k1 + r.y) * k2; + colors[i].z = (colors[i].z * k1 + r.z) * k2; + } + + pixels[y * width + x] = toInt(colors[i].x) | + (toInt(colors[i].y) << 8) | + (toInt(colors[i].z) << 16); + + seedsInput[gid2] = seed0; + seedsInput[gid2 + 1] = seed1; +} diff --git a/scene.h b/scene.h new file mode 100644 index 0000000..7dfec0d --- /dev/null +++ b/scene.h @@ -0,0 +1,53 @@ +/* +Copyright (c) 2009 David Bucciarelli (davibu@interfree.it) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef _SCENE_H +#define _SCENE_H + +#include "geomfunc.h" + +#define WALL_RAD 1e4f +static Sphere CornellSpheres[] = { /* Scene: radius, position, emission, color, material */ + { WALL_RAD, {WALL_RAD + 1.f, 40.8f, 81.6f}, {0.f, 0.f, 0.f}, {.75f, .25f, .25f}, DIFF }, /* Left */ + { WALL_RAD, {-WALL_RAD + 99.f, 40.8f, 81.6f}, {0.f, 0.f, 0.f}, {.25f, .25f, .75f}, DIFF }, /* Rght */ + { WALL_RAD, {50.f, 40.8f, WALL_RAD}, {0.f, 0.f, 0.f}, {.75f, .75f, .75f}, DIFF }, /* Back */ + { WALL_RAD, {50.f, 40.8f, -WALL_RAD + 270.f}, {0.f, 0.f, 0.f}, {0.f, 0.f, 0.f}, DIFF }, /* Frnt */ + { WALL_RAD, {50.f, WALL_RAD, 81.6f}, {0.f, 0.f, 0.f}, {.75f, .75f, .75f}, DIFF }, /* Botm */ + { WALL_RAD, {50.f, -WALL_RAD + 81.6f, 81.6f}, {0.f, 0.f, 0.f}, {.75f, .75f, .75f}, DIFF }, /* Top */ + { 16.5f, {27.f, 16.5f, 47.f}, {0.f, 0.f, 0.f}, {.9f, .9f, .9f}, SPEC }, /* Mirr */ + { 16.5f, {73.f, 16.5f, 78.f}, {0.f, 0.f, 0.f}, {.9f, .9f, .9f}, REFR }, /* Glas */ + { 7.f, {50.f, 81.6f - 15.f, 81.6f}, {12.f, 12.f, 12.f}, {0.f, 0.f, 0.f}, DIFF } /* Lite */ +}; + +#ifdef SCENE_TEST +static const Sphere spheres[] = { /* Scene: radius, position, emission, color, material */ + { 1000.f, {0.f, -1000.f, 0.f}, {0.f, 0.f, 0.f}, {.75f, .75f, .75f}, DIFF }, /* Ground */ + { 15.f, {10.f, 15.f, 0.0f}, {0.f, 0.f, 0.f}, {.75f, 0.f, 0.f}, DIFF }, /* Red */ + { 20.f, {-40.f, 20.f, 0.0f}, {0.f, 0.f, 0.f}, {0.f, 0.f, .75f}, DIFF }, /* Blue */ + { 10.f, {-5.f, 10.f, 20.0f}, {0.f, 0.f, 0.f}, {0.f, .75f, .0f}, DIFF }, /* Blue */ + { 10.f, {-30.f, 100.0f, 20.f}, {12.f, 12.f, 12.f}, {0.f, 0.f, 0.f}, DIFF } /* Lite */ +}; +#endif + +#endif /* _SCENE_H */ + diff --git a/scene_build_complex.pl b/scene_build_complex.pl new file mode 100644 index 0000000..bb1c881 --- /dev/null +++ b/scene_build_complex.pl @@ -0,0 +1,60 @@ +#!/usr/bin/perl + +$maxDepth = 4.0; + +sub PrintSphere { + my $depth = shift; + my $posx = shift; + my $posy = shift; + my $posz = shift; + my $rad = shift; + + my $k = $depth / $maxDepth; + my $col1 = 0.75 * $k; + my $col2 = 0.75 * (1.0 - $k); + + print "sphere $rad $posx $posy $posz 0 0 0 $col2 0 $col1 0\n"; +} + +sub HyperSphere { + my $depth = shift; + if ($depth <= $maxDepth) { + my $posx = shift; + my $posy = shift; + my $posz = shift; + my $rad = shift; + my $direction = shift; + + PrintSphere($depth, $posx, $posy, $posz, $rad); + + my $newRad = $rad / 2.0; + if ($direction != 0) { + HyperSphere($depth + 1.0, $posx - $rad - $newRad, $posy, $posz, $newRad, 1); + } + if ($direction != 1) { + HyperSphere($depth + 1.0, $posx + $rad + $newRad, $posy, $posz, $newRad, 0); + } + if ($direction != 2) { + HyperSphere($depth + 1.0, $posx, $posy - $rad - $newRad, $posz, $newRad, 3); + } + if ($direction != 3) { + HyperSphere($depth + 1.0, $posx, $posy + $rad + $newRad, $posz, $newRad, 2); + } + if ($direction != 4) { + HyperSphere($depth + 1.0, $posx, $posy, $posz - $rad - $newRad, $newRad, 5); + } + if ($direction != 5) { + HyperSphere($depth + 1.0, $posx, $posy, $posz + $rad + $newRad, $newRad, 4); + } + } +} + +# Directions: +# 0 - from -x +# 1 - from +x +# 2 - from -y +# 3 - from +y +# 4 - from -z +# 5 - from +z + +HyperSphere(0.0, 0.0, 0.0, 0.0, 15.0, 2); \ No newline at end of file diff --git a/scenes/caustic.scn b/scenes/caustic.scn new file mode 100644 index 0000000..87a7799 --- /dev/null +++ b/scenes/caustic.scn @@ -0,0 +1,5 @@ +camera 20 80 300 0 40 0 +size 3 +sphere 1000 0 -1000 0 0 0 0 0.75 0.75 0.75 0 +sphere 15 0 30 0 0 0 0 0.9 0.9 0.9 2 +sphere 15 0 100 0 15 15 15 0 0 0 0 diff --git a/scenes/caustic3.scn b/scenes/caustic3.scn new file mode 100644 index 0000000..f4c8935 --- /dev/null +++ b/scenes/caustic3.scn @@ -0,0 +1,7 @@ +camera 20 100 300 0 25 0 +size 5 +sphere 1000 0 -1000 0 0 0 0 0.75 0.75 0.75 0 +sphere 10 35 15 0 0 0 0 0.9 0 0 2 +sphere 15 -35 20 0 0 0 0 0 0.9 0 2 +sphere 20 0 25 -35 0 0 0 0 0 0.9 2 +sphere 8 0 60 0 15 15 15 0 0 0 0 diff --git a/scenes/complex.scn b/scenes/complex.scn new file mode 100644 index 0000000..1cf69c3 --- /dev/null +++ b/scenes/complex.scn @@ -0,0 +1,785 @@ +camera 20 80 150 0 15 0 +size 783 +sphere 8 50 80 90 25 25 25 0 0 0 0 +sphere 10000 0 -10050 0 0 0 0 0.75 0.75 0.75 0 +sphere 15 0 0 0 0 0 0 0.75 0 0 0 +sphere 7.5 -22.5 0 0 0 0 0 0.5625 0 0.1875 0 +sphere 3.75 -33.75 0 0 0 0 0 0.375 0 0.375 0 +sphere 1.875 -39.375 0 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -42.1875 0 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -39.375 -2.8125 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -39.375 2.8125 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -39.375 0 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -39.375 0 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 -33.75 -5.625 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -36.5625 -5.625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -30.9375 -5.625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -33.75 -8.4375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -33.75 -5.625 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -33.75 -5.625 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 -33.75 5.625 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -36.5625 5.625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -30.9375 5.625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -33.75 8.4375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -33.75 5.625 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -33.75 5.625 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 -33.75 0 -5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -36.5625 0 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -30.9375 0 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -33.75 -2.8125 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -33.75 2.8125 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -33.75 0 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 -33.75 0 5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -36.5625 0 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -30.9375 0 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -33.75 -2.8125 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -33.75 2.8125 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -33.75 0 8.4375 0 0 0 0 0 0.75 0 +sphere 3.75 -22.5 -11.25 0 0 0 0 0.375 0 0.375 0 +sphere 1.875 -28.125 -11.25 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -30.9375 -11.25 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 -14.0625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 -8.4375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 -11.25 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 -11.25 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 -16.875 -11.25 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 -11.25 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 -14.0625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 -8.4375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 -11.25 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 -11.25 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 -22.5 -16.875 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -25.3125 -16.875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -19.6875 -16.875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -19.6875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -16.875 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -16.875 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 -22.5 -11.25 -5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -25.3125 -11.25 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -19.6875 -11.25 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -14.0625 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -8.4375 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -11.25 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 -22.5 -11.25 5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -25.3125 -11.25 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -19.6875 -11.25 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -14.0625 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -8.4375 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -11.25 8.4375 0 0 0 0 0 0.75 0 +sphere 3.75 -22.5 11.25 0 0 0 0 0.375 0 0.375 0 +sphere 1.875 -28.125 11.25 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -30.9375 11.25 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 8.4375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 14.0625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 11.25 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 11.25 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 -16.875 11.25 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 11.25 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 8.4375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 14.0625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 11.25 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 11.25 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 -22.5 16.875 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -25.3125 16.875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -19.6875 16.875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 19.6875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 16.875 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 16.875 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 -22.5 11.25 -5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -25.3125 11.25 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -19.6875 11.25 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 8.4375 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 14.0625 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 11.25 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 -22.5 11.25 5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -25.3125 11.25 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -19.6875 11.25 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 8.4375 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 14.0625 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 11.25 8.4375 0 0 0 0 0 0.75 0 +sphere 3.75 -22.5 0 -11.25 0 0 0 0.375 0 0.375 0 +sphere 1.875 -28.125 0 -11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -30.9375 0 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 -2.8125 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 2.8125 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 0 -14.0625 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 0 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 -16.875 0 -11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 0 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 -2.8125 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 2.8125 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 0 -14.0625 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 0 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 -22.5 -5.625 -11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -25.3125 -5.625 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -19.6875 -5.625 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -8.4375 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -5.625 -14.0625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -5.625 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 -22.5 5.625 -11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -25.3125 5.625 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -19.6875 5.625 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 8.4375 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 5.625 -14.0625 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 5.625 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 -22.5 0 -16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -25.3125 0 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -19.6875 0 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -2.8125 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 2.8125 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 0 -19.6875 0 0 0 0 0 0.75 0 +sphere 3.75 -22.5 0 11.25 0 0 0 0.375 0 0.375 0 +sphere 1.875 -28.125 0 11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -30.9375 0 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 -2.8125 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 2.8125 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 0 8.4375 0 0 0 0 0 0.75 0 +sphere 0.9375 -28.125 0 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 -16.875 0 11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 0 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 -2.8125 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 2.8125 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 0 8.4375 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 0 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 -22.5 -5.625 11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -25.3125 -5.625 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -19.6875 -5.625 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -8.4375 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -5.625 8.4375 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -5.625 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 -22.5 5.625 11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -25.3125 5.625 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -19.6875 5.625 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 8.4375 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 5.625 8.4375 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 5.625 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 -22.5 0 16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -25.3125 0 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -19.6875 0 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 -2.8125 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 2.8125 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -22.5 0 19.6875 0 0 0 0 0 0.75 0 +sphere 7.5 22.5 0 0 0 0 0 0.5625 0 0.1875 0 +sphere 3.75 33.75 0 0 0 0 0 0.375 0 0.375 0 +sphere 1.875 39.375 0 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 42.1875 0 0 0 0 0 0 0 0.75 0 +sphere 0.9375 39.375 -2.8125 0 0 0 0 0 0 0.75 0 +sphere 0.9375 39.375 2.8125 0 0 0 0 0 0 0.75 0 +sphere 0.9375 39.375 0 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 39.375 0 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 33.75 -5.625 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 30.9375 -5.625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 36.5625 -5.625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 33.75 -8.4375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 33.75 -5.625 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 33.75 -5.625 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 33.75 5.625 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 30.9375 5.625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 36.5625 5.625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 33.75 8.4375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 33.75 5.625 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 33.75 5.625 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 33.75 0 -5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 30.9375 0 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 36.5625 0 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 33.75 -2.8125 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 33.75 2.8125 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 33.75 0 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 33.75 0 5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 30.9375 0 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 36.5625 0 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 33.75 -2.8125 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 33.75 2.8125 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 33.75 0 8.4375 0 0 0 0 0 0.75 0 +sphere 3.75 22.5 -11.25 0 0 0 0 0.375 0 0.375 0 +sphere 1.875 16.875 -11.25 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 14.0625 -11.25 0 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 -14.0625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 -8.4375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 -11.25 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 -11.25 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 28.125 -11.25 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 30.9375 -11.25 0 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 -14.0625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 -8.4375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 -11.25 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 -11.25 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 22.5 -16.875 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 -16.875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 25.3125 -16.875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -19.6875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -16.875 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -16.875 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 22.5 -11.25 -5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 -11.25 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 25.3125 -11.25 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -14.0625 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -8.4375 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -11.25 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 22.5 -11.25 5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 -11.25 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 25.3125 -11.25 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -14.0625 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -8.4375 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -11.25 8.4375 0 0 0 0 0 0.75 0 +sphere 3.75 22.5 11.25 0 0 0 0 0.375 0 0.375 0 +sphere 1.875 16.875 11.25 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 14.0625 11.25 0 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 8.4375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 14.0625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 11.25 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 11.25 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 28.125 11.25 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 30.9375 11.25 0 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 8.4375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 14.0625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 11.25 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 11.25 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 22.5 16.875 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 16.875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 25.3125 16.875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 19.6875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 16.875 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 16.875 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 22.5 11.25 -5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 11.25 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 25.3125 11.25 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 8.4375 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 14.0625 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 11.25 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 22.5 11.25 5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 11.25 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 25.3125 11.25 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 8.4375 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 14.0625 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 11.25 8.4375 0 0 0 0 0 0.75 0 +sphere 3.75 22.5 0 -11.25 0 0 0 0.375 0 0.375 0 +sphere 1.875 16.875 0 -11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 14.0625 0 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 -2.8125 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 2.8125 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 0 -14.0625 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 0 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 28.125 0 -11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 30.9375 0 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 -2.8125 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 2.8125 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 0 -14.0625 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 0 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 22.5 -5.625 -11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 -5.625 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 25.3125 -5.625 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -8.4375 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -5.625 -14.0625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -5.625 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 22.5 5.625 -11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 5.625 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 25.3125 5.625 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 8.4375 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 5.625 -14.0625 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 5.625 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 22.5 0 -16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 0 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 25.3125 0 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -2.8125 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 2.8125 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 0 -19.6875 0 0 0 0 0 0.75 0 +sphere 3.75 22.5 0 11.25 0 0 0 0.375 0 0.375 0 +sphere 1.875 16.875 0 11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 14.0625 0 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 -2.8125 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 2.8125 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 0 8.4375 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 0 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 28.125 0 11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 30.9375 0 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 -2.8125 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 2.8125 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 0 8.4375 0 0 0 0 0 0.75 0 +sphere 0.9375 28.125 0 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 22.5 -5.625 11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 -5.625 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 25.3125 -5.625 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -8.4375 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -5.625 8.4375 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -5.625 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 22.5 5.625 11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 5.625 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 25.3125 5.625 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 8.4375 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 5.625 8.4375 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 5.625 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 22.5 0 16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 0 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 25.3125 0 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 -2.8125 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 2.8125 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 22.5 0 19.6875 0 0 0 0 0 0.75 0 +sphere 7.5 0 22.5 0 0 0 0 0.5625 0 0.1875 0 +sphere 3.75 -11.25 22.5 0 0 0 0 0.375 0 0.375 0 +sphere 1.875 -16.875 22.5 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -19.6875 22.5 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 19.6875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 25.3125 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 22.5 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 22.5 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 -11.25 16.875 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 16.875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -8.4375 16.875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 14.0625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 16.875 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 16.875 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 -11.25 28.125 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 28.125 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -8.4375 28.125 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 30.9375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 28.125 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 28.125 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 -11.25 22.5 -5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 22.5 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -8.4375 22.5 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 19.6875 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 25.3125 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 22.5 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 -11.25 22.5 5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 22.5 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -8.4375 22.5 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 19.6875 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 25.3125 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 22.5 8.4375 0 0 0 0 0 0.75 0 +sphere 3.75 11.25 22.5 0 0 0 0 0.375 0 0.375 0 +sphere 1.875 16.875 22.5 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 22.5 0 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 19.6875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 25.3125 0 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 22.5 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 22.5 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 11.25 16.875 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 16.875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 14.0625 16.875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 14.0625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 16.875 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 16.875 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 11.25 28.125 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 28.125 0 0 0 0 0 0 0.75 0 +sphere 0.9375 14.0625 28.125 0 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 30.9375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 28.125 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 28.125 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 11.25 22.5 -5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 22.5 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 14.0625 22.5 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 19.6875 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 25.3125 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 22.5 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 11.25 22.5 5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 22.5 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 14.0625 22.5 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 19.6875 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 25.3125 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 22.5 8.4375 0 0 0 0 0 0.75 0 +sphere 3.75 0 33.75 0 0 0 0 0.375 0 0.375 0 +sphere 1.875 -5.625 33.75 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -8.4375 33.75 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 30.9375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 36.5625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 33.75 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 33.75 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 5.625 33.75 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 33.75 0 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 30.9375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 36.5625 0 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 33.75 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 33.75 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 0 39.375 0 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 39.375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 39.375 0 0 0 0 0 0 0.75 0 +sphere 0.9375 0 42.1875 0 0 0 0 0 0 0.75 0 +sphere 0.9375 0 39.375 -2.8125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 39.375 2.8125 0 0 0 0 0 0.75 0 +sphere 1.875 0 33.75 -5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 33.75 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 33.75 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 0 30.9375 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 0 36.5625 -5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 0 33.75 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 0 33.75 5.625 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 33.75 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 33.75 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 0 30.9375 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 0 36.5625 5.625 0 0 0 0 0 0.75 0 +sphere 0.9375 0 33.75 8.4375 0 0 0 0 0 0.75 0 +sphere 3.75 0 22.5 -11.25 0 0 0 0.375 0 0.375 0 +sphere 1.875 -5.625 22.5 -11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -8.4375 22.5 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 19.6875 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 25.3125 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 22.5 -14.0625 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 22.5 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 5.625 22.5 -11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 22.5 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 19.6875 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 25.3125 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 22.5 -14.0625 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 22.5 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 0 16.875 -11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 16.875 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 16.875 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 0 14.0625 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 0 16.875 -14.0625 0 0 0 0 0 0.75 0 +sphere 0.9375 0 16.875 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 0 28.125 -11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 28.125 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 28.125 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 0 30.9375 -11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 0 28.125 -14.0625 0 0 0 0 0 0.75 0 +sphere 0.9375 0 28.125 -8.4375 0 0 0 0 0 0.75 0 +sphere 1.875 0 22.5 -16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 22.5 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 22.5 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 19.6875 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 25.3125 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 22.5 -19.6875 0 0 0 0 0 0.75 0 +sphere 3.75 0 22.5 11.25 0 0 0 0.375 0 0.375 0 +sphere 1.875 -5.625 22.5 11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -8.4375 22.5 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 19.6875 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 25.3125 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 22.5 8.4375 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 22.5 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 5.625 22.5 11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 22.5 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 19.6875 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 25.3125 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 22.5 8.4375 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 22.5 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 0 16.875 11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 16.875 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 16.875 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 0 14.0625 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 0 16.875 8.4375 0 0 0 0 0 0.75 0 +sphere 0.9375 0 16.875 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 0 28.125 11.25 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 28.125 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 28.125 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 0 30.9375 11.25 0 0 0 0 0 0.75 0 +sphere 0.9375 0 28.125 8.4375 0 0 0 0 0 0.75 0 +sphere 0.9375 0 28.125 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 0 22.5 16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 22.5 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 22.5 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 19.6875 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 25.3125 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 22.5 19.6875 0 0 0 0 0 0.75 0 +sphere 7.5 0 0 -22.5 0 0 0 0.5625 0 0.1875 0 +sphere 3.75 -11.25 0 -22.5 0 0 0 0.375 0 0.375 0 +sphere 1.875 -16.875 0 -22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -19.6875 0 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 -2.8125 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 2.8125 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 0 -25.3125 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 0 -19.6875 0 0 0 0 0 0.75 0 +sphere 1.875 -11.25 -5.625 -22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 -5.625 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -8.4375 -5.625 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 -8.4375 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 -5.625 -25.3125 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 -5.625 -19.6875 0 0 0 0 0 0.75 0 +sphere 1.875 -11.25 5.625 -22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 5.625 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -8.4375 5.625 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 8.4375 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 5.625 -25.3125 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 5.625 -19.6875 0 0 0 0 0 0.75 0 +sphere 1.875 -11.25 0 -28.125 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 0 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 -8.4375 0 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 -2.8125 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 2.8125 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 0 -30.9375 0 0 0 0 0 0.75 0 +sphere 1.875 -11.25 0 -16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 0 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -8.4375 0 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 -2.8125 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 2.8125 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 0 -14.0625 0 0 0 0 0 0.75 0 +sphere 3.75 11.25 0 -22.5 0 0 0 0.375 0 0.375 0 +sphere 1.875 16.875 0 -22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 0 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 -2.8125 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 2.8125 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 0 -25.3125 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 0 -19.6875 0 0 0 0 0 0.75 0 +sphere 1.875 11.25 -5.625 -22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 -5.625 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 14.0625 -5.625 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 -8.4375 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 -5.625 -25.3125 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 -5.625 -19.6875 0 0 0 0 0 0.75 0 +sphere 1.875 11.25 5.625 -22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 5.625 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 14.0625 5.625 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 8.4375 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 5.625 -25.3125 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 5.625 -19.6875 0 0 0 0 0 0.75 0 +sphere 1.875 11.25 0 -28.125 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 0 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 14.0625 0 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 -2.8125 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 2.8125 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 0 -30.9375 0 0 0 0 0 0.75 0 +sphere 1.875 11.25 0 -16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 0 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 14.0625 0 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 -2.8125 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 2.8125 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 0 -14.0625 0 0 0 0 0 0.75 0 +sphere 3.75 0 -11.25 -22.5 0 0 0 0.375 0 0.375 0 +sphere 1.875 -5.625 -11.25 -22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -8.4375 -11.25 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 -14.0625 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 -8.4375 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 -11.25 -25.3125 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 -11.25 -19.6875 0 0 0 0 0 0.75 0 +sphere 1.875 5.625 -11.25 -22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 -11.25 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 -14.0625 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 -8.4375 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 -11.25 -25.3125 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 -11.25 -19.6875 0 0 0 0 0 0.75 0 +sphere 1.875 0 -16.875 -22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 -16.875 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 -16.875 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -19.6875 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -16.875 -25.3125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -16.875 -19.6875 0 0 0 0 0 0.75 0 +sphere 1.875 0 -11.25 -28.125 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 -11.25 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 -11.25 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -14.0625 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -8.4375 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -11.25 -30.9375 0 0 0 0 0 0.75 0 +sphere 1.875 0 -11.25 -16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 -11.25 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 -11.25 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -14.0625 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -8.4375 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -11.25 -14.0625 0 0 0 0 0 0.75 0 +sphere 3.75 0 11.25 -22.5 0 0 0 0.375 0 0.375 0 +sphere 1.875 -5.625 11.25 -22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -8.4375 11.25 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 8.4375 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 14.0625 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 11.25 -25.3125 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 11.25 -19.6875 0 0 0 0 0 0.75 0 +sphere 1.875 5.625 11.25 -22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 11.25 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 8.4375 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 14.0625 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 11.25 -25.3125 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 11.25 -19.6875 0 0 0 0 0 0.75 0 +sphere 1.875 0 16.875 -22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 16.875 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 16.875 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 0 19.6875 -22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 0 16.875 -25.3125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 16.875 -19.6875 0 0 0 0 0 0.75 0 +sphere 1.875 0 11.25 -28.125 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 11.25 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 11.25 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 8.4375 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 14.0625 -28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 11.25 -30.9375 0 0 0 0 0 0.75 0 +sphere 1.875 0 11.25 -16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 11.25 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 11.25 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 8.4375 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 14.0625 -16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 11.25 -14.0625 0 0 0 0 0 0.75 0 +sphere 3.75 0 0 -33.75 0 0 0 0.375 0 0.375 0 +sphere 1.875 -5.625 0 -33.75 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -8.4375 0 -33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 -2.8125 -33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 2.8125 -33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 0 -36.5625 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 0 -30.9375 0 0 0 0 0 0.75 0 +sphere 1.875 5.625 0 -33.75 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 0 -33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 -2.8125 -33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 2.8125 -33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 0 -36.5625 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 0 -30.9375 0 0 0 0 0 0.75 0 +sphere 1.875 0 -5.625 -33.75 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 -5.625 -33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 -5.625 -33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -8.4375 -33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -5.625 -36.5625 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -5.625 -30.9375 0 0 0 0 0 0.75 0 +sphere 1.875 0 5.625 -33.75 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 5.625 -33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 5.625 -33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 0 8.4375 -33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 0 5.625 -36.5625 0 0 0 0 0 0.75 0 +sphere 0.9375 0 5.625 -30.9375 0 0 0 0 0 0.75 0 +sphere 1.875 0 0 -39.375 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 0 -39.375 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 0 -39.375 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -2.8125 -39.375 0 0 0 0 0 0.75 0 +sphere 0.9375 0 2.8125 -39.375 0 0 0 0 0 0.75 0 +sphere 0.9375 0 0 -42.1875 0 0 0 0 0 0.75 0 +sphere 7.5 0 0 22.5 0 0 0 0.5625 0 0.1875 0 +sphere 3.75 -11.25 0 22.5 0 0 0 0.375 0 0.375 0 +sphere 1.875 -16.875 0 22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -19.6875 0 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 -2.8125 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 2.8125 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 0 19.6875 0 0 0 0 0 0.75 0 +sphere 0.9375 -16.875 0 25.3125 0 0 0 0 0 0.75 0 +sphere 1.875 -11.25 -5.625 22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 -5.625 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -8.4375 -5.625 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 -8.4375 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 -5.625 19.6875 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 -5.625 25.3125 0 0 0 0 0 0.75 0 +sphere 1.875 -11.25 5.625 22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 5.625 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -8.4375 5.625 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 8.4375 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 5.625 19.6875 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 5.625 25.3125 0 0 0 0 0 0.75 0 +sphere 1.875 -11.25 0 16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 0 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -8.4375 0 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 -2.8125 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 2.8125 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 0 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 -11.25 0 28.125 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -14.0625 0 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 -8.4375 0 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 -2.8125 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 2.8125 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 -11.25 0 30.9375 0 0 0 0 0 0.75 0 +sphere 3.75 11.25 0 22.5 0 0 0 0.375 0 0.375 0 +sphere 1.875 16.875 0 22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 19.6875 0 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 -2.8125 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 2.8125 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 0 19.6875 0 0 0 0 0 0.75 0 +sphere 0.9375 16.875 0 25.3125 0 0 0 0 0 0.75 0 +sphere 1.875 11.25 -5.625 22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 -5.625 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 14.0625 -5.625 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 -8.4375 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 -5.625 19.6875 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 -5.625 25.3125 0 0 0 0 0 0.75 0 +sphere 1.875 11.25 5.625 22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 5.625 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 14.0625 5.625 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 8.4375 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 5.625 19.6875 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 5.625 25.3125 0 0 0 0 0 0.75 0 +sphere 1.875 11.25 0 16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 0 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 14.0625 0 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 -2.8125 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 2.8125 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 0 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 11.25 0 28.125 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 0 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 14.0625 0 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 -2.8125 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 2.8125 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 11.25 0 30.9375 0 0 0 0 0 0.75 0 +sphere 3.75 0 -11.25 22.5 0 0 0 0.375 0 0.375 0 +sphere 1.875 -5.625 -11.25 22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -8.4375 -11.25 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 -14.0625 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 -8.4375 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 -11.25 19.6875 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 -11.25 25.3125 0 0 0 0 0 0.75 0 +sphere 1.875 5.625 -11.25 22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 -11.25 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 -14.0625 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 -8.4375 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 -11.25 19.6875 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 -11.25 25.3125 0 0 0 0 0 0.75 0 +sphere 1.875 0 -16.875 22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 -16.875 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 -16.875 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -19.6875 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -16.875 19.6875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -16.875 25.3125 0 0 0 0 0 0.75 0 +sphere 1.875 0 -11.25 16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 -11.25 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 -11.25 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -14.0625 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -8.4375 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -11.25 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 0 -11.25 28.125 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 -11.25 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 -11.25 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -14.0625 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -8.4375 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -11.25 30.9375 0 0 0 0 0 0.75 0 +sphere 3.75 0 11.25 22.5 0 0 0 0.375 0 0.375 0 +sphere 1.875 -5.625 11.25 22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -8.4375 11.25 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 8.4375 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 14.0625 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 11.25 19.6875 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 11.25 25.3125 0 0 0 0 0 0.75 0 +sphere 1.875 5.625 11.25 22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 11.25 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 8.4375 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 14.0625 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 11.25 19.6875 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 11.25 25.3125 0 0 0 0 0 0.75 0 +sphere 1.875 0 16.875 22.5 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 16.875 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 16.875 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 0 19.6875 22.5 0 0 0 0 0 0.75 0 +sphere 0.9375 0 16.875 19.6875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 16.875 25.3125 0 0 0 0 0 0.75 0 +sphere 1.875 0 11.25 16.875 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 11.25 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 11.25 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 8.4375 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 14.0625 16.875 0 0 0 0 0 0.75 0 +sphere 0.9375 0 11.25 14.0625 0 0 0 0 0 0.75 0 +sphere 1.875 0 11.25 28.125 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 11.25 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 11.25 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 8.4375 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 14.0625 28.125 0 0 0 0 0 0.75 0 +sphere 0.9375 0 11.25 30.9375 0 0 0 0 0 0.75 0 +sphere 3.75 0 0 33.75 0 0 0 0.375 0 0.375 0 +sphere 1.875 -5.625 0 33.75 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -8.4375 0 33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 -2.8125 33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 2.8125 33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 0 30.9375 0 0 0 0 0 0.75 0 +sphere 0.9375 -5.625 0 36.5625 0 0 0 0 0 0.75 0 +sphere 1.875 5.625 0 33.75 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 8.4375 0 33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 -2.8125 33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 2.8125 33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 0 30.9375 0 0 0 0 0 0.75 0 +sphere 0.9375 5.625 0 36.5625 0 0 0 0 0 0.75 0 +sphere 1.875 0 -5.625 33.75 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 -5.625 33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 -5.625 33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -8.4375 33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -5.625 30.9375 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -5.625 36.5625 0 0 0 0 0 0.75 0 +sphere 1.875 0 5.625 33.75 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 5.625 33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 5.625 33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 0 8.4375 33.75 0 0 0 0 0 0.75 0 +sphere 0.9375 0 5.625 30.9375 0 0 0 0 0 0.75 0 +sphere 0.9375 0 5.625 36.5625 0 0 0 0 0 0.75 0 +sphere 1.875 0 0 39.375 0 0 0 0.1875 0 0.5625 0 +sphere 0.9375 -2.8125 0 39.375 0 0 0 0 0 0.75 0 +sphere 0.9375 2.8125 0 39.375 0 0 0 0 0 0.75 0 +sphere 0.9375 0 -2.8125 39.375 0 0 0 0 0 0.75 0 +sphere 0.9375 0 2.8125 39.375 0 0 0 0 0 0.75 0 +sphere 0.9375 0 0 42.1875 0 0 0 0 0 0.75 0 diff --git a/scenes/cornell.scn b/scenes/cornell.scn new file mode 100644 index 0000000..5500fe1 --- /dev/null +++ b/scenes/cornell.scn @@ -0,0 +1,11 @@ +camera 50 45 205.6 50 44.957388 204.6 +size 9 +sphere 10000 10001 40.8 81.6 0 0 0 0.75 .25 0.25 0 +sphere 10000 -9901 40.8 81.6 0 0 0 0.25 .25 0.75 0 +sphere 10000 50 40.8 10000 0 0 0 0.75 .75 0.75 0 +sphere 10000 50 40.8 -9730 0 0 0 0 0 0 0 +sphere 10000 50 10000 81.6 0 0 0 0.75 .75 0.75 0 +sphere 10000 50 -9918.4 81.6 0 0 0 0.75 .75 0.75 0 +sphere 16.5 27 16.5 47 0 0 0 0.9 0.9 0.9 1 +sphere 16.5 73 16.5 78 0 0 0 0.9 0.9 0.9 2 +sphere 7 50 66.6 81.6 12 12 12 0 0 0 0 diff --git a/scenes/cornell_large.scn b/scenes/cornell_large.scn new file mode 100644 index 0000000..57ea6a6 --- /dev/null +++ b/scenes/cornell_large.scn @@ -0,0 +1,11 @@ +camera 50 45 295.6 50 44.957388 294.6 +size 9 +sphere 10000 10001 40.8 81.6 0 0 0 0.75 .25 0.25 0 +sphere 10000 -9801 40.8 81.6 0 0 0 0.25 .25 0.75 0 +sphere 10000 50 40.8 10000 0 0 0 0.75 .75 0.75 0 +sphere 10000 50 40.8 -9530 0 0 0 0.75 .75 0.75 0 +sphere 10000 50 10000 81.6 0 0 0 0.75 .75 0.75 0 +sphere 10000 50 -9818.4 81.6 0 0 0 0.75 .75 0.75 0 +sphere 16.5 27 16.5 47 0 0 0 0.9 0.9 0.9 1 +sphere 16.5 73 16.5 78 0 0 0 0.9 0.9 0.9 2 +sphere 7 50 66.6 81.6 12 12 12 0 0 0 0 diff --git a/scenes/simple.scn b/scenes/simple.scn new file mode 100644 index 0000000..5b8f1eb --- /dev/null +++ b/scenes/simple.scn @@ -0,0 +1,7 @@ +camera 20 80 300 0 15 0 +size 5 +sphere 1000 0 -1000 0 0 0 0 0.75 0.75 0.75 0 +sphere 10 35 10 0 0 0 0 0.75 0 0 0 +sphere 15 -35 15 0 0 0 0 0 0.75 0 0 +sphere 20 0 20 -35 0 0 0 0 0 0.75 0 +sphere 8 0 60 0 15 15 15 0 0 0 0 diff --git a/simplernd.h b/simplernd.h new file mode 100644 index 0000000..b9bdbf0 --- /dev/null +++ b/simplernd.h @@ -0,0 +1,53 @@ +/* +Copyright (c) 2009 David Bucciarelli (davibu@interfree.it) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef _SIMPLERND_H +#define _SIMPLERND_H + +/* + * A Simple Random number generator + * from http://en.wikipedia.org/wiki/Random_number_generator + */ + +#ifndef SMALLPT_GPU + +static float GetRandom(unsigned int *seed0, unsigned int *seed1) { + *seed0 = 36969 * ((*seed0) & 65535) + ((*seed0) >> 16); + *seed1 = 18000 * ((*seed1) & 65535) + ((*seed1) >> 16); + + unsigned int ires = ((*seed0) << 16) + (*seed1); + + /* Convert to float */ + union { + float f; + unsigned int ui; + } res; + res.ui = (ires & 0x007fffff) | 0x40000000; + + return (res.f - 2.f) / 2.f; +} + +#endif + +#endif /* _SIMPLERND_H */ + diff --git a/smallptCPU b/smallptCPU new file mode 100644 index 0000000000000000000000000000000000000000..66e38687c42628c1d7599879943686631b5ffbe6 GIT binary patch literal 39244 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 +#include +#include +#include +#include + +#include "camera.h" +#include "scene.h" +#include "displayfunc.h" + +int workGroupSize = 1; + +static Vec *colors; +static unsigned int *seeds; +Camera camera; +static int currentSample = 0; +Sphere *spheres; +unsigned int sphereCount; + +void FreeBuffers() { + free(seeds); + free(colors); + free(pixels); +} + +void AllocateBuffers() { + const int pixelCount = height * width ; + int i; + colors = malloc(sizeof(Vec[pixelCount])); + + seeds = malloc(sizeof(unsigned int[pixelCount * 2])); + for (i = 0; i < pixelCount * 2; i++) { + seeds[i] = rand(); + if (seeds[i] < 2) + seeds[i] = 2; + } + + pixels = malloc(sizeof(unsigned int[pixelCount])); +} + +void UpdateRendering(void) { + double startTime = WallClockTime(); + + const float invWidth = 1.f / width; + const float invHeight = 1.f / height; + + int x, y; + for (y = 0; y < height; y++) { /* Loop over image rows */ + for (x = 0; x < width; x++) { /* Loop cols */ + const int i = (height - y - 1) * width + x; + const int i2 = 2 * i; + + const float r1 = GetRandom(&seeds[i2], &seeds[i2 + 1]) - .5f; + const float r2 = GetRandom(&seeds[i2], &seeds[i2 + 1]) - .5f; + const float kcx = (x + r1) * invWidth - .5f; + const float kcy = (y + r2) * invHeight - .5f; + + Vec rdir; + vinit(rdir, + camera.x.x * kcx + camera.y.x * kcy + camera.dir.x, + camera.x.y * kcx + camera.y.y * kcy + camera.dir.y, + camera.x.z * kcx + camera.y.z * kcy + camera.dir.z); + + Vec rorig; + vsmul(rorig, 0.1f, rdir); + vadd(rorig, rorig, camera.orig) + + vnorm(rdir); + const Ray ray = {rorig, rdir}; + Vec r; + RadiancePathTracing(spheres, sphereCount, &ray, + &seeds[i2], &seeds[i2 + 1], &r); + + if (currentSample == 0) + colors[i] = r; + else { + const float k1 = currentSample; + const float k2 = 1.f / (k1 + 1.f); + colors[i].x = (colors[i].x * k1 + r.x) * k2; + colors[i].y = (colors[i].y * k1 + r.y) * k2; + colors[i].z = (colors[i].z * k1 + r.z) * k2; + } + + pixels[y * width + x] = toInt(colors[i].x) | + (toInt(colors[i].y) << 8) | + (toInt(colors[i].z) << 16); + } + } + + const float elapsedTime = WallClockTime() - startTime; + const float sampleSec = height * width / elapsedTime; + sprintf(captionBuffer, "Rendering time %.3f sec (pass %d) Sample/sec %.1fK\n", + elapsedTime, currentSample, sampleSec / 1000.f); + + currentSample++; +} + +void ReInitScene() { + currentSample = 0; +} + +void ReInit(const int reallocBuffers) { + // Check if I have to reallocate buffers + if (reallocBuffers) { + FreeBuffers(); + AllocateBuffers(); + } + + UpdateCamera(); + currentSample = 0; + UpdateRendering(); +} + +int main(int argc, char *argv[]) { + amiSmallptCPU = 1; + + fprintf(stderr, "Usage: %s\n", argv[0]); + fprintf(stderr, "Usage: %s \n", argv[0]); + + if (argc == 4) { + width = atoi(argv[1]); + height = atoi(argv[2]); + ReadScene(argv[3]); + } else if (argc == 1) { + spheres = CornellSpheres; + sphereCount = sizeof(CornellSpheres) / sizeof(Sphere); + + vinit(camera.orig, 50.f, 45.f, 205.6f); + vinit(camera.target, 50.f, 45 - 0.042612f, 204.6); + } else + exit(-1); + + UpdateCamera(); + + /*------------------------------------------------------------------------*/ + + AllocateBuffers(); + + /*------------------------------------------------------------------------*/ + + InitGlut(argc, argv, "SmallPT CPU V1.6 (Written by David Bucciarelli)"); + + glutMainLoop( ); + + return 0; +} diff --git a/smallptGPU b/smallptGPU new file mode 100644 index 0000000000000000000000000000000000000000..65ce6d759bd51037d38c242d3c35acc4de854758 GIT binary patch literal 40909 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 literal 0 HcmV?d00001 diff --git a/smallptGPU.c b/smallptGPU.c new file mode 100644 index 0000000..b5a2384 --- /dev/null +++ b/smallptGPU.c @@ -0,0 +1,862 @@ +/* +Copyright (c) 2009 David Bucciarelli (davibu@interfree.it) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + * Based on smallpt, a Path Tracer by Kevin Beason, 2008 + * Modified by David Bucciarelli to show the output via OpenGL/GLUT, ported + * to C, work with float, fixed RR, ported to OpenCL, etc. + */ + +#include +#include +#include +#include +#include + +// Jens's patch for MacOS +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "camera.h" +#include "scene.h" +#include "displayfunc.h" + +/* Options */ +static int useGPU = 1; +static int forceWorkSize = 0; + +/* OpenCL variables */ +static cl_context context; +static cl_mem colorBuffer; +static cl_mem pixelBuffer; +static cl_mem seedBuffer; +static cl_mem sphereBuffer; +static cl_mem cameraBuffer; +static cl_command_queue commandQueue; +static cl_program program; +static cl_kernel kernel; +static unsigned int workGroupSize = 1; +static char *kernelFileName = "rendering_kernel.cl"; + +static Vec *colors; +static unsigned int *seeds; +Camera camera; +static int currentSample = 0; +Sphere *spheres; +unsigned int sphereCount; + +static void FreeBuffers() { + cl_int status = clReleaseMemObject(colorBuffer); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to release OpenCL color buffer: %d\n", status); + exit(-1); + } + + status = clReleaseMemObject(pixelBuffer); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to release OpenCL pixel buffer: %d\n", status); + exit(-1); + } + + status = clReleaseMemObject(seedBuffer); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to release OpenCL seed buffer: %d\n", status); + exit(-1); + } + + free(seeds); + free(colors); + free(pixels); +} + +static void AllocateBuffers() { + const int pixelCount = width * height; + int i; + colors = (Vec *)malloc(sizeof(Vec) * pixelCount); + + seeds = (unsigned int *)malloc(sizeof(unsigned int) * pixelCount * 2); + for (i = 0; i < pixelCount * 2; i++) { + seeds[i] = rand(); + if (seeds[i] < 2) + seeds[i] = 2; + } + + pixels = (unsigned int *)malloc(sizeof(unsigned int) * pixelCount); + // Test colors + for(i = 0; i < pixelCount; ++i) + pixels[i] = i; + + cl_int status; + cl_uint sizeBytes = sizeof(Vec) * width * height; + colorBuffer = clCreateBuffer( + context, + CL_MEM_READ_WRITE, + sizeBytes, + NULL, + &status); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to create OpenCL output buffer: %d\n", status); + exit(-1); + } + + sizeBytes = sizeof(unsigned int) * width * height; + pixelBuffer = clCreateBuffer( + context, + CL_MEM_WRITE_ONLY, + sizeBytes, + NULL, + &status); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to create OpenCL pixel buffer: %d\n", status); + exit(-1); + } + + sizeBytes = sizeof(unsigned int) * width * height * 2; + seedBuffer = clCreateBuffer( + context, + CL_MEM_READ_WRITE, + sizeBytes, + NULL, + &status); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to create OpenCL seed buffer: %d\n", status); + exit(-1); + } + status = clEnqueueWriteBuffer( + commandQueue, + seedBuffer, + CL_TRUE, + 0, + sizeBytes, + seeds, + 0, + NULL, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to write the OpenCL seeds buffer: %d\n", status); + exit(-1); + } +} + +static char *ReadSources(const char *fileName) { + FILE *file = fopen(fileName, "r"); + if (!file) { + fprintf(stderr, "Failed to open file '%s'\n", fileName); + exit(-1); + } + + if (fseek(file, 0, SEEK_END)) { + fprintf(stderr, "Failed to seek file '%s'\n", fileName); + exit(-1); + } + + long size = ftell(file); + if (size == 0) { + fprintf(stderr, "Failed to check position on file '%s'\n", fileName); + exit(-1); + } + + rewind(file); + + char *src = (char *)malloc(sizeof(char) * size + 1); + if (!src) { + fprintf(stderr, "Failed to allocate memory for file '%s'\n", fileName); + exit(-1); + } + + fprintf(stderr, "Reading file '%s' (size %ld bytes)\n", fileName, size); + size_t res = fread(src, 1, sizeof(char) * size, file); + if (res != sizeof(char) * size) { + fprintf(stderr, "Failed to read file '%s' (read %ld)\n", fileName, res); + exit(-1); + } + src[size] = '\0'; /* NULL terminated */ + + fclose(file); + + return src; + +} + +static void SetUpOpenCL() { + cl_device_type dType; + + if (useGPU) + dType = CL_DEVICE_TYPE_GPU; + else + dType = CL_DEVICE_TYPE_CPU; + + // Select the platform + + cl_uint numPlatforms; + cl_platform_id platform = NULL; + cl_int status = clGetPlatformIDs(0, NULL, &numPlatforms); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL platforms\n"); + exit(-1); + } + + if (numPlatforms > 0) { + cl_platform_id *platforms = (cl_platform_id *)malloc(sizeof(cl_platform_id) * numPlatforms); + status = clGetPlatformIDs(numPlatforms, platforms, NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL platform IDs\n"); + exit(-1); + } + + unsigned int i; + for (i = 0; i < numPlatforms; ++i) { + char pbuf[100]; + status = clGetPlatformInfo(platforms[i], + CL_PLATFORM_VENDOR, + sizeof(pbuf), + pbuf, + NULL); + + status = clGetPlatformIDs(numPlatforms, platforms, NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL platform IDs\n"); + exit(-1); + } + + fprintf(stderr, "OpenCL Platform %d: %s\n", i, pbuf); + } + + platform = platforms[0]; + free(platforms); + } + + // Select the device + + cl_device_id devices[32]; + cl_uint deviceCount; + status = clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, 32, devices, &deviceCount); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL device IDs\n"); + exit(-1); + } + + int deviceFound = 0; + cl_device_id selectedDevice; + unsigned int i; + for (i = 0; i < deviceCount; ++i) { + cl_device_type type = 0; + status = clGetDeviceInfo(devices[i], + CL_DEVICE_TYPE, + sizeof(cl_device_type), + &type, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL device info: %d\n", status); + exit(-1); + } + + char *stype; + switch (type) { + case CL_DEVICE_TYPE_ALL: + stype = "TYPE_ALL"; + break; + case CL_DEVICE_TYPE_DEFAULT: + stype = "TYPE_DEFAULT"; + break; + case CL_DEVICE_TYPE_CPU: + stype = "TYPE_CPU"; + if (!useGPU && !deviceFound) { + selectedDevice = devices[i]; + deviceFound = 1; + } + break; + case CL_DEVICE_TYPE_GPU: + stype = "TYPE_GPU"; + if (useGPU && !deviceFound) { + selectedDevice = devices[i]; + deviceFound = 1; + } + break; + default: + stype = "TYPE_UNKNOWN"; + break; + } + fprintf(stderr, "OpenCL Device %d: Type = %s\n", i, stype); + + char buf[256]; + status = clGetDeviceInfo(devices[i], + CL_DEVICE_NAME, + sizeof(char[256]), + &buf, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL device info: %d\n", status); + exit(-1); + } + + fprintf(stderr, "OpenCL Device %d: Name = %s\n", i, buf); + + cl_uint units = 0; + status = clGetDeviceInfo(devices[i], + CL_DEVICE_MAX_COMPUTE_UNITS, + sizeof(cl_uint), + &units, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL device info: %d\n", status); + exit(-1); + } + + fprintf(stderr, "OpenCL Device %d: Compute units = %u\n", i, units); + + size_t gsize = 0; + status = clGetDeviceInfo(devices[i], + CL_DEVICE_MAX_WORK_GROUP_SIZE, + sizeof(size_t), + &gsize, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL device info: %d\n", status); + exit(-1); + } + + fprintf(stderr, "OpenCL Device %d: Max. work group size = %d\n", i, (unsigned int)gsize); + } + + if (!deviceFound) { + fprintf(stderr, "Unable to select an appropriate device\n"); + exit(-1); + } + + // Create the context + + cl_context_properties cps[3] = { + CL_CONTEXT_PLATFORM, + (cl_context_properties) platform, + 0 + }; + + cl_context_properties *cprops = (NULL == platform) ? NULL : cps; + context = clCreateContext( + cprops, + 1, + &selectedDevice, + NULL, + NULL, + &status); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to open OpenCL context\n"); + exit(-1); + } + + /* Get the device list data */ + size_t deviceListSize; + status = clGetContextInfo( + context, + CL_CONTEXT_DEVICES, + 32, + devices, + &deviceListSize); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL context info: %d\n", status); + exit(-1); + } + + /* Print devices list */ + for (i = 0; i < deviceListSize / sizeof(cl_device_id); ++i) { + cl_device_type type = 0; + status = clGetDeviceInfo(devices[i], + CL_DEVICE_TYPE, + sizeof(cl_device_type), + &type, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL device info: %d\n", status); + exit(-1); + } + + char *stype; + switch (type) { + case CL_DEVICE_TYPE_ALL: + stype = "TYPE_ALL"; + break; + case CL_DEVICE_TYPE_DEFAULT: + stype = "TYPE_DEFAULT"; + break; + case CL_DEVICE_TYPE_CPU: + stype = "TYPE_CPU"; + break; + case CL_DEVICE_TYPE_GPU: + stype = "TYPE_GPU"; + break; + default: + stype = "TYPE_UNKNOWN"; + break; + } + fprintf(stderr, "[SELECTED] OpenCL Device %d: Type = %s\n", i, stype); + + char buf[256]; + status = clGetDeviceInfo(devices[i], + CL_DEVICE_NAME, + sizeof(char[256]), + &buf, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL device info: %d\n", status); + exit(-1); + } + + fprintf(stderr, "[SELECTED] OpenCL Device %d: Name = %s\n", i, buf); + + cl_uint units = 0; + status = clGetDeviceInfo(devices[i], + CL_DEVICE_MAX_COMPUTE_UNITS, + sizeof(cl_uint), + &units, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL device info: %d\n", status); + exit(-1); + } + + fprintf(stderr, "[SELECTED] OpenCL Device %d: Compute units = %u\n", i, units); + + size_t gsize = 0; + status = clGetDeviceInfo(devices[i], + CL_DEVICE_MAX_WORK_GROUP_SIZE, + sizeof(size_t), + &gsize, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL device info: %d\n", status); + exit(-1); + } + + fprintf(stderr, "[SELECTED] OpenCL Device %d: Max. work group size = %d\n", i, (unsigned int)gsize); + } + + cl_command_queue_properties prop = 0; + commandQueue = clCreateCommandQueue( + context, + devices[0], + prop, + &status); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to create OpenCL command queue: %d\n", status); + exit(-1); + } + + /*------------------------------------------------------------------------*/ + + sphereBuffer = clCreateBuffer( + context, +#ifdef __APPLE__ + CL_MEM_READ_WRITE, // NOTE: not READ_ONLY because of Apple's OpenCL bug +#else + CL_MEM_READ_ONLY, +#endif + sizeof(Sphere) * sphereCount, + NULL, + &status); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to create OpenCL scene buffer: %d\n", status); + exit(-1); + } + status = clEnqueueWriteBuffer( + commandQueue, + sphereBuffer, + CL_TRUE, + 0, + sizeof(Sphere) * sphereCount, + spheres, + 0, + NULL, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to write the OpenCL scene buffer: %d\n", status); + exit(-1); + } + + cameraBuffer = clCreateBuffer( + context, +#ifdef __APPLE__ + CL_MEM_READ_WRITE, // NOTE: not READ_ONLY because of Apple's OpenCL bug +#else + CL_MEM_READ_ONLY, +#endif + sizeof(Camera), + NULL, + &status); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to create OpenCL camera buffer: %d\n", status); + exit(-1); + } + status = clEnqueueWriteBuffer( + commandQueue, + cameraBuffer, + CL_TRUE, + 0, + sizeof(Camera), + &camera, + 0, + NULL, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to write the OpenCL camera buffer: %d\n", status); + exit(-1); + } + + AllocateBuffers(); + + /*------------------------------------------------------------------------*/ + + /* Create the kernel program */ + const char *sources = ReadSources(kernelFileName); + program = clCreateProgramWithSource( + context, + 1, + &sources, + NULL, + &status); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to open OpenCL kernel sources: %d\n", status); + exit(-1); + } + +#ifdef __APPLE__ + status = clBuildProgram(program, 1, devices, "-I. -D__APPLE__", NULL, NULL); +#else + status = clBuildProgram(program, 1, devices, "-I. ", NULL, NULL); +#endif + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to build OpenCL kernel: %d\n", status); + + size_t retValSize; + status = clGetProgramBuildInfo( + program, + devices[0], + CL_PROGRAM_BUILD_LOG, + 0, + NULL, + &retValSize); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL kernel info size: %d\n", status); + exit(-1); + } + + char *buildLog = (char *)malloc(retValSize + 1); + status = clGetProgramBuildInfo( + program, + devices[0], + CL_PROGRAM_BUILD_LOG, + retValSize, + buildLog, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL kernel info: %d\n", status); + exit(-1); + } + buildLog[retValSize] = '\0'; + + fprintf(stderr, "OpenCL Programm Build Log: %s\n", buildLog); + exit(-1); + } + + kernel = clCreateKernel(program, "RadianceGPU", &status); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to create OpenCL kernel: %d\n", status); + exit(-1); + } + + // LordCRC's patch for better workGroupSize + size_t gsize = 0; + status = clGetKernelWorkGroupInfo(kernel, + devices[0], + CL_KERNEL_WORK_GROUP_SIZE, + sizeof(size_t), + &gsize, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to get OpenCL kernel work group size info: %d\n", status); + exit(-1); + } + + workGroupSize = (unsigned int) gsize; + fprintf(stderr, "OpenCL Device 0: kernel work group size = %d\n", workGroupSize); + + if (forceWorkSize > 0) { + fprintf(stderr, "OpenCL Device 0: forced kernel work group size = %d\n", forceWorkSize); + workGroupSize = forceWorkSize; + } +} + +static void ExecuteKernel() { + /* Enqueue a kernel run call */ + size_t globalThreads[1]; + globalThreads[0] = width * height; + if (globalThreads[0] % workGroupSize != 0) + globalThreads[0] = (globalThreads[0] / workGroupSize + 1) * workGroupSize; + size_t localThreads[1]; + localThreads[0] = workGroupSize; + + cl_int status = clEnqueueNDRangeKernel( + commandQueue, + kernel, + 1, + NULL, + globalThreads, + localThreads, + 0, + NULL, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to enqueue OpenCL work: %d\n", status); + exit(-1); + } +} + +void UpdateRendering() { + double startTime = WallClockTime(); + int startSampleCount = currentSample; + + /* Set kernel arguments */ + cl_int status = clSetKernelArg( + kernel, + 0, + sizeof(cl_mem), + (void *)&colorBuffer); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to set OpenCL arg. #1: %d\n", status); + exit(-1); + } + + status = clSetKernelArg( + kernel, + 1, + sizeof(cl_mem), + (void *)&seedBuffer); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to set OpenCL arg. #2: %d\n", status); + exit(-1); + } + + status = clSetKernelArg( + kernel, + 2, + sizeof(cl_mem), + (void *)&sphereBuffer); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to set OpenCL arg. #3: %d\n", status); + exit(-1); + } + + status = clSetKernelArg( + kernel, + 3, + sizeof(cl_mem), + (void *)&cameraBuffer); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to set OpenCL arg. #4: %d\n", status); + exit(-1); + } + + status = clSetKernelArg( + kernel, + 4, + sizeof(unsigned int), + (void *)&sphereCount); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to set OpenCL arg. #5: %d\n", status); + exit(-1); + } + + status = clSetKernelArg( + kernel, + 5, + sizeof(int), + (void *)&width); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to set OpenCL arg. #6: %d\n", status); + exit(-1); + } + + status = clSetKernelArg( + kernel, + 6, + sizeof(int), + (void *)&height); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to set OpenCL arg. #7: %d\n", status); + exit(-1); + } + + status = clSetKernelArg( + kernel, + 7, + sizeof(int), + (void *)¤tSample); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to set OpenCL arg. #8: %d\n", status); + exit(-1); + } + + status = clSetKernelArg( + kernel, + 8, + sizeof(cl_mem), + (void *)&pixelBuffer); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to set OpenCL arg. #9: %d\n", status); + exit(-1); + } + + //-------------------------------------------------------------------------- + + if (currentSample < 20) { + ExecuteKernel(); + currentSample++; + } else { + /* After first 20 samples, continue to execute kernels for more and more time */ + const float k = min(currentSample - 20, 100) / 100.f; + const float tresholdTime = 0.5f * k; + for (;;) { + ExecuteKernel(); + clFinish(commandQueue); + currentSample++; + + const float elapsedTime = WallClockTime() - startTime; + if (elapsedTime > tresholdTime) + break; + } + } + + //-------------------------------------------------------------------------- + + /* Enqueue readBuffer */ + status = clEnqueueReadBuffer( + commandQueue, + pixelBuffer, + CL_TRUE, + 0, + width * height * sizeof(unsigned int), + pixels, + 0, + NULL, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to read the OpenCL pixel buffer: %d\n", status); + exit(-1); + } + + /*------------------------------------------------------------------------*/ + + const double elapsedTime = WallClockTime() - startTime; + const int samples = currentSample - startSampleCount; + const double sampleSec = samples * height * width / elapsedTime; + sprintf(captionBuffer, "Rendering time %.3f sec (pass %d) Sample/sec %.1fK\n", + elapsedTime, currentSample, sampleSec / 1000.f); +} + +void ReInitScene() { + currentSample = 0; + + // Redownload the scene + + cl_int status = clEnqueueWriteBuffer( + commandQueue, + sphereBuffer, + CL_TRUE, + 0, + sizeof(Sphere) * sphereCount, + spheres, + 0, + NULL, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to write the OpenCL scene buffer: %d\n", status); + exit(-1); + } +} + +void ReInit(const int reallocBuffers) { + // Check if I have to reallocate buffers + if (reallocBuffers) { + FreeBuffers(); + UpdateCamera(); + AllocateBuffers(); + } else + UpdateCamera(); + + cl_int status = clEnqueueWriteBuffer( + commandQueue, + cameraBuffer, + CL_TRUE, + 0, + sizeof(Camera), + &camera, + 0, + NULL, + NULL); + if (status != CL_SUCCESS) { + fprintf(stderr, "Failed to write the OpenCL camera buffer: %d\n", status); + exit(-1); + } + + currentSample = 0; +} + +int main(int argc, char *argv[]) { + amiSmallptCPU = 0; + + fprintf(stderr, "Usage: %s\n", argv[0]); + fprintf(stderr, "Usage: %s 0 and power of 2)> \n", argv[0]); + + if (argc == 7) { + useGPU = atoi(argv[1]); + forceWorkSize = atoi(argv[2]); + kernelFileName = argv[3]; + width = atoi(argv[4]); + height = atoi(argv[5]); + ReadScene(argv[6]); + } else if (argc == 1) { + spheres = CornellSpheres; + sphereCount = sizeof(CornellSpheres) / sizeof(Sphere); + + vinit(camera.orig, 50.f, 45.f, 205.6f); + vinit(camera.target, 50.f, 45 - 0.042612f, 204.6); + } else + exit(-1); + + UpdateCamera(); + + /*------------------------------------------------------------------------*/ + + SetUpOpenCL(); + + /*------------------------------------------------------------------------*/ + + InitGlut(argc, argv, "SmallPT GPU V1.6 (Written by David Bucciarelli)"); + + glutMainLoop(); + + return 0; +} diff --git a/vec.h b/vec.h new file mode 100644 index 0000000..b2bd23f --- /dev/null +++ b/vec.h @@ -0,0 +1,66 @@ +/* +Copyright (c) 2009 David Bucciarelli (davibu@interfree.it) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef _VEC_H +#define _VEC_H + +typedef struct { + float x, y, z; // position, also color (r,g,b) +} Vec; + +#define vinit(v, a, b, c) { (v).x = a; (v).y = b; (v).z = c; } +#define vassign(a, b) vinit(a, (b).x, (b).y, (b).z) +#define vclr(v) vinit(v, 0.f, 0.f, 0.f) +#define vadd(v, a, b) vinit(v, (a).x + (b).x, (a).y + (b).y, (a).z + (b).z) +#define vsub(v, a, b) vinit(v, (a).x - (b).x, (a).y - (b).y, (a).z - (b).z) +#define vsadd(v, a, b) { float k = (a); vinit(v, (b).x + k, (b).y + k, (b).z + k) } +#define vssub(v, a, b) { float k = (a); vinit(v, (b).x - k, (b).y - k, (b).z - k) } +#define vmul(v, a, b) vinit(v, (a).x * (b).x, (a).y * (b).y, (a).z * (b).z) +#define vsmul(v, a, b) { float k = (a); vinit(v, k * (b).x, k * (b).y, k * (b).z) } +#define vdot(a, b) ((a).x * (b).x + (a).y * (b).y + (a).z * (b).z) +#define vnorm(v) { float l = 1.f / sqrt(vdot(v, v)); vsmul(v, l, v); } +#define vxcross(v, a, b) vinit(v, (a).y * (b).z - (a).z * (b).y, (a).z * (b).x - (a).x * (b).z, (a).x * (b).y - (a).y * (b).x) +#define vfilter(v) ((v).x > (v).y && (v).x > (v).z ? (v).x : (v).y > (v).z ? (v).y : (v).z) +#define viszero(v) (((v).x == 0.f) && ((v).x == 0.f) && ((v).z == 0.f)) + +#ifndef GPU_KERNEL +#define clamp(x, a, b) ((x) < (a) ? (a) : ((x) > (b) ? (b) : (x))) +#define max(x, y) ( (x) > (y) ? (x) : (y)) +#define min(x, y) ( (x) < (y) ? (x) : (y)) +#define sign(x) ((x) > 0 ? 1 : -1) +#endif + +#define toInt(x) ((int)(pow(clamp(x, 0.f, 1.f), 1.f / 2.2f) * 255.f + .5f)) + +// Rendering flags +#define RFLAGS_DISABLE_DIFFUSE_PATH 1 + +// NOTE: workaround for an Apple OpenCL compiler bug +#ifdef __APPLE__ +#define OCL_CONSTANT_BUFFER __global +#else +#define OCL_CONSTANT_BUFFER __constant +#endif + +#endif /* _VEC_H */ +