- •«Северный (Арктический) федеральный университет имени м.В. Ломоносова»
- •«Северный (Арктический) Федеральный Университет имени м.В. Ломоносова»
- •Постановка задачи
- •Алгоритм удаления невидимых граней
- •Определение лицевых и не лицевых граней с помощью вектора нормали
- •2.4 Закраска трехмерного объекта с использованием простой модели освещения
- •Основные переменные и их предназначение
- •Результат
-
Результат
В результате выполнения программы, получаем следующие результаты, показанные на рисунках 4-8.

Рисунок 4 – Часть стакана скрыта непрозрачным кубом

Рисунок 5 – Видимость куба сквозь прозрачный стакан

Рисунок 6 – Куб находится позади стакана

Рисунок 7 – Пример масштабирования на уменьшение сцены

Рисунок 8 – Пример масштабирования на увеличение сцены
ЗАКЛЮЧЕНИЕ
В результате выполнения поставленной задачи, получили сцену, состоящую из двух предметов (прозрачный стакан и непрозрачный куб) на статичном фоне, замощенным загруженным изображением. По заданию необходимо было доказать свойства прозрачных тел и свойства непрозрачных тел, а также проверить это опытным путем в результате их взаимодействия. В итоге получили необходимые результаты: непрозрачный предмет скрывает часть прозрачного предмета, а сквозь прозрачный предмет при любом ракурсе виден непрозрачный предмет.
Для получения данных результатов был применен ряд алгоритмов: алгоритм преобразования координат, алгоритм удаления невидимых граней, алгоритм, определяющий лицевые и не лицевые грани с помощью вектора нормали, проведенного к поверхности, а также алгоритм закраски трехмерного объекта с использованием простой модели освещения, основанной на ранее вычисленных значениях вектора нормали.
Итогом работы стало приложение «C# Alpha Blending», наглядно показывающий принцип работы альфа-смешения, а также доказывающий возможность создания трехмерной сцены посредством программирования на языке высокого уровня C#.
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
1 Кузнецова Е.А., Лукошков М.М., Майоров И.С. Методические указания к выполнению лабораторных работ по дисциплине «Компьютерная геометрия и графика». [Текст] – Издательство: « Архангельский Государственный Технический Университет», г. Архангельск, 2006 г. – стр. 36;
2 Википедия – Свободная Энциклопедия. [Электронный Ресурс] – Режим доступа: http://ru.wikipedia.org/, свободный;
3 Иванов В.П., Батраков А.С. Трехмерная компьютерная графика. [Текст] – Издательство: «Радио и связь», ISBN 5-256-01204-5, г. Москва, 1995 г. – стр. 224;
4 Роджерс Д., Адамс Дж. Математические основы машинной графики. [Текст] – Издательство: «Мир», перевод с английского, ISBN 5-03-002143-4, г. Москва, 2001 г. – стр. 604.
ПРИЛОЖЕНИЕ А
(обязательное)
Листинг основных компонентов
Листинг модуля Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
// Структура координат
public struct Point3D
{
public double x;
public double y;
public double z;
public Point3D(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}
}
public partial class Form1 : Form
{
Bitmap b;
Graphics g;
Image image;
Point3D[] Vertex = new Point3D[48]; // Массив координат вершин (x, y, z)
Point3D[] View = new Point3D[48]; // Массив видовых координат
Point[] Scrn = new Point[48]; // Массив экранных координат
Point[] Perspective = new Point[48]; // Массив перспективных координат
double[] NZ = new double[27]; // Массив нормалей для освещения
int[] Order = new int[27]; // Массив порядка отображения видимых граней
double[] D = new double[27]; // Массив, содержащий средние значения Z-граней
double Teta = Math.PI / 180; // Угол тета = 45 градусов
double Phi = Math.PI / 180; // Угол фи = 45 градусов
bool mousePress = false;
double sX = 0;
double sY = 0;
double nx, ny, nz;
int D0 = 500;
int R0 = 300;
public Form1()
{
InitializeComponent();
this.MouseWheel += new MouseEventHandler(Form1_MouseWheel);
b = new Bitmap("Background.jpg"); // Экземпляр класса Bitmap
image = Image.FromFile("Background.jpg"); // Загружаем фоновое изображение
g = Graphics.FromImage(b); // Задаем поверхность для рисования
}
// Построение объектов сцены
void ПостроитьСцену()
{
g.DrawImage(image, 0, 0);
for (int i = 0; i < 48; i++)
{
// Преобразование из мировых в видовые координаты
View[i].x = 20 * (int)Math.Round(Vertex[i].x * (-Math.Sin(Teta)) + Vertex[i].y * Math.Cos(Teta));
View[i].y = 20 * (int)Math.Round(Vertex[i].x * (-Math.Cos(Phi) * Math.Cos(Teta)) - Vertex[i].y * (Math.Cos(Phi) * Math.Sin(Teta)) + Vertex[i].z * Math.Sin(Phi));
View[i].z = -20 * (int)Math.Round(Vertex[i].x * (-Math.Sin(Phi) * Math.Cos(Teta)) - Vertex[i].y * (Math.Sin(Phi) * Math.Sin(Teta)) - Vertex[i].z * (Math.Cos(Phi)) + R0);
// Перспективное преобразование видовых координат
Perspective[i].X = pictureBox1.Width / 2 + (int)Math.Round(D0 * (View[i].x / View[i].z));
Perspective[i].Y = pictureBox1.Height / 2 + (int)Math.Round(D0 * (View[i].y / View[i].z));
if (View[i].z == 0)
{
Perspective[i].X = 0;
Perspective[i].Y = 0;
}
else
{
Perspective[i].X = (int)(Math.Round(D0 * (View[i].x / View[i].z)));
Perspective[i].Y = (int)(Math.Round(D0 * (View[i].y / View[i].z)));
}
// Получение экранных координат
Scrn[i].X = (int)Perspective[i].X + pictureBox1.Width / 2;
Scrn[i].Y = (int)Perspective[i].Y + pictureBox1.Height / 2;
}
// Находим среднее значение по координате Z каждой грани
for (int i = 0; i < 19; i++)
{
D[i] = (View[i].z + View[i + 20].z + View[i + 21].z + View[i + 1].z) / 4;
}
// Конечная грань стакана
D[19] = (View[19].z + View[39].z + View[20].z + View[0].z) / 4;
// Грань - основание стакана
for (int i = 0; i < 19; i++)
{
D[20] += View[i].z / 20;
}
// Грани ядовито-желтого куба
D[21] = (View[40].z + View[41].z + View[42].z + View[43].z) / 4;
D[22] = (View[40].z + View[44].z + View[47].z + View[43].z) / 4;
D[23] = (View[43].z + View[47].z + View[46].z + View[42].z) / 4;
D[24] = (View[42].z + View[46].z + View[45].z + View[41].z) / 4;
D[25] = (View[41].z + View[45].z + View[44].z + View[40].z) / 4;
D[26] = (View[44].z + View[45].z + View[46].z + View[47].z) / 4;
// Заполняем список граней
List<Point[]> Plane = new List<Point[]>();
for (int i = 0; i < 19; i++)
{
Plane.Add(new Point[] { Scrn[i], Scrn[20 + i], Scrn[21 + i], Scrn[1 + i] });
}
Plane.Add(new Point[] { Scrn[19], Scrn[39], Scrn[20], Scrn[0] });
Plane.Add(new Point[] {Scrn[0], Scrn[1], Scrn[2], Scrn[3], Scrn[4], Scrn[5], Scrn[6], Scrn[7], Scrn[8], Scrn[9], Scrn[10], Scrn[11], Scrn[12], Scrn[13], Scrn[14], Scrn[15], Scrn[16], Scrn[17], Scrn[18], Scrn[19]});
// Куб ядовито-желтого цвета
Plane.Add(new Point[] { Scrn[40], Scrn[41], Scrn[42], Scrn[43] });
Plane.Add(new Point[] { Scrn[40], Scrn[44], Scrn[47], Scrn[43] });
Plane.Add(new Point[] { Scrn[43], Scrn[47], Scrn[46], Scrn[42] });
Plane.Add(new Point[] { Scrn[42], Scrn[46], Scrn[45], Scrn[41] });
Plane.Add(new Point[] { Scrn[41], Scrn[45], Scrn[44], Scrn[40] });
Plane.Add(new Point[] { Scrn[44], Scrn[45], Scrn[46], Scrn[47] });
// Список цветов для граней сцены
List<Color> PlaneColor = new List<Color>()
{
Color.FromArgb(80, 208, 208, 208), // 1 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 2 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 3 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 4 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 5 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 6 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 7 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 8 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 9 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 10 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 11 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 12 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 13 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 14 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 15 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 16 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 17 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 18 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 19 Грань стакана
Color.FromArgb(80, 208, 208, 208), // 20 Грань стакана
Color.FromArgb(100, 102, 102, 102), // 21 - основание стакана
Color.FromArgb(255, 204, 255, 0), // Грань куба
Color.FromArgb(255, 204, 255, 0), // Грань куба
Color.FromArgb(255, 204, 255, 0), // Грань куба
Color.FromArgb(255, 204, 255, 0), // Грань куба
Color.FromArgb(255, 204, 255, 0), // Грань куба
Color.FromArgb(255, 204, 255, 0), // Грань куба
};
СортировкаГраней(27, 0); // Сортировка 27 граней по глубине, начиная с 0-ой грани
ВычислениеНормали(); // Вычисление нормали к плоскости по трем точкам
for (int i = 0; i < 27; i++) // Перебираем все грани
{
if (NZ[Order[i]] >= 0) // Если грань не видна
{
int A = (int)((double)PlaneColor[Order[i]].A); // Прозрачность
int R = (int)((double)PlaneColor[Order[i]].R * NZ[Order[i]]); // Красный цвет * интенсивность
int G = (int)((double)PlaneColor[Order[i]].G * NZ[Order[i]]); // Зеленый цвет * интенсивность
int B = (int)((double)PlaneColor[Order[i]].B * NZ[Order[i]]); // Синий цвет * интенсивность
Color Glass = Color.FromArgb(A, R, G, B); // Задаем цвет
g.FillPolygon(new SolidBrush(Glass), Plane[Order[i]]); // Закраска
}
if (NZ[Order[i]] <= 0) // Если грань видна
{
int A = (int)Math.Abs(((double)PlaneColor[Order[i]].A));
int R = (int)Math.Abs(((double)PlaneColor[Order[i]].R * NZ[Order[i]]));
int G = (int)Math.Abs(((double)PlaneColor[Order[i]].G * NZ[Order[i]]));
int B = (int)Math.Abs(((double)PlaneColor[Order[i]].B * NZ[Order[i]]));
Color Glass = Color.FromArgb(A, R, G, B);
g.FillPolygon(new SolidBrush(Glass), Plane[Order[i]]);
}
}
}
// Процедура нахождения нормалей к поверхностям
void ВычислениеНормали()
{
// Получение нормалей к первым 19 граням стакана
for (int i = 0; i < 19; i++)
{
nx = View[i].y * (View[i + 20].z - View[i + 21].z) + View[i + 20].y * (View[i + 21].z - View[i].z) + View[i + 21].y * (View[i].z - View[i + 20].z);
ny = View[i].z * (View[i + 20].x - View[i + 21].x) + View[i + 20].z * (View[i + 21].x - View[i].x) + View[i + 21].z * (View[i].x - View[i + 20].x);
nz = View[i].x * (View[i + 20].y - View[i + 21].y) + View[i + 20].x * (View[i + 21].y - View[i].y) + View[i + 21].x * (View[i].y - View[i + 20].y);
NZ[i] = nz / Math.Sqrt(nz * nz + ny * ny + nx * nx);
}
// Нормаль к 19-ой грани (не поддается циклу)
nx = View[19].y * (View[39].z - View[20].z) + View[39].y * (View[20].z - View[19].z) + View[20].y * (View[19].z - View[39].z);
ny = View[19].z * (View[39].x - View[20].x) + View[39].z * (View[20].x - View[19].x) + View[20].z * (View[19].x - View[39].x);
nz = View[19].x * (View[39].y - View[20].y) + View[39].x * (View[20].y - View[19].y) + View[20].x * (View[19].y - View[39].y);
NZ[19] = nz / Math.Sqrt(nz * nz + ny * ny + nx * nx);
// Нормаль к грани (основание стакана), грань имеет 10 вершин, взято 3 (0, 10, 19)
nx = View[0].y * (View[10].z - View[19].z) + View[10].y * (View[19].z - View[0].z) + View[19].y * (View[0].z - View[10].z);
ny = View[0].z * (View[10].x - View[19].x) + View[10].z * (View[19].x - View[0].x) + View[19].z * (View[0].x - View[10].x);
nz = View[0].x * (View[10].y - View[19].y) + View[10].x * (View[19].y - View[0].y) + View[19].x * (View[0].y - View[10].y);
NZ[20] = nz / Math.Sqrt(nz * nz + ny * ny + nx * nx);
// 40, 41, 42
nx = View[40].y * (View[41].z - View[42].z) + View[41].y * (View[42].z - View[40].z) + View[42].y * (View[40].z - View[41].z);
ny = View[40].z * (View[41].x - View[42].x) + View[41].z * (View[42].x - View[40].x) + View[42].z * (View[40].x - View[41].x);
nz = View[40].x * (View[41].y - View[42].y) + View[41].x * (View[42].y - View[40].y) + View[42].x * (View[40].y - View[41].y);
NZ[21] = nz / Math.Sqrt(nz * nz + ny * ny + nx * nx);
// 40, 44, 47
nx = View[40].y * (View[44].z - View[47].z) + View[44].y * (View[47].z - View[40].z) + View[47].y * (View[40].z - View[44].z);
ny = View[40].z * (View[44].x - View[47].x) + View[44].z * (View[47].x - View[40].x) + View[47].z * (View[40].x - View[44].x);
nz = View[40].x * (View[44].y - View[47].y) + View[44].x * (View[47].y - View[40].y) + View[47].x * (View[40].y - View[44].y);
NZ[22] = nz / Math.Sqrt(nz * nz + ny * ny + nx * nx);
// 43, 47, 46
nx = View[43].y * (View[47].z - View[46].z) + View[47].y * (View[46].z - View[43].z) + View[46].y * (View[43].z - View[47].z);
ny = View[43].z * (View[47].x - View[46].x) + View[47].z * (View[46].x - View[43].x) + View[46].z * (View[43].x - View[47].x);
nz = View[43].x * (View[47].y - View[46].y) + View[47].x * (View[46].y - View[43].y) + View[46].x * (View[43].y - View[47].y);
NZ[23] = nz / Math.Sqrt(nz * nz + ny * ny + nx * nx);
// 42, 46, 45
nx = View[42].y * (View[46].z - View[45].z) + View[46].y * (View[45].z - View[42].z) + View[45].y * (View[42].z - View[46].z);
ny = View[42].z * (View[46].x - View[45].x) + View[46].z * (View[45].x - View[42].x) + View[45].z * (View[42].x - View[46].x);
nz = View[42].x * (View[46].y - View[45].y) + View[46].x * (View[45].y - View[42].y) + View[45].x * (View[42].y - View[46].y);
NZ[24] = nz / Math.Sqrt(nz * nz + ny * ny + nx * nx);
// 41, 45, 44
nx = View[41].y * (View[45].z - View[44].z) + View[45].y * (View[44].z - View[41].z) + View[44].y * (View[41].z - View[45].z);
ny = View[41].z * (View[45].x - View[44].x) + View[45].z * (View[44].x - View[41].x) + View[44].z * (View[41].x - View[45].x);
nz = View[41].x * (View[45].y - View[44].y) + View[45].x * (View[44].y - View[41].y) + View[44].x * (View[41].y - View[45].y);
NZ[25] = nz / Math.Sqrt(nz * nz + ny * ny + nx * nx);
// 44, 45, 46
nx = View[44].y * (View[45].z - View[46].z) + View[45].y * (View[46].z - View[44].z) + View[46].y * (View[44].z - View[45].z);
ny = View[44].z * (View[45].x - View[46].x) + View[45].z * (View[46].x - View[44].x) + View[46].z * (View[44].x - View[45].x);
nz = View[44].x * (View[45].y - View[46].y) + View[45].x * (View[46].y - View[44].y) + View[46].x * (View[44].y - View[45].y);
NZ[26] = nz / Math.Sqrt(nz * nz + ny * ny + nx * nx);
}
// Получить изображение куба
void ПоказатьСцену()
{
ПостроитьСцену();
pictureBox1.Image = b;
}
// Метод-флаг
bool Flag(int i, int PlaneNumber, int[] Order)
{
bool flag = true; // Флаг поднят
for (int j = 0; j < PlaneNumber; j++) // Перебираем грани по их номерам
if (Order[j] == i) flag = false; // Сравниваем номер грани в массиве порядка с номером данной грани
return flag; // Возвращаем значение флага
}
// Метод сортировки граней
void СортировкаГраней(int PlanesCount, int PlaneNumber)
{
double k = 1000; // Показатель глубины
for (int i = 0; i < 27; i++) // Перебираем все грани
{
if (Flag(i, PlaneNumber, Order)) //
if (D[i] < k) // Если ср. знач. по Z < показателя глубины
{ // то...
k = D[i]; // Показатель глубины равняется конкретному значению
Order[PlaneNumber] = i; // Заполнения массива по порядку расположения граней
}
}
PlanesCount --; // Уменьшаем количество граней на 1
PlaneNumber ++; // Увеличиваем номер грани на 1
if (PlanesCount > 0) // Если количество граней > 0, вызываем рекурсию
СортировкаГраней(PlanesCount, PlaneNumber);
}
// События, обрабатываемые при загрузке формы приложения
private void Form1_Load(object sender, EventArgs e)
{
Teta = 89.549087;
Phi = 134.99817;
Vertex[0] = new Point3D(-50, -50, 0); // A
Vertex[1] = new Point3D(-48, -50, -15); // B
Vertex[2] = new Point3D(-40, -50, -30); // C
Vertex[3] = new Point3D(-28, -50, -42); // D
Vertex[4] = new Point3D(-15, -50, -48); // E
Vertex[5] = new Point3D(0, -50, -50); // F
Vertex[6] = new Point3D(15, -50, -48); // G
Vertex[7] = new Point3D(28, -50, -42); // H
Vertex[8] = new Point3D(40, -50, -30); // I
Vertex[9] = new Point3D(48, -50, -15); // J
Vertex[10] = new Point3D(50, -50, 0); // K
Vertex[11] = new Point3D(48, -50, 15); // L
Vertex[12] = new Point3D(40, -50, 30); // M
Vertex[13] = new Point3D(28, -50, 42); // N
Vertex[14] = new Point3D(15, -50, 48); // O
Vertex[15] = new Point3D(0, -50, 50); // P
Vertex[16] = new Point3D(-15, -50, 48); // R
Vertex[17] = new Point3D(-28, -50, 42); // S
Vertex[18] = new Point3D(-40, -50, 30); // T
Vertex[19] = new Point3D(-48, -50, 15); // U
// Верхняя плоскость стакана
Vertex[20] = new Point3D(-60, 100, 0); // A-1
Vertex[21] = new Point3D(-58, 100, -19); // B-1
Vertex[22] = new Point3D(-48, 100, -36); // C-1
Vertex[23] = new Point3D(-35, 100, -50); // D-1
Vertex[24] = new Point3D(-18, 100, -58); // E-1
Vertex[25] = new Point3D(0, 100, -60); // F-1
Vertex[26] = new Point3D(18, 100, -58); // G-1
Vertex[27] = new Point3D(35, 100, -50); // H-1
Vertex[28] = new Point3D(48, 100, -36); // I-1
Vertex[29] = new Point3D(58, 100, -19); // J-1
Vertex[30] = new Point3D(60, 100, 0); // K-11
Vertex[31] = new Point3D(58, 100, 19); // L-12
Vertex[32] = new Point3D(48, 100, 36); // M-13
Vertex[33] = new Point3D(35, 100, 50); // N-14
Vertex[34] = new Point3D(18, 100, 58); // O-15
Vertex[35] = new Point3D(0, 100, 60); // P-16
Vertex[36] = new Point3D(-18, 100, 58); // R-17
Vertex[37] = new Point3D(-35, 100, 50); // S-18
Vertex[38] = new Point3D(-48, 100, 36); // T-19
Vertex[39] = new Point3D(-58, 100, 19); // U-20
// Ядовито-желтый куб (а = 50)
// Нижняя грань
Vertex[40] = new Point3D(-60, -50, -65); // A
Vertex[41] = new Point3D(-60, -50, -115); // B
Vertex[42] = new Point3D(-10, -50, -115); // C
Vertex[43] = new Point3D(-10, -50, -65); // D
// Верхняя грань
Vertex[44] = new Point3D(-60, 0, -65); // A-1
Vertex[45] = new Point3D(-60, 0, -115); // B-1
Vertex[46] = new Point3D(-10, 0, -115); // C-1
Vertex[47] = new Point3D(-10, 0, -65); // D-1
ПоказатьСцену();
}
// Управление сценой с помощью клавиш-стрелок
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Up)
{
Phi = Phi + Math.PI / 128;
ПоказатьСцену();
}
if (e.KeyCode == Keys.Down)
{
Phi = Phi - Math.PI / 128;
ПоказатьСцену();
}
if (e.KeyCode == Keys.Right)
{
Teta = Teta + Math.PI / 128;
ПоказатьСцену();
}
if (e.KeyCode == Keys.Left)
{
Teta = Teta - Math.PI / 128;
ПоказатьСцену();
}
}
// Управление сценой с помощью мыши
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (mousePress)
{
if (e.X < sX)
{
Teta = Teta - Math.PI / 128;
sX = e.X;
}
if (e.X > sX)
{
Teta = Teta + Math.PI / 128;
sX = e.X;
}
if (e.Y > sY)
{
Phi = Phi - Math.PI / 128;
sY = e.Y;
}
if (e.Y < sY)
{
Phi = Phi + Math.PI / 128;
sY = e.Y;
}
ПоказатьСцену();
}
}
// Событие, вохникающее при отпускании мыши
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
mousePress = false;
}
// Событие, возникающее при нажатии мыши
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
mousePress = true;
if (e.Button == MouseButtons.Right)
{
R0 = 300;
ПоказатьСцену();
}
}
// Событие при прокрутке колеса мыши
void Form1_MouseWheel(object sender, MouseEventArgs e)
{
if ((e.Delta == 120) && (R0 != 150))
{
R0 -= 5;
ПоказатьСцену();
}
else if ((e.Delta == -120) && (R0 != 800))
{
R0 += 5;
ПоказатьСцену();
}
}
}
}
