├── .gitignore ├── Assets ├── Loader.cs ├── Loader.cs.meta ├── RayCaster.shader ├── RayCaster.shader.meta ├── Transformer.cs ├── Transformer.cs.meta ├── VolumetricData.mat ├── VolumetricData.mat.meta ├── data_cubes.py ├── data_cubes.py.meta ├── scene.unity ├── scene.unity.meta ├── skull-A8.asset ├── skull-A8.asset.meta ├── skull.bin8 └── skull.bin8.meta └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # from https://github.com/github/gitignore/blob/master/Unity.gitignore 2 | 3 | /[Ll]ibrary/ 4 | /[Tt]emp/ 5 | /[Oo]bj/ 6 | /[Bb]uild/ 7 | /[Bb]uilds/ 8 | /Assets/AssetStoreTools* 9 | /[Pp]roject[Ss]ettings/ 10 | # Visual Studio 2015 cache directory 11 | /.vs/ 12 | 13 | # Autogenerated VS/MD/Consulo solution and project files 14 | ExportedObj/ 15 | .consulo/ 16 | *.csproj 17 | *.unityproj 18 | *.sln 19 | *.suo 20 | *.tmp 21 | *.user 22 | *.userprefs 23 | *.pidb 24 | *.booproj 25 | *.svd 26 | *.pdb 27 | 28 | 29 | # Unity3D generated meta files 30 | *.pidb.meta 31 | 32 | # Unity3D Generated File On Crash Reports 33 | sysinfo.txt 34 | 35 | # Builds 36 | *.apk 37 | *.unitypackage 38 | -------------------------------------------------------------------------------- /Assets/Loader.cs: -------------------------------------------------------------------------------- 1 | // loads the raw binary data into a texture saved as a Unity asset 2 | // (so can be de-activated after a given data cube has been converted) 3 | // adapted from a XNA project by Kyle Hayward 4 | // http://graphicsrunner.blogspot.ca/2009/01/volume-rendering-101.html 5 | // data can be UInt8 or Float32, expected to be normalized in [0, 1] 6 | // Gilles Ferrand, University of Manitoba / RIKEN, 2016–2017 7 | 8 | #if UNITY_EDITOR 9 | using UnityEditor; 10 | #endif 11 | using UnityEngine; 12 | using System.IO; // to get BinaryReader 13 | using System.Linq; // to get array's Min/Max 14 | 15 | public class Loader : MonoBehaviour { 16 | 17 | public string path = @"Assets/"; 18 | public string filename = "skull"; 19 | public string extension = ".raw"; 20 | public int[] size = new int[3] {256, 256, 256}; 21 | public bool mipmap; 22 | public enum Format {Uint8, Float32}; 23 | public Format format; 24 | 25 | void Start() { 26 | Color[] colors = new Color[0]; 27 | TextureFormat textureformat = new TextureFormat(); 28 | Vector4 channels = new Vector4(); 29 | string suffix = ""; 30 | // load the raw data 31 | switch (format) { 32 | case(Format.Uint8): 33 | colors = LoadRAWUint8File (); 34 | textureformat = TextureFormat.Alpha8; 35 | channels = new Vector4 (0, 0, 0, 1); 36 | suffix = "A8"; 37 | break; 38 | case(Format.Float32): 39 | colors = LoadRAWFloat32File (); 40 | textureformat = TextureFormat.RFloat; 41 | channels = new Vector4 (1, 0, 0, 0); 42 | suffix = "R32"; 43 | break; 44 | } 45 | // create the texture 46 | Texture3D texture = new Texture3D (size[0], size[1], size[2], textureformat, mipmap); 47 | texture.SetPixels (colors); 48 | texture.Apply (); 49 | // assign it to the material of the parent object 50 | try { 51 | Material material = GetComponent ().material; 52 | material.SetTexture ("_Data", texture); 53 | material.SetVector ("_DataChannel", channels); 54 | } 55 | catch { 56 | Debug.Log ("Cannot attach the texture to the parent object"); 57 | } 58 | // save it as an asset for re-use 59 | #if UNITY_EDITOR 60 | AssetDatabase.CreateAsset(texture, path+filename+"-"+suffix+".asset"); 61 | #endif 62 | } 63 | 64 | private Color[] LoadRAWUint8File() 65 | { 66 | Color[] colors; // NB: data value goes into A channel only 67 | 68 | Debug.Log ("Opening file " + path + filename + extension); 69 | FileStream file = new FileStream (path + filename + extension, FileMode.Open); 70 | Debug.Log ("File length = " + file.Length + " bytes, Data size = " + size [0] * size [1] * size [2] + " points -> " + file.Length / (size [0] * size [1] * size [2]) + " byte(s) per point"); 71 | 72 | BinaryReader reader = new BinaryReader (file); 73 | byte[] buffer = new byte[size [0] * size [1] * size [2]]; 74 | reader.Read (buffer, 0, sizeof(byte) * buffer.Length); 75 | reader.Close (); 76 | 77 | colors = new Color[buffer.Length]; 78 | Color color = Color.black; 79 | for (int i = 0; i < buffer.Length; i++) { 80 | color = Color.black; 81 | color.a = (float)buffer [i] / byte.MaxValue; 82 | colors [i] = color; 83 | } 84 | 85 | return colors; 86 | } 87 | 88 | private Color[] LoadRAWFloat32File() 89 | { 90 | Color[] colors; // NB: data value goes into R channel only 91 | 92 | Debug.Log ("Opening file "+path+filename+extension); 93 | FileStream file = new FileStream(path+filename+extension, FileMode.Open); 94 | Debug.Log ("File length = "+file.Length+" bytes, Data size = "+size[0]*size[1]*size[2]+" points -> "+file.Length/(size[0]*size[1]*size[2])+" byte(s) per point"); 95 | 96 | BinaryReader reader = new BinaryReader(file); 97 | float[] buffer = new float[size[0] * size[1] * size[2]]; 98 | for (int i = 0; i < buffer.Length; i++) { 99 | buffer [i] = reader.ReadSingle (); 100 | } 101 | reader.Close(); 102 | 103 | colors = new Color[buffer.Length]; 104 | Color color = Color.black; 105 | for (int i = 0; i < buffer.Length; i++) { 106 | color = Color.black; 107 | color.r = (float)buffer[i]; 108 | colors [i] = color; 109 | } 110 | 111 | return colors; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /Assets/Loader.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a439add944f35694da16a0c637cbe329 3 | timeCreated: 1469652680 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/RayCaster.shader: -------------------------------------------------------------------------------- 1 | // shader that performs ray casting using a 3D texture 2 | // adapted from a Cg example by Nvidia 3 | // http://developer.download.nvidia.com/SDK/10/opengl/samples.html 4 | // Gilles Ferrand, University of Manitoba / RIKEN, 2016–2017 5 | 6 | Shader "Custom/Ray Casting" { 7 | 8 | Properties { 9 | // the data cube 10 | [NoScaleOffset] _Data ("Data Texture", 3D) = "" {} 11 | _DataChannel ("Data Channel", Vector) = (0,0,0,1) // in which channel were the data value stored? 12 | _Axis ("Axes order", Vector) = (1, 2, 3) // coordinate i=0,1,2 in Unity corresponds to coordinate _Axis[i]-1 in the data 13 | _TexFilling ("Data filling factors", Vector) = (1, 1, 1) // if only a fraction of the data texture is to be sampled 14 | // data slicing and thresholding (X, Y, Z are user coordinates) 15 | _SliceAxis1Min ("Slice along axis X: min", Range(0,1)) = 0 16 | _SliceAxis1Max ("Slice along axis X: max", Range(0,1)) = 1 17 | _SliceAxis2Min ("Slice along axis Y: min", Range(0,1)) = 0 18 | _SliceAxis2Max ("Slice along axis Y: max", Range(0,1)) = 1 19 | _SliceAxis3Min ("Slice along axis Z: min", Range(0,1)) = 0 20 | _SliceAxis3Max ("Slice along axis Z: max", Range(0,1)) = 1 21 | _DataMin ("Data threshold: min", Range(0,1)) = 0 22 | _DataMax ("Data threshold: max", Range(0,1)) = 1 23 | _StretchPower ("Data stretch power", Range(0.1,3)) = 1 // increase it to highlight the highest data values 24 | // normalization of data intensity (has to be adjusted for each data set) 25 | _NormPerStep ("Intensity normalization per step", Float) = 1 26 | _NormPerRay ("Intensity normalization per ray" , Float) = 1 27 | _Steps ("Max number of steps", Range(1,1024)) = 128 // should ideally be as large as data resolution, strongly affects frame rate 28 | } 29 | 30 | SubShader { 31 | 32 | Tags { "Queue" = "Transparent" } 33 | 34 | Pass { 35 | Blend SrcAlpha OneMinusSrcAlpha 36 | Cull Off 37 | ZTest LEqual 38 | ZWrite Off 39 | Fog { Mode off } 40 | 41 | CGPROGRAM 42 | #pragma target 3.0 43 | #pragma vertex vert 44 | #pragma fragment frag 45 | 46 | #include "UnityCG.cginc" 47 | 48 | sampler3D _Data; 49 | float4 _DataChannel; 50 | float3 _Axis; 51 | float3 _TexFilling; 52 | float _SliceAxis1Min, _SliceAxis1Max; 53 | float _SliceAxis2Min, _SliceAxis2Max; 54 | float _SliceAxis3Min, _SliceAxis3Max; 55 | float _DataMin, _DataMax; 56 | float _StretchPower; 57 | float _NormPerStep; 58 | float _NormPerRay; 59 | float _Steps; 60 | 61 | // calculates intersection between a ray and a box 62 | // http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter3.htm 63 | bool IntersectBox(float3 ray_o, float3 ray_d, float3 boxMin, float3 boxMax, out float tNear, out float tFar) 64 | { 65 | // compute intersection of ray with all six bbox planes 66 | float3 invR = 1.0 / ray_d; 67 | float3 tBot = invR * (boxMin.xyz - ray_o); 68 | float3 tTop = invR * (boxMax.xyz - ray_o); 69 | // re-order intersections to find smallest and largest on each axis 70 | float3 tMin = min (tTop, tBot); 71 | float3 tMax = max (tTop, tBot); 72 | // find the largest tMin and the smallest tMax 73 | float2 t0 = max (tMin.xx, tMin.yz); 74 | float largest_tMin = max (t0.x, t0.y); 75 | t0 = min (tMax.xx, tMax.yz); 76 | float smallest_tMax = min (t0.x, t0.y); 77 | // check for hit 78 | bool hit = (largest_tMin <= smallest_tMax); 79 | tNear = largest_tMin; 80 | tFar = smallest_tMax; 81 | return hit; 82 | } 83 | 84 | struct vert_input { 85 | float4 pos : POSITION; 86 | }; 87 | 88 | struct frag_input { 89 | float4 pos : SV_POSITION; 90 | float3 ray_o : TEXCOORD1; // ray origin 91 | float3 ray_d : TEXCOORD2; // ray direction 92 | }; 93 | 94 | // vertex program 95 | frag_input vert(vert_input i) 96 | { 97 | frag_input o; 98 | 99 | // calculate eye ray in object space 100 | o.ray_d = -ObjSpaceViewDir(i.pos); 101 | o.ray_o = i.pos.xyz - o.ray_d; 102 | // calculate position on screen (unused) 103 | o.pos = UnityObjectToClipPos(i.pos); 104 | 105 | return o; 106 | } 107 | 108 | // gets data value at a given position 109 | float4 get_data(float3 pos) { 110 | // sample texture (pos is normalized in [0,1]) 111 | float3 posTex = float3(pos[_Axis[0]-1],pos[_Axis[1]-1],pos[_Axis[2]-1]); 112 | posTex = (posTex-0.5) * _TexFilling + 0.5; 113 | float4 data4 = tex3Dlod(_Data, float4(posTex,0)); 114 | float data = _DataChannel[0]*data4.r + _DataChannel[1]*data4.g + _DataChannel[2]*data4.b + _DataChannel[3]*data4.a; 115 | // slice and threshold 116 | data *= step(_SliceAxis1Min, posTex.x); 117 | data *= step(_SliceAxis2Min, posTex.y); 118 | data *= step(_SliceAxis3Min, posTex.z); 119 | data *= step(posTex.x, _SliceAxis1Max); 120 | data *= step(posTex.y, _SliceAxis2Max); 121 | data *= step(posTex.z, _SliceAxis3Max); 122 | data *= step(_DataMin, data); 123 | data *= step(data, _DataMax); 124 | // colourize 125 | float4 col = float4(data, data, data, data); 126 | return col; 127 | } 128 | 129 | #define FRONT_TO_BACK // ray integration order (BACK_TO_FRONT not working when being inside the cube) 130 | 131 | // fragment program 132 | float4 frag(frag_input i) : COLOR 133 | { 134 | i.ray_d = normalize(i.ray_d); 135 | // calculate eye ray intersection with cube bounding box 136 | float3 boxMin = { -0.5, -0.5, -0.5 }; 137 | float3 boxMax = { 0.5, 0.5, 0.5 }; 138 | float tNear, tFar; 139 | bool hit = IntersectBox(i.ray_o, i.ray_d, boxMin, boxMax, tNear, tFar); 140 | if (!hit) discard; 141 | if (tNear < 0.0) tNear = 0.0; 142 | // calculate intersection points 143 | float3 pNear = i.ray_o + i.ray_d*tNear; 144 | float3 pFar = i.ray_o + i.ray_d*tFar; 145 | // convert to texture space 146 | pNear = pNear + 0.5; 147 | pFar = pFar + 0.5; 148 | 149 | // march along ray inside the cube, accumulating color 150 | #ifdef FRONT_TO_BACK 151 | float3 ray_pos = pNear; 152 | float3 ray_dir = pFar - pNear; 153 | #else 154 | float3 ray_pos = pFar; 155 | float3 ray_dir = pNear - pFar; 156 | #endif 157 | float3 ray_step = normalize(ray_dir) * sqrt(3) / _Steps; 158 | float4 ray_col = 0; 159 | for(int k = 0; k < _Steps; k++) 160 | { 161 | float4 voxel_col = get_data(ray_pos); 162 | voxel_col.a = _NormPerStep * length(ray_step) * pow(voxel_col.a,_StretchPower); 163 | #ifdef FRONT_TO_BACK 164 | //voxel_col.rgb *= voxel_col.a; 165 | //ray_col = (1.0f - ray_col.a) * voxel_col + ray_col; 166 | ray_col.rgb = ray_col.rgb + (1 - ray_col.a) * voxel_col.a * voxel_col.rgb; 167 | ray_col.a = ray_col.a + (1 - ray_col.a) * voxel_col.a; 168 | #else 169 | ray_col.rgb = (1-voxel_col.a)*ray_col.rgb + voxel_col.a * voxel_col.rgb; 170 | ray_col.a = (1-voxel_col.a)*ray_col.a + voxel_col.a; 171 | #endif 172 | ray_pos += ray_step; 173 | if (ray_pos.x < 0 || ray_pos.y < 0 || ray_pos.z < 0) break; 174 | if (ray_pos.x > 1 || ray_pos.y > 1 || ray_pos.z > 1) break; 175 | } 176 | ray_col *= _NormPerRay; 177 | ray_col = clamp(ray_col,0,1); 178 | return ray_col; 179 | } 180 | 181 | ENDCG 182 | 183 | } 184 | 185 | } 186 | 187 | FallBack Off 188 | } -------------------------------------------------------------------------------- /Assets/RayCaster.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 049fd2c33ad2b5f47984159b5eb9f5cb 3 | timeCreated: 1469658036 4 | licenseType: Free 5 | ShaderImporter: 6 | defaultTextures: [] 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/Transformer.cs: -------------------------------------------------------------------------------- 1 | // transforms an object: 2 | // - hold and drag primary mouse button to rotate the cube around its centre 3 | // - hold and drag secondary mouse button to rotate the cube around the camera (useful when inside the cube) 4 | // - use arrow keys to translate the cube left/right and forward/backward 5 | // - scroll wheel to scale the cube up/down 6 | // adapted from various online sources 7 | // Gilles Ferrand, University of Manitoba / RIKEN, 2016–2017 8 | 9 | using UnityEngine; 10 | 11 | public class Transformer : MonoBehaviour { 12 | 13 | // rotate 14 | public float rotSpeed = 4.0f; 15 | bool isRotating; 16 | Vector3 rotationAxisX; 17 | Vector3 rotationAxisY; 18 | Vector3 mouseOrigin; 19 | Vector3 angleDelta; 20 | GameObject rotationCentre; 21 | 22 | // translate 23 | public float panSpeed = 2.0f; 24 | bool isMovingLeft, isMovingRight; 25 | bool isMovingUp, isMovingDown; 26 | bool isMovingForward, isMovingBackward; 27 | Vector3 translationAxis; 28 | 29 | // scale 30 | public float zoomSpeed = 1.0f; 31 | private float scaleMin = 0.01f; 32 | private float scaleMax = 100f; 33 | bool isScaling; 34 | float scale; // same for the 3 axes 35 | 36 | void Awake () { 37 | } 38 | 39 | void Start () { 40 | scale = Mathf.Clamp (this.transform.localScale[0], scaleMin, scaleMax); 41 | } 42 | 43 | void Update () { 44 | 45 | // rotate 46 | 47 | if (Input.GetMouseButtonDown (0) || Input.GetMouseButtonDown (1)) { 48 | isRotating = true; 49 | if (Input.GetMouseButtonDown (0)) rotationCentre = this.gameObject; 50 | if (Input.GetMouseButtonDown (1)) rotationCentre = Camera.main.gameObject; 51 | mouseOrigin = Input.mousePosition; 52 | } 53 | 54 | if (isRotating) { 55 | rotationAxisX = Camera.main.transform.up; 56 | rotationAxisY = Camera.main.transform.right; 57 | angleDelta = (Input.mousePosition - mouseOrigin)/Screen.width; 58 | angleDelta *= rotSpeed; 59 | angleDelta.x *= -1; 60 | this.transform.RotateAround (rotationCentre.transform.position, rotationAxisX, angleDelta.x); 61 | this.transform.RotateAround (rotationCentre.transform.position, rotationAxisY, angleDelta.y); 62 | if (!Input.GetMouseButton(0) && !Input.GetMouseButton(1)) isRotating = false; 63 | } 64 | 65 | // translate 66 | 67 | isMovingRight = Input.GetKey (KeyCode.RightArrow); 68 | isMovingLeft = Input.GetKey (KeyCode.LeftArrow); 69 | isMovingForward = Input.GetKey (KeyCode.UpArrow); 70 | isMovingBackward = Input.GetKey (KeyCode.DownArrow); 71 | 72 | if (isMovingRight || isMovingLeft) { 73 | translationAxis = Camera.main.transform.right; 74 | float distance = panSpeed * Time.deltaTime; 75 | if (isMovingRight) this.transform.position += translationAxis * distance; 76 | if (isMovingLeft) this.transform.position -= translationAxis * distance; 77 | } 78 | 79 | if (isMovingForward || isMovingBackward) { 80 | translationAxis = Camera.main.transform.forward; 81 | float distance = panSpeed * Time.deltaTime; 82 | if (isMovingForward) this.transform.position += translationAxis * distance; 83 | if (isMovingBackward) this.transform.position -= translationAxis * distance; 84 | } 85 | 86 | // scale 87 | 88 | float scroll = Input.GetAxis ("Mouse ScrollWheel"); 89 | isScaling = scroll != 0; 90 | 91 | if(isScaling) { 92 | scale *= 1 + scroll*zoomSpeed; 93 | scale = Mathf.Clamp(scale, scaleMin, scaleMax); 94 | this.transform.localScale = new Vector3(scale, scale, scale); 95 | if (scroll == 0) isScaling = false; 96 | } 97 | 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /Assets/Transformer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dc5665e142bc95243a82dba8c52fafe9 3 | timeCreated: 1469659734 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/VolumetricData.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesferrand/Unity-RayTracing/6e4bc157fea0a97b12e6f5f8332dced67b89d5e1/Assets/VolumetricData.mat -------------------------------------------------------------------------------- /Assets/VolumetricData.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 83ab474cbb242b744825db2d0bc653d1 3 | timeCreated: 1469656179 4 | licenseType: Free 5 | NativeFormatImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/data_cubes.py: -------------------------------------------------------------------------------- 1 | # generates mock 3D data cubes for loading into Unity 2 | # Gilles Ferrand, RIKEN, 2017 3 | 4 | import numpy as np 5 | import scipy.interpolate 6 | 7 | def axes(N_bins=256, formats=[8]): 8 | """ generates coordinate axes at the centre of the cube """ 9 | cube = np.zeros((N_bins,N_bins,N_bins)) 10 | half = int(N_bins/2) 11 | radius = 2 12 | cube[half:int(half*(1+3/3.))+1,half-radius:half+radius+1,half-radius:half+radius+1] = 1 # long segment along 1st dimension 13 | cube[half-radius:half+radius+1,half:int(half*(1+2/3.))+1,half-radius:half+radius+1] = 1 # medium segment along 2nd dimension 14 | cube[half-radius:half+radius+1,half-radius:half+radius+1,half:int(half*(1+1/3.))+1] = 1 # short segment along 3rd dimension 15 | for format in formats: tofile(cube, name='cube_axes', format=format, T=(3,2,1)) 16 | 17 | def random(N_bins=256, N_dots=None, rand_power=0, rand_min=0, rand_max=1, gauss_centre=0.5, gauss_width=0, types=["sparse","dense"], formats=[8]): 18 | """ generate dots at random locations inside the cube """ 19 | if N_dots==None: N_dots = N_bins**2 20 | # set random coordinates 21 | x = np.random.rand(N_dots) * N_bins 22 | y = np.random.rand(N_dots) * N_bins 23 | z = np.random.rand(N_dots) * N_bins 24 | # set data 25 | d = np.random.rand(N_dots)**rand_power 26 | d = rand_min + d * (rand_max-rand_min) 27 | if gauss_width>0: # Gaussian distribution in space 28 | gauss_centre *= N_bins 29 | gauss_width *= N_bins 30 | d *= np.exp(-((x-gauss_centre)/gauss_width)**2-((y-gauss_centre)/gauss_width)**2-((z-gauss_centre)/gauss_width)**2) 31 | # put dots inside a regular cube 32 | if "sparse" in types: 33 | cube = np.zeros((N_bins,N_bins,N_bins)) 34 | weight = np.zeros((N_bins,N_bins,N_bins)) 35 | for i in range(N_dots): 36 | cube [int(x[i]),int(y[i]),int(z[i])] += d[i] 37 | weight[int(x[i]),int(y[i]),int(z[i])] += 1. 38 | i_dots = np.where(weight>=1) 39 | cube[i_dots] /= weight[i_dots] 40 | cube /= cube.max() 41 | print "%i dots put in the cube at %i locations"%(N_dots,len(i_dots[0])) 42 | for format in formats: tofile(cube, name='cube_random_sparse', format=format, T=(3,2,1)) 43 | # interpolate the scattered data 44 | if "dense" in types: 45 | print "interpolating values on a regular grid" 46 | t = np.linspace(0, N_bins, N_bins) 47 | X, Y, Z = np.meshgrid(t,t,t) 48 | D = scipy.interpolate.griddata((x,y,z), d, (X,Y,Z), method='linear', fill_value=0) 49 | if np.isnan(D).any(): print "! interpolation failed" 50 | for format in formats: tofile(D, name='cube_random_dense', format=format, T=(3,2,1)) 51 | 52 | def tofile(cube_norm, name='cube', format=8, T=(3,2,1)): 53 | """ writes a data cube in an 8-bit binary file """ 54 | # the data are expected to be normalized in [0,1] 55 | if format==8: 56 | cube_fmt = (cube_norm*255).astype('uint8') 57 | elif format==32: 58 | cube_fmt = cube_norm.astype('float32') 59 | else: 60 | print "unsupported format" 61 | return 62 | filename = '%s_T%i%i%i_%ix%ix%i.bin%i'%(name,T[0],T[1],T[2],cube_norm.shape[0],cube_norm.shape[1],cube_norm.shape[2],format) 63 | print "writing "+filename 64 | cube_fmt.transpose(T[0]-1,T[1]-1,T[2]-1).tofile(filename) 65 | # note: data is always written in C order by tofile() 66 | # transpose(3,2,1) is needed so that cube dimensions 1,2,3 map to Unity axes x,y,z (left -handed coordinate system) 67 | # transpose(2,3,1) is needed so that cube dimensions 1,2,3 map to Unity axes x,z,y (right-handed coordinate system) 68 | 69 | def examples(N_bins=256, formats=[8]): 70 | # coordinate system 71 | axes(N_bins=N_bins) 72 | # dots with Gaussian spatial distribution 73 | random(N_bins=N_bins,rand_power=0,rand_max=1.0,gauss_width=0.0,types=["sparse"],formats=formats) 74 | # cube filled with spongy texture 75 | random(N_bins=N_bins,rand_power=2,rand_max=0.5,gauss_width=0.0,types=["dense" ],formats=formats) 76 | 77 | if __name__ == "__main__": 78 | examples() 79 | -------------------------------------------------------------------------------- /Assets/data_cubes.py.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e98cbc8a5bf8542208a6706aaf67030e 3 | timeCreated: 1509006989 4 | licenseType: Free 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/scene.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesferrand/Unity-RayTracing/6e4bc157fea0a97b12e6f5f8332dced67b89d5e1/Assets/scene.unity -------------------------------------------------------------------------------- /Assets/scene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3738b5a4a3ab6c54497859ac2c535b20 3 | timeCreated: 1469650523 4 | licenseType: Free 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/skull-A8.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesferrand/Unity-RayTracing/6e4bc157fea0a97b12e6f5f8332dced67b89d5e1/Assets/skull-A8.asset -------------------------------------------------------------------------------- /Assets/skull-A8.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a4313a4fe86a7334997911c55b2ecf81 3 | timeCreated: 1509008369 4 | licenseType: Free 5 | NativeFormatImporter: 6 | mainObjectFileID: 11700000 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/skull.bin8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesferrand/Unity-RayTracing/6e4bc157fea0a97b12e6f5f8332dced67b89d5e1/Assets/skull.bin8 -------------------------------------------------------------------------------- /Assets/skull.bin8.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 13b06356c388e854585090c18ec4bc20 3 | timeCreated: 1469650566 4 | licenseType: Free 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity-RayTracing 2 | Demo of volume rendering a data cube in Unity by performing ray tracing on the GPU 3 | 4 | The Loader script reads a raw binary file as a 3D texture, assigns the texture to the VolumetricData material of the Data Cube object, and saves it as an asset for re-use. 5 | The RayCaster shader performs volume rendering on the GPU, by casting rays inside the cube and sampling the 3D texture along each ray. It includes slicing and thresholding of the data cube. 6 | The Transformer script provides basic runtime interaction using a mouse and keyboard: rotate, translate, and scale the cube. 7 | 8 | This demo was made as part of an astronomy project, see report at www.arxiv.org/abs/1607.08874 --------------------------------------------------------------------------------