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