├── README ├── Tutorial1 ├── Makefile └── src │ ├── Grid.cpp │ ├── Grid.d │ ├── Grid.h │ ├── Vec3.h │ ├── VolumeRenderer.cpp │ ├── VolumeRenderer.d │ ├── VolumeRenderer.h │ ├── main.cpp │ └── main.d └── Tutorial2 ├── Makefile └── src ├── .Grid.h.swp ├── .main.cpp.swp ├── Grid.cpp ├── Grid.d ├── Grid.h ├── Vec3.h ├── VolumeRenderer.cpp ├── VolumeRenderer.d ├── VolumeRenderer.h ├── main.cpp └── main.d /README: -------------------------------------------------------------------------------- 1 | Volume Rendering Example Code 2 | Brandon Pelfrey - January 2011 3 | 4 | This code is part of an ongoing tutorial on my blog @ brandonpelfrey.com/blog. 5 | I will organize the lessons into separate folders and comment as much of the code as possible 6 | in an attempt to supplement the tutorial text. 7 | 8 | -- Building 9 | This example requires ImageMagick's C++ bindings. 10 | 11 | Ubuntu/Debian: sudo apt-get install libmagick++-dev 12 | Fedora: yum install ImageMagick-c++-devel 13 | 14 | After that, all that is needed in order to build is to run make. The executable is placed in the 15 | "bin" folder. 16 | 17 | -------------------------------------------------------------------------------- /Tutorial1/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | RELEASE := 1 3 | 4 | OPT := -Wall -fopenmp 5 | ifeq ($(RELEASE),1) 6 | OPT += -O3 -ffast-math 7 | else 8 | OPT += -g 9 | endif 10 | 11 | NAME := VolumeRenderer 12 | DEBUG := 0 13 | BINDIR := bin 14 | SRCDIR := src 15 | SRC := $(shell find $(SRCDIR) -name "*.cpp") 16 | HEADER := $(shell find $(SRCDIR) -name "*.h") 17 | INC := $(addprefix -I , $(shell find $(SRCDIR) -name ".svn" -prune -o -type d)) 18 | ARCH := $(shell uname) 19 | SUM = $(shell find . -name "*.h" -print0 -o -name "*.cpp" -print0 | xargs -0 wc -l | grep total ) 20 | 21 | OPT += $(shell Magick++-config --cppflags --cxxflags --ldflags --libs) 22 | 23 | ifeq ($(ARCH),Darwin) 24 | LIB := -fopenmp 25 | endif 26 | 27 | ifeq ($(ARCH),Linux) 28 | LIB := -fopenmp 29 | endif 30 | 31 | all: 32 | @make --no-print-directory depend 33 | @make --no-print-directory $(BINDIR)/$(NAME) #DEP=1 34 | @echo "Line sum: ${SUM}" 35 | 36 | $(BINDIR)/$(NAME) : $(SRC:.cpp=.o) 37 | @mkdir -p $(BINDIR) 38 | @$(CXX) -o $@ $(SRC:.cpp=.o) $(LIB) $(OPT) 39 | @echo "Linking $@..." 40 | 41 | run: all 42 | cd $(BINDIR); ./$(NAME) 43 | 44 | depend : $(SRC:.cpp=.d) 45 | %.d : %.cpp 46 | @echo "$@ $(dir $@)$$(gcc $(INC) -MM -MG $<)" > $@ 47 | 48 | %.o : %.cpp 49 | @$(CXX) $(OPT) $(INC) -o $@ -c $< 50 | @echo "Compiling $< ..." 51 | 52 | .PHONY: clean 53 | clean: 54 | rm -rf $(shell find . -name "*.o" -o -name "*.d") 55 | rm -rf $(BINDIR)/$(NAME) 56 | rm -rf $(BINDIR)/*.bmp 57 | rm -rf GPATH GRTAGS GSYMS GTAGS HTML 58 | 59 | ifeq ($(DEP), 1) 60 | -include $(SRC:.cpp=.d) 61 | endif 62 | -------------------------------------------------------------------------------- /Tutorial1/src/Grid.cpp: -------------------------------------------------------------------------------- 1 | #include "Grid.h" 2 | #include 3 | 4 | #define GRID_INDEX(X,Y,Z) ( (X) + (Y)*res[0] + (Z)*res[0]*res[1] ) 5 | 6 | Grid::Grid(int _nx, int _ny, int _nz, Vec3 _bmin, Vec3 _bmax) 7 | { 8 | bbox[0] = _bmin; 9 | bbox[1] = _bmax; 10 | bbox_size = bbox[1] - bbox[0]; 11 | res[0] = _nx; 12 | res[1] = _ny; 13 | res[2] = _nz; 14 | int N = res[0] * res[1] * res[2]; 15 | 16 | printf("Allocating grid of size %dx%dx%d (%.02f MB)...\n", 17 | _nx,_ny,_nz, N*sizeof(float)/(1024.f*1024.f)); 18 | 19 | data = new float[N]; 20 | } 21 | 22 | void Grid::setVoxel(int x, int y, int z, float value) { 23 | data[GRID_INDEX(x,y,z)] = value; 24 | } 25 | 26 | float Grid::read(const Vec3& x) const { 27 | // Note: A lot of stuff in here is usually optimized. 28 | // Left here for clarity. 29 | 30 | // Compute the relative position inside the grid 31 | Vec3 relative = x - bbox[0]; 32 | 33 | relative.x *= res[0] / (bbox_size.x); 34 | relative.y *= res[1] / (bbox_size.y); 35 | relative.z *= res[2] / (bbox_size.z); 36 | 37 | int i = relative.x; 38 | int j = relative.y; 39 | int k = relative.z; 40 | 41 | // If we're outside the grid, then return zero (as a convention). 42 | if(i<0 || j<0 || k<0 || i>=res[0]-1 || j>=res[1]-1 || k>=res[2]-1) 43 | return 0.f; 44 | 45 | // We're inside the box! Let's do trilinear interpolation. 46 | 47 | // Compute weights 48 | Vec3 w = relative - Vec3(i,j,k); 49 | Vec3 wi = Vec3(1.f,1.f,1.f) - w; 50 | 51 | return 52 | data[GRID_INDEX(i ,j ,k )] * wi.x * wi.y * wi.z + 53 | data[GRID_INDEX(i+1,j ,k )] * w.x * wi.y * wi.z + 54 | data[GRID_INDEX(i ,j+1,k )] * wi.x * w.y * wi.z + 55 | data[GRID_INDEX(i+1,j+1,k )] * w.x * w.y * wi.z + 56 | data[GRID_INDEX(i ,j ,k+1)] * wi.x * wi.y * w.z + 57 | data[GRID_INDEX(i+1,j ,k+1)] * w.x * wi.y * w.z + 58 | data[GRID_INDEX(i ,j+1,k+1)] * wi.x * w.y * w.z + 59 | data[GRID_INDEX(i+1,j+1,k+1)] * w.x * w.y * w.z ; 60 | } 61 | -------------------------------------------------------------------------------- /Tutorial1/src/Grid.d: -------------------------------------------------------------------------------- 1 | src/Grid.d src/Grid.o: src/Grid.cpp src/Grid.h src/Vec3.h 2 | -------------------------------------------------------------------------------- /Tutorial1/src/Grid.h: -------------------------------------------------------------------------------- 1 | #ifndef Grid_h 2 | #define Grid_h 3 | 4 | #include "Vec3.h" 5 | 6 | class Grid { 7 | private: 8 | int res[3]; 9 | float *data; 10 | Vec3 bbox[2]; 11 | Vec3 bbox_size; 12 | 13 | public: 14 | /*! Create a grid of some resolution, enclosed in the 15 | * axis-aligned bounding box of [bmin,bmax] 16 | * @param[in] _nx X-axis resolution of the grid 17 | * @param[in] _ny Y-axis resolution of the grid 18 | * @param[in] _nz Z-axis resolution of the grid 19 | * @param[in] _bmin The lower corner of the AABB for the grid 20 | * @param[in] _bmax The upper corner of the AABB for the grid 21 | */ 22 | Grid(int _nx, int _ny, int _nz, Vec3 _bmin, Vec3 _bmax); 23 | 24 | //! Set the value of a single voxel in the grid 25 | void setVoxel(int x, int y, int z, float value); 26 | 27 | //! Read a value from the grid at a particular point 28 | // in space. Returns 0.f if outside the grid. 29 | float read(const Vec3& x) const; 30 | 31 | const int* getDimensions() const { return res; } 32 | }; 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /Tutorial1/src/Vec3.h: -------------------------------------------------------------------------------- 1 | #ifndef Vec3_h_ 2 | #define Vec3_h_ 3 | 4 | #include 5 | 6 | struct Vec3; 7 | Vec3 operator*(float r, const Vec3& v); 8 | 9 | struct Vec3 { 10 | union { 11 | struct { 12 | float x,y,z; 13 | }; 14 | float D[3]; 15 | }; 16 | 17 | Vec3() { } 18 | Vec3(float _x, float _y, float _z) 19 | { x=_x; y=_y; z=_z; } 20 | 21 | float& operator[](unsigned int i) { 22 | return D[i]; 23 | } 24 | 25 | const float& operator[](unsigned int i) const { 26 | return D[i]; 27 | } 28 | 29 | Vec3 operator+(const Vec3& r) const { 30 | return Vec3(x+r.x, y+r.y, z+r.z); 31 | } 32 | 33 | Vec3 operator-(const Vec3& r) const { 34 | return Vec3(x-r.x, y-r.y, z-r.z); 35 | } 36 | 37 | Vec3 cmul(const Vec3& r) const { 38 | return Vec3(x*r.x, y*r.y, z*r.z); 39 | } 40 | 41 | Vec3 cdiv(const Vec3& r) const { 42 | return Vec3(x/r.x, y/r.y, z/r.z); 43 | } 44 | 45 | Vec3 operator*(float r) const { 46 | return Vec3(x*r,y*r,z*r); 47 | } 48 | 49 | 50 | Vec3 operator/(float r) const { 51 | return Vec3(x/r, y/r, z/r); 52 | } 53 | 54 | //// 55 | Vec3& operator+=(const Vec3& r) { 56 | x+=r.x; 57 | y+=r.y; 58 | z+=r.z; 59 | return *this; 60 | } 61 | 62 | Vec3& operator-=(const Vec3& r) { 63 | x-=r.x; 64 | y-=r.y; 65 | z-=r.z; 66 | return *this; 67 | } 68 | 69 | Vec3& operator*=(float r) { 70 | x*=r; y*=r; z*=r; 71 | return *this; 72 | } 73 | 74 | float operator*(const Vec3& r) const { 75 | return x*r.x + y*r.y + z*r.z; 76 | } 77 | 78 | float norm() const { 79 | return sqrtf(x*x+y*y+z*z); 80 | } 81 | 82 | float normSquared() const { 83 | return x*x + y*y + z*z; 84 | } 85 | 86 | Vec3 operator^(const Vec3& r) const { 87 | return Vec3( 88 | y * r.z - z * r.y, 89 | z * r.x - x * r.z, 90 | x * r.y - y * r.x 91 | ); 92 | } 93 | 94 | void normalize() { 95 | *this = *this / norm(); 96 | } 97 | 98 | Vec3 normalized() const { 99 | return *this / norm(); 100 | } 101 | 102 | Vec3 reflect(const Vec3& normal) const { 103 | return *this - (2.f * (*this * normal)) * normal; 104 | } 105 | 106 | void tangentSpace(Vec3* a, Vec3* b) const { 107 | const Vec3 special(0.577350269f, 0.577350269f, 0.577350269f); // 1/sqrt(3) 108 | *a = special ^ *this; 109 | *b = *a ^ *this; 110 | } 111 | 112 | }; 113 | 114 | //* 115 | inline Vec3 operator*(float r, const Vec3& v) { 116 | return Vec3(v.x*r, v.y*r, v.z*r); 117 | } 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /Tutorial1/src/VolumeRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include "VolumeRenderer.h" 2 | #include 3 | 4 | VolumeRenderer::VolumeRenderer(int _width, int _height, Grid* dataSource) 5 | : width(_width), height(_height), volumeData(dataSource) 6 | { 7 | int N = width * height * 4; 8 | 9 | // Allocate pixels for our output image (RGBA) 10 | image = new float[N]; 11 | 12 | // Clear the image to black 13 | for(int i=0;iread(rayPosition); 81 | 82 | // Compute the amount of light that reaches this point. 83 | // We'll sample the light twice as coarsely as the main integral for now. 84 | float lightValue = sampleLighting(rayPosition, Vec3(0,1,0), ds); 85 | 86 | // Hint at the future... We can play with the light absorption. (You can ignore this if you want.) 87 | lightValue = powf(lightValue, 3.f); 88 | 89 | // Let's also boost the amount of light so that everything is brighter. 90 | lightValue *= 5; 91 | 92 | // Numerical integration of the path integral. Theory will be covered in 93 | // tutorial two. 94 | float dT = expf(density * -ds * kappa); 95 | T *= dT; 96 | finalColor += ((1.f-dT)*T/kappa)*densityColor*lightValue; 97 | } 98 | 99 | // Write out the output of our ray march into the image buffer. 100 | // We'll composite this image on top of some other color, 101 | // just to demonstrate 102 | Vec3 backgroundColor(0,0,0); 103 | finalColor = (1.f-T)*finalColor + T*backgroundColor; 104 | 105 | for(int c=0;c<3;++c) { 106 | image[pndx + c] = finalColor[c]; 107 | } 108 | 109 | // For the purposes of this tutorial, the final alpha for this image will be 1. 110 | image[pndx+3] = 1.f; 111 | } 112 | 113 | // Print out some progress information 114 | #pragma omp critical 115 | { 116 | printf("\rRender progress: %.02f%%", 100.f*pi++/(width-1)); 117 | fflush(stdout); 118 | } 119 | } 120 | 121 | printf("\n"); 122 | } 123 | 124 | float VolumeRenderer::sampleLighting(const Vec3& x, const Vec3& lightPosition, float stepSize) { 125 | Vec3 lightDirection = lightPosition - x; 126 | float lightDistance = lightDirection.norm(); 127 | 128 | // Normalize the direction vector that we will now march along. 129 | lightDirection *= 1.f/lightDistance; 130 | 131 | float densitySum = 0.f; 132 | for(float s=0; sread( samplePosition ); 135 | } 136 | 137 | return exp(-stepSize * 1.f * densitySum); 138 | } 139 | 140 | void VolumeRenderer::writeImage(const char *path) { 141 | // We'll use ImageMagick's c++ bindings here to make life way simpler. 142 | // This gives us support for PNGs, JPEGs, BMP, TGA, etc for free. 143 | Magick::Image output; 144 | output.read(width,height,"RGBA",Magick::FloatPixel,image); 145 | output.write(path); 146 | } 147 | 148 | -------------------------------------------------------------------------------- /Tutorial1/src/VolumeRenderer.d: -------------------------------------------------------------------------------- 1 | src/VolumeRenderer.d src/VolumeRenderer.o: src/VolumeRenderer.cpp src/VolumeRenderer.h src/Grid.h \ 2 | src/Vec3.h 3 | -------------------------------------------------------------------------------- /Tutorial1/src/VolumeRenderer.h: -------------------------------------------------------------------------------- 1 | #ifndef VolumeRenderer_h 2 | #define VolumeRenderer_h 3 | 4 | #include "Grid.h" 5 | #include "Vec3.h" 6 | 7 | class VolumeRenderer { 8 | private: 9 | int width, height; 10 | float *image; 11 | Grid* volumeData; 12 | 13 | public: 14 | /*! Set up a simple renderer with a certain width and height for the final image. 15 | * @param[in] _width The width of the output image. 16 | * @param[in] _height The height of the output image. 17 | * @param[in] dataSource The grid from which we will read our data. 18 | */ 19 | VolumeRenderer(int _width, int _height, Grid* dataSource); 20 | ~VolumeRenderer(); 21 | 22 | /*! The meat of the system. This function does volume ray marching 23 | * using approximate single scattering. 24 | * @param[in] cameraPosition The position of the camera rendering the scene. 25 | * @param[in] cameraFocus A point in space which will be the center of the image. 26 | * @param[in] marchDistance The maximum distance that will be marched away from the camera. 27 | * @param[in] marchingStepCount The number of steps to march along the path from the camera into the scene. 28 | */ 29 | void render(const Vec3& cameraPosition, const Vec3& cameraFocus, float marchDistance, int marchingStepCount); 30 | 31 | /*! 32 | * Compute the light attenuation from a point in space to a light. The result 33 | * is in the range (0,1]. 34 | * @param[in] x The position we in question 35 | * @param[in] lightPosition The position of the light in 3D space. 36 | * @param[in] stepSize The step size in integrating the light attenuation. 37 | */ 38 | float sampleLighting(const Vec3& x, const Vec3& lightPosition, float stepSize); 39 | 40 | /*! Writes the contents of the image buffer to a file. 41 | * @param[out] path The location where the image file will be written to. 42 | */ 43 | void writeImage(const char *path); 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /Tutorial1/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "VolumeRenderer.h" 2 | #include "Grid.h" 3 | 4 | void populateGrid(Grid* grid) { 5 | const int* dims = grid->getDimensions(); 6 | for(int i=0;isetVoxel(i,j,k,value); 23 | } 24 | } 25 | 26 | int main(int argc, char* const argv[]) { 27 | Grid grid(128,128,128, Vec3(-1,-1,-1), Vec3(1,1,1)); 28 | VolumeRenderer renderer(512,512, &grid); 29 | 30 | // Let's fill the grid with something to render. 31 | populateGrid(&grid); 32 | 33 | // Ray marching 34 | renderer.render(Vec3(1,1,1), Vec3(0,0,0), 2, 128); 35 | 36 | // Output an image to look at. 37 | renderer.writeImage("output.png"); 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /Tutorial1/src/main.d: -------------------------------------------------------------------------------- 1 | src/main.d src/main.o: src/main.cpp src/VolumeRenderer.h src/Grid.h src/Vec3.h 2 | -------------------------------------------------------------------------------- /Tutorial2/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | RELEASE := 1 3 | 4 | OPT := -Wall -fopenmp 5 | ifeq ($(RELEASE),1) 6 | OPT += -O3 -ffast-math 7 | else 8 | OPT += -g 9 | endif 10 | 11 | NAME := VolumeRenderer 12 | DEBUG := 0 13 | BINDIR := bin 14 | SRCDIR := src 15 | SRC := $(shell find $(SRCDIR) -name "*.cpp") 16 | HEADER := $(shell find $(SRCDIR) -name "*.h") 17 | INC := $(addprefix -I , $(shell find $(SRCDIR) -name ".svn" -prune -o -type d)) 18 | ARCH := $(shell uname) 19 | SUM = $(shell find . -name "*.h" -print0 -o -name "*.cpp" -print0 | xargs -0 wc -l | grep total ) 20 | 21 | OPT += $(shell Magick++-config --cppflags --cxxflags --ldflags --libs) 22 | 23 | ifeq ($(ARCH),Darwin) 24 | LIB := -fopenmp 25 | endif 26 | 27 | ifeq ($(ARCH),Linux) 28 | LIB := -fopenmp 29 | endif 30 | 31 | all: 32 | @make --no-print-directory depend 33 | @make --no-print-directory $(BINDIR)/$(NAME) #DEP=1 34 | @echo "Line sum: ${SUM}" 35 | 36 | $(BINDIR)/$(NAME) : $(SRC:.cpp=.o) 37 | @mkdir -p $(BINDIR) 38 | @$(CXX) -o $@ $(SRC:.cpp=.o) $(LIB) $(OPT) 39 | @echo "Linking $@..." 40 | 41 | run: all 42 | cd $(BINDIR); ./$(NAME) 43 | 44 | depend : $(SRC:.cpp=.d) 45 | %.d : %.cpp 46 | @echo "$@ $(dir $@)$$(gcc $(INC) -MM -MG $<)" > $@ 47 | 48 | %.o : %.cpp 49 | @$(CXX) $(OPT) $(INC) -o $@ -c $< 50 | @echo "Compiling $< ..." 51 | 52 | .PHONY: clean 53 | clean: 54 | rm -rf $(shell find . -name "*.o" -o -name "*.d") 55 | rm -rf $(BINDIR)/$(NAME) 56 | rm -rf $(BINDIR)/*.bmp 57 | rm -rf GPATH GRTAGS GSYMS GTAGS HTML 58 | 59 | ifeq ($(DEP), 1) 60 | -include $(SRC:.cpp=.d) 61 | endif 62 | -------------------------------------------------------------------------------- /Tutorial2/src/.Grid.h.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonpelfrey/Volume-Rendering-Tutorial/6e8a2b8068178b2d6b46341d8a0991631e404140/Tutorial2/src/.Grid.h.swp -------------------------------------------------------------------------------- /Tutorial2/src/.main.cpp.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonpelfrey/Volume-Rendering-Tutorial/6e8a2b8068178b2d6b46341d8a0991631e404140/Tutorial2/src/.main.cpp.swp -------------------------------------------------------------------------------- /Tutorial2/src/Grid.cpp: -------------------------------------------------------------------------------- 1 | #include "Grid.h" 2 | #include 3 | 4 | #define GRID_INDEX(X,Y,Z) ( (X) + (Y)*res[0] + (Z)*res[0]*res[1] ) 5 | 6 | Grid::Grid(int _nx, int _ny, int _nz, Vec3 _bmin, Vec3 _bmax, float _default_value) : default_value(_default_value) 7 | { 8 | bbox[0] = _bmin; 9 | bbox[1] = _bmax; 10 | bbox_size = bbox[1] - bbox[0]; 11 | res[0] = _nx; 12 | res[1] = _ny; 13 | res[2] = _nz; 14 | int N = res[0] * res[1] * res[2]; 15 | 16 | printf("Allocating grid of size %dx%dx%d (%.02f MB)...\n", 17 | _nx,_ny,_nz, N*sizeof(float)/(1024.f*1024.f)); 18 | 19 | data = new float[N]; 20 | } 21 | 22 | void Grid::setVoxel(int x, int y, int z, float value) { 23 | data[GRID_INDEX(x,y,z)] = value; 24 | } 25 | 26 | void Grid::computeDSM(const Grid& density, const float kappa, const float ds, const Vec3& lightPosition) { 27 | 28 | // Repeated calculation brought out of the loop 29 | const Vec3 step = bbox_size.cdiv(Vec3(res[0]-1,res[1]-1,res[2]-1)); 30 | 31 | // For each voxel... 32 | #pragma omp parallel for 33 | for(int i=0;i light 49 | float density_sum = 0; 50 | for(float s=0; s < smax; s += ds) { 51 | const Vec3 sample = x + lightDirection * s; 52 | density_sum += density.read(sample); 53 | } 54 | setVoxel(i,j,k, expf(-kappa*ds*density_sum) ); 55 | } 56 | } 57 | 58 | float Grid::read(const Vec3& x) const { 59 | // Note: A lot of stuff in here is usually optimized. 60 | // Left here for clarity. 61 | 62 | // Compute the relative position inside the grid 63 | Vec3 relative = x - bbox[0]; 64 | 65 | relative.x *= res[0] / (bbox_size.x); 66 | relative.y *= res[1] / (bbox_size.y); 67 | relative.z *= res[2] / (bbox_size.z); 68 | 69 | int i = relative.x; 70 | int j = relative.y; 71 | int k = relative.z; 72 | 73 | // If we're outside the grid, then return zero (as a convention). 74 | if(i<0 || j<0 || k<0 || i>=res[0]-1 || j>=res[1]-1 || k>=res[2]-1) 75 | return 0.f; 76 | 77 | // We're inside the box! Let's do trilinear interpolation. 78 | 79 | // Compute weights 80 | Vec3 w = relative - Vec3(i,j,k); 81 | Vec3 wi = Vec3(1.f,1.f,1.f) - w; 82 | 83 | return 84 | data[GRID_INDEX(i ,j ,k )] * wi.x * wi.y * wi.z + 85 | data[GRID_INDEX(i+1,j ,k )] * w.x * wi.y * wi.z + 86 | data[GRID_INDEX(i ,j+1,k )] * wi.x * w.y * wi.z + 87 | data[GRID_INDEX(i+1,j+1,k )] * w.x * w.y * wi.z + 88 | data[GRID_INDEX(i ,j ,k+1)] * wi.x * wi.y * w.z + 89 | data[GRID_INDEX(i+1,j ,k+1)] * w.x * wi.y * w.z + 90 | data[GRID_INDEX(i ,j+1,k+1)] * wi.x * w.y * w.z + 91 | data[GRID_INDEX(i+1,j+1,k+1)] * w.x * w.y * w.z ; 92 | } 93 | -------------------------------------------------------------------------------- /Tutorial2/src/Grid.d: -------------------------------------------------------------------------------- 1 | src/Grid.d src/Grid.o: src/Grid.cpp src/Grid.h src/Vec3.h 2 | -------------------------------------------------------------------------------- /Tutorial2/src/Grid.h: -------------------------------------------------------------------------------- 1 | #ifndef Grid_h 2 | #define Grid_h 3 | 4 | #include "Vec3.h" 5 | 6 | class Grid { 7 | private: 8 | int res[3]; 9 | float *data; 10 | Vec3 bbox[2]; 11 | Vec3 bbox_size; 12 | float default_value; // Value that should be return if we access outside the grid. 13 | 14 | public: 15 | /*! Create a grid of some resolution, enclosed in the 16 | * axis-aligned bounding box of [bmin,bmax] 17 | * @param[in] _nx X-axis resolution of the grid 18 | * @param[in] _ny Y-axis resolution of the grid 19 | * @param[in] _nz Z-axis resolution of the grid 20 | * @param[in] _bmin The lower corner of the AABB for the grid 21 | * @param[in] _bmax The upper corner of the AABB for the grid 22 | * @param[in] _default_value The default value that is returned outside the grid 23 | */ 24 | Grid(int _nx, int _ny, int _nz, Vec3 _bmin, Vec3 _bmax, float _default_value=0.f); 25 | 26 | //! Set the value of a single voxel in the grid 27 | void setVoxel(int x, int y, int z, float value); 28 | 29 | //! Read a value from the grid at a particular point 30 | //! in space. Returns 0.f if outside the grid. 31 | float read(const Vec3& x) const; 32 | 33 | const int* getDimensions() const { return res; } 34 | 35 | /*! Compute a Deep Shadow Map based on the density of another grid. 36 | * @param[in] density The field to evaluate against 37 | * @param[in] kappa The volume scattering coefficient 38 | * @param[in] ds The volume step size 39 | * @param[in] lightPosition The position of the light that we're using for illumination. 40 | */ 41 | void computeDSM(const Grid& density, const float kappa, const float ds, const Vec3& lightPosition); 42 | 43 | }; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /Tutorial2/src/Vec3.h: -------------------------------------------------------------------------------- 1 | #ifndef Vec3_h_ 2 | #define Vec3_h_ 3 | 4 | #include 5 | 6 | struct Vec3; 7 | Vec3 operator*(float r, const Vec3& v); 8 | 9 | struct Vec3 { 10 | union { 11 | struct { 12 | float x,y,z; 13 | }; 14 | float D[3]; 15 | }; 16 | 17 | Vec3() { } 18 | Vec3(float _x, float _y, float _z) 19 | { x=_x; y=_y; z=_z; } 20 | 21 | float& operator[](unsigned int i) { 22 | return D[i]; 23 | } 24 | 25 | const float& operator[](unsigned int i) const { 26 | return D[i]; 27 | } 28 | 29 | Vec3 operator+(const Vec3& r) const { 30 | return Vec3(x+r.x, y+r.y, z+r.z); 31 | } 32 | 33 | Vec3 operator-(const Vec3& r) const { 34 | return Vec3(x-r.x, y-r.y, z-r.z); 35 | } 36 | 37 | Vec3 cmul(const Vec3& r) const { 38 | return Vec3(x*r.x, y*r.y, z*r.z); 39 | } 40 | 41 | Vec3 cdiv(const Vec3& r) const { 42 | return Vec3(x/r.x, y/r.y, z/r.z); 43 | } 44 | 45 | Vec3 operator*(float r) const { 46 | return Vec3(x*r,y*r,z*r); 47 | } 48 | 49 | 50 | Vec3 operator/(float r) const { 51 | return Vec3(x/r, y/r, z/r); 52 | } 53 | 54 | //// 55 | Vec3& operator+=(const Vec3& r) { 56 | x+=r.x; 57 | y+=r.y; 58 | z+=r.z; 59 | return *this; 60 | } 61 | 62 | Vec3& operator-=(const Vec3& r) { 63 | x-=r.x; 64 | y-=r.y; 65 | z-=r.z; 66 | return *this; 67 | } 68 | 69 | Vec3& operator*=(float r) { 70 | x*=r; y*=r; z*=r; 71 | return *this; 72 | } 73 | 74 | float operator*(const Vec3& r) const { 75 | return x*r.x + y*r.y + z*r.z; 76 | } 77 | 78 | float norm() const { 79 | return sqrtf(x*x+y*y+z*z); 80 | } 81 | 82 | float normSquared() const { 83 | return x*x + y*y + z*z; 84 | } 85 | 86 | Vec3 operator^(const Vec3& r) const { 87 | return Vec3( 88 | y * r.z - z * r.y, 89 | z * r.x - x * r.z, 90 | x * r.y - y * r.x 91 | ); 92 | } 93 | 94 | void normalize() { 95 | *this = *this / norm(); 96 | } 97 | 98 | Vec3 normalized() const { 99 | return *this / norm(); 100 | } 101 | 102 | Vec3 reflect(const Vec3& normal) const { 103 | return *this - (2.f * (*this * normal)) * normal; 104 | } 105 | 106 | void tangentSpace(Vec3* a, Vec3* b) const { 107 | const Vec3 special(0.577350269f, 0.577350269f, 0.577350269f); // 1/sqrt(3) 108 | *a = special ^ *this; 109 | *b = *a ^ *this; 110 | } 111 | 112 | }; 113 | 114 | //* 115 | inline Vec3 operator*(float r, const Vec3& v) { 116 | return Vec3(v.x*r, v.y*r, v.z*r); 117 | } 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /Tutorial2/src/VolumeRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include "VolumeRenderer.h" 2 | #include 3 | 4 | VolumeRenderer::VolumeRenderer(int _width, int _height, Grid* dataSource) 5 | : width(_width), height(_height), volumeData(dataSource) 6 | { 7 | int N = width * height * 4; 8 | 9 | // Allocate pixels for our output image (RGBA) 10 | image = new float[N]; 11 | 12 | // Clear the image to black 13 | for(int i=0;iread(rayPosition); 81 | 82 | // Compute the amount of light that reaches this point. 83 | // We'll sample the light twice as coarsely as the main integral for now. 84 | //float lightValue = sampleLighting(rayPosition, Vec3(0,1,0), ds); 85 | float lightValue = dsm.read(rayPosition); 86 | 87 | // Hint at the future... We can play with the light absorption. (You can ignore this if you want.) 88 | lightValue = powf(lightValue, 3.f); 89 | 90 | // Let's also boost the amount of light so that everything is brighter. 91 | lightValue *= 5; 92 | 93 | // Numerical integration of the path integral. Theory will be covered in 94 | // tutorial two. 95 | float dT = expf(density * -ds * kappa); 96 | T *= dT; 97 | finalColor += ((1.f-dT)*T/kappa)*densityColor*lightValue; 98 | } 99 | 100 | // Write out the output of our ray march into the image buffer. 101 | // We'll composite this image on top of some other color, 102 | // just to demonstrate 103 | Vec3 backgroundColor(0,0,0); 104 | finalColor = (1.f-T)*finalColor + T*backgroundColor; 105 | 106 | for(int c=0;c<3;++c) { 107 | image[pndx + c] = finalColor[c]; 108 | } 109 | 110 | // For the purposes of this tutorial, the final alpha for this image will be 1. 111 | image[pndx+3] = 1.f; 112 | } 113 | 114 | // Print out some progress information 115 | #pragma omp critical 116 | { 117 | printf("\rRender progress: %.02f%%", 100.f*pi++/(width-1)); 118 | fflush(stdout); 119 | } 120 | } 121 | 122 | printf("\n"); 123 | } 124 | 125 | float VolumeRenderer::sampleLighting(const Vec3& x, const Vec3& lightPosition, float stepSize) { 126 | Vec3 lightDirection = lightPosition - x; 127 | float lightDistance = lightDirection.norm(); 128 | 129 | // Normalize the direction vector that we will now march along. 130 | lightDirection *= 1.f/lightDistance; 131 | 132 | float densitySum = 0.f; 133 | for(float s=0; sread( samplePosition ); 136 | } 137 | 138 | return exp(-stepSize * 1.f * densitySum); 139 | } 140 | 141 | void VolumeRenderer::writeImage(const char *path) { 142 | // We'll use ImageMagick's c++ bindings here to make life way simpler. 143 | // This gives us support for PNGs, JPEGs, BMP, TGA, etc for free. 144 | Magick::Image output; 145 | output.read(width,height,"RGBA",Magick::FloatPixel,image); 146 | output.write(path); 147 | } 148 | 149 | -------------------------------------------------------------------------------- /Tutorial2/src/VolumeRenderer.d: -------------------------------------------------------------------------------- 1 | src/VolumeRenderer.d src/VolumeRenderer.o: src/VolumeRenderer.cpp src/VolumeRenderer.h src/Grid.h \ 2 | src/Vec3.h 3 | -------------------------------------------------------------------------------- /Tutorial2/src/VolumeRenderer.h: -------------------------------------------------------------------------------- 1 | #ifndef VolumeRenderer_h 2 | #define VolumeRenderer_h 3 | 4 | #include "Grid.h" 5 | #include "Vec3.h" 6 | 7 | class VolumeRenderer { 8 | private: 9 | int width, height; 10 | float *image; 11 | Grid* volumeData; 12 | 13 | public: 14 | /*! Set up a simple renderer with a certain width and height for the final image. 15 | * @param[in] _width The width of the output image. 16 | * @param[in] _height The height of the output image. 17 | * @param[in] dataSource The grid from which we will read our data. 18 | */ 19 | VolumeRenderer(int _width, int _height, Grid* dataSource); 20 | ~VolumeRenderer(); 21 | 22 | /*! The meat of the system. This function does volume ray marching 23 | * using approximate single scattering. 24 | * @param[in] cameraPosition The position of the camera rendering the scene. 25 | * @param[in] cameraFocus A point in space which will be the center of the image. 26 | * @param[in] marchDistance The maximum distance that will be marched away from the camera. 27 | * @param[in] marchingStepCount The number of steps to march along the path from the camera into the scene. 28 | * @param[in] dsm The dsm to use for calculating lighting attenuation. 29 | */ 30 | void render(const Vec3& cameraPosition, const Vec3& cameraFocus, float marchDistance, int marchingStepCount, const Grid& dsm); 31 | 32 | /*! 33 | * Compute the light attenuation from a point in space to a light. The result 34 | * is in the range (0,1]. 35 | * @param[in] x The position we in question 36 | * @param[in] lightPosition The position of the light in 3D space. 37 | * @param[in] stepSize The step size in integrating the light attenuation. 38 | */ 39 | float sampleLighting(const Vec3& x, const Vec3& lightPosition, float stepSize); 40 | 41 | /*! Writes the contents of the image buffer to a file. 42 | * @param[out] path The location where the image file will be written to. 43 | */ 44 | void writeImage(const char *path); 45 | }; 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /Tutorial2/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "VolumeRenderer.h" 3 | #include "Grid.h" 4 | 5 | void populateGrid(Grid* grid, float T) { 6 | const int* dims = grid->getDimensions(); 7 | for(int i=0;isetVoxel(i,j,k,value); 25 | } 26 | } 27 | 28 | int main(int /*argc*/, char* const /*argv*/[]) { 29 | Grid grid(128,128,128, Vec3(-1,-1,-1), Vec3(1,1,1)); 30 | VolumeRenderer renderer(512,512, &grid); 31 | 32 | // Render some frames with the dense occluding ball moving 33 | for(int frame=0;frame<100;++frame) { 34 | 35 | // Let's fill the grid with something to render. 36 | populateGrid(&grid, (float)frame / 99); 37 | 38 | // Compute a DSM for faster light querying. 39 | // Light attenuation outside of the grid is none, so default 40 | // value outside of the grid is exp(0) = 1. 41 | Grid dsm(128,128,128, Vec3(-1,-1,-1), Vec3(1,1,1), 1); 42 | printf("Computing DSM ... "); fflush(stdout); 43 | dsm.computeDSM(grid, 1.f, .025, Vec3(0,1,0)); 44 | printf("done.\n"); fflush(stdout); 45 | 46 | // Ray marching 47 | renderer.render(Vec3(1,1,1), Vec3(0,0,0), 2, 128, dsm); 48 | 49 | // Output an image to look at. 50 | char frame_path[256]; 51 | sprintf(frame_path,"frame_%04d.png",frame); 52 | renderer.writeImage(frame_path); 53 | printf("Finished rendering frame %d / 100.\n", frame+1); 54 | } 55 | 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /Tutorial2/src/main.d: -------------------------------------------------------------------------------- 1 | src/main.d src/main.o: src/main.cpp src/VolumeRenderer.h src/Grid.h src/Vec3.h 2 | --------------------------------------------------------------------------------