Search code examples
c#unity-game-engineraytracing

Raytracing in Unity error C#


I have begun a project in unity, a Roll a ball game. Keep in mind, I am very novice, so detailed explanations would really help. Anyway, I decided that the scene looks not to great, and decided to add a raytracing script in C#. I got it from this website, http://laht.info/ray-tracing-in-unity/

Here is the code, which is attached to the MainCamera

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 = GetComponent<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.GetComponent<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;
    }
}

And here is the code attached to the objects in the scene, like the plane or the sphere

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 (!GetComponent<Renderer>().material.mainTexture)
        {
            GetComponent<Renderer>().material.color = baseColor;
        }
    }
}

Anyway, when I try to run the program, I get this error.

NullReferenceException: Object reference not set to an instance of an object RayTracer.LightTrace (.RayTracerObject rto, UnityEngine.Light light, Vector3 viewVector, Vector3 pos, Vector3 normal) (at Assets/Scripts/RayTracer.cs:127) RayTracer.TraceLight (.RayTracerObject rto, Vector3 viewVector, Vector3 pos, Vector3 normal) (at Assets/Scripts/RayTracer.cs:102) RayTracer.TraceRay (Ray ray, Color color, Int32 recursiveLevel) (at Assets/Scripts/RayTracer.cs:73) RayTracer.RayTrace () (at Assets/Scripts/RayTracer.cs:42)

If you can figure this out, posting the code would help a lot. Thank you.


Solution

  • Posting the line in which the error occures would have been helpfull. Anyways, line 127 is this line within the LightTrace() function.

    if (rto.lambertCoeff > 0)
    {
        contribution += dot * rto.lambertCoeff;
    }
    

    So, the rto variable must be null. I have no idea on why it is null, though. The LightTrace() function is called only from here:

    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;
    }
    

    So where does the TraceLight function get the rto object from? From here: (This is within the TraceRay function)

    RayTracerObject rto = hit.collider.gameObject.GetComponent<RayTracerObject>();
    
    Material mat = hit.collider.GetComponent<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);
    

    Here we can see, that it is not beeing checked that the GetComponent<RayTracerObject>() call returns the actual RayTracerObject script, and not null. Maybe they're expecting each and every object that is in the scene to have this script. Anyway, place a != null check right after the assignment to the rto variable, and you should be good to go. Also, you could go on end debug it further, like e.g. Debug.Log()ing what exact object it hit that didn't have the RayTracerObject on it.

    EDIT: So, you should start debugging your scene by taking a look at what the Physics.Raycast() hit that didn't have a RayTracerObject script on it. When using that script you should make sure that absolutley every object, which can be hit with a raycast, has the RayTracerObject script on it. Within the TraceRay() function, add some debugging.

    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>();
                //Does the object we hit have that script? 
                if(rto == null) 
                {
                     var GO = hit.collider.gameObject;
                     Debug.Log("Raycast hit failure! On " + GO.name + " position " + GO.transform.position.ToString());
                     return color; //exit out
                }
    

    Happy debugging.