Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Numerics;
- using System.Threading.Tasks;
- using System.Windows;
- using System.Windows.Media;
- using System.Windows.Media.Imaging;
- using AKG.Core.Extensions;
- using AKG.Core.Objects;
- using AKG.Core.VectorTransformations;
- namespace AKG.Core.Renderer
- {
- public static class Rasterizer
- {
- // Z-буфер: массив [width, height] (первый индекс – x, второй – y)
- private static float[,]? _zBuffer;
- /// <summary>
- /// Инициализирует Z-буфер заданного размера, заполняя его значениями, равными camera.ZFar.
- /// </summary>
- public static void ClearZBuffer(int width, int height, Camera camera)
- {
- _zBuffer ??= new float[width, height];
- float initDepth = camera.ZFar;
- for (int x = 0; x < width; x++)
- for (int y = 0; y < height; y++)
- _zBuffer[x, y] = initDepth;
- }
- /// <summary>
- /// Растеризует треугольники для каждой грани модели:
- /// - Для граней с 3+ вершинами выполняется фан‑трайангуляция.
- /// - Выполняется backface culling (на основе нормали) и рассчитывается интенсивность по модели Ламберта.
- /// - Каждый полученный треугольник заполняется с использованием сканирующей линии и Z‑теста.
- /// </summary>
- public static unsafe void DrawFilledTriangle(ObjModel model, WriteableBitmap wb, Color color, Camera camera)
- {
- int width = wb.PixelWidth;
- int height = wb.PixelHeight;
- // Вычисляем мировую матрицу модели
- Matrix4x4 world = Transformations.CreateWorldTransform(
- model.Scale,
- Matrix4x4.CreateFromYawPitchRoll(model.Rotation.Y, model.Rotation.X, model.Rotation.Z),
- model.Translation);
- wb.Lock();
- unsafe
- {
- int* buffer = (int*)wb.BackBuffer;
- // Обнуляем Z-буфер для данного кадра
- ClearZBuffer(width, height, camera);
- // Обработка граней параллельно
- Parallel.ForEach(model.Faces, face =>
- {
- if (face.Vertices.Count < 3)
- return;
- // Вычисляем мировые координаты первых трёх вершин для нормали
- Vector3 worldV0 = Vector4.Transform(model.OriginalVertices[face.Vertices[0].VertexIndex - 1], world).AsVector3();
- Vector3 worldV1 = Vector4.Transform(model.OriginalVertices[face.Vertices[1].VertexIndex - 1], world).AsVector3();
- Vector3 worldV2 = Vector4.Transform(model.OriginalVertices[face.Vertices[2].VertexIndex - 1], world).AsVector3();
- // Вычисляем нормаль грани
- Vector3 normal = Vector3.Normalize(Vector3.Cross(worldV1 - worldV0, worldV2 - worldV0));
- // Backface culling: если грань повернута от камеры, пропускаем её
- if (Vector3.Dot(normal, camera.Eye - worldV0) <= 0)
- return;
- // Применяем затенение по Ламберту: базовый цвет модифицируется в зависимости от интенсивности освещения
- Color shadedColor = color.ApplyLambert(normal, camera.LambertLight);
- // Фан-трайангуляция: разбиваем грань на треугольники (v0, v_i, v_{i+1})
- for (int j = 1; j < face.Vertices.Count - 1; j++)
- {
- int idx0 = face.Vertices[0].VertexIndex - 1;
- int idx1 = face.Vertices[j].VertexIndex - 1;
- int idx2 = face.Vertices[j + 1].VertexIndex - 1;
- if (idx0 < 0 || idx1 < 0 || idx2 < 0 ||
- idx0 >= model.TransformedVertices.Length ||
- idx1 >= model.TransformedVertices.Length ||
- idx2 >= model.TransformedVertices.Length)
- continue;
- // Получаем экранные координаты из TransformedVertices
- Vector3 s0 = model.TransformedVertices[idx0].AsVector3();
- Vector3 s1 = model.TransformedVertices[idx1].AsVector3();
- Vector3 s2 = model.TransformedVertices[idx2].AsVector3();
- // Запускаем алгоритм сканирующей линии для заполнения треугольника
- DrawFilledTriangleScanline(s0, s1, s2, shadedColor, buffer, width, height);
- }
- });
- }
- wb.AddDirtyRect(new Int32Rect(0, 0, wb.PixelWidth, wb.PixelHeight));
- wb.Unlock();
- }
- /// <summary>
- /// Заполняет один треугольник, заданный тремя экранными вершинами, с использованием алгоритма сканирующей линии.
- /// Для каждой scanline вычисляются точки пересечения с двумя сторонами треугольника,
- /// после чего по горизонтали выполняется линейная интерполяция глубины (z) и производится Z-тест.
- /// </summary>
- private static unsafe void DrawFilledTriangleScanline(Vector3 v0, Vector3 v1, Vector3 v2, Color color, int* buffer, int width, int height)
- {
- // Сортировка вершин по Y (от наименьшей к наибольшей)
- Vector3[] verts = new Vector3[] { v0, v1, v2 };
- Array.Sort(verts, (a, b) => a.Y.CompareTo(b.Y));
- v0 = verts[0];
- v1 = verts[1];
- v2 = verts[2];
- // Определяем границы scanline (округленные и ограниченные экраном)
- int yStart = Math.Max((int)Math.Ceiling(v0.Y), 0);
- int yEnd = Math.Min((int)Math.Floor(v2.Y), height - 1);
- // Вычисляем параметры для основной стороны (от v0 до v2)
- float invDy02 = (v2.Y - v0.Y) != 0 ? 1f / (v2.Y - v0.Y) : 0f;
- float x02Slope = (v2.X - v0.X) * invDy02;
- float z02Slope = (v2.Z - v0.Z) * invDy02;
- // Для верхней части (от v0 до v1)
- float invDy01 = (v1.Y - v0.Y) != 0 ? 1f / (v1.Y - v0.Y) : 0f;
- float x01Slope = (v1.X - v0.X) * invDy01;
- float z01Slope = (v1.Z - v0.Z) * invDy01;
- // Для нижней части (от v1 до v2)
- float invDy12 = (v2.Y - v1.Y) != 0 ? 1f / (v2.Y - v1.Y) : 0f;
- float x12Slope = (v2.X - v1.X) * invDy12;
- float z12Slope = (v2.Z - v1.Z) * invDy12;
- // Проходим по каждой scanline
- for (int y = yStart; y <= yEnd; y++)
- {
- // t – параметр для основной стороны v0->v2
- float t = (y - v0.Y);
- float xA_main = v0.X + x02Slope * t;
- float zA_main = v0.Z + z02Slope * t;
- // Определяем, какую сторону использовать для левой части:
- float xA_side, zA_side;
- if (y < v1.Y)
- {
- // Верхняя часть: используем сторону v0->v1
- float tSide = (y - v0.Y);
- xA_side = v0.X + x01Slope * tSide;
- zA_side = v0.Z + z01Slope * tSide;
- }
- else
- {
- // Нижняя часть: используем сторону v1->v2
- float tSide = (y - v1.Y);
- xA_side = v1.X + x12Slope * tSide;
- zA_side = v1.Z + z12Slope * tSide;
- }
- // Определяем левую и правую точки для данной scanline:
- float xLeft = Math.Min(xA_main, xA_side);
- float xRight = Math.Max(xA_main, xA_side);
- float zLeft = (xA_main <= xA_side) ? zA_main : zA_side;
- float zRight = (xA_main <= xA_side) ? zA_side : zA_main;
- // Ограничиваем по границам экрана
- int xStart = Math.Max((int)Math.Ceiling(xLeft), 0);
- int xEnd = Math.Min((int)Math.Floor(xRight), width - 1);
- // Заполняем scanline с интерполяцией глубины
- float span = xRight - xLeft;
- for (int x = xStart; x <= xEnd; x++)
- {
- float factor = (span != 0) ? (x - xLeft) / span : 0f;
- float depth = zLeft + factor * (zRight - zLeft);
- // Z-тест (индексация Z-буфера: [x, y])
- if (depth < _zBuffer![x, y])
- {
- _zBuffer[x, y] = depth;
- buffer[y * width + x] = color.ColorToIntBGRA();
- }
- }
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement