diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e3d90ed..63f4a48 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,8 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: 3.1.301 + - name: Clean + run: dotnet clean --configuration Release && dotnet nuget locals all --clear - name: Install dependencies run: dotnet restore - name: Build diff --git a/.github/workflows/test_workflow.yml b/.github/workflows/test_workflow.yml index 0f7e6e5..42ec54c 100644 --- a/.github/workflows/test_workflow.yml +++ b/.github/workflows/test_workflow.yml @@ -15,6 +15,8 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: 3.1.301 + - name: Clean + run: dotnet clean --configuration Release && dotnet nuget locals all --clear - name: Install dependencies run: dotnet restore - name: Build diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e0232a8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2021 Tomasz Herman, Karol Krupa, Paweł Flis + +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. \ No newline at end of file diff --git a/MainForm.resx b/MainForm.resx deleted file mode 100644 index 1af7de1..0000000 --- a/MainForm.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/RayTracer/RayTracer.csproj b/RayTracer/RayTracer.csproj index 7d4403a..9a90784 100644 --- a/RayTracer/RayTracer.csproj +++ b/RayTracer/RayTracer.csproj @@ -5,24 +5,20 @@ - - - - - - - - + + + + + + + + - - + + + + - - - - - - \ No newline at end of file diff --git a/RayTracer/Resources/Shaders/shader.frag b/RayTracer/Resources/Shaders/shader.frag index b91c0a0..c25f895 100644 --- a/RayTracer/Resources/Shaders/shader.frag +++ b/RayTracer/Resources/Shaders/shader.frag @@ -1,9 +1,84 @@ #version 330 core +#define MAX_LIGHTS 5 +#define MATERIALS 3 + +struct Light +{ + vec3 position; + + vec3 diffuse; +}; + +struct Material +{ + float part; + int useColor; + vec3 color; + sampler2D texture; +}; -in vec2 texCoord; out vec4 FragColor; +in vec2 texCoord; +in vec3 Normal; +in vec3 FragPos; + +uniform int lightsCount; +uniform Material materials[MATERIALS]; +uniform Light light[MAX_LIGHTS]; +uniform vec3 ambientLight; +uniform vec3 cameraPosition; +uniform float disturbance; + +vec3 Phong(); +vec4 GetMaterialColor(Material material); +float Attenuation(float dist); +vec4 Clamp(vec4 vec); +vec3 Clamp(vec3 vec); + void main() { - FragColor = vec4(0.0f, texCoord, 0.0f); + FragColor = vec4(Phong(), 1.0); +} + +vec3 Phong() { + vec4 ambient = Clamp(GetMaterialColor(materials[0])); + vec4 diffuse = GetMaterialColor(materials[1]); + vec4 specular = GetMaterialColor(materials[2]); + + vec3 normal = normalize(Normal); + vec3 viewDirection = normalize(cameraPosition - vec3(FragPos)); + + vec3 a = vec3(ambient), d = vec3(diffuse) * ambientLight, s = vec3(0); + + // Point lights + for (int i = 0; i < lightsCount; i++) { + float lightDistance = length(vec3(light[i].position) - FragPos); + vec3 lightDir = normalize(light[i].position - FragPos); + float diff = max(dot(normal, lightDir), 0.0); + d += Clamp(light[i].diffuse) * diff * vec3(diffuse); + vec3 reflectDir = reflect(-lightDir, normal); + float reflectAngle = max(dot(reflectDir, viewDirection), 0); + s += vec3(specular * pow(reflectAngle, 1 + (1-disturbance)*99)); + } + + return a * materials[0].part + d * materials[1].part + s * materials[2].part; +} + +vec4 GetMaterialColor(Material material) +{ + return material.useColor == 1 ? vec4(material.color, 1.0) : texture(material.texture, texCoord); +} + +float Attenuation(float dist) +{ + return 1 + 0.007 * dist + 0.0002 * dist * dist; +} + +vec3 Clamp(vec3 vec) { + return vec3(clamp(vec.x, 0, 1), clamp(vec.y, 0, 1), clamp(vec.z, 0, 1)); +} + +vec4 Clamp(vec4 vec) { + return vec4(clamp(vec.x, 0, 1), clamp(vec.y, 0, 1), clamp(vec.z, 0, 1), clamp(vec.w, 0, 1)); } \ No newline at end of file diff --git a/RayTracer/Resources/Shaders/shader.vert b/RayTracer/Resources/Shaders/shader.vert index bc7ac43..9678161 100644 --- a/RayTracer/Resources/Shaders/shader.vert +++ b/RayTracer/Resources/Shaders/shader.vert @@ -1,15 +1,20 @@ #version 330 core layout (location = 0) in vec3 aPosition; +layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoord; +out vec2 texCoord; +out vec3 Normal; +out vec3 FragPos; + uniform mat4 model; -uniform mat4 view; -uniform mat4 projection; +uniform mat4 mvp; -out vec2 texCoord; -void main() +void main(void) { texCoord = aTexCoord; - mat4 vp = view * projection; - gl_Position = vec4(aPosition, 1.0) * model * vp ; + Normal = aNormal * mat3(model); + FragPos = vec3(vec4(aPosition, 1.0)*model); + + gl_Position = vec4(aPosition, 1.0)*mvp; } \ No newline at end of file diff --git a/RayTracer/Resources/Textures/blue_something.jpg b/RayTracer/Resources/Textures/blue_something.jpg new file mode 100644 index 0000000..0b50734 Binary files /dev/null and b/RayTracer/Resources/Textures/blue_something.jpg differ diff --git a/RayTracer/Resources/Textures/earthmap.jpg b/RayTracer/Resources/Textures/earthmap.jpg new file mode 100644 index 0000000..908c160 Binary files /dev/null and b/RayTracer/Resources/Textures/earthmap.jpg differ diff --git a/RayTracer/Resources/Textures/wood.jpg b/RayTracer/Resources/Textures/wood.jpg new file mode 100644 index 0000000..acb9396 Binary files /dev/null and b/RayTracer/Resources/Textures/wood.jpg differ diff --git a/RayTracer/Source/BVH/BvhNode.cs b/RayTracer/Source/BVH/BvhNode.cs new file mode 100644 index 0000000..2531fc5 --- /dev/null +++ b/RayTracer/Source/BVH/BvhNode.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using RayTracing.Maths; +using RayTracing.RayTracing; +using RayTracing.Sampling; + +namespace RayTracing.BVH +{ + public class BvhNode : IHittable + { + private const int SAMPLES = 10000; + private readonly IHittable _left; + private readonly IHittable _right; + private readonly AABB _box; + + // https://raytracing.github.io/books/RayTracingTheNextWeek.html#boundingvolumehierarchies + public BvhNode(List srcObjects, int start, int end, AbstractSampler sampler = null) + { + sampler ??= new ThreadSafeSampler(count => IntSampling.Random(count, 0, 3), SAMPLES); + + var objects = srcObjects; + int axis = sampler.GetSample(); + int Comparator(IHittable a, IHittable b) => BoxCompare(a, b, axis); + + int objectSpan = end - start; + switch (objectSpan) + { + case 1: + _left = _right = objects[start]; + break; + case 2 when Comparator(objects[start], objects[start + 1]) < 0: + _left = objects[start]; + _right = objects[start + 1]; + break; + case 2: + _left = objects[start + 1]; + _right = objects[start]; + break; + default: + objects.Sort(start, objectSpan - 1, new FuncComparer(Comparator)); + int mid = start + objectSpan / 2; + _left = new BvhNode(objects, start, mid, sampler); + _right = new BvhNode(objects, mid, end, sampler); + break; + } + + var boxLeft = _left.BoundingBox(); + var boxRight = _right.BoundingBox(); + + _box = boxLeft + boxRight; + } + + public bool HitTest(Ray ray, ref HitInfo hit, float @from, float to) + { + if (!_box.Test(ref ray, from, to)) + return false; + + bool hitLeft = _left.HitTest(ray, ref hit, from, to); + bool hitRight = _right.HitTest(ray, ref hit, from, hitLeft ? hit.Distance : to); + + return hitLeft || hitRight; + } + + public AABB BoundingBox() + { + return _box; + } + + public List Preprocess() + { + throw new System.NotImplementedException(); + } + + private int BoxCompare(IHittable a, IHittable b, int axis) + { + var boxA = a.BoundingBox(); + var boxB = b.BoundingBox(); + + return boxA.Min[axis].CompareTo(boxB.Min[axis]); + } + + private class FuncComparer : IComparer + { + private readonly Func _func; + + public FuncComparer(Func comparerFunc) + { + _func = comparerFunc; + } + + public int Compare(T x, T y) + { + return _func(x, y); + } + } + } +} \ No newline at end of file diff --git a/RayTracer/Source/Cameras/Camera.cs b/RayTracer/Source/Cameras/Camera.cs index c2bd718..637041b 100644 --- a/RayTracer/Source/Cameras/Camera.cs +++ b/RayTracer/Source/Cameras/Camera.cs @@ -12,7 +12,7 @@ public abstract class Camera protected float Pitch { get; set; } protected float Yaw { get; set; } = -MathHelper.PiOver2; private float _aspectRatio = 16f / 9; - protected Vector3 Position { get; set; } + public Vector3 Position { get; set; } protected Vector3 Horizontal { get; set; } protected Vector3 Vertical { get; set; } protected Vector3 LowerLeft { get; set; } diff --git a/RayTracer/Source/Materials/Diffuse.cs b/RayTracer/Source/Materials/Diffuse.cs index 965d4e7..d8099f0 100644 --- a/RayTracer/Source/Materials/Diffuse.cs +++ b/RayTracer/Source/Materials/Diffuse.cs @@ -1,26 +1,41 @@ -using System; using OpenTK; using RayTracing.Maths; using RayTracing.Sampling; +using RayTracing.Shaders; namespace RayTracing.Materials { public class Diffuse : IMaterial { - public Color Albedo { get; set; } + public ITexture Albedo { get; set; } private readonly AbstractSampler _sampler; - public Diffuse(Color albedo, AbstractSampler sampler = null) + public Diffuse(ITexture albedo, AbstractSampler sampler = null) { - _sampler = sampler ?? new ThreadSafeSampler(Vec3Sampling.UniformSphere, 125, 8); + _sampler = sampler ?? + new ThreadSafeSampler(Vec3Sampling.UniformSphere, 10000, 8, + Vec3Sampling.ToSphereSurface); Albedo = albedo; } + public Diffuse(Color albedo, AbstractSampler sampler = null) + { + _sampler = sampler ?? + new ThreadSafeSampler(Vec3Sampling.UniformSphere, 10000, 8, + Vec3Sampling.ToSphereSurface); + Albedo = new SolidColor(albedo); + } + public bool Scatter(ref Ray ray, ref HitInfo hit, out Color attenuation, out Ray scattered) { scattered = new Ray(hit.HitPoint, hit.Normal + _sampler.Sample); - attenuation = Albedo; + attenuation = Albedo[hit.TexCoord.X, hit.TexCoord.Y]; return true; } + + public void Use(Shader shader, float part) + { + Albedo.Use(shader, 1, part); + } } } \ No newline at end of file diff --git a/RayTracer/Source/Materials/Emissive.cs b/RayTracer/Source/Materials/Emissive.cs new file mode 100644 index 0000000..2905376 --- /dev/null +++ b/RayTracer/Source/Materials/Emissive.cs @@ -0,0 +1,67 @@ +using OpenTK; +using OpenTK.Graphics; +using RayTracing.Maths; +using RayTracing.Sampling; +using RayTracing.Shaders; + +namespace RayTracing.Materials +{ + public class Emissive : IMaterial + { + private const int SAMPLES = 10000; + private ITexture _emit; + + public Color AverageColor => _averageColor; + + public ITexture Emit + { + get => _emit; + set + { + _emit = value; + UpdateAverageColor(value); + } + } + + private Color _averageColor; + + public Emissive(ITexture emit) + { + Emit = emit; + } + + public Emissive(Color emitColor) + { + Emit = new SolidColor(emitColor); + } + + private void UpdateAverageColor(ITexture texture) + { + var sampler = new ThreadSafeSampler(Vec2Sampling.Random, SAMPLES); + for (int i = 0; i < SAMPLES; i++) + { + var coords = sampler.GetSample(); + _averageColor += texture[coords.X, coords.Y]; + } + + _averageColor /= SAMPLES; + } + + public bool Scatter(ref Ray ray, ref HitInfo hit, out Color attenuation, out Ray scattered) + { + attenuation = Color.FromColor4(Color4.Black); + scattered = new Ray(); + return false; + } + + public Color Emitted(float u, float v) + { + return Emit[u, v]; + } + + public void Use(Shader shader, float part) + { + Emit.Use(shader, 0, part); + } + } +} \ No newline at end of file diff --git a/RayTracer/Source/Materials/IMaterial.cs b/RayTracer/Source/Materials/IMaterial.cs index 7e22e68..dd65278 100644 --- a/RayTracer/Source/Materials/IMaterial.cs +++ b/RayTracer/Source/Materials/IMaterial.cs @@ -1,10 +1,18 @@ -using System; -using RayTracing.Maths; +using RayTracing.Maths; +using RayTracing.Shaders; namespace RayTracing.Materials { public interface IMaterial { + Color AverageColor => new Color(); bool Scatter(ref Ray ray, ref HitInfo hit, out Color attenuation, out Ray scattered); + + Color Emitted(float u, float v) + { + return new Color(0, 0, 0); + } + + void Use(Shader shader, float part = 1); } } \ No newline at end of file diff --git a/RayTracer/Source/Materials/ITexture.cs b/RayTracer/Source/Materials/ITexture.cs new file mode 100644 index 0000000..1ed6803 --- /dev/null +++ b/RayTracer/Source/Materials/ITexture.cs @@ -0,0 +1,33 @@ +using OpenTK.Graphics.OpenGL4; +using RayTracing.Maths; +using RayTracing.Shaders; + +namespace RayTracing.Materials +{ + public interface ITexture + { + Color this[float x, float y] { get; } + + void Use(Shader shader, int matNum, float part); + + static string MaterialUseColorUniformName(int matNum) + { + return $"materials[{matNum}].useColor"; + } + + static string MaterialColorUniformName(int matNum) + { + return $"materials[{matNum}].color"; + } + + static string MaterialPartUniformName(int matNum) + { + return $"materials[{matNum}].part"; + } + + static TextureUnit MaterialTextureUnit(int matNum) + { + return TextureUnit.Texture0 + matNum; + } + } +} \ No newline at end of file diff --git a/RayTracer/Source/Materials/MasterMaterial.cs b/RayTracer/Source/Materials/MasterMaterial.cs new file mode 100644 index 0000000..9cd4a11 --- /dev/null +++ b/RayTracer/Source/Materials/MasterMaterial.cs @@ -0,0 +1,115 @@ +using RayTracing.Maths; +using RayTracing.Sampling; +using RayTracing.Shaders; + +namespace RayTracing.Materials +{ + public class MasterMaterial : IMaterial + { + private const int SAMPLES = 10000; + + private readonly IMaterial[] _materials; + + public Emissive Emissive + { + get => (Emissive) _materials[1]; + set => _materials[1] = value; + } + + public Diffuse Diffuse + { + get => (Diffuse) _materials[2]; + set => _materials[2] = value; + } + + public Reflective Reflective + { + get => (Reflective) _materials[3]; + set => _materials[3] = value; + } + + public Refractive Refractive + { + get => (Refractive) _materials[4]; + set => _materials[4] = value; + } + + public Color AverageColor => Emissive.AverageColor; + + public (float emissive, float diffuse, float reflective, float refractive) Parts + { + get => _parts; + set + { + _parts = value; + _sampler = NewSampler(); + } + } + + private AbstractSampler _sampler; + private (float emissive, float diffuse, float reflective, float refractive) _parts; + + public MasterMaterial(params IMaterial[] materials) + { + _materials = new IMaterial[] + { + new Emissive(new Color()), //DummyColor, used when all probabilities are 0, ~black body + new Emissive(new Color()), + new Diffuse(new Color()), + new Reflective(new Color()), + new Refractive(new Color(), 1) + }; + + foreach (var material in materials) + { + switch (material) + { + case Emissive mat: + Emissive = mat; + _parts.emissive = 1; + break; + case Diffuse mat: + Diffuse = mat; + _parts.diffuse = 1; + break; + case Reflective mat: + Reflective = mat; + _parts.reflective = 1; + break; + case Refractive mat: + Refractive = mat; + _parts.refractive = 1; + break; + } + } + + _sampler = NewSampler(); + } + + private ThreadSafeSampler NewSampler() + { + return new ThreadSafeSampler(count => IntSampling.Distribution(count, + _parts.emissive, + _parts.diffuse, + _parts.reflective, + _parts.refractive), SAMPLES); + } + + public bool Scatter(ref Ray ray, ref HitInfo hit, out Color attenuation, out Ray scattered) + { + return _materials[_sampler.Sample].Scatter(ref ray, ref hit, out attenuation, out scattered); + } + + public Color Emitted(float u, float v) + { + return _materials[_sampler.Sample].Emitted(u, v); + } + + public void Use(Shader shader, float part) + { + Emissive.Use(shader, Parts.emissive); + Diffuse.Use(shader, Parts.diffuse); + Reflective.Use(shader, Parts.reflective); + } + } +} \ No newline at end of file diff --git a/RayTracer/Source/Materials/Reflective.cs b/RayTracer/Source/Materials/Reflective.cs index 82a5741..a979f23 100644 --- a/RayTracer/Source/Materials/Reflective.cs +++ b/RayTracer/Source/Materials/Reflective.cs @@ -1,18 +1,26 @@ using OpenTK; using RayTracing.Maths; using RayTracing.Sampling; +using RayTracing.Shaders; namespace RayTracing.Materials { public class Reflective : IMaterial { - public Color Albedo { get; set; } + public ITexture Albedo { get; set; } public float Disturbance { get; set; } private readonly AbstractSampler _sampler; public Reflective(Color albedo, float disturbance = 0, AbstractSampler sampler = null) { - _sampler = sampler ?? new ThreadSafeSampler(Vec3Sampling.UniformSphere, 125, 8); + _sampler = sampler ?? new ThreadSafeSampler(Vec3Sampling.UniformSphere, 10000, 8); + Albedo = new SolidColor(albedo); + Disturbance = disturbance; + } + + public Reflective(ITexture albedo, float disturbance = 0, AbstractSampler sampler = null) + { + _sampler = sampler ?? new ThreadSafeSampler(Vec3Sampling.UniformSphere, 10000, 8); Albedo = albedo; Disturbance = disturbance; } @@ -21,8 +29,14 @@ public bool Scatter(ref Ray ray, ref HitInfo hit, out Color attenuation, out Ray { Vector3 reflected = ray.Direction.Reflect(hit.Normal); scattered = new Ray(hit.HitPoint, reflected + Disturbance * _sampler.Sample); - attenuation = Albedo; + attenuation = Albedo[hit.TexCoord.X, hit.TexCoord.Y]; return Vector3.Dot(scattered.Direction, hit.Normal) > 0; } + + public void Use(Shader shader, float part) + { + Albedo.Use(shader, 2, part); + shader.SetFloat("disturbance", Disturbance); + } } } \ No newline at end of file diff --git a/RayTracer/Source/Materials/Refractive.cs b/RayTracer/Source/Materials/Refractive.cs new file mode 100644 index 0000000..a4917b1 --- /dev/null +++ b/RayTracer/Source/Materials/Refractive.cs @@ -0,0 +1,75 @@ +using System; +using OpenTK; +using RayTracing.Maths; +using RayTracing.Sampling; +using RayTracing.Shaders; + +namespace RayTracing.Materials +{ + // https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics + public class Refractive : IMaterial + { + public ITexture Albedo { get; set; } + public float RefractiveIndex { get; set; } + private readonly AbstractSampler _sampler; + + public Refractive(Color albedo, float refractiveIndex, AbstractSampler sampler = null) + { + Albedo = new SolidColor(albedo); + RefractiveIndex = refractiveIndex; + _sampler = sampler ?? new ThreadSafeSampler(FloatSampling.Random, 10000); + } + + public Refractive(ITexture albedo, float refractiveIndex, AbstractSampler sampler = null) + { + Albedo = albedo; + RefractiveIndex = refractiveIndex; + _sampler = sampler ?? new ThreadSafeSampler(FloatSampling.Random, 10000); + } + + public bool Scatter(ref Ray ray, ref HitInfo hit, out Color attenuation, out Ray scattered) + { + attenuation = Albedo[hit.TexCoord.X, hit.TexCoord.Y]; + double refractionRatio = hit.FrontFace ? (1.0 / RefractiveIndex) : RefractiveIndex; + Vector3 unitDirection = ray.Direction.Normalized(); + + double cosTheta = Math.Min(Vector3.Dot(-unitDirection, hit.Normal), 1.0); + double sinTheta = Math.Sqrt(1.0 - cosTheta * cosTheta); + + bool cannotRefract = refractionRatio * sinTheta > 1.0; + Vector3 direction; + + if (cannotRefract || Reflectance(cosTheta, refractionRatio) > _sampler.GetSample()) + { + direction = ray.Direction.Normalized().Reflect(hit.Normal); + } + else + { + direction = Refract(unitDirection, hit.Normal, refractionRatio); + } + + scattered = new Ray(hit.HitPoint, direction); + return true; + } + + static double Reflectance(double cosine, double refIdx) + { + var r0 = (1 - refIdx) / (1 + refIdx); + r0 *= r0; + return r0 + (1 - r0) * Math.Pow(1 - cosine, 5); + } + + Vector3 Refract(Vector3 uv, Vector3 n, double etaiOverEtat) + { + var cosTheta = (float) Math.Min(Vector3.Dot(-uv, n), 1.0); + Vector3 rOutPerp = (float) etaiOverEtat * (uv + cosTheta * n); + Vector3 rOutParallel = -(float) Math.Sqrt(Math.Abs(1.0 - rOutPerp.LengthSquared)) * n; + return rOutPerp + rOutParallel; + } + + public void Use(Shader shader, float part) + { + + } + } +} \ No newline at end of file diff --git a/RayTracer/Source/Materials/SolidColor.cs b/RayTracer/Source/Materials/SolidColor.cs new file mode 100644 index 0000000..adbbf75 --- /dev/null +++ b/RayTracer/Source/Materials/SolidColor.cs @@ -0,0 +1,24 @@ +using RayTracing.Maths; +using RayTracing.Shaders; + +namespace RayTracing.Materials +{ + public class SolidColor : ITexture + { + public Color Color; + + public SolidColor(Color color) + { + Color = color; + } + + public Color this[float x, float y] => Color; + + public void Use(Shader shader, int matNum, float part) + { + shader.SetInt(ITexture.MaterialUseColorUniformName(matNum), 1); + shader.SetVector3(ITexture.MaterialColorUniformName(matNum), Color.ToVector3()); + shader.SetFloat(ITexture.MaterialPartUniformName(matNum), part); + } + } +} \ No newline at end of file diff --git a/RayTracer/Source/Materials/Texture.cs b/RayTracer/Source/Materials/Texture.cs index f2b5684..4016e9f 100644 --- a/RayTracer/Source/Materials/Texture.cs +++ b/RayTracer/Source/Materials/Texture.cs @@ -1,58 +1,106 @@ using System; using System.IO; -using OpenTK.Graphics.OpenGL; +using OpenTK.Graphics.OpenGL4; using RayTracing.Maths; +using RayTracing.Shaders; using StbImageSharp; using StbImageWriteSharp; using ColorComponents = StbImageSharp.ColorComponents; namespace RayTracing.Materials { - public class Texture : IDisposable + public class Texture : ITexture, IDisposable { + private const string TexturesPath = "RayTracer.Resources.Textures."; private int _id; private Color[,] _data; - public int Width => _data.GetLength(1); - public int Height => _data.GetLength(0); + public int Width => _data.GetLength(0); + public int Height => _data.GetLength(1); public int Id => _id; public Texture(int width, int height) { - _data = new Color[height, width]; + _data = new Color[width, height]; } public Texture(Texture image) // copy constructor { - _data = new Color[image.Height, image.Width]; - for (int i = 0; i < Height; i++) - for (int j = 0; j < Width; j++) + _data = new Color[image.Width, image.Height]; + for (int i = 0; i < Width; i++) + for (int j = 0; j < Height; j++) _data[i, j] = image._data[i, j]; } - public Texture(string path) + public Texture(string path, bool glLoad = true) { - LoadFromPath(path); - LoadGLTexture(); + Log.Info($"Loading texture from path: {path}"); + Stream stream; + if (Path.IsPathRooted(path)) + stream = File.OpenRead(path); + else + { + var assembly = GetType().Assembly; + stream = assembly.GetManifestResourceStream(TexturesPath + path); + } + + LoadFromStream(stream); + if (glLoad) LoadGLTexture(); + stream.Dispose(); + } + + public Color this[float u, float v] + { + get => _data[(int) (u * (Width - 1)), (int) (v * (Height - 1))]; } public Color this[int w, int h] { - get => _data[h, w]; - set => _data[h, w] = value; + get => _data[w, h]; + set => _data[w, h] = value; + } + + public void Bloom(Color color, int w, int h, int strength = 1) + { + bool In(int x, int y) + { + return x >= 0 && x < Width && y >= 0 && y < Height; + } + + for (int i = -strength; i < strength; i++) + for (int j = -strength; j < strength; j++) + { + if (In(w + i, h + j)) + { + int rs = i * i + j * j; + if (rs <= strength * strength) + _data[w + i, h + j] += color / (10*strength * (rs + 1)); + } + } } public void Process(Func function) { - for (int i = 0; i < Height; i++) - for (int j = 0; j < Width; j++) + for (int i = 0; i < Width; i++) + for (int j = 0; j < Height; j++) _data[i, j] = function(_data[i, j]); } - private void LoadFromPath(string path) + public void AutoGammaCorrect() + { + float sum = 0; + for (int i = 0; i < Width; i++) + for (int j = 0; j < Height; j++) + sum += _data[i, j].GetBrightness(); + float brightness = sum / (Width * Height); + float correction = 2.0f - 1.5f * brightness; + Log.Info( + $"Auto gamma correcting image. Calculated average brightness: {brightness}, applying correction: {correction}."); + Process(c => c.GammaCorrection(correction)); + } + + private void LoadFromStream(Stream stream) { - Log.Info($"Loading texture from path: {path}"); - using var stream = File.OpenRead(path); ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlue); _data = new Color[image.Width, image.Height]; for (int i = 0; i < image.Width * image.Height; ++i) @@ -68,13 +116,29 @@ public byte[] RawData() { byte[] raw = new byte[Width * Height * 3]; - for (int i = 0; i < Height; i++) - for (int j = 0; j < Width; j++) + for (int i = 0; i < Width; i++) + for (int j = 0; j < Height; j++) { - Color color = _data[Height - i - 1, j]; - raw[i * Width * 3 + j * 3 + 0] = color.RComp; - raw[i * Width * 3 + j * 3 + 1] = color.GComp; - raw[i * Width * 3 + j * 3 + 2] = color.BComp; + Color color = _data[i, j]; + raw[j * Width * 3 + i * 3 + 0] = color.RComp; + raw[j * Width * 3 + i * 3 + 1] = color.GComp; + raw[j * Width * 3 + i * 3 + 2] = color.BComp; + } + + return raw; + } + + public byte[] FlippedRawData() + { + byte[] raw = new byte[Width * Height * 3]; + + for (int i = 0; i < Width; i++) + for (int j = 0; j < Height; j++) + { + Color color = _data[i, j]; + raw[(Height - j - 1) * Width * 3 + i * 3 + 0] = color.RComp; + raw[(Height - j - 1) * Width * 3 + i * 3 + 1] = color.GComp; + raw[(Height - j - 1) * Width * 3 + i * 3 + 2] = color.BComp; } return raw; @@ -82,20 +146,21 @@ public byte[] RawData() public void Write(string path) { - byte[] raw = RawData(); + byte[] raw = FlippedRawData(); using Stream stream = File.OpenWrite(path); ImageWriter writer = new ImageWriter(); writer.WritePng(raw, Width, Height, StbImageWriteSharp.ColorComponents.RedGreenBlue, stream); } - public void LoadGLTexture() + public void LoadGLTexture(TextureUnit unit = TextureUnit.Texture0) { byte[] raw = RawData(); _id = GL.GenTexture(); - Use(); + Use(unit); + GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1); GL.TexImage2D(TextureTarget.Texture2D, 0, - PixelInternalFormat.Rgba, + PixelInternalFormat.Rgb, Width, Height, 0, @@ -104,16 +169,15 @@ public void LoadGLTexture() raw); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, - (int) TextureMinFilter.Linear); + (int) TextureMinFilter.LinearMipmapLinear); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) TextureWrapMode.Repeat); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) TextureWrapMode.Repeat); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapR, (int) TextureWrapMode.Repeat); GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); - Clear(); + Clear(unit); } private static void Use(int id, TextureUnit unit = TextureUnit.Texture0) @@ -127,6 +191,13 @@ public void Use(TextureUnit unit = TextureUnit.Texture0) Use(_id, unit); } + public void Use(Shader shader, int matNum, float part) + { + shader.SetInt(ITexture.MaterialUseColorUniformName(matNum), 0); + Use(ITexture.MaterialTextureUnit(matNum)); + shader.SetFloat(ITexture.MaterialPartUniformName(matNum), part); + } + public static void Clear(TextureUnit unit = TextureUnit.Texture0) { Use(0, unit); diff --git a/RayTracer/Source/Maths/AABB.cs b/RayTracer/Source/Maths/AABB.cs index 3e7ab22..42e84cb 100644 --- a/RayTracer/Source/Maths/AABB.cs +++ b/RayTracer/Source/Maths/AABB.cs @@ -46,6 +46,5 @@ public bool Test(ref Ray ray, float from = 0, float to = float.PositiveInfinity) return new AABB(Vector3.ComponentMin(first.Min, second.Min), Vector3.ComponentMax(first.Max, second.Max)); } - } } \ No newline at end of file diff --git a/RayTracer/Source/Maths/Color.cs b/RayTracer/Source/Maths/Color.cs index c674a48..3f4dfb5 100644 --- a/RayTracer/Source/Maths/Color.cs +++ b/RayTracer/Source/Maths/Color.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using Assimp; +using OpenTK; using OpenTK.Graphics; namespace RayTracing.Maths @@ -7,14 +9,14 @@ namespace RayTracing.Maths public struct Color { public float R, G, B; - public IEnumerable Components() => new[] { RComp, GComp, BComp }; + public IEnumerable Components() => new[] {RComp, GComp, BComp}; public byte RComp => (byte) (R * 255); public byte GComp => (byte) (G * 255); public byte BComp => (byte) (B * 255); - + private const float MinVal = 0f; private const float MaxVal = 1f; - + public Color(float r, float g, float b) { R = r; @@ -31,6 +33,16 @@ public static Color FromColor4(Color4 color4) B = color4.B }; } + + public static Color FromAssimpColor4(Color4D color4) + { + return new Color + { + R = color4.R, + G = color4.G, + B = color4.B + }; + } public static Color operator +(Color first, Color second) { @@ -55,7 +67,7 @@ public static Color FromColor4(Color4 color4) first.B *= scalar; return first; } - + public static Color operator /(Color first, float scalar) { first.R /= scalar; @@ -85,5 +97,65 @@ public Color GammaCorrection(float gamma) B = (float) Math.Pow(B, exp); return this; } + + public Vector3 ToVector3() + { + return new Vector3(R, G, B); + } + + // from C++ codebase, and previously from JavaFX sources, now in c# code + public (float hue, float saturation, float brightness) ToHsb() + { + float hue, saturation, brightness; + float cmax = (R > G) ? R : G; + if (B > cmax) cmax = B; + float cmin = (R < G) ? R : G; + if (B < cmin) cmin = B; + + brightness = cmax; + if (cmax != 0) + saturation = (cmax - cmin) / cmax; + else + saturation = 0; + + if (saturation == 0) + { + hue = 0; + } + else + { + float redc = (cmax - R) / (cmax - cmin); + float greenc = (cmax - G) / (cmax - cmin); + float bluec = (cmax - B) / (cmax - cmin); + if (R == cmax) + hue = bluec - greenc; + else if (G == cmax) + hue = 2.0f + redc - bluec; + else + hue = 4.0f + greenc - redc; + hue = hue / 6.0f; + if (hue < 0) + hue = hue + 1.0f; + } + + return (hue * 360, saturation, brightness); + } + + public float GetHue() + { + return ToHsb().hue; + } + + public float GetSaturation() + { + return ToHsb().saturation; + } + + public float GetBrightness() + { + float cmax = (R > G) ? R : G; + if (B > cmax) cmax = B; + return cmax; + } } } \ No newline at end of file diff --git a/RayTracer/Source/Models/Cube.cs b/RayTracer/Source/Models/Cube.cs index 0d8afa6..d848f50 100644 --- a/RayTracer/Source/Models/Cube.cs +++ b/RayTracer/Source/Models/Cube.cs @@ -1,12 +1,17 @@ using System.Collections.Generic; using OpenTK; using RayTracing.Maths; +using RayTracing.RayTracing; namespace RayTracing.Models { - public class Cube : Model + public class Cube : CustomModel { - public override Vector3 Rotation { get; set; } + public Cube() + { + var buffers = GetBuffers(); + Mesh = new Mesh(buffers.vertexBuffer, buffers.normalBuffer, buffers.texBuffer, buffers.indicesBuffer); + } private List GetVertices() { @@ -103,8 +108,6 @@ private List GetTexInds() private protected override void LoadInternal() { - var buffers = GetBuffers(); - Mesh = new Mesh(buffers.vertexBuffer, buffers.normalBuffer, buffers.texBuffer, buffers.indicesBuffer); Mesh.Load(); } @@ -117,5 +120,10 @@ public override Mesh GetMesh() { return Mesh; } + + public override List Preprocess() + { + return MeshToTriangles(); + } } } \ No newline at end of file diff --git a/RayTracer/Source/Models/CustomModel.cs b/RayTracer/Source/Models/CustomModel.cs index 4b78e0e..fdb97b4 100644 --- a/RayTracer/Source/Models/CustomModel.cs +++ b/RayTracer/Source/Models/CustomModel.cs @@ -1,5 +1,7 @@ -using OpenTK; +using System.Collections.Generic; +using OpenTK; using RayTracing.Maths; +using RayTracing.RayTracing; namespace RayTracing.Models { @@ -11,13 +13,13 @@ public CustomModel() public void SetMesh(Mesh mesh) { - base.Mesh = mesh; + Mesh = mesh; loaded = false; } public CustomModel(Mesh mesh) { - base.Mesh = mesh; + Mesh = mesh; } public override Vector3 Rotation { get; set; } @@ -36,5 +38,10 @@ public override Mesh GetMesh() { return Mesh; } + + public override List Preprocess() + { + return MeshToTriangles(); + } } } \ No newline at end of file diff --git a/RayTracer/Source/Models/Cylinder.cs b/RayTracer/Source/Models/Cylinder.cs new file mode 100644 index 0000000..1130f57 --- /dev/null +++ b/RayTracer/Source/Models/Cylinder.cs @@ -0,0 +1,323 @@ +using System; +using System.Collections.Generic; +using OpenTK; +using RayTracing.Maths; + +namespace RayTracing.Models +{ + public class Cylinder : Model + { + private Vector3 _normal; + private Vector3 _bottom; + private Vector3 _top; + + private float _height; + private float _aspect; + private readonly int _sectorCount; + + // Radius = Scale + // Height = Scale * Aspect + public Cylinder(float aspect = 1.0f, int sectorCount = 100) + { + _sectorCount = sectorCount; + Aspect = aspect; + } + + public override Vector3 Rotation + { + set + { + base.Rotation = value; + Recalculate(); + } + } + + public override float Scale + { + set + { + base.Scale = value; + _height = _aspect * value; + Recalculate(); + } + } + + public override Vector3 Position + { + set + { + base.Position = value; + Recalculate(); + } + } + + public float Aspect + { + get => _aspect; + set + { + _aspect = value; + _height = _aspect * Scale; + Recalculate(); + if (loaded) + { + Unload(); + Load(); + } + } + } + + private void Recalculate() + { + _normal = Vector3.UnitY * RotationMatrix; + _bottom = -_height * 0.5f * _normal + Position; + _top = _height * 0.5f * _normal + Position; + } + + private protected override void LoadInternal() + { + var buffers = GetBuffers(); + Mesh = new Mesh(buffers.vertexBuffer, buffers.normalBuffer, buffers.texBuffer, buffers.indicesBuffer); + Mesh.Load(); + } + + // https://mrl.cs.nyu.edu/~dzorin/rendering/lectures/lecture3/lecture3-6pp.pdf + public override bool HitTest(Ray ray, ref HitInfo hit, float from, float to) + { + hit.Distance = to; + Vector3 deltaOrigins = ray.Origin - _bottom; + Vector3 rayDirection = ray.Direction; + + Vector3 aVec = rayDirection - Vector3.Dot(rayDirection, _normal) * _normal; + Vector3 bVec = deltaOrigins - Vector3.Dot(deltaOrigins, _normal) * _normal; + + float a = Vector3.Dot(aVec, aVec); + float bHalf = Vector3.Dot(aVec, bVec); + float c = Vector3.Dot(bVec, bVec) - Scale * Scale; + + float delta = bHalf * bHalf - a * c; + + if (delta < 0) + return false; + + float deltaSq = (float) Math.Sqrt(delta); + + float root = (-bHalf - deltaSq) / a; + if (root < from || to < root) + { + root = (-bHalf + deltaSq) / a; + if (root < from || to < root) + return false; + } + + Vector3 hitPoint = ray.Origin + ray.Direction * root; + + if (Vector3.Dot(hitPoint - _top, _normal) < 0 && + Vector3.Dot(hitPoint - _bottom, _normal) > 0) + { + hit.Distance = root; + hit.ModelHit = this; + hit.HitPoint = hitPoint; + + Vector3 normal = Vector3.Normalize(_bottom - hit.HitPoint); + normal = Vector3.Normalize(Vector3.Cross(normal, _normal)); + normal = Vector3.Cross(normal, _normal); + hit.SetNormal(ref ray, ref normal); + GetCylinderUV(hitPoint, ref hit.TexCoord); + } + + CapHitTest(ref ray, ref hit, from, to, ref _bottom); + CapHitTest(ref ray, ref hit, from, to, ref _top); + + return hit.Distance != to; + } + + private void CapHitTest(ref Ray ray, ref HitInfo hit, float from, float to, ref Vector3 capCenter) + { + float t = Vector3.Dot(capCenter - ray.Origin, _normal) / Vector3.Dot(ray.Direction, _normal); + if (t > hit.Distance) return; + Vector3 hitPoint = ray.Origin + ray.Direction * t; + if (t > from && t < to && (hitPoint - capCenter).LengthSquared < Scale * Scale) + { + hit.Distance = t; + hit.HitPoint = hitPoint; + hit.ModelHit = this; + hit.SetNormal(ref ray, ref _normal); + GetCapUV(hitPoint, ref hit.TexCoord); + } + } + + // https://www.iquilezles.org/www/articles/diskbbox/diskbbox.htm + public override AABB BoundingBox() + { + var normal = _normal.Normalized(); + float x = (float) Math.Sqrt(1 - normal.X * normal.X); + float y = (float) Math.Sqrt(1 - normal.Y * normal.Y); + float z = (float) Math.Sqrt(1 - normal.Z * normal.Z); + Vector3 e = new Vector3(x, y, z) * Scale; + return new AABB( + Vector3.ComponentMin(_top - e, _bottom - e), + Vector3.ComponentMax(_top + e, _bottom + e)); + } + + private void GetCylinderUV(Vector3 hitPoint, ref Vector2 uv) + { + var hitVector = RotationMatrix * (hitPoint - Position) / _height; + + var phi = Math.Atan2(hitVector.Z, -hitVector.X) + Math.PI; + uv.X = (float) (phi / (2 * Math.PI)); + uv.Y = 0.5f - hitVector.Y; + } + + private void GetCapUV(Vector3 hitPoint, ref Vector2 uv) + { + var hitVector = RotationMatrix * (hitPoint - Position) / Scale; + uv.X = 1 - (hitVector.X + 1) * 0.5f; + uv.Y = 1 - (hitVector.Z + 1) * 0.5f; + } + + public override Mesh GetMesh() + { + return Mesh; + } + + // http://www.songho.ca/opengl/gl_cylinder.html#cylinder + private List GetUnitCircleVertices() + { + float sectorStep = (float) (2 * Math.PI / _sectorCount); + + var unitCircleVertices = new List(); + for(int i = 0; i <= _sectorCount; ++i) + { + var sectorAngle = i * sectorStep; + unitCircleVertices.Add((float)Math.Cos(sectorAngle)); + unitCircleVertices.Add((float)Math.Sin(sectorAngle)); + unitCircleVertices.Add(0); + } + return unitCircleVertices; + } + + // http://www.songho.ca/opengl/gl_cylinder.html#cylinder + private (List indicesBuffer, List vertexBuffer, List texBuffer, List normalBuffer) GetBuffers() + { + var unitVertices = GetUnitCircleVertices(); + var vertices = new List(); + var normals = new List(); + var texCoords = new List(); + + float height = _aspect; + float radius = 1f; + + for(int i = 0; i < 2; ++i) + { + float h = -height / 2.0f + i * height; + float t = 1.0f - i; + + for(int j = 0, k = 0; j <= _sectorCount; ++j, k += 3) + { + float ux = unitVertices[k]; + float uy = unitVertices[k+1]; + float uz = unitVertices[k+2]; + + vertices.Add(ux * radius); + vertices.Add(h); + vertices.Add(uy * radius); + + normals.Add(ux); + normals.Add(uz); + normals.Add(uy); + + texCoords.Add(1 - (float)j / _sectorCount); + texCoords.Add(t); + } + } + + int baseCenterIndex = vertices.Count / 3; + int topCenterIndex = baseCenterIndex + _sectorCount + 1; + + for(int i = 0; i < 2; ++i) + { + float h = -height / 2.0f + i * height; + float nz = -1 + i * 2; + + vertices.Add(0); vertices.Add(h); vertices.Add(0); + normals.Add(0); normals.Add(nz); normals.Add(0); + texCoords.Add(0.5f); texCoords.Add(0.5f); + + for(int j = 0, k = 0; j < _sectorCount; ++j, k += 3) + { + float ux = unitVertices[k]; + float uy = unitVertices[k+1]; + + vertices.Add(ux * radius); + vertices.Add(h); + vertices.Add(uy * radius); + + normals.Add(0); + normals.Add(nz); + normals.Add(0); + + texCoords.Add(-ux * 0.5f + 0.5f); + texCoords.Add(-uy * 0.5f + 0.5f); + } + } + + var indices = GenIndices(topCenterIndex, baseCenterIndex); + + return (indices, vertices, texCoords, normals); + } + + // http://www.songho.ca/opengl/gl_cylinder.html#cylinder + private List GenIndices(int topCenterIndex, int baseCenterIndex) + { + var indices = new List(); + int k1 = 0; + int k2 = _sectorCount + 1; + + for(int i = 0; i < _sectorCount; ++i, ++k1, ++k2) + { + indices.Add(k1); + indices.Add(k1 + 1); + indices.Add(k2); + + indices.Add(k2); + indices.Add(k1 + 1); + indices.Add(k2 + 1); + } + + for(int i = 0, k = baseCenterIndex + 1; i < _sectorCount; ++i, ++k) + { + if(i < _sectorCount - 1) + { + indices.Add(baseCenterIndex); + indices.Add(k + 1); + indices.Add(k); + } + else + { + indices.Add(baseCenterIndex); + indices.Add(baseCenterIndex + 1); + indices.Add(k); + } + } + + for(int i = 0, k = topCenterIndex + 1; i < _sectorCount; ++i, ++k) + { + if(i < _sectorCount - 1) + { + indices.Add(topCenterIndex); + indices.Add(k); + indices.Add(k + 1); + } + else + { + indices.Add(topCenterIndex); + indices.Add(k); + indices.Add(topCenterIndex + 1); + } + } + + return indices; + } + } +} \ No newline at end of file diff --git a/RayTracer/Source/Models/Mesh.cs b/RayTracer/Source/Models/Mesh.cs index 851e1a9..0840b2a 100644 --- a/RayTracer/Source/Models/Mesh.cs +++ b/RayTracer/Source/Models/Mesh.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Runtime.Intrinsics.X86; +using OpenTK; using OpenTK.Graphics.OpenGL4; namespace RayTracing.Models @@ -36,6 +38,18 @@ public void Load() LoadIndexBuffer(Indices); } + public void Unload() + { + foreach (var vbo in vboIdList) + { + GL.DeleteBuffer(vbo); + } + + vboIdList.Clear(); + GL.DeleteVertexArray(vaoId); + vaoId = 0; + } + private void LoadDataBuffer(List buffer, int index, int size) { int vboId = GL.GenBuffer(); @@ -81,5 +95,28 @@ public void Render() GL.DrawElements(PrimitiveType.Triangles, vertexCount, DrawElementsType.UnsignedInt, 0); End(); } + + public Vector3 GetPostion(int index) + { + float vx = Positions[3 * Indices[index] + 0]; + float vy = Positions[3 * Indices[index] + 1]; + float vz = Positions[3 * Indices[index] + 2]; + return new Vector3(vx, vy, vz); + } + + public Vector3 GetNormal(int index) + { + float vx = Normals[3 * Indices[index] + 0]; + float vy = Normals[3 * Indices[index] + 1]; + float vz = Normals[3 * Indices[index] + 2]; + return new Vector3(vx, vy, vz); + } + + public Vector2 GetTexCoord(int index) + { + float vx = TexCoords[2 * Indices[index] + 0]; + float vy = TexCoords[2 * Indices[index] + 1]; + return new Vector2(vx, vy); + } } } \ No newline at end of file diff --git a/RayTracer/Source/Models/Model.cs b/RayTracer/Source/Models/Model.cs index 01df422..877ec44 100644 --- a/RayTracer/Source/Models/Model.cs +++ b/RayTracer/Source/Models/Model.cs @@ -1,4 +1,5 @@ -using OpenTK; +using System.Collections.Generic; +using OpenTK; using RayTracing.Materials; using RayTracing.Maths; using RayTracing.RayTracing; @@ -8,12 +9,33 @@ namespace RayTracing.Models public abstract class Model : IHittable { protected Mesh Mesh { get; set; } - public IMaterial Material; - public Vector3 Position; - public float Scale = 1f; - public abstract Vector3 Rotation { get; set; } - public bool Loaded => loaded; protected bool loaded; + private Vector3 _rotation; + + public bool Loaded => loaded; + public IMaterial Material; + public virtual Vector3 Position { get; set; } + public virtual float Scale { get; set; } = 1f; + + public virtual Vector3 Rotation + { + get => _rotation; + set + { + _rotation = value; + UpdateRotationMatrix(value); + } + } + + protected virtual Matrix3 RotationMatrix { get; set; } = Matrix3.Identity; + + protected void UpdateRotationMatrix(Vector3 newRotation) + { + RotationMatrix = Matrix3.CreateRotationX(newRotation.X) * + Matrix3.CreateRotationY(newRotation.Y) * + Matrix3.CreateRotationZ(newRotation.Z); + } + private protected abstract void LoadInternal(); public Model Load() @@ -23,8 +45,51 @@ public Model Load() return this; } + public Model Unload() + { + Mesh?.Unload(); + loaded = false; + return this; + } + public abstract bool HitTest(Ray ray, ref HitInfo hit, float from, float to); + public virtual AABB BoundingBox() + { + throw new System.NotImplementedException(); + } + + public virtual List Preprocess() + { + return new List {this}; + } + + public List MeshToTriangles() + { + List hittables = new List(); + if (Mesh == null) return hittables; + + Matrix4 modelMatrix = GetModelMatrix(); + + for (var index = 0; index < Mesh.Indices.Count; index += 3) + { + Vector3 v1 = Mesh.GetPostion(index); + Vector3 v2 = Mesh.GetPostion(index + 1); + Vector3 v3 = Mesh.GetPostion(index + 2); + Vector2 tc1 = Mesh.GetTexCoord(index); + Vector2 tc2 = Mesh.GetTexCoord(index + 1); + Vector2 tc3 = Mesh.GetTexCoord(index + 2); + v1 = (new Vector4(v1, 1.0f) * modelMatrix).Xyz; + v2 = (new Vector4(v2, 1.0f) * modelMatrix).Xyz; + v3 = (new Vector4(v3, 1.0f) * modelMatrix).Xyz; + Triangle t = new Triangle(v1, v2, v3, tc1, tc2, tc3); + t.Material = Material; + hittables.Add(t); + } + + return hittables; + } + public abstract Mesh GetMesh(); public Matrix4 GetModelMatrix() diff --git a/RayTracer/Source/Models/ModelLoader.cs b/RayTracer/Source/Models/ModelLoader.cs index 54798c2..b9e971c 100644 --- a/RayTracer/Source/Models/ModelLoader.cs +++ b/RayTracer/Source/Models/ModelLoader.cs @@ -1,12 +1,140 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using Assimp; using Assimp.Configs; +using RayTracing.Materials; +using RayTracing.Maths; namespace RayTracing.Models { public class ModelLoader { + public static CustomModel Load(string path) + { + return Load(path, + PostProcessSteps.Triangulate | + PostProcessSteps.GenerateNormals | + PostProcessSteps.JoinIdenticalVertices | + PostProcessSteps.FixInFacingNormals); + } + + public static CustomModel Load(string path, PostProcessSteps ppSteps, params PropertyConfig[] configs) + { + Log.Info($"Loading model: {path}"); + + var model = new CustomModel(); + + if (!File.Exists(path)) + { + Log.Error($"Model {path} does not exist."); + return model; + } + + var importer = new AssimpContext(); + if (configs != null) + { + foreach (var config in configs) + importer.SetConfig(config); + } + + try + { + var scene = importer.ImportFile(path, ppSteps); + if (scene == null) + { + Log.Error($"Error loading model {path}. Scene was null."); + return model; + } + + if (scene.Meshes.Count == 0) + { + Log.Error($"Error loading model {path}. No meshes found."); + return model; + } + + if (scene.Meshes.Count > 1) + { + Log.Warn($"Model {path} containing more than one mesh. Using first mesh."); + } + + var mesh = ProcessMesh(scene.Meshes[0]); + var material = ProcessMaterial(scene.Materials[scene.Meshes[0].MaterialIndex], Path.GetDirectoryName(Path.GetFullPath(path))); + + model.SetMesh(mesh); + model.Material = material; + + return model; + } + catch (AssimpException e) + { + Log.Error("Assimp has thrown an exception.", e); + return model; + } + } + + private static IMaterial ProcessMaterial(Assimp.Material material, string dir) + { + Log.Info($"Processing material:: {material.Name}"); + return new MasterMaterial + { + Emissive = + { + Emit = ProcessTexture(material.HasTextureAmbient, material.ColorAmbient, + material.TextureAmbient, dir) + }, + Diffuse = + { + Albedo = ProcessTexture(material.HasTextureDiffuse, material.ColorDiffuse, + material.TextureDiffuse, dir) + }, + Reflective = + { + Albedo = + ProcessTexture(material.HasTextureSpecular, material.ColorSpecular, + material.TextureSpecular, dir), + Disturbance = Math.Min(Math.Max(1 - material.ShininessStrength / 100, 0), 1) + }, + Parts = ProcessMaterialParts(material.ColorAmbient, material.ColorDiffuse, material.ColorSpecular) + }; + } + + private static ITexture ProcessTexture(bool hasTexture, Color4D color4, TextureSlot textureSlot, string dir) + { + Log.Info($"Processing texture:: hasTex:{hasTexture}, Color: {color4}, Texture:{textureSlot.FilePath}"); + return hasTexture + ? (ITexture) new Texture(Path.Combine(dir, textureSlot.FilePath)) + : new SolidColor(Color.FromAssimpColor4(color4)); + } + + private static (float emissive, float diffuse, float reflective, float refractive) ProcessMaterialParts( + Color4D ambient, Color4D diffuse, Color4D specular) + { + (float emissive, float diffuse, float reflective, float refractive) parts = + (Color.FromAssimpColor4(ambient).GetBrightness(), Color.FromAssimpColor4(diffuse).GetBrightness(), + Color.FromAssimpColor4(specular).GetBrightness(), 0); + + float sum = parts.emissive + parts.diffuse + parts.reflective; + if (sum != 0) + { + parts.emissive /= sum; + parts.diffuse /= sum; + parts.reflective /= sum; + } + + return parts; + } + + private static Mesh ProcessMesh(Assimp.Mesh mesh) + { + var positions = ProcessVertices(mesh); + var textures = ProcessTextCoord(mesh); + var normals = ProcessNormals(mesh); + var indices = ProcessIndices(mesh); + + return new Mesh(positions, normals, textures, indices); + } + private static List ProcessVertices(Assimp.Mesh mesh) { List vertices = new List(); @@ -59,47 +187,5 @@ private static List ProcessIndices(Assimp.Mesh mesh) return indices; } - - private static Mesh ProcessMesh(Scene scene, Assimp.Mesh mesh) - { - var positions = ProcessVertices(mesh); - var textures = ProcessTextCoord(mesh); - var normals = ProcessNormals(mesh); - var indices = ProcessIndices(mesh); - - return new Mesh(positions, normals, textures, indices); - } - - public static Mesh LoadMesh(string path, PostProcessSteps ppSteps, params PropertyConfig[] configs) - { - if (!File.Exists(path)) - return null; - - AssimpContext importer = new AssimpContext(); - if (configs != null) - { - foreach (PropertyConfig config in configs) - importer.SetConfig(config); - } - - Scene scene = importer.ImportFile(path, ppSteps); - if (scene == null) - return null; - - var meshes = new List(); - for (int i = 0; i < scene.Meshes.Count; i++) - { - var m = ProcessMesh(scene, scene.Meshes[i]); - meshes.Add(m); - } - - Log.Info($"meshes loaded: {meshes.Count}"); - return meshes[0]; - } - - public static CustomModel LoadModel(string path, PostProcessSteps ppSteps, params PropertyConfig[] configs) - { - return new CustomModel(LoadMesh(path, ppSteps, configs)); - } } } \ No newline at end of file diff --git a/RayTracer/Source/Models/Plane.cs b/RayTracer/Source/Models/Plane.cs index ca28b43..b05e7de 100644 --- a/RayTracer/Source/Models/Plane.cs +++ b/RayTracer/Source/Models/Plane.cs @@ -8,24 +8,107 @@ namespace RayTracing.Models public class Plane : Model { private Vector3 _normal = Vector3.UnitY; - private Vector3 _rotation; + private int _sideCount = 1000; public override Vector3 Rotation { - get => _rotation; set { - _rotation = value; - _normal= Vector3.UnitY; - _normal *= Matrix3.CreateRotationX(_rotation.X); - _normal *= Matrix3.CreateRotationY(_rotation.Y); - _normal *= Matrix3.CreateRotationZ(_rotation.Z); + base.Rotation = value; + _normal = Vector3.UnitY * RotationMatrix; } } private protected override void LoadInternal() { - Mesh = new Rectangle(1000,1000,100,100).Load().GetMesh(); + var buffers = GetBuffers(); + Mesh = new Mesh(buffers.vertexBuffer, buffers.normalBuffer, buffers.texBuffer, buffers.indicesBuffer); + Mesh.Load(); + } + + private Vector3[] GetVertices() + { + var vertices = new Vector3[(_sideCount + 1) * (_sideCount + 1)]; + var halfWidth = _sideCount / 2; + var halfHeight = _sideCount / 2; + for (int i = 0, y = 0; y <= _sideCount; y++) + { + for (int x = 0; x <= _sideCount; x++, i++) + { + vertices[i] = new Vector3(x - halfWidth, 0, y - halfHeight); + } + } + + return vertices; + } + + private int[] GetIndices() + { + var indices = new int[_sideCount * _sideCount * 6]; + for (int ti = 0, vi = 0, y = 0; y < _sideCount; y++, vi++) + { + for (int x = 0; x < _sideCount; x++, ti += 6, vi++) + { + indices[ti] = vi; + indices[ti + 3] = indices[ti + 2] = vi + 1; + indices[ti + 4] = indices[ti + 1] = vi + _sideCount + 1; + indices[ti + 5] = vi + _sideCount + 2; + } + } + + return indices; + } + + private Vector2[] GetTexCoords() + { + return new[] + { + new Vector2(0, 1), + new Vector2(1, 1), + new Vector2(1, 0), + new Vector2(0, 0) + }; + } + + private int[] GetTexInds() + { + return new[] + { + 0, 1, 3, 3, 1, 2 + }; + } + + private (List indicesBuffer, List vertexBuffer, List texBuffer, List normalBuffer) + GetBuffers() + { + var vertices = GetVertices(); + var indices = GetIndices(); + var normal = new Vector3(0, 1, 0); + var texCoords = GetTexCoords(); + var texInds = GetTexInds(); + + var vertexBuffer = new List(); + var textureBuffer = new List(); + var normalBuffer = new List(); + var indicesBuffer = new List(); + + for (int i = 0; i < indices.Length; i++) + { + vertexBuffer.Add(vertices[indices[i]].X); + vertexBuffer.Add(vertices[indices[i]].Y); + vertexBuffer.Add(vertices[indices[i]].Z); + + textureBuffer.Add(texCoords[texInds[i % 6]].X); + textureBuffer.Add(texCoords[texInds[i % 6]].Y); + + normalBuffer.Add(normal.X); + normalBuffer.Add(normal.Y); + normalBuffer.Add(normal.Z); + + indicesBuffer.Add(i); + } + + return (indicesBuffer, vertexBuffer, textureBuffer, normalBuffer); } public override bool HitTest(Ray ray, ref HitInfo hit, float from, float to) @@ -37,6 +120,10 @@ public override bool HitTest(Ray ray, ref HitInfo hit, float from, float to) hit.HitPoint = ray.Origin + ray.Direction * hit.Distance; hit.ModelHit = this; hit.SetNormal(ref ray, ref _normal); + var hitVector = RotationMatrix * (hit.HitPoint - Position); + hit.TexCoord = new Vector2( + hitVector.Z % Scale < 0 ? 1 + hitVector.Z % Scale : hitVector.Z % Scale, + 1 - (hitVector.X % Scale < 0 ? 1 + hitVector.X % Scale : hitVector.X % Scale)); return true; } diff --git a/RayTracer/Source/Models/Rectangle.cs b/RayTracer/Source/Models/Rectangle.cs index da13174..a271433 100644 --- a/RayTracer/Source/Models/Rectangle.cs +++ b/RayTracer/Source/Models/Rectangle.cs @@ -1,23 +1,17 @@ using System.Collections.Generic; using OpenTK; using RayTracing.Maths; +using RayTracing.RayTracing; namespace RayTracing.Models { public class Rectangle : Model { - public override Vector3 Rotation { get; set; } - private float _width; - private float _height; - private int _xCount; - private int _yCount; + private float _aspectRatio = 1; - public Rectangle(float width, float height, int xCount, int yCount) + public Rectangle(float aspect) { - _width = width; - _height = height; - _xCount = xCount; - _yCount = yCount; + _aspectRatio = aspect; } private protected override void LoadInternal() @@ -29,40 +23,26 @@ private protected override void LoadInternal() private Vector3[] GetVertices() { - var vertices = new Vector3[(_xCount + 1) * (_yCount + 1)]; - var halfWidth = _width / 2; - var halfHeight = _height / 2; - for (int i = 0, y = 0; y <= _yCount; y++) + return new[] { - for (int x = 0; x <= _xCount; x++, i++) - { - vertices[i] = new Vector3(_width * x / _xCount-halfWidth, 0, _height * y / _yCount-halfHeight); - } - } - - return vertices; + new Vector3(-0.5f * _aspectRatio, 0, -0.5f), + new Vector3(0.5f * _aspectRatio, 0, -0.5f), + new Vector3(-0.5f * _aspectRatio, 0, 0.5f), + new Vector3(0.5f * _aspectRatio, 0, 0.5f), + }; } private int[] GetIndices() { - var indices = new int[_xCount * _yCount * 6]; - for (int ti = 0, vi = 0, y = 0; y < _yCount; y++, vi++) + return new[] { - for (int x = 0; x < _xCount; x++, ti += 6, vi++) - { - indices[ti] = vi; - indices[ti + 3] = indices[ti + 2] = vi + 1; - indices[ti + 4] = indices[ti + 1] = vi + _xCount + 1; - indices[ti + 5] = vi + _xCount + 2; - } - } - - return indices; + 0, 2, 1, 1, 2, 3 + }; } private Vector2[] GetTexCoords() { - return new [] + return new[] { new Vector2(0, 0), new Vector2(1, 0), @@ -70,10 +50,10 @@ private Vector2[] GetTexCoords() new Vector2(0, 1) }; } - + private int[] GetTexInds() { - return new [] + return new[] { 0, 1, 3, 3, 1, 2 }; @@ -121,5 +101,10 @@ public override Mesh GetMesh() { return Mesh; } + + public override List Preprocess() + { + return MeshToTriangles(); + } } } \ No newline at end of file diff --git a/RayTracer/Source/Models/Sphere.cs b/RayTracer/Source/Models/Sphere.cs index be4250b..b99bd3d 100644 --- a/RayTracer/Source/Models/Sphere.cs +++ b/RayTracer/Source/Models/Sphere.cs @@ -7,8 +7,6 @@ namespace RayTracing.Models { public class Sphere : Model { - public override Vector3 Rotation { get; set; } - private protected override void LoadInternal() { var (positions, texCoords) = GetVertexList(100, 100); @@ -44,16 +42,31 @@ public override bool HitTest(Ray ray, ref HitInfo hit, float from, float to) hit.Distance = root; hit.HitPoint = ray.Origin + ray.Direction * hit.Distance; hit.ModelHit = this; - Vector3 normal = (hit.HitPoint - Position) / Scale; + Vector3 normal = hit.HitPoint - Position; + normal.Normalize(); + GetSphereUV(RotationMatrix * normal, ref hit.TexCoord); hit.SetNormal(ref ray, ref normal); return true; } + private void GetSphereUV(Vector3 normal, ref Vector2 UV) + { + var theta = Math.Acos(normal.Y); + var phi = Math.Atan2(normal.Z, -normal.X) + Math.PI; + + UV.X = (float) (phi / (2 * Math.PI)); + UV.Y = (float) (theta / Math.PI); + } + public override Mesh GetMesh() { return Mesh; } + public override AABB BoundingBox() + { + return new AABB(Position - new Vector3(Scale), Position + new Vector3(Scale)); + } private (List, List) GetVertexList(short rings, short sectors) { @@ -75,8 +88,10 @@ public override Mesh GetMesh() positions.Add(x); positions.Add(y); positions.Add(z); + Vector3 normal = new Vector3(x, y, z); + normal.Normalize(); texCoords.Add(1 - s * S); - texCoords.Add(r * R); + texCoords.Add(1 - r * R); } } diff --git a/RayTracer/Source/Models/Triangle.cs b/RayTracer/Source/Models/Triangle.cs new file mode 100644 index 0000000..87a7f3a --- /dev/null +++ b/RayTracer/Source/Models/Triangle.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using OpenTK; +using RayTracing.Maths; + +namespace RayTracing.Models +{ + public class Triangle : Model + { + private List _vertices; + private List _texCoords; + private Vector3 _normal; + + public Triangle(Vector3 v1, Vector3 v2, Vector3 v3, Vector2 tc1 = new Vector2(), Vector2 tc2 = new Vector2(), + Vector2 tc3 = new Vector2()) + { + _vertices = new List {v1, v2, v3}; + var a = _vertices[1] - _vertices[0]; + var b = _vertices[2] - _vertices[0]; + _normal = Vector3.Cross(a, b); + _normal.Normalize(); + _texCoords = new List {tc1, tc2, tc3}; + } + + private protected override void LoadInternal() + { + throw new Exception("Triangle is used only in ray tracing"); + } + + public override AABB BoundingBox() + { + var outputBox = new AABB( + Vector3.ComponentMin(Vector3.ComponentMin(_vertices[0], _vertices[1]), _vertices[2]), + Vector3.ComponentMax(Vector3.ComponentMax(_vertices[0], _vertices[1]), _vertices[2]) + ); + const float thickness = (float) 1e-5; + const float threshold = (float) 1e-7; + for (int i = 0; i < 3; i++) + { + if (Math.Abs(outputBox.Max[i] - outputBox.Min[i]) < threshold) + { + var vec = outputBox.Min; + vec[i] -= thickness; + outputBox.Min = vec; + + vec = outputBox.Max; + vec[i] += thickness; + outputBox.Max = vec; + } + } + + return outputBox; + } + + //source: https://www.scratchapixel.com/code.php?id=9&origin=/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle + public override bool HitTest(Ray ray, ref HitInfo hit, float @from, float to) + { + ray.Direction = ray.Direction.Normalized(); + float u, v, t; + var v0v1 = _vertices[1] - _vertices[0]; + var v0v2 = _vertices[2] - _vertices[0]; + var pvec = Vector3.Cross(ray.Direction, v0v2); + var det = Vector3.Dot(v0v1, pvec); + + if (Math.Abs(det) < Ray.Epsilon) return false; + var invDet = 1 / det; + + var tvec = ray.Origin - _vertices[0]; + u = Vector3.Dot(tvec, pvec) * invDet; + if (u < 0 || u > 1) return false; + + var qvec = Vector3.Cross(tvec, v0v1); + v = Vector3.Dot(ray.Direction, qvec) * invDet; + if (v < 0 || u + v > 1) return false; + + t = Vector3.Dot(v0v2, qvec) * invDet; + + if (t > to || t < from) return false; + + var uv = _texCoords[0] + u * (_texCoords[1] - _texCoords[0]) + v * (_texCoords[2] - _texCoords[0]); + + hit.ModelHit = this; + hit.TexCoord = uv; + hit.Distance = t; + hit.HitPoint = ray.Origin + ray.Direction * t; + hit.SetNormal(ref ray, ref _normal); + + return true; + } + + public override Mesh GetMesh() + { + throw new Exception("Triangle is used only in ray tracing"); + } + } +} \ No newline at end of file diff --git a/RayTracer/Source/RayTracing/IHittable.cs b/RayTracer/Source/RayTracing/IHittable.cs index b035cc1..ae1c889 100644 --- a/RayTracer/Source/RayTracing/IHittable.cs +++ b/RayTracer/Source/RayTracing/IHittable.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using RayTracing.Maths; namespace RayTracing.RayTracing @@ -5,5 +6,7 @@ namespace RayTracing.RayTracing public interface IHittable { bool HitTest(Ray ray, ref HitInfo hit, float from, float to); + List Preprocess(); + AABB BoundingBox(); } } \ No newline at end of file diff --git a/RayTracer/Source/Renderer/FileRayTracer.cs b/RayTracer/Source/Renderer/FileRayTracer.cs index e41dff3..a57d362 100644 --- a/RayTracer/Source/Renderer/FileRayTracer.cs +++ b/RayTracer/Source/Renderer/FileRayTracer.cs @@ -22,6 +22,7 @@ public FileRayTracer(string path, int maxDepth, int samples, Func c / Samples); + image.Process(c => (c / Samples).Clamp()); + image.AutoGammaCorrect(); image.Write(_path); } } diff --git a/RayTracer/Source/Renderer/IncrementalRayTracer.cs b/RayTracer/Source/Renderer/IncrementalRayTracer.cs index ad98bd0..4e55203 100644 --- a/RayTracer/Source/Renderer/IncrementalRayTracer.cs +++ b/RayTracer/Source/Renderer/IncrementalRayTracer.cs @@ -1,57 +1,20 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; using OpenTK; -using OpenTK.Graphics.OpenGL4; using RayTracing.Cameras; -using RayTracing.Materials; -using RayTracing.Maths; -using RayTracing.Models; -using RayTracing.Sampling; -using RayTracing.Shaders; using RayTracing.World; namespace RayTracing { - public class IncrementalRayTracer : RayTracer, IRenderer + public abstract class IncrementalRayTracer : RayTracer, IRenderer { - public Action OnFrameReady { get; set; } + public Action OnFrameReady { get; set; } public Func IsCancellationRequested { get; set; } + public abstract void Render(Scene scene, Camera camera); public IncrementalRayTracer(int maxDepth, int samples, Func> sampling, int resolution) : base(maxDepth, samples, sampling, resolution) { } - - public void Render(Scene scene, Camera camera) - { - int width = Resolution; - int height = (int) (width / camera.AspectRatio); - var image = new Texture(width, height); - AbstractSampler sampler = new ThreadSafeSampler(Sampling, Samples); - - for (int k = 0; k < Samples; k++) - { - Parallel.For(0, width, i => - { - if (IsCancellationRequested != null && IsCancellationRequested()) - return; - for (int j = 0; j < height; j++) - { - var sample = sampler.GetSample(k); - float u = (i + sample.X) / (width - 1); - float v = (j + sample.Y) / (height - 1); - Ray ray = camera.GetRay(u, v); - image[i, height - 1 - j] += Shade(ray, scene, MaxDepth); - } - }); - if (IsCancellationRequested != null && IsCancellationRequested()) - return; - - var output = new Texture(image); - output.Process(c => c / (k + 1)); - OnFrameReady?.Invoke((k + 1) * 100 / Samples, output); - } - } } } \ No newline at end of file diff --git a/RayTracer/Source/Renderer/RayTracer.cs b/RayTracer/Source/Renderer/RayTracer.cs index 78d8726..1592218 100644 --- a/RayTracer/Source/Renderer/RayTracer.cs +++ b/RayTracer/Source/Renderer/RayTracer.cs @@ -29,12 +29,15 @@ public Color Shade(Ray ray, Scene scene, int depth) if (scene.HitTest(ray, ref hitInfo, 0.001f, float.PositiveInfinity)) { + var emitted = + hitInfo.ModelHit.Material.Emitted(hitInfo.TexCoord.X, hitInfo.TexCoord.Y); + if (hitInfo.ModelHit.Material.Scatter(ref ray, ref hitInfo, out Color attenuation, out Ray scattered)) { - return attenuation * Shade(scattered, scene, depth - 1); + return emitted + attenuation * Shade(scattered, scene, depth - 1); } - return new Color(); + return emitted; } return scene.AmbientLight.Color; diff --git a/RayTracer/Source/Renderer/RecursionRayTracer.cs b/RayTracer/Source/Renderer/RecursionRayTracer.cs new file mode 100644 index 0000000..333b5fa --- /dev/null +++ b/RayTracer/Source/Renderer/RecursionRayTracer.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using OpenTK; +using RayTracing.Cameras; +using RayTracing.World; + +namespace RayTracing +{ + public class RecursionRayTracer : SamplesRayTracer + { + public RecursionRayTracer(int maxDepth, int samples, Func> sampling, int resolution) : base( + maxDepth, samples, sampling, resolution, 1) + { + } + + public override void Render(Scene scene, Camera camera) + { + var maxDepth = MaxDepth; + for (int recDepth = 1; recDepth <= maxDepth; recDepth++) + { + MaxDepth = recDepth; + base.Render(scene, camera); + } + } + } +} \ No newline at end of file diff --git a/RayTracer/Source/Renderer/Renderer.cs b/RayTracer/Source/Renderer/Renderer.cs index dbaed3b..ac0d407 100644 --- a/RayTracer/Source/Renderer/Renderer.cs +++ b/RayTracer/Source/Renderer/Renderer.cs @@ -1,4 +1,5 @@ -using OpenTK.Graphics.OpenGL4; +using System; +using OpenTK.Graphics.OpenGL4; using RayTracing.Cameras; using RayTracing.Shaders; using RayTracing.World; @@ -7,6 +8,7 @@ namespace RayTracing { public class Renderer : IRenderer { + private const int MAX_LIGHTS = 5; private readonly Shader _shader = new Shader("shader.vert", "shader.frag"); public void Render(Scene scene, Camera camera) @@ -16,13 +18,30 @@ public void Render(Scene scene, Camera camera) GL.ClearColor(ambient.R, ambient.G, ambient.B, 1.0f); _shader.Use(); - _shader.SetMatrix4("view", camera.GetViewMatrix()); - _shader.SetMatrix4("projection", camera.GetProjectionMatrix()); + for (int i = 0; i < 3; i++) + { + _shader.SetInt($"materials[{i}].texture", i); + } + + _shader.SetVector3("ambientLight", ambient.ToVector3()); + _shader.SetVector3("cameraPosition", camera.Position); + var lightsCount = Math.Min(MAX_LIGHTS, scene.Lights.Count); + _shader.SetInt("lightsCount", lightsCount); + for (int i = 0; i < lightsCount; i++) + { + var light = scene.Lights[i]; + _shader.SetVector3($"light[{i}].position", light.Position); + var color = light.Material.AverageColor; + _shader.SetVector3($"light[{i}].diffuse", color.ToVector3()); + } foreach (var model in scene.Models) { if (!model.Loaded) continue; _shader.SetMatrix4("model", model.GetModelMatrix()); + _shader.SetMatrix4("mvp", + model.GetModelMatrix() * camera.GetViewMatrix() * camera.GetProjectionMatrix()); + model.Material.Use(_shader); model.GetMesh().Render(); } } diff --git a/RayTracer/Source/Renderer/SamplesRayTracer.cs b/RayTracer/Source/Renderer/SamplesRayTracer.cs new file mode 100644 index 0000000..c91e7ea --- /dev/null +++ b/RayTracer/Source/Renderer/SamplesRayTracer.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using OpenTK; +using RayTracing.Cameras; +using RayTracing.Materials; +using RayTracing.Maths; +using RayTracing.Sampling; +using RayTracing.World; + +namespace RayTracing +{ + public class SamplesRayTracer : IncrementalRayTracer + { + private readonly int _samplesRenderStep; + + public SamplesRayTracer(int maxDepth, int samples, Func> sampling, int resolution, int samplesRenderStep) : base( + maxDepth, samples, sampling, resolution) + { + _samplesRenderStep = samplesRenderStep; + } + + public override void Render(Scene scene, Camera camera) + { + scene.Preprocess(); + int width = Resolution; + int height = (int) (width / camera.AspectRatio); + var image = new Texture(width, height); + AbstractSampler sampler = new ThreadSafeSampler(Sampling, Samples); + + for (int k = 0; k < Samples; k++) + { + Parallel.For(0, width, i => + { + if (IsCancellationRequested != null && IsCancellationRequested()) + return; + for (int j = 0; j < height; j++) + { + var sample = sampler.GetSample(k); + float u = (i + sample.X) / (width - 1); + float v = (j + sample.Y) / (height - 1); + Ray ray = camera.GetRay(u, v); + image[i, j] += Shade(ray, scene, MaxDepth); + } + }); + if (IsCancellationRequested != null && IsCancellationRequested()) + return; + + if (k % _samplesRenderStep == 0 || k == Samples - 1) + { + var output = new Texture(image); + output.Process(c => (c / (k + 1)).Clamp()); + output.AutoGammaCorrect(); + var percentage = (k + 1) * 100 / Samples; + OnFrameReady?.Invoke(percentage, output); + } + } + } + } +} \ No newline at end of file diff --git a/RayTracer/Source/Sampling/FloatSampling.cs b/RayTracer/Source/Sampling/FloatSampling.cs new file mode 100644 index 0000000..4645a78 --- /dev/null +++ b/RayTracer/Source/Sampling/FloatSampling.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace RayTracing.Sampling +{ + public static class FloatSampling + { + private static readonly Random _random = new Random(); + + public static List Random(int count) + { + List samples = new List(); + for (int i = 0; i < count; ++i) + { + samples.Add((float) _random.NextDouble()); + } + + return samples; + } + } +} \ No newline at end of file diff --git a/RayTracer/Source/Sampling/IntSampling.cs b/RayTracer/Source/Sampling/IntSampling.cs new file mode 100644 index 0000000..ee8c112 --- /dev/null +++ b/RayTracer/Source/Sampling/IntSampling.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RayTracing.Sampling +{ + public static class IntSampling + { + private static readonly Random _random = new Random(); + + public static List Distribution(int count, params float[] probabilities) + { + if (probabilities.Length == 0) + throw new ArgumentException("Zero lenght probabilities array"); + if(probabilities.Any(probability => probability < 0)) + throw new ArgumentException("Probability lesser than zero."); + + float sum = probabilities.Sum(); + + if(sum == 0) + { + Log.Warn("Probabilities sum to 0 returning list containing zeroes"); + return Enumerable.Repeat(0, count).ToList(); + } + + List samples = new List(); + + for (int i = 0; i < count; i++) + { + float next = (float)_random.NextDouble() * sum; + float counter = 0; + for (int j = 0; j < probabilities.Length; j++) + { + counter += probabilities[j]; + if (counter >= next) + { + samples.Add(j + 1); + break; + } + } + } + + return samples; + } + + public static List Random(int count, int minInclusive, int maxExclusive) + { + if(count < 0) + throw new ArgumentException("Count less than zero"); + if (minInclusive >= maxExclusive) + throw new ArgumentException("Wrong limits"); + + List samples = new List(); + + for (int i = 0; i < count; i++) + { + samples.Add(_random.Next(minInclusive, maxExclusive)); + } + + return samples; + } + } +} \ No newline at end of file diff --git a/RayTracer/Source/Sampling/Vec3Sampling.cs b/RayTracer/Source/Sampling/Vec3Sampling.cs index f752f12..20cfe66 100644 --- a/RayTracer/Source/Sampling/Vec3Sampling.cs +++ b/RayTracer/Source/Sampling/Vec3Sampling.cs @@ -22,5 +22,10 @@ public static List UniformSphere(int count) { } return samples; } + + public static Vector3 ToSphereSurface(Vector3 sample) + { + return sample != Vector3.Zero ? sample.Normalized() : UniformSphere(1)[0].Normalized(); + } } } \ No newline at end of file diff --git a/RayTracer/Source/Shaders/Shader.cs b/RayTracer/Source/Shaders/Shader.cs index 2a48456..56d7364 100644 --- a/RayTracer/Source/Shaders/Shader.cs +++ b/RayTracer/Source/Shaders/Shader.cs @@ -76,6 +76,24 @@ public void SetMatrix4(string name, Matrix4 data) GL.UseProgram(_handle); GL.UniformMatrix4(_uniformLocations[name], true, ref data); } + + public void SetVector3(string name, Vector3 data) + { + GL.UseProgram(_handle); + GL.Uniform3(_uniformLocations[name], data); + } + + public void SetInt(string name, int data) + { + GL.UseProgram(_handle); + GL.Uniform1(_uniformLocations[name], data); + } + + public void SetFloat(string name, float data) + { + GL.UseProgram(_handle); + GL.Uniform1(_uniformLocations[name], data); + } public void Use() { diff --git a/RayTracer/Source/World/Scene.cs b/RayTracer/Source/World/Scene.cs index 20e36a2..d448c4c 100644 --- a/RayTracer/Source/World/Scene.cs +++ b/RayTracer/Source/World/Scene.cs @@ -1,25 +1,30 @@ using System.Collections.Generic; +using RayTracing.BVH; using RayTracing.Lights; +using RayTracing.Materials; using RayTracing.Maths; using RayTracing.Models; using RayTracing.RayTracing; +using RayTracing.Sampling; namespace RayTracing.World { public class Scene : IHittable { + public bool BvhMode { get; set; } = true; public List Models { get; } = new List(); - public List Lights { get; } = new List(); + public List Hittables { get; } = new List(); + public List Lights { get; } = new List(); public AmbientLight AmbientLight { get; set; } public void AddModel(Model model) { Models.Add(model); - } - - public void AddLight(Light light) - { - Lights.Add(light); + if (model.Material is Emissive || model.Material is MasterMaterial && + (model.Material as MasterMaterial).Parts.emissive != 0) // add MasterMaterial + { + Lights.Add(model); + } } public bool HitTest(Ray ray, ref HitInfo hit, float from, float to) @@ -28,9 +33,9 @@ public bool HitTest(Ray ray, ref HitInfo hit, float from, float to) bool hitAnything = false; float closest = to; - foreach (var model in Models) + foreach (var hittable in Hittables) { - if (model.HitTest(ray, ref tempHitInfo, from, closest)) + if (hittable.HitTest(ray, ref tempHitInfo, from, closest)) { hitAnything = true; closest = tempHitInfo.Distance; @@ -40,5 +45,56 @@ public bool HitTest(Ray ray, ref HitInfo hit, float from, float to) return hitAnything; } + + public AABB BoundingBox() + { + throw new System.NotImplementedException(); + } + + private List StandardPreprocess() + { + Hittables.Clear(); + foreach (var model in Models) + { + Hittables.AddRange(((IHittable) model).Preprocess()); + } + + return Hittables; + } + + private List BvhPreprocess() + { + Hittables.Clear(); + var planes = new List(); + var hittablesToBvh = new List(); + foreach (var model in Models) + { + if (model is Plane) + { + planes.Add(model); + } + else + { + hittablesToBvh.AddRange(((IHittable) model).Preprocess()); + } + } + + var node = new BvhNode(hittablesToBvh, 0, hittablesToBvh.Count); + Hittables.AddRange(planes); + Hittables.Add(node); + return Hittables; + } + + public List Preprocess() + { + if (BvhMode) + { + return BvhPreprocess(); + } + else + { + return StandardPreprocess(); + } + } } } \ No newline at end of file diff --git a/RayTracerApp/Controls/Features/CustomModelFeatureControl.cs b/RayTracerApp/Controls/Features/CustomModelFeatureControl.cs index 59ca7de..93d3c9e 100644 --- a/RayTracerApp/Controls/Features/CustomModelFeatureControl.cs +++ b/RayTracerApp/Controls/Features/CustomModelFeatureControl.cs @@ -72,8 +72,9 @@ private void loadFromFileButton_Click(object sender, EventArgs e) if (openFileDialog.ShowDialog() == DialogResult.OK) { filePath = openFileDialog.FileName; - var mesh = ModelLoader.LoadMesh(filePath, Assimp.PostProcessSteps.Triangulate); - (_controller.GetModel() as CustomModel).SetMesh(mesh); + var model = ModelLoader.Load(filePath); + _controller.GetModel()?.Unload(); + _controller.SetModel(model); _controller.GetModel().Load(); } } diff --git a/RayTracerApp/Controls/PositionPanel.cs b/RayTracerApp/Controls/PositionPanel.cs index 38b5bf2..4846195 100644 --- a/RayTracerApp/Controls/PositionPanel.cs +++ b/RayTracerApp/Controls/PositionPanel.cs @@ -45,19 +45,25 @@ public void HidePanel() private void zUpDown_ValueChanged(object sender, EventArgs e) { var nud = sender as NumericUpDown; - _controller.GetModel().Position.Z = (float) nud.Value; + var pos = _controller.GetModel().Position; + pos.Z = (float) nud.Value; + _controller.GetModel().Position = pos; } private void yUpDown_ValueChanged(object sender, EventArgs e) { var nud = sender as NumericUpDown; - _controller.GetModel().Position.Y = (float) nud.Value; + var pos = _controller.GetModel().Position; + pos.Y = (float) nud.Value; + _controller.GetModel().Position = pos; } private void xUpDown_ValueChanged(object sender, EventArgs e) { var nud = sender as NumericUpDown; - _controller.GetModel().Position.X = (float) nud.Value; + var pos = _controller.GetModel().Position; + pos.X = (float) nud.Value; + _controller.GetModel().Position = pos; } private void pitchUpDown_ValueChanged(object sender, EventArgs e) diff --git a/RayTracerApp/Forms/MainForm.cs b/RayTracerApp/Forms/MainForm.cs index d4f3f2a..a8a6cc9 100644 --- a/RayTracerApp/Forms/MainForm.cs +++ b/RayTracerApp/Forms/MainForm.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Windows.Forms; using OpenTK; @@ -9,13 +10,13 @@ using RayTracing.Cameras; using RayTracing.Lights; using RayTracing.Materials; -using RayTracing.Maths; using RayTracing.Models; using RayTracing.Sampling; using RayTracing.World; using Camera = RayTracing.Cameras.Camera; using Timer = System.Windows.Forms.Timer; using RayTracerApp.SceneControllers; +using Color = RayTracing.Maths.Color; namespace RayTracerApp.Forms { @@ -55,27 +56,57 @@ private void GLControl_Load(object sender, EventArgs e) { GL.Enable(EnableCap.DepthTest); _renderer = new Renderer(); - _rayTracer = new IncrementalRayTracer(10, 64, Vec2Sampling.Jittered, gLControl.Width); + _rayTracer = new SamplesRayTracer(8, 1024, Vec2Sampling.Jittered, gLControl.Width, 32); _cameraController = new CameraController(_camera, gLControl, UpdateLastModification); _scene.AmbientLight = new AmbientLight {Color = Color.FromColor4(Color4.LightSkyBlue)}; + var bulb = new MasterMaterial(); + bulb.Emissive.Emit = new SolidColor(Color.FromColor4(Color4.Yellow) * 25); + bulb.Parts = (1, 0, 0, 0); _scene.AddModel(new Sphere { - Position = new Vector3(0, 0.5f, 0), Scale = 1, Material = new Diffuse(Color.FromColor4(Color4.Orange)) + Position = new Vector3(0, 5.5f, 0), Scale = 1, + Material = new MasterMaterial(new Emissive(Color.FromColor4(Color4.LightYellow) * 25)) }.Load()); _scene.AddModel(new Sphere { Position = new Vector3(-2.5f, 0.5f, 1), Scale = 1, - Material = new Reflective(Color.FromColor4(Color4.Azure), 0.1f) + Material = new MasterMaterial(new Reflective(Color.FromColor4(Color4.Azure), 0.1f)) }.Load()); _scene.AddModel(new Sphere { Position = new Vector3(2.5f, 0.5f, 1), Scale = 1, - Material = new Reflective(Color.FromColor4(Color4.Aqua), 0.75f) + Material = new MasterMaterial(new Diffuse(new Texture("earthmap.jpg"))) + }.Load()); + _scene.AddModel(new Cylinder(2) + { + Position = new Vector3(5f, 0.5f, 0), Scale = 1, + Material = new MasterMaterial(new Diffuse(Color.FromColor4(Color4.Chocolate))) + }.Load()); + _scene.AddModel(new Cylinder(2) + { + Position = new Vector3(5f, 0.5f, 4), Scale = 1, + Material = new MasterMaterial(new Diffuse(new Texture("wood.jpg"))) + }.Load()); + _scene.AddModel(new Cube() + { + Position = new Vector3(0, 0.5f, -3), Scale = 1, + Material = new MasterMaterial(new Reflective(new Texture("wood.jpg"), 0.75f)) + }.Load()); + _scene.AddModel(new Rectangle(2) + { + Position = new Vector3(0, 0.5f, -1.99f), Scale = 0.8f, + Material = new MasterMaterial(new Emissive(Color.FromColor4(Color4.White) * 8)), + Rotation = new Vector3((float) Math.PI / 2, 0, 0) }.Load()); _scene.AddModel(new Plane { Position = new Vector3(0, -0.5f, 0), Scale = 1, - Material = new Diffuse(Color.FromColor4(Color4.ForestGreen)) + Material = new MasterMaterial(new Diffuse(Color.FromColor4(Color4.Green)), + new Reflective(Color.FromColor4(Color4.White), 0.1f), + new Refractive(Color.FromColor4(Color4.Green), 1)) + { + Parts = (0.0f, 0.8f, 0.1f, 0.0f) + } }.Load()); InitializeFpsTimer(); @@ -109,6 +140,7 @@ private void OnTimerTick(object sender, EventArgs e) private void OnResize(object sender, EventArgs e) { + UpdateLastModification(); UpdateViewport(); } @@ -132,10 +164,11 @@ private void StartRender(object sender, DoWorkEventArgs e) private void BackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e) { - (e.UserState as Texture)?.Blit(); + var texture = (Texture) e.UserState; + texture?.Blit(); gLControl.SwapBuffers(); - (e.UserState as Texture)?.Dispose(); - Text = e.ProgressPercentage + "%"; + texture?.Dispose(); + Text = $@"{e.ProgressPercentage}%"; } private void InitializeBackgroundWorker() @@ -148,16 +181,18 @@ private void InitializeBackgroundWorker() _backgroundWorker.DoWork += StartRender; _backgroundWorker.ProgressChanged += BackgroundWorkerProgressChanged; } - + private void newObjectButton_Click(object sender, EventArgs e) { - var form = new NewObjectForm(new NewObjectController(_scene)); + var form = new NewObjectForm(new NewObjectController(_scene)) + {StartPosition = FormStartPosition.Manual, Location = Location + Size / 3}; form.Show(); } - + private void editObjectButton_Click(object sender, EventArgs e) { - var form = new EditObjectForm(new EditObjectController(_scene, _scene.Models[0])); + var form = new EditObjectForm(new EditObjectController(_scene, _scene.Models[0])) + {StartPosition = FormStartPosition.Manual, Location = Location + Size / 3}; form.Show(); } } diff --git a/RayTracerApp/RayTracer.csproj b/RayTracerApp/RayTracer.csproj deleted file mode 100644 index 1d27ac3..0000000 --- a/RayTracerApp/RayTracer.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - WinExe - netcoreapp3.1 - true - true - true - win10-x64 - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/RayTracerDemo/Program.cs b/RayTracerDemo/Program.cs index d0f9404..c421ebe 100644 --- a/RayTracerDemo/Program.cs +++ b/RayTracerDemo/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using OpenTK; using OpenTK.Graphics; using RayTracing; @@ -16,12 +17,29 @@ class Program { static void Main(string[] args) { + var scene = new Scene {AmbientLight = new AmbientLight {Color = Color.FromColor4(Color4.Black)}}; + scene.AddModel(new Cylinder(5) {Position = new Vector3(-5f, 0.5f, -1), Scale = 1, Rotation = new Vector3(1.15f, 0, 3.14f / 2), Material = new Diffuse(Color.FromColor4(Color4.Firebrick))}); Camera camera = new LensCamera(new Vector3(0, 0, 8), 0.1f, 8f); - var scene = new Scene {AmbientLight = new AmbientLight {Color = Color.FromColor4(Color4.LightSkyBlue)}}; - scene.AddModel(new Sphere {Position = new Vector3(0, 0.5f, 0), Scale = 1, Material = new Diffuse(Color.FromColor4(Color4.Orange))}); + scene.AddModel(new Cylinder(2) {Position = new Vector3(5f, 0.5f, 0), Scale = 1, Material = new Diffuse(Color.FromColor4(Color4.Chocolate))}); + scene.AddModel(new Sphere {Position = new Vector3(-1, 0.5f, -0.5f), Scale = 1, Material = new Diffuse(Color.FromColor4(Color4.Orange))}); + scene.AddModel(new Sphere {Position = new Vector3(0, 5.5f, 0), Scale = 1, Material = new Emissive(Color.FromColor4(Color4.White)*10)}); scene.AddModel(new Sphere {Position = new Vector3(-2.5f, 0.5f, 1), Scale = 1, Material = new Reflective(Color.FromColor4(Color4.Azure), 0.1f)}); - scene.AddModel(new Sphere {Position = new Vector3(2.5f, 0.5f, 1), Scale = 1, Material = new Reflective(Color.FromColor4(Color4.Aqua), 0.75f)}); + var matteGlass = new MasterMaterial(); + matteGlass.Diffuse.Albedo = new SolidColor(Color.FromColor4(Color4.White)); + matteGlass.Reflective.Albedo = new SolidColor(Color.FromColor4(Color4.White)); + matteGlass.Refractive.Albedo = new SolidColor(Color.FromColor4(Color4.White)); + matteGlass.Refractive.RefractiveIndex = 1.5f; + matteGlass.Parts = (0, 2, 4, 4); + scene.AddModel(new Sphere {Position = new Vector3(2.5f, 0.5f, 1), Scale = 1, Material = matteGlass}); scene.AddModel(new Plane {Position = new Vector3(0, -0.5f, 0), Scale = 1, Material = new Diffuse(Color.FromColor4(Color4.ForestGreen))}); + scene.AddModel(new Sphere {Position = new Vector3(5f, 0.5f, 1.5f), Scale = 1, Material = new Diffuse(new Texture("earthmap.jpg", false))}); + scene.AddModel(new Triangle( + new Vector3(-4f, 0f, -1f), + new Vector3(-5f, 2f, 2.5f), + new Vector3(-5f, -0.5f, 2f) + ) {Material = new Reflective(Color.FromColor4(Color4.AliceBlue), 0.05f)}); + scene.AddModel(new Cube {Position = new Vector3(1.0f, 0.5f, -1.5f), Rotation = new Vector3(-0, 2f, 0), Material = new Diffuse(Color.FromColor4(Color4.Crimson))}); + scene.AddModel(new Sphere {Position = new Vector3(0f, 0.0f, 3.5f), Scale = 0.5f, Material = new Refractive(Color.FromColor4(Color4.White), 1.333f)}); var rayTracer = new FileRayTracer("RenderedScene.png", 10, 64, Vec2Sampling.Jittered, 1280); rayTracer.Render(scene, camera); Console.WriteLine("done"); diff --git a/RayTracerTests/Source/Utils/TextureTests.cs b/RayTracerTests/Source/Utils/TextureTests.cs index 5ec4cd2..a6c6ffb 100644 --- a/RayTracerTests/Source/Utils/TextureTests.cs +++ b/RayTracerTests/Source/Utils/TextureTests.cs @@ -104,16 +104,16 @@ public void RawDataShouldHaveRGBOrder() [Test] - public void RawDataShouldContainRowByRowFlippedInYAxis() + public void RawDataShouldContainRowByRow() { Texture image = new Texture(3, 2) { - [0, 1] = new Color(1.0f / 255.0f, 2.0f / 255.0f, 3.0f / 255.0f), - [1, 1] = new Color(4.0f / 255.0f, 5.0f / 255.0f, 6.0f / 255.0f), - [2, 1] = new Color(7.0f / 255.0f, 8.0f / 255.0f, 9.0f / 255.0f), - [0, 0] = new Color(10.0f / 255.0f, 11.0f / 255.0f, 12.0f / 255.0f), - [1, 0] = new Color(13.0f / 255.0f, 14.0f / 255.0f, 15.0f / 255.0f), - [2, 0] = new Color(16.0f / 255.0f, 17.0f / 255.0f, 18.0f / 255.0f) + [0, 0] = new Color(1.0f / 255.0f, 2.0f / 255.0f, 3.0f / 255.0f), + [1, 0] = new Color(4.0f / 255.0f, 5.0f / 255.0f, 6.0f / 255.0f), + [2, 0] = new Color(7.0f / 255.0f, 8.0f / 255.0f, 9.0f / 255.0f), + [0, 1] = new Color(10.0f / 255.0f, 11.0f / 255.0f, 12.0f / 255.0f), + [1, 1] = new Color(13.0f / 255.0f, 14.0f / 255.0f, 15.0f / 255.0f), + [2, 1] = new Color(16.0f / 255.0f, 17.0f / 255.0f, 18.0f / 255.0f) }; byte[] raw = image.RawData(); diff --git a/RayTracerTests/Source/World/SceneTest.cs b/RayTracerTests/Source/World/SceneTest.cs index d0945ed..0285ffe 100644 --- a/RayTracerTests/Source/World/SceneTest.cs +++ b/RayTracerTests/Source/World/SceneTest.cs @@ -18,8 +18,8 @@ public void HitTestShouldCallHitTestOnAllItsElements() Mock modelA = new Mock(); Mock modelB = new Mock(); Scene scene = new Scene(); - scene.AddModel(modelA.Object); - scene.AddModel(modelB.Object); + scene.Hittables.Add(modelA.Object); + scene.Hittables.Add(modelB.Object); HitInfo info = new HitInfo(); scene.HitTest(new Ray(), ref info, 0, 0);