File BaseCamera.cs
File List > Render > src > BaseCamera.cs
Go to the documentation of this file.
using System;
using System.Linq;
using System.Drawing;
using Qkmaxware.Geometry;
using System.Collections.Generic;
namespace Qkmaxware.Rendering {
public abstract class BaseCamera : SceneNode {
public Size Size {get; private set;}
public Color[,] PixelBuffer {get; private set;}
public double[,] DepthBuffer {get; private set;}
public Skybox Skybox {get; set;} = new Skybox();
protected readonly double focallength = 1;
protected double nearClipDistance = 0.1;
protected double farClipDistance = 1000;
public BaseCamera(Size size) {
this.Size = size;
this.PixelBuffer = new Color[size.Height,size.Width];
this.DepthBuffer = new double[size.Height,size.Width];
Dirty();
}
public void SetClippingDistance(double near, double far) {
this.nearClipDistance = Math.Min(near, far);
this.farClipDistance = Math.Max(near, far);
Dirty();
}
protected abstract void Dirty();
public abstract Vec3 WorldToScreenPoint(Vec3 world);
public abstract Vec3 ScreenToWorldPoint(Vec2 screen);
private void ClearPixels() {
for (int i = 0; i < PixelBuffer.GetLength(0); i++)
for (int j = 0; j < PixelBuffer.GetLength(1); j++)
PixelBuffer[i, j] = Skybox.GetPixel(this, j, i);
}
private void ClearDepth() {
for (int i = 0; i < DepthBuffer.GetLength(0); i++)
for (int j = 0; j < DepthBuffer.GetLength(1); j++)
DepthBuffer[i, j] = farClipDistance;
}
public void Render(Scene scene) {
// Clean data
ClearPixels();
ClearDepth();
// Set constant shader properties
var vars = new ShaderVariables();
vars.WorldCameraPosition = this.Position;
vars.LightSources = scene.OfType<LightSource>().ToList().AsReadOnly();
// Loop over all models
foreach (var renderable in scene.OfType<IRenderable>()) {
vars.ModelToWorld = renderable.LocalToWorldMatrix;
if (renderable.Mesh != null && renderable.Material != null) {
Render(ref vars, renderable.Mesh, renderable.UVs, (Material)renderable.Material);
}
}
}
private void Render(ref ShaderVariables shader, IEnumerable<Triangle> tris, IUvMap? uvs, Material material) {
var backwards = this.Backward;
foreach (var tri in tris) {
var worldTri = tri.Transform(shader.ModelToWorld); // Convert to world space
shader.WorldNormal = worldTri.Normal;
// Backface culling
if (!material.TwoSided) {
var normal = worldTri.Normal;
if (Vec3.Dot(backwards, normal) < -0.1) {
continue;
}
}
// Get triangles
var v1 = WorldToScreenPoint(worldTri.Item1);
var v2 = WorldToScreenPoint(worldTri.Item2);
var v3 = WorldToScreenPoint(worldTri.Item3);
// Get the UV coordinates
Vec2 uv1 = (uvs != null) ? uvs[tri.Item1] : Vec2.Zero;
Vec2 uv2 = (uvs != null) ? uvs[tri.Item2] : Vec2.Zero;
Vec2 uv3 = (uvs != null) ? uvs[tri.Item3] : Vec2.Zero;
Rasterize(ref shader, worldTri, v1, v2, v3, uv1, uv2, uv3, material);
}
}
private void Rasterize(ref ShaderVariables shader, Triangle worldTri, Vec3 v1, Vec3 v2, Vec3 v3, Vec2 uv1, Vec2 uv2, Vec2 uv3, Material material) {
// Sort vertices into ascending order of y
var worldV1 = worldTri.Item1;
var worldV2 = worldTri.Item2;
var worldV3 = worldTri.Item3;
if (v1.Y > v2.Y) {
(v1, v2) = (v2, v1);
(uv1, uv2) = (uv2, uv1);
(worldV1, worldV2) = (worldV2, worldV1);
}
if (v2.Y > v3.Y) {
(v2, v3) = (v3, v2);
(uv2, uv3) = (uv3, uv2);
(worldV2, worldV3) = (worldV3, worldV2);
}
if (v1.Y > v2.Y) {
(v1, v2) = (v2, v1);
(uv1, uv2) = (uv2, uv1);
(worldV1, worldV2) = (worldV2, worldV1);
}
// Rasterize
if (v2.Y == v3.Y) {
// Flat Bottom Triangle
DrawFlatBottomTriangle (worldV1, worldV2, worldV3, v1, v2, v3, uv1, uv2, uv3, material, ref shader);
DrawLine(worldV1, worldV2, v1, v2, uv1, uv2, material, ref shader, surface: false);
DrawLine(worldV2, worldV3, v2, v3, uv2, uv3, material, ref shader, surface: false);
DrawLine(worldV1, worldV3, v1, v3, uv1, uv3, material, ref shader, surface: false);
DrawVertex(worldV1, v1, uv1, material, ref shader);
DrawVertex(worldV2, v2, uv2, material, ref shader);
DrawVertex(worldV3, v3, uv3, material, ref shader);
} else if (v1.Y == v2.Y) {
// Flat Top Triangle
DrawFlatTopTriangle (worldV1, worldV2, worldV3, v1, v2, v3, uv1, uv2, uv3, material, ref shader);
DrawLine(worldV1, worldV2, v1, v2, uv1, uv2, material, ref shader, surface: false);
DrawLine(worldV2, worldV3, v2, v3, uv2, uv3, material, ref shader, surface: false);
DrawLine(worldV1, worldV3, v1, v3, uv1, uv3, material, ref shader, surface: false);
DrawVertex(worldV1, v1, uv1, material, ref shader);
DrawVertex(worldV2, v2, uv2, material, ref shader);
DrawVertex(worldV3, v3, uv3, material, ref shader);
} else {
// General Triangle
double t = (v2.Y - v3.Y)/(v1.Y - v3.Y);
Vec3 d = new Vec3(
(v1.X) + ((v2.Y - v1.Y) / (v3.Y - v1.Y)) * (v3.X - v1.X),
v2.Y,
Lerp(v3.Z, v1.Z, t)
);
Vec3 worldD = Vec3.Lerp(worldV3, worldV1, t);
Vec2 uvd = new Vec2(
Lerp(uv3.X, uv1.X, t),
Lerp(uv3.Y, uv1.Y, t)
);
DrawFlatBottomTriangle (worldV1, worldV2, worldD, v1, v2, d, uv1, uv2, uvd, material, ref shader);
DrawFlatTopTriangle (worldV2, worldD, worldV3, v2, d, v3, uv2, uvd, uv3, material, ref shader);
DrawLine(worldV1, worldV2, v1, v2, uv1, uv2, material, ref shader, surface: false);
DrawLine(worldV2, worldV3, v2, v3, uv2, uv3, material, ref shader, surface: false);
DrawLine(worldV1, worldV3, v1, v3, uv1, uv3, material, ref shader, surface: false);
DrawVertex(worldV1, v1, uv1, material, ref shader);
DrawVertex(worldV2, v2, uv2, material, ref shader);
DrawVertex(worldV3, v3, uv3, material, ref shader);
}
}
private static double Lerp(double a, double b, double t) {
return (1 - t) * a + t * b;
}
private static Color Blend(Color backColor, Color color, double amount) {
byte r = (byte) ((color.R * amount) + backColor.R * (1 - amount));
byte g = (byte) ((color.G * amount) + backColor.G * (1 - amount));
byte b = (byte) ((color.B * amount) + backColor.B * (1 - amount));
return Color.FromArgb(r, g, b);
}
private void SetPixel(Vec3 pixel, Color c) {
var x = (int)Math.Floor(pixel.X);
var y = Size.Height - (int)Math.Floor(pixel.Y);
var depth = pixel.Z;
if (x >= 0 && y >= 0 && x < Size.Width && y < Size.Height) {
if (depth >= nearClipDistance && depth < DepthBuffer[y,x]) {
var opacity = c.A / 255.0;
// If fully opaque, set the depth buffer
DepthBuffer[y,x] = Lerp(DepthBuffer[y,x], depth, opacity);
PixelBuffer[y,x] = Blend(PixelBuffer[y,x], c, opacity);
}
}
}
private void DrawVertex(Vec3 world, Vec3 screen, Vec2 uv, Material material, ref ShaderVariables vars) {
vars.WorldPosition = world;
vars.ScreenPixel = screen;
vars.UVCoordinates = uv;
var colour = material.Vert(vars);
SetPixel(screen, colour);
}
private void DrawLine(Vec3 worldV1, Vec3 worldV2, Vec3 v1, Vec3 v2, Vec2 uv1, Vec2 uv2, Material material, ref ShaderVariables shader, bool surface) {
double dist2d = Math.Sqrt((v2.X - v1.X)*(v2.X - v1.X) + (v2.Y - v1.Y)*(v2.Y - v1.Y));
if (dist2d != 0) {
double invdist = 1/ dist2d;
for (double i = 0; i < 1; i += invdist) {
Vec3 world = Vec3.Lerp(worldV1, worldV2, i);
Vec3 pixel = Vec3.Lerp(v1, v2, i);
Vec2 uv = Vec2.Lerp(uv1, uv2, i);
shader.WorldPosition = world;
shader.ScreenPixel = pixel;
shader.UVCoordinates = uv;
var colour = surface ? material.Fragment(shader) : material.Edge(shader);
SetPixel(pixel, colour);
}
}
}
private void DrawFlatBottomTriangle(Vec3 worldV1, Vec3 worldV2, Vec3 worldV3, Vec3 a, Vec3 b, Vec3 c, Vec2 uva, Vec2 uvb, Vec2 uvc, Material img, ref ShaderVariables shader){
double invslope1 = (b.X - a.X)/(b.Y - a.Y);
double invslope2 = (c.X - a.X)/(c.Y - a.Y);
double leftX = Math.Floor(a.X);
double rightX = Math.Floor(a.X);
int startH = (int)Math.Floor(a.Y);
int endH = (int)Math.Floor(b.Y);
for(int scan = startH; scan < endH; scan++){
//Texturing only
double t = (scan - startH)/(endH - startH);
Vec2 left = Vec2.Lerp(uva, uvb, t);
Vec2 right = Vec2.Lerp(uva, uvc, t);
Vec3 worldLeft = Vec3.Lerp(worldV1, worldV2, t);
Vec3 worldRight = Vec3.Lerp(worldV1, worldV3, t);
double zL = Lerp(a.Z, b.Z, t);
double zR = Lerp(a.Z, c.Z, t);
DrawLine(worldLeft, worldRight, new Vec3(leftX,scan,zL),new Vec3(rightX,scan,zR),left,right,img, ref shader, surface: true);
leftX += invslope1;
rightX += invslope2;
}
}
private void DrawFlatTopTriangle (Vec3 worldV1, Vec3 worldV2, Vec3 worldV3, Vec3 a, Vec3 b, Vec3 c, Vec2 uva, Vec2 uvb, Vec2 uvc, Material img, ref ShaderVariables shader){
double invslope1 = (c.X - a.X) / (c.Y - a.Y);
double invslope2 = (c.X - b.X) / (c.Y - b.Y);
double leftX = Math.Floor(c.X);
double rightX = Math.Floor(c.X);
int startH = (int)Math.Floor(c.Y);
int endH = (int)Math.Floor(a.Y);
for(int scan = startH; scan > endH; scan--){
double t = (scan - startH)/(endH - startH);
Vec2 left = Vec2.Lerp(uvc, uva, t);
Vec2 right = Vec2.Lerp(uvc, uvb, t);
Vec3 worldLeft = Vec3.Lerp(worldV3, worldV1, t);
Vec3 worldRight = Vec3.Lerp(worldV3, worldV2, t);
double zL = Lerp(c.Z, a.Z, t);
double zR = Lerp(c.Z, b.Z, t);
DrawLine(worldLeft, worldRight, new Vec3(leftX,scan, zL),new Vec3(rightX,scan, zR),left,right,img, ref shader, surface: true);
leftX -= invslope1;
rightX -= invslope2;
}
}
}
}