Ray Tracing in Unity

Recently I implemented a Ray tracer in Unity in connection with an assignment given to me in one of my master courses.

I’m quite happy with the result, so I’ll post my code in this post.

The Ray Tracer supports

  • Lambertian shading
  • Phong Shading
  • Blinn Phong Shading
  • Reflective surfaces
  • Transparent surfaces
  • Ray traced Unity scene
    Ray traced Unity scene

    Another Ray Traced scene
    Another Ray Traced scene

    Note that this in not a tutorial in any way. The code provided is not documented, so you’ll have to dig into the content on your own.

    Usage:

    For all object in the scene attach the ‘RayTracerObject’ script (not lights). Then set the properties using this script.
    Attach the ‘RayTracer’ script to the camera, then run the scene.

    Raytracer.cs

    The MIT License (MIT)
    
    Copyright (c) 2014 Lars Ivar Hatledal
    
    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.
    */
    
    
    using UnityEngine;
    using System.Collections;
    
    public class RayTracer : MonoBehaviour {
    
    	public Color backgroundColor = Color.black;
    	public float RenderResolution = 1f;
    	public float maxDist = 100f;
    	public int maxRecursion = 4;
    
    	
    	private Light[] lights;
    	private Texture2D renderTexture;
    
    	void Awake () {
    		renderTexture = new Texture2D ((int)(Screen.width * RenderResolution), (int)(Screen.height * RenderResolution));
    		lights = FindObjectsOfType (typeof(Light)) as Light[];
    	}
    
    	void Start () {
    		RayTrace ();
    	}
    
    	void OnGUI () {
    		GUI.DrawTexture (new Rect (0, 0, Screen.width, Screen.height), renderTexture);
    	}
    
    	void RayTrace () {
    		for (int x = 0; x < renderTexture.width; x ++) {
    			for (int y = 0; y < renderTexture.height; y ++) {
    
    				Color color = Color.black;
    				Ray ray = camera.ScreenPointToRay (new Vector3 (x / RenderResolution, y / RenderResolution, 0));
    
    				renderTexture.SetPixel (x, y, TraceRay (ray, color, 0));
    			}
    		}
    		
    		renderTexture.Apply ();
    	}
    
    	Color TraceRay (Ray ray, Color color, int recursiveLevel) {
    
    		if (recursiveLevel < maxRecursion) {
    			RaycastHit hit;
    			if (Physics.Raycast (ray, out hit, maxDist)) {
    				Vector3 viewVector = ray.direction;
    				Vector3 pos = hit.point + hit.normal * 0.0001f;
    				Vector3 normal = hit.normal;
    				
    				RayTracerObject rto = hit.collider.gameObject.GetComponent<RayTracerObject> ();
    				
    				Material mat = hit.collider.renderer.material;
    				if (mat.mainTexture) {
    					color += (mat.mainTexture as Texture2D).GetPixelBilinear (hit.textureCoord.x, hit.textureCoord.y);
    				} else {
    					color += mat.color;
    				}
    
    				color *= TraceLight (rto, viewVector, pos, normal);
    
    				if (rto.reflectiveCoeff > 0) {
    					float reflet = 2.0f * Vector3.Dot (viewVector, normal);
    					Ray newRay = new Ray (pos, viewVector - reflet * normal);
    					color += rto.reflectiveCoeff * TraceRay (newRay, color, recursiveLevel + 1);
    				}
    
    				if (rto.transparentCoeff > 0) {
    					Ray newRay = new Ray (hit.point - hit.normal * 0.0001f, viewVector);
    					color += rto.transparentCoeff * TraceRay (newRay, color, recursiveLevel + 1);
    				}
    			}
    		}
    
    		return color;
    
    	}
    
    	Color TraceLight (RayTracerObject rto, Vector3 viewVector, Vector3 pos, Vector3 normal) {
    		Color c = RenderSettings.ambientLight;
    
    		foreach (Light light in lights) {
    			if (light.enabled) {
    				c += LightTrace (rto, light, viewVector, pos, normal);
    			}
    		}
    		return c;
    	}
    
    	Color LightTrace (RayTracerObject rto, Light light, Vector3 viewVector, Vector3 pos, Vector3 normal) {
    	
    
    		float dot, distance, contribution;
    		Vector3 direction;
    		switch (light.type) {
    		case LightType.Directional:
    			contribution = 0;
    			direction = -light.transform.forward;
    			dot = Vector3.Dot (direction, normal);
    			if (dot > 0) {
    				if (Physics.Raycast (pos, direction, maxDist)) {
    					return Color.black;
    				}
    
    				if (rto.lambertCoeff > 0) {
    					contribution += dot * rto.lambertCoeff;
    				}
    				if (rto.reflectiveCoeff > 0) {
    					if (rto.phongCoeff > 0) {
    						float reflet = 2.0f * Vector3.Dot (viewVector, normal);
    						Vector3 phongDir = viewVector - reflet * normal;
    						float phongTerm = max (Vector3.Dot (phongDir, viewVector), 0.0f);
    						phongTerm = rto.reflectiveCoeff * Mathf.Pow (phongTerm, rto.phongPower) * rto.phongCoeff;
    						
    						contribution += phongTerm;
    					}
    					if (rto.blinnPhongCoeff > 0) {
    						Vector3 blinnDir = -light.transform.forward - viewVector;
    						float temp = Mathf.Sqrt (Vector3.Dot (blinnDir, blinnDir));
    						if (temp != 0.0f) {
    							blinnDir = (1.0f / temp) * blinnDir;
    							float blinnTerm = max (Vector3.Dot (blinnDir, normal), 0.0f);
    							blinnTerm = rto.reflectiveCoeff * Mathf.Pow (blinnTerm, rto.blinnPhongPower) * rto.blinnPhongCoeff;
    							
    							contribution += blinnTerm;
    						}
    					}
    				}
    			} 
    			return light.color * light.intensity * contribution;
    		case LightType.Point:
    			contribution = 0;
    			direction = (light.transform.position - pos).normalized;
    			dot = Vector3.Dot (normal, direction);
    			distance = Vector3.Distance (pos, light.transform.position);
    			if ((distance < light.range) && (dot > 0)) {
    				if (Physics.Raycast (pos, direction, distance)) {
    					return Color.black;
    				}
    				
    				if (rto.lambertCoeff > 0) {
    					contribution += dot * rto.lambertCoeff;
    				}
    				if (rto.reflectiveCoeff > 0) {
    					if (rto.phongCoeff > 0) {
    						float reflet = 2.0f * Vector3.Dot (viewVector, normal);
    						Vector3 phongDir = viewVector - reflet * normal;
    						float phongTerm = max (Vector3.Dot (phongDir, viewVector), 0.0f);
    						phongTerm = rto.reflectiveCoeff * Mathf.Pow (phongTerm, rto.phongPower) * rto.phongCoeff;
    						
    						contribution += phongTerm;
    					}
    					if (rto.blinnPhongCoeff > 0) {
    						Vector3 blinnDir = -light.transform.forward - viewVector;
    						float temp = Mathf.Sqrt (Vector3.Dot (blinnDir, blinnDir));
    						if (temp != 0.0f) {
    							blinnDir = (1.0f / temp) * blinnDir;
    							float blinnTerm = max (Vector3.Dot (blinnDir, normal), 0.0f);
    							blinnTerm = rto.reflectiveCoeff * Mathf.Pow (blinnTerm, rto.blinnPhongPower) * rto.blinnPhongCoeff;
    							
    							contribution += blinnTerm;
    						}
    					}
    				}
    			} 
    			if (contribution == 0) {
    				return Color.black;
    			}
    			return light.color * light.intensity * contribution;
    		case LightType.Spot:
    			contribution = 0;
    			direction = (light.transform.position - pos).normalized;
    			dot = Vector3.Dot (normal, direction);
    			distance = Vector3.Distance (pos, light.transform.position);
    			if (distance < light.range && dot > 0) {
    				float dot2 = Vector3.Dot (-light.transform.forward, direction);
    				if (dot2 > (1 - light.spotAngle / 180)) {
    					if (Physics.Raycast (pos, direction, distance)) {
    						return Color.black;
    					}
    					if (rto.lambertCoeff > 0) {
    						contribution += dot * rto.lambertCoeff;
    					}
    					if (rto.reflectiveCoeff > 0) {
    						if (rto.phongCoeff > 0) {
    							float reflet = 2.0f * Vector3.Dot (viewVector, normal);
    							Vector3 phongDir = viewVector - reflet * normal;
    							float phongTerm = max (Vector3.Dot (phongDir, viewVector), 0.0f);
    							phongTerm = rto.reflectiveCoeff * Mathf.Pow (phongTerm, rto.phongPower) * rto.phongCoeff;
    							
    							contribution += phongTerm;
    						}
    						if (rto.blinnPhongCoeff > 0) {
    							Vector3 blinnDir = -light.transform.forward - viewVector;
    							float temp = Mathf.Sqrt (Vector3.Dot (blinnDir, blinnDir));
    							if (temp != 0.0f) {
    								blinnDir = (1.0f / temp) * blinnDir;
    								float blinnTerm = max (Vector3.Dot (blinnDir, normal), 0.0f);
    								blinnTerm = rto.reflectiveCoeff * Mathf.Pow (blinnTerm, rto.blinnPhongPower) * rto.blinnPhongCoeff;
    								
    								contribution += blinnTerm;
    							}
    						}
    					}
    				} 
    			}
    			if (contribution == 0) {
    				return Color.black;
    			}
    			return light.color * light.intensity * contribution;
    		}
    		return Color.black;
    	}
    
    	float max (float x0, float x1) {
    		return x0 > x1 ? x0 : x1;
    	}
    }
    

    RayTracerObject.cs

    using UnityEngine;
    using System.Collections;
    
    public class RayTracerObject : MonoBehaviour {
    
    	public float lambertCoeff = 1f;
    
    	public float reflectiveCoeff = 0f;
    
    	public float phongCoeff = 1f;
    	public float phongPower = 2f;
    
    	public float blinnPhongCoeff = 1f;
    	public float blinnPhongPower = 2f;
    
    	public float transparentCoeff = 0f;
    
    
    	public Color baseColor = Color.gray;
    
    	void Awake () {
    		if (!renderer.material.mainTexture) {
    			renderer.material.color = baseColor;
    		}
    	}
    }
    

    5 thoughts on “Ray Tracing in Unity”

    1. Hi Lars!
      There is something wrong in your code… Not work for me… Anyway, good job!
      This is the error:

      NullReferenceException: Object reference not set to an instance of an object
      RayTracer.TraceRay (Ray ray, Color color, Int32 recursiveLevel) (at Assets/Lars Ivar Ray Trace/RayTracer.cs:85)
      RayTracer.TraceRay (Ray ray, Color color, Int32 recursiveLevel) (at Assets/Lars Ivar Ray Trace/RayTracer.cs:88)
      RayTracer.RayTrace () (at Assets/Lars Ivar Ray Trace/RayTracer.cs:58)
      RayTracer.Start () (at Assets/Lars Ivar Ray Trace/RayTracer.cs:44)

      1. Sorry, for not responding right away. Quite busy with work you see. Anyhow, I just ran the scripts in a new scene (Unity 4.x) and it worked. It is impossible to know what caused your issue based on that error message.

      1. If you don’t like free code, look somewhere else. I do not work for you..
        You should have the knowledge of fixing whatever problem you have on your own, if not then aquire it.

    Leave a Reply

    Your email address will not be published. Required fields are marked *