public void Emission_ShouldBeOneSided() { var mesh = new Mesh( new Vector3[] { new Vector3(-1, 10, -1), new Vector3(1, 10, -1), new Vector3(1, 10, 1), new Vector3(-1, 10, 1) }, new int[] { 0, 1, 2, 0, 2, 3 }, shadingNormals: new Vector3[] { new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) } ); var emitter = new DiffuseEmitter(mesh, RgbColor.White); var dummyHit = new SurfacePoint { Mesh = mesh }; var r1 = emitter.EmittedRadiance(dummyHit, new Vector3(0, 1, 1)); var r2 = emitter.EmittedRadiance(dummyHit, new Vector3(0, -1, 1)); Assert.True(r1.R > 0); Assert.Equal(0, r2.R); }
public void EmittedRays_SampleInverse() { var mesh = new Mesh( new Vector3[] { new Vector3(-1, 10, -1), new Vector3(1, 10, -1), new Vector3(1, 10, 1), new Vector3(-1, 10, 1) }, new int[] { 0, 1, 2, 0, 2, 3 }, shadingNormals: new Vector3[] { new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) } ); var emitter = new DiffuseEmitter(mesh, RgbColor.White); var sample = emitter.SampleRay(new Vector2(0.3f, 0.8f), new Vector2(0.56f, 0.03f)); var(posP, dirP) = emitter.SampleRayInverse(sample.Point, sample.Direction); Assert.Equal(0.3f, posP.X, 3); Assert.Equal(0.8f, posP.Y, 3); Assert.Equal(0.56f, dirP.X, 3); Assert.Equal(0.03f, dirP.Y, 3); }
public void EmittedRays_Sidedness_ShouldBeNegative() { var mesh = new Mesh( new Vector3[] { new Vector3(-1, 10, -1), new Vector3(1, 10, -1), new Vector3(1, 10, 1), new Vector3(-1, 10, 1) }, new int[] { 0, 1, 2, 0, 2, 3 }, shadingNormals: new Vector3[] { new Vector3(0, -1, 0), new Vector3(0, -1, 0), new Vector3(0, -1, 0), new Vector3(0, -1, 0) } ); var emitter = new DiffuseEmitter(mesh, RgbColor.White); var sample = emitter.SampleRay(new Vector2(0.3f, 0.8f), new Vector2(0.56f, 0.03f)); Assert.True(sample.Direction.Y < 0); }
public void EmittedRays_Pdf_ShouldBeCosHemisphere() { var mesh = new Mesh( new Vector3[] { new Vector3(-1, 10, -1), new Vector3(1, 10, -1), new Vector3(1, 10, 1), new Vector3(-1, 10, 1) }, new int[] { 0, 1, 2, 0, 2, 3 }, shadingNormals: new Vector3[] { new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) } ); var emitter = new DiffuseEmitter(mesh, RgbColor.White); var sample = emitter.SampleRay(new Vector2(0.3f, 0.8f), new Vector2(0.56f, 0.03f)); float c = Vector3.Dot(sample.Direction, new Vector3(0, 1, 0)); Assert.Equal(0.25f * c / MathF.PI, sample.Pdf); }
public void EmittedRays_Pdf_ShouldBeOneSided() { var mesh = new Mesh( new Vector3[] { new Vector3(-1, 10, -1), new Vector3(1, 10, -1), new Vector3(1, 10, 1), new Vector3(-1, 10, 1) }, new int[] { 0, 1, 2, 0, 2, 3 }, shadingNormals: new Vector3[] { new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) } ); var emitter = new DiffuseEmitter(mesh, RgbColor.White); var dummyHit = new SurfacePoint { Mesh = mesh }; var p1 = emitter.PdfRay(dummyHit, new Vector3(0, 1, 1)); var p2 = emitter.PdfRay(dummyHit, new Vector3(0, -1, 1)); Assert.True(p1 > 0); Assert.Equal(0, p2); }
public void EmittedRays_ShouldHaveOffset() { var mesh = new Mesh( new Vector3[] { new Vector3(-1, 10, -1), new Vector3(1, 10, -1), new Vector3(1, 10, 1), new Vector3(-1, 10, 1) }, new int[] { 0, 1, 2, 0, 2, 3 } ); var emitter = new DiffuseEmitter(mesh, RgbColor.White); var sample = emitter.SampleRay(new Vector2(0.3f, 0.8f), new Vector2(0.56f, 0.03f)); Assert.True(sample.Point.ErrorOffset > 0); }
public void EmittedRays_Weight_ShouldBeRadianceOverPdf() { var mesh = new Mesh( new Vector3[] { new Vector3(-1, 10, -1), new Vector3(1, 10, -1), new Vector3(1, 10, 1), new Vector3(-1, 10, 1) }, new int[] { 0, 1, 2, 0, 2, 3 }, shadingNormals: new Vector3[] { new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) } ); var emitter = new DiffuseEmitter(mesh, RgbColor.White); var sample = emitter.SampleRay(new Vector2(0.3f, 0.8f), new Vector2(0.56f, 0.03f)); float c = Vector3.Dot(sample.Direction, new Vector3(0, 1, 0)); float expectedPdf = 0.25f * c / MathF.PI; Assert.Equal(expectedPdf, sample.Pdf); var expectedWeight = emitter.EmittedRadiance(sample.Point, sample.Direction) * c / expectedPdf; Assert.Equal(expectedWeight.R, sample.Weight.R); Assert.Equal(expectedWeight.G, sample.Weight.G); Assert.Equal(expectedWeight.B, sample.Weight.B); }
public override Scene MakeScene() { var scene = new Scene(); // Create the camera var worldToCamera = Matrix4x4.CreateLookAt(-1 * Vector3.UnitZ, Vector3.Zero, Vector3.UnitY); scene.Camera = new PerspectiveCamera(worldToCamera, 45); // Create the two transmissive planes in-between // first plane var mesh = new Mesh(new Vector3[] { new Vector3(-1, -1, 0), new Vector3(1, -1, 0), new Vector3(1, 1, 0), new Vector3(-1, 1, 0), }, new int[] { 0, 1, 2, 0, 2, 3, }); mesh.Material = new GenericMaterial(new GenericMaterial.Parameters { BaseColor = new TextureRgb(RgbColor.White), Roughness = new TextureMono(0.5f), SpecularTransmittance = 1, Thin = true, DiffuseTransmittance = 1, }); scene.Meshes.Add(mesh); // second plane mesh = new Mesh(new Vector3[] { new Vector3(-1, -1, 1), new Vector3(1, -1, 1), new Vector3(1, 1, 1), new Vector3(-1, 1, 1), }, new int[] { 0, 1, 2, 0, 2, 3, }); mesh.Material = new GenericMaterial(new GenericMaterial.Parameters { BaseColor = new TextureRgb(RgbColor.White), Roughness = new TextureMono(0.5f), Thin = true, DiffuseTransmittance = 1, }); scene.Meshes.Add(mesh); // Create the light source var lightArea = 0.1f; var pos = MathF.Sqrt(lightArea) / 2; var lightMesh = new Mesh(new Vector3[] { new Vector3(-pos, -pos, 1 + 10), new Vector3(pos, -pos, 1 + 10), new Vector3(pos, pos, 1 + 10), new Vector3(-pos, pos, 1 + 10), }, new int[] { 0, 1, 2, 0, 2, 3, }, new Vector3[] { -Vector3.UnitZ, -Vector3.UnitZ, -Vector3.UnitZ, -Vector3.UnitZ, }); lightMesh.Material = new DiffuseMaterial(new DiffuseMaterial.Parameters { BaseColor = new TextureRgb(RgbColor.Black) }); scene.Meshes.Add(lightMesh); var lightPower = 500; var radiance = lightPower / (MathF.PI * lightArea); var emitter = new DiffuseEmitter(lightMesh, RgbColor.White * radiance); scene.Emitters.Add(emitter); scene.FrameBuffer = new FrameBuffer(512, 512, ""); scene.Prepare(); return(scene); }
/// <summary> /// Loads a mesh from a .fbx file and adds it to the given scene. Technically, this would also work /// for any other file format supported by Assimp, but we are doing some .fbx specific hacks to achieve /// the correct scale and other conventions. /// </summary> /// <param name="filename">Path to an existing .fbx mesh</param> /// <param name="scene">The scene to which the mesh should be added</param> /// <param name="materialOverride"> /// Materials from the .fbx with a name matching one of the keys in this dictionary will be /// replaced by the corresponding dictionary entry /// </param> /// <param name="emissionOverride"> /// If a material name is a key in this dictionary, all meshes with that material will be /// converted to diffuse emitters. The value from the dictionary determines their emitted radiance. /// </param> public static void AddToScene(string filename, Scene scene, Dictionary <string, Material> materialOverride, Dictionary <string, RgbColor> emissionOverride = null) { // Load the file with some basic post-processing Assimp.AssimpContext context = new(); var assimpScene = context.ImportFile(filename, Assimp.PostProcessSteps.Triangulate | Assimp.PostProcessSteps.PreTransformVertices); // Add all meshes to the scene foreach (var m in assimpScene.Meshes) { var material = assimpScene.Materials[m.MaterialIndex]; string materialName = material.Name; Vector3[] vertices = new Vector3[m.VertexCount]; for (int i = 0; i < m.VertexCount; ++i) { vertices[i] = new Vector3(-m.Vertices[i].X, m.Vertices[i].Z, m.Vertices[i].Y) * 0.01f; } Vector3[] normals = null; if (m.HasNormals) { normals = new Vector3[m.VertexCount]; for (int i = 0; i < m.VertexCount; ++i) { normals[i] = new(-m.Normals[i].X, m.Normals[i].Z, m.Normals[i].Y); } } // We currently only support a single uv channel Vector2[] texCoords = null; if (m.HasTextureCoords(0)) { texCoords = new Vector2[m.VertexCount]; var texCoordChannel = m.TextureCoordinateChannels[0]; for (int i = 0; i < m.VertexCount; ++i) { texCoords[i] = new(texCoordChannel[i].X, 1 - texCoordChannel[i].Y); } } if (m.TextureCoordinateChannelCount > 1) { Logger.Log($"Ignoring additional uv channels in mesh \"{m.Name}\" read from \"{filename}\"", Verbosity.Warning); } // If the material is not overridden by json, we load the diffuse color, or set an error color Material mat; if (materialOverride.ContainsKey(materialName)) { mat = materialOverride[materialName]; } else if (material.HasColorDiffuse) { var c = material.ColorDiffuse; mat = new DiffuseMaterial(new() { BaseColor = new Image.TextureRgb(new RgbColor(c.R, c.G, c.B)) }); } else { mat = new DiffuseMaterial(new() { BaseColor = new Image.TextureRgb(new RgbColor(1, 0, 1)) }); } Mesh mesh = new(vertices, m.GetIndices(), normals, texCoords) { Material = mat }; scene.Meshes.Add(mesh); // Create an emitter if requested if (emissionOverride != null && emissionOverride.TryGetValue(materialName, out var emission)) { var emitter = new DiffuseEmitter(mesh, emission); scene.Emitters.Add(emitter); } } } }