├── Assets ├── ComputeShaderTest.unity ├── ComputeShaderTest.unity.meta ├── ComputeShaders.meta ├── ComputeShaders │ ├── RayTracer.compute │ ├── RayTracer.compute.meta │ ├── SimpleGradient.compute │ └── SimpleGradient.compute.meta ├── RunComputeShader.cs └── RunComputeShader.cs.meta ├── LICENSE ├── README.md └── example.png /Assets/ComputeShaderTest.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arlorean/UnityComputeShaderTest/6d6e5233af314c296f69b1c9e7999af2dd532443/Assets/ComputeShaderTest.unity -------------------------------------------------------------------------------- /Assets/ComputeShaderTest.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6a3654b75e9220049931ff83611f196e 3 | timeCreated: 1495293407 4 | licenseType: Pro 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/ComputeShaders.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a0c3e0cec24a5754392f872986439bc3 3 | folderAsset: yes 4 | timeCreated: 1495380179 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/ComputeShaders/RayTracer.compute: -------------------------------------------------------------------------------- 1 |  2 | // Unity Compute Shader Globals and Entry Point 3 | #pragma kernel CSMain 4 | 5 | float iGlobalTime = 0; 6 | float4 iResolution = float4(256, 256, 0, 0); 7 | RWTexture2D Result; 8 | 9 | // Forward declare fragment shader 10 | void mainImage(out float4 fragColor, in float2 fragCoord); 11 | 12 | [numthreads(8, 8, 1)] 13 | void CSMain(uint3 id : SV_DispatchThreadID) 14 | { 15 | float4 fragColor; 16 | float2 fragCoord = float2(id.x, id.y); 17 | mainImage(fragColor, fragCoord); 18 | Result[id.xy] = fragColor; 19 | } 20 | 21 | // HLSL Port of https://www.tinycranes.com/blog/2015/05/annotated-realtime-raytracing/ 22 | 23 | // Ray tracing is a topic I have always wanted to explore, but never really had 24 | // the opportunity to do so until now. What exactly is ray tracing? Consider a 25 | // lamp hanging from the ceiling. Light is constantly being emitted from the 26 | // lamp in the form of light rays, which bounce around the room until they hit 27 | // your eye. Ray tracing follows a similar concept by simulating the path of 28 | // light through a scene, except in reverse. There is no point in doing the math 29 | // for light rays you cannot see! 30 | 31 | // Algorithmically, ray tracing is very elegant. For each pixel, shoot a light 32 | // ray from the camera through each pixel on screen. If the ray collides with 33 | // geometry in the scene, create new rays that perform the same process for both 34 | // reflection, as in a mirror, and refraction, as in through water. Repeat 35 | // to your satisfaction. 36 | 37 | // Having worked extensively with OpenCL in the past, this seemed like a good 38 | // candidate to port to a parallel runtime on a GPU. Inspired by the [smallpt](http://www.kevinbeason.com/smallpt/#moreinfo) 39 | // line-by-line explanation, I decided to write a parallel ray tracer with 40 | // extensive annotations. The results are below ... 41 | 42 | // ![screenshot](/uploads/raytracer.png) 43 | 44 | // I start with a simple ray definition, consisting of an origin point and a 45 | // direction vector. I also define a directional light to illuminate my scene. 46 | struct Ray { 47 | float3 origin; 48 | float3 direction; 49 | }; 50 | 51 | struct Light { 52 | float3 color; 53 | float3 direction; 54 | }; 55 | 56 | // In real life, objects have many different material properties. Some objects 57 | // respond very differently to light than others. For instance, a sheet of paper 58 | // and a polished mirror. The former exhibits a strong *diffuse* response; 59 | // incoming light is reflected at many angles. The latter is an example of a 60 | // *specular* response, where incoming light is reflected in a single direction. 61 | // To model this, I create a basic material definition. Objects in my scene 62 | // share a single (RGB) color with diffuse and specular weights. 63 | struct Material { 64 | float3 color; 65 | float diffuse; 66 | float specular; 67 | }; 68 | 69 | // To render the scene, I need to know where a ray intersects with an object. 70 | // Since rays have infinite length from an origin, I can model the point of 71 | // intersection by storing the distance along the ray. I also need to store the 72 | // surface normal so I know which way to bounce! Once I create a ray, it loses 73 | // the concept of scene geometry, so one more thing I do is forward the surface 74 | // material properties. 75 | struct Intersect { 76 | float len; 77 | float3 normal; 78 | Material material; 79 | }; 80 | 81 | // The last data structures I create are for objects used to fill my scene. The 82 | // most basic object I can model is a sphere, which is defined as a radius at 83 | // some center position, with some material properties. To draw the floor, I 84 | // also define a simple horizontal plane centered at the origin, with a normal 85 | // vector pointing upwards. 86 | struct Sphere { 87 | float radius; 88 | float3 position; 89 | Material material; 90 | }; 91 | 92 | struct Plane { 93 | float3 normal; 94 | Material material; 95 | }; 96 | 97 | // At this point, I define some global variables. A more advanced program might 98 | // pass these values in as uniforms, but for now, this is easier to tinker with. 99 | // Due to floating point precision errors, when a ray intersects geometry at a 100 | // surface, the point of intersection could possibly be just below the surface. 101 | // The subsequent reflection ray would then bounce off the *inside* wall of the 102 | // surface. This is known as self-intersection. When creating new rays, I 103 | // initialize them at a slightly offset origin to help mitigate this problem. 104 | static const float epsilon = 1e-3; 105 | 106 | // The classical ray tracing algorithm is recursive. However, GLSL does not 107 | // support recursion, so I instead use an iterative approach to control the 108 | // number of light bounces. 109 | static const int iterations = 16; 110 | 111 | // Next, I define an exposure time and gamma value. At this point, I also create 112 | // a basic directional light and define the ambient light color; the color here 113 | // is mostly a matter of taste. Basically ... lighting controls. 114 | static const float exposure = 1e-2; 115 | static const float gamma = 2.2; 116 | static const float intensity = 100.0; 117 | static const float3 ambient = float3(0.6, 0.8, 1.0) * intensity / gamma; 118 | 119 | // For a Static Light 120 | static const Light light = { float3(1.0, 1.0, 1.0) * intensity, normalize(float3(-1.0, 0.75, 1.0)) }; 121 | 122 | // For a Rotating Light 123 | // Light light = Light(float3(1.0) * intensity, normalize( 124 | // float3(-1.0 + 4.0 * cos(iGlobalTime), 4.75, 125 | // 1.0 + 4.0 * sin(iGlobalTime)))); 126 | 127 | // I strongly dislike this line. I needed to know when a ray hits or misses a 128 | // surface. If it hits geometry, I returned the point at the surface. Otherwise, 129 | // the ray misses all geometry and instead hits the sky box. In a language that 130 | // supports dynamic return values, I could `return false`, but that is not an 131 | // option in GLSL. In the interests of making progress, I created an intersect 132 | // of distance zero to represent a miss and moved on. 133 | static const Intersect miss = { 0.0, float3(0.0, 0.0, 0.0), { float3(0.0, 0.0, 0.0), 0.0, 0.0 } }; 134 | 135 | // As indicated earlier, I implement ray tracing for spheres. I need to compute 136 | // the point at which a ray intersects with a sphere. [Line-Sphere](http://en.wikipedia.org/wiki/Line-sphere_intersection) 137 | // intersection is relatively straightforward. For reflection purposes, a ray 138 | // either hits or misses, so I need to check for no solutions, or two solutions. 139 | // In the latter case, I need to determine which solution is "in front" so I can 140 | // return an intersection of appropriate distance from the ray origin. 141 | Intersect intersect(Ray ray, Sphere sphere) { 142 | // Check for a Negative Square Root 143 | float3 oc = sphere.position - ray.origin; 144 | float l = dot(ray.direction, oc); 145 | float det = pow(l, 2.0) - dot(oc, oc) + pow(sphere.radius, 2.0); 146 | if (det < 0.0) return miss; 147 | 148 | // Find the Closer of Two Solutions 149 | float len = l - sqrt(det); 150 | if (len < 0.0) len = l + sqrt(det); 151 | if (len < 0.0) return miss; 152 | Intersect result = { len, (ray.origin + len*ray.direction - sphere.position) / sphere.radius, sphere.material }; 153 | return result; 154 | } 155 | 156 | // Since I created a floor plane, I likewise have to handle reflections for 157 | // planes by implementing [Line-Plane](http://en.wikipedia.org/wiki/Line-plane_intersection) 158 | // intersection. I only care about the intersect for the purposes of reflection, 159 | // so I only check if the quotient is non-zero. 160 | Intersect intersect(Ray ray, Plane plane) { 161 | float len = -dot(ray.origin, plane.normal) / dot(ray.direction, plane.normal); 162 | if (len < 0.0) return miss; 163 | Intersect result = { len, plane.normal, plane.material }; 164 | return result; 165 | } 166 | 167 | // In a *real* ray tracing renderer, geometry would be passed in from the host 168 | // as a mesh containing vertices, normals, and texture coordinates, but for the 169 | // sake of simplicity, I hand-coded the scene-graph. In this function, I take an 170 | // input ray and iterate through all geometry to determine intersections. 171 | Intersect trace(Ray ray) { 172 | const int num_spheres = 3; 173 | Sphere spheres[num_spheres] = { 174 | 175 | // I initially started with the [smallpt](www.kevinbeason.com/smallpt/) 176 | // scene definition, but soon found performance was abysmal on very large 177 | // spheres. I kept the general format, modified to fit my data structures. 178 | 179 | { 2.0, float3(-4.0, 3.0 + sin(iGlobalTime), 0), { float3(1.0, 0.0, 0.2), 1.0, 0.001 } }, 180 | { 3.0, float3(4.0 + cos(iGlobalTime), 3.0, 0), { float3(0.0, 0.2, 1.0), 1.0, 0.0 } }, 181 | { 1.0, float3(0.5, 1.0, 6.0), { float3(1.0, 1.0, 1.0), 0.5, 0.25 } } 182 | }; 183 | 184 | // Since my ray tracing approach involves drawing to a 2D quad, I can no 185 | // longer use the OpenGL Depth and Stencil buffers to control the draw 186 | // order. Drawing is therefore sensitive to z-indexing, so I first intersect 187 | // with the plane, then loop through all spheres back-to-front. 188 | 189 | Intersect intersection = miss; 190 | Plane floor = { float3(0, 1, 0),{ float3(1.0, 1.0, 1.0), 1.0, 0.0 } }; 191 | Intersect plane = intersect(ray, floor); 192 | if (plane.material.diffuse > 0.0 || plane.material.specular > 0.0) { intersection = plane; } 193 | for (int i = 0; i < num_spheres; i++) { 194 | Intersect sphere = intersect(ray, spheres[i]); 195 | if (sphere.material.diffuse > 0.0 || sphere.material.specular > 0.0) 196 | intersection = sphere; 197 | } 198 | return intersection; 199 | } 200 | 201 | // This is the critical part of writing a ray tracer. I start with some empty 202 | // scratch vectors for color data and the Fresnel factor. I trace the scene with 203 | // using an input ray, and continue to fire new rays until the iteration depth 204 | // is reached, at which point I return the total sum of the color values from 205 | // computed at each bounce. 206 | float3 radiance(Ray ray) { 207 | float3 color = float3(0.0, 0.0, 0.0), fresnel = float3(0.0, 0.0, 0.0); 208 | float3 mask = float3(1.0, 1.0, 1.0); 209 | for (int i = 0; i <= iterations; ++i) { 210 | Intersect hit = trace(ray); 211 | 212 | // This goes back to the dummy "miss" intersect. Basically, if the scene 213 | // trace returns an intersection with either a diffuse or specular 214 | // coefficient, then it has encountered a surface of a sphere or plane. 215 | // Otherwise, the current ray has reached the ambient-colored sky box. 216 | 217 | if (hit.material.diffuse > 0.0 || hit.material.specular > 0.0) { 218 | 219 | // Here I use the [Schlick Approximation](http://en.wikipedia.org/wiki/Schlick's_approximation) 220 | // to determine the Fresnel specular contribution factor, a measure 221 | // of how much incoming light is reflected or refracted. I compute 222 | // the Fresnel term and use a mask to track the fraction of 223 | // reflected light in the current ray with respect to the original. 224 | 225 | float3 r0 = hit.material.color.rgb * hit.material.specular; 226 | float hv = clamp(dot(hit.normal, -ray.direction), 0.0, 1.0); 227 | fresnel = r0 + (1.0 - r0) * pow(1.0 - hv, 5.0); 228 | mask *= fresnel; 229 | 230 | // I handle shadows and diffuse colors next. I condensed this part 231 | // into one conditional evaluation for brevity. Remember `epsilon`? 232 | // I use it to trace a ray slightly offset from the point of 233 | // intersection to the light source. If the shadow ray does not hit 234 | // an object, it will be a "miss" as it hits the skybox. This means 235 | // there are no objects between the point and the light, at which 236 | // point I can add the diffuse color to the fragment color since the 237 | // object is not in shadow. 238 | 239 | Ray shadow = { ray.origin + hit.len * ray.direction + epsilon * light.direction, light.direction }; 240 | Intersect shadowedBy = trace(shadow); 241 | if (shadowedBy.len == 0.0) { 242 | color += clamp(dot(hit.normal, light.direction), 0.0, 1.0) * light.color 243 | * hit.material.color.rgb * hit.material.diffuse 244 | * (1.0 - fresnel) * mask / fresnel; 245 | } 246 | 247 | // After computing diffuse colors, I then generate a new reflection 248 | // ray and overwrite the original ray that was passed in as an 249 | // argument to the radiance(...) function. Then I repeat until I 250 | // reach the iteration depth. 251 | 252 | float3 reflection = reflect(ray.direction, hit.normal); 253 | Ray reflectionRay = { ray.origin + hit.len * ray.direction + epsilon * reflection, reflection }; 254 | ray = reflectionRay; 255 | } 256 | else { 257 | 258 | // This is the other half of the tracing branch. If the trace failed 259 | // to return an intersection with an attached material, then it is 260 | // safe to assume that the ray points at the sky, or out of bounds 261 | // of the scene. At this point I realized that real objects have a 262 | // small sheen to them, so I hard-coded a small spotlight pointing 263 | // in the same direction as the main light for pseudo-realism. 264 | 265 | float3 spotlight = float3(1e6, 1e6, 1e6) * pow(abs(dot(ray.direction, light.direction)), 250.0); 266 | color += mask * (ambient + spotlight); break; 267 | } 268 | } 269 | return color; 270 | } 271 | 272 | // The main function primarily deals with organizing data from OpenGL into a 273 | // format that the ray tracer can use. For ray tracing, I need to fire a ray for 274 | // each pixel, or more precisely, a ray for every fragment. However, pixels to 275 | // fragment coordinates do not map one a one-to-one basis, so I need to divide 276 | // the fragment coordinates by the viewport resolution. I then offset that by a 277 | // fixed value to re-center the coordinate system. 278 | 279 | void mainImage(out float4 fragColor, in float2 fragCoord) { 280 | float2 uv = fragCoord.xy / iResolution.xy - float2(0.5, 0.5); 281 | uv.x *= iResolution.x / iResolution.y; 282 | 283 | // For each fragment, create a ray at a fixed point of origin directed at 284 | // the coordinates of each fragment. The last thing before writing the color 285 | // to the fragment is to post-process the pixel values using tone-mapping. 286 | // In this case, I adjust for exposure and perform linear gamma correction. 287 | 288 | Ray ray = { float3(0.0, 2.5, 12.0), normalize(float3(uv.x, uv.y, -1.0)) }; 289 | fragColor = float4(pow(abs(radiance(ray) * exposure), float3(1.0 / gamma, 1.0 / gamma, 1.0 / gamma)), 1.0); 290 | } 291 | 292 | // If all goes well, you should see an animated scene below! 293 | // 295 | 296 | // This was my first foray into ray tracing. Originally, I wanted to write this 297 | // using the OpenGL Compute Shader. That was harder to setup than I originally 298 | // realized, and I spent a fair bit of time mucking around with OpenGL and cmake 299 | // before deciding to just sit down and start programming. 300 | 301 | // All things considered, this is a pretty limited ray tracer. Some low hanging 302 | // fruit might be to add anti-aliasing and soft shadows. The former was not an 303 | // issue until I ported this from a HiDPI display onto the WebGL canvas. The 304 | // latter involves finding a quality random number generator. Maybe a summer 305 | // project before I start working ... 306 | -------------------------------------------------------------------------------- /Assets/ComputeShaders/RayTracer.compute.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 80dd619cb95084c49bb66f3cf7ed1f9d 3 | timeCreated: 1495370135 4 | licenseType: Pro 5 | ComputeShaderImporter: 6 | currentAPIMask: 4 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/ComputeShaders/SimpleGradient.compute: -------------------------------------------------------------------------------- 1 | float iGlobalTime = 0; 2 | float4 iResolution = float4(256, 256, 0, 0); 3 | 4 | // Each #kernel tells which function to compile; you can have many kernels 5 | #pragma kernel CSMain 6 | 7 | // Create a RenderTexture with enableRandomWrite flag and set it 8 | // with cs.SetTexture 9 | RWTexture2D Result; 10 | 11 | [numthreads(8, 8, 1)] 12 | void CSMain (uint3 id : SV_DispatchThreadID) 13 | { 14 | Result[id.xy] = float4(id.x/ iResolution.x, id.y/ iResolution.y, sin(iGlobalTime), 1.0); 15 | } 16 | -------------------------------------------------------------------------------- /Assets/ComputeShaders/SimpleGradient.compute.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 41cb4279e23fc5c43a4e2f44ef64dee6 3 | timeCreated: 1495293708 4 | licenseType: Pro 5 | ComputeShaderImporter: 6 | currentAPIMask: 4 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/RunComputeShader.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UI; 3 | using System.Collections; 4 | 5 | public class RunComputeShader : MonoBehaviour { 6 | public ComputeShader shader; 7 | RenderTexture texture; 8 | int numThreadsGroupsX, numThreadsGroupsY, numThreadsGroupsZ = 1; 9 | 10 | void Start() { 11 | UpdateTextureSize(); 12 | } 13 | 14 | void UpdateTextureSize() { 15 | var width = Screen.width; 16 | var height = Screen.height; 17 | if (texture != null && texture.width == width && texture.height == height) { 18 | return; 19 | } 20 | if (texture != null) { 21 | texture.Release(); 22 | } 23 | 24 | texture = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); 25 | texture.filterMode = FilterMode.Point; 26 | texture.enableRandomWrite = true; 27 | texture.Create(); 28 | 29 | var rawImage = GetComponent(); 30 | rawImage.rectTransform.anchorMin = Vector2.zero; 31 | rawImage.rectTransform.anchorMax = Vector2.one; 32 | rawImage.rectTransform.sizeDelta = Vector2.zero; 33 | rawImage.texture = texture; 34 | 35 | uint numThreadsX, numThreadsY, numThreadsZ; 36 | shader.GetKernelThreadGroupSizes(0, out numThreadsX, out numThreadsY, out numThreadsZ); 37 | numThreadsGroupsX = (int)(texture.width / numThreadsX); 38 | numThreadsGroupsY = (int)(texture.height / numThreadsY); 39 | 40 | shader.SetTexture(0, "Result", texture); 41 | shader.SetVector("iResolution", new Vector4(width, height)); 42 | } 43 | 44 | private void Update() { 45 | UpdateTextureSize(); 46 | 47 | shader.SetFloat("iGlobalTime", Time.time); 48 | shader.Dispatch(0, numThreadsGroupsX, numThreadsGroupsY, numThreadsGroupsZ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Assets/RunComputeShader.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 795d568cbe3f70b48ae5ad78f3fe5d40 3 | timeCreated: 1495293831 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Adam Davidson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity Compute Shader Realtime Ray Tracing 2 | 3 | This is a Unity/HLSL port of the excellent [Annotated Realtime Raytracing](https://www.tinycranes.com/blog/2015/05/annotated-realtime-raytracing/) blog post by [Kevin Fung](https://github.com/Polytonic/). 4 | 5 | The original GLSL shader runs live in a web browser on [ShaderToy](https://www.shadertoy.com/view/4ljGRd). Unity looks the same but at 138fps (Intel HD GPU). 6 | 7 | ![Unity Editor Screenshot](https://github.com/Arlorean/UnityComputeShaderTest/raw/master/example.png) 8 | 9 | ## Porting GLSL to HLSL 10 | 11 | This was much easier than I thought it would be. Here are the changes I made: 12 | * Added HLSL kernel (`CSMain`) that calls the GLSL fragment shader (`mainImage`) 13 | * Added `iGlobalTime` and `iResolution` shader variables 14 | * Replaced `vec2` with `float2` 15 | * Replaced `vec3` with `float3` 16 | * Replaced `vec4` with `float4` 17 | * Expand constructors with 1 argument to n, e.g. `vec3(1.0)` -> `float3(1.0,1.0,1.0)` 18 | * Change `const` to `static const` 19 | * Change struct initializer syntax, e.g. `Material(vec3(0.0,0.2,1.0),1.0,0.0)` -> `{ float3(0.0,0.2,1.0),1.0,0.0 }` 20 | 21 | ## Unity Compute Shaders 22 | 23 | I learnt about Unity Compute Shaders from [Coxlin's Blog](http://www.lindsaygcox.co.uk/tutorials/unity-shader-tutorial-an-intro-to-compute-shaders/). This article just multiplies 4 integers by 2.0 in a Computer Shader and prints the results to the console. Everything you need to know to get started. 24 | 25 | Understanding the relationship between the `[numthreads(x,y,z)]` attribute in the shader, and the `shader.Dispatch(kernelIndex, gx,gy,gz)` call in C#, is fundamental. The kernel `CSMain` is executed `(x*y*z) * (gx*gy*gz)` times and the `id` passed into the kernel ranges from `(0,0,0)` to `(x*gx, y*gy, z*gz)`. 26 | 27 | Here is how the kernel is evaluated for each thread in pseudocode: 28 | 29 | ``` 30 | const int numThreadsX, numThreadsY, numThreadsZ; 31 | 32 | static void kernel(int x, int y, int z) { } 33 | 34 | static void Dispatch(int threadGroupsX, int threadGroupsY, int threadGroupsZ) { 35 | for (var gx = 0; gx < threadGroupsX; gx++) { 36 | for (var gy = 0; gy < threadGroupsY; gy++) { 37 | for (var gz = 0; gz < threadGroupsZ; gz++) { 38 | 39 | // Dispatch group 40 | for (var x = 0; x < numThreadsX; x++) { 41 | for (var y = 0; y < numThreadsY; y++) { 42 | for (var z = 0; z < numThreadsZ; z++) { 43 | kernel( 44 | gx * numThreadsX + x, 45 | gy * numThreadsY + y, 46 | gz * numThreadsZ + z 47 | ); 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arlorean/UnityComputeShaderTest/6d6e5233af314c296f69b1c9e7999af2dd532443/example.png --------------------------------------------------------------------------------