Приводятся C#-функции, воспроизводящие средствами OpenTK-библиотек (OpenTK, OpenTK.Graphics.OpenGL, OpenTK.Input) сферу (рис. 1 и рис. 2).
Рис. 1. Сфера с нормалями
Рис. 2. Сфера без нормалей
Сфера создается как полигональный объект с числом сегментов по X и Y соответственно nx и ny.
При выводе сферы формируются нормали к ее вершинам и рассчитываются и задаются координаты текстуры.
Рассматриваются два варианта проекта.
В первом применяется WindowsFormsApplication, в форме которого (см. рис. 1) определено поле графического вывода GLControl библиотеки OpenTK.GLControl.dll.
Ссылка на библиотеку добавляется в Solution Explorre. (Solution Explorre – References – правая кнопка мыши – Add Reference – Browse).
Сам же элемент GLControl добавляется из меню Tools - Choose Toolbox Items - вкладка .NET Framework Components - GLControl.
Если этот элемент в списке отсутствует, то его нужно найти, употребив кнопку Browse.
В этом проекте выводится каркасная модель сферы с нормалями и без них (см. рис. 1 и рис. 2).
Во втором используется консоль-проект с GameWindow, в пространство имен которого добавляются недостающие ссылки (Solution Explorre – References – правая кнопка мыши – Add Reference – Browse):
using System;
using System.Drawing; // Color
using System.Drawing.Imaging; // BitmapData
using OpenTK; // WindowState , Exit() and so on
using OpenTK.Graphics.OpenGL;
using OpenTK.Input; // KeyboardKeyEventArgs
Вид сферы в консоль-проекте определяется следующими переменными:
bool showTexture; // Выводим сферу с текстуройЕсли все перечисленные переменные равны false, то выводится чисто полигональная модель сферы.
Тонированное изображение будет сглаженным, если переменная
flat = false;В противном случае вывод полигонов будет выполняться без интерполяции цветов.
Смена варианта вывода сферы обеспечивается клавишами F7, F8 и F9:
Текстура формируется на основе показанного на рис. 3 образа.
Рис. 3. Карта Земли
Сферы с текстурой и материалами сглаженная и без сглаживания показаны на рис. 4.
Рис. 4. Сферы с текстурой и материалами в режимах Smooth и Flat
Нижеприводимая процедура обеспечивает:
// Вывод сферы, формирование координат нормалей и текстуры
// r - радиус сферы
// nx - число полигонов (сегментов) по X
// ny - число полигонов (сегментов) по Y
void sphere(double r, int nx, int ny)
{
int ix, iy;
double x, y, z, sy, cy, sy1, cy1, sx, cx, piy, pix, ay, ay1, ax, tx, ty, ty1, dnx, dny, diy;
dnx = 1.0 / (double)nx;
dny = 1.0 / (double)ny;
// Рисуем полигональную модель сферы, формируем нормали и задаем коодинаты текстуры
// Каждый полигон - это трапеция. Трапеции верхнего и нижнего слоев вырождаются в треугольники
GL.Begin(PrimitiveType.QuadStrip);
piy = Math.PI * dny;
pix = Math.PI * dnx;
for (iy = 0; iy < ny; iy++)
{
diy = (double)iy;
ay = diy * piy;
sy = Math.Sin(ay);
cy = Math.Cos(ay);
ty = diy * dny;
ay1 = ay + piy;
sy1 = Math.Sin(ay1);
cy1 = Math.Cos(ay1);
ty1 = ty + dny;
for (ix = 0; ix <= nx; ix++)
{
ax = 2.0 * ix * pix;
sx = Math.Sin(ax);
cx = Math.Cos(ax);
x = r * sy * cx;
y = r * sy * sx;
z = r * cy;
tx = (double)ix * dnx;
// Координаты нормали в текущей вершине
GL.Normal3(x, y, z); // Нормаль направлена от центра
// Координаты текстуры в текущей вершине
GL.TexCoord2(tx, ty);
GL.Vertex3(x, y, z);
x = r * sy1 * cx;
y = r * sy1 * sx;
z = r * cy1;
GL.Normal3(x, y, z);
GL.TexCoord2(tx, ty1);
GL.Vertex3(x, y, z);
}
}
GL.End();
// Показываем нормали
if (showNormals)
{
double rv = 1.15 * r;
// Толщина линии, отображающей нормаль
GL.LineWidth(2);
GL.Color3(Color.White);
GL.Begin(PrimitiveType.Lines);
piy = Math.PI * dny;
pix = Math.PI * dnx;
for (iy = 0; iy < ny; iy++)
{
diy = (double)iy;
ay = diy * piy;
sy = Math.Sin(ay);
cy = Math.Cos(ay);
ay1 = ay + piy;
sy1 = Math.Sin(ay1);
cy1 = Math.Cos(ay1);
for (ix = 0; ix <= nx; ix++)
{
ax = 2.0 * ix * pix;
sx = Math.Sin(ax);
cx = Math.Cos(ax);
x = r * sy * cx;
y = r * sy * sx;
z = r * cy;
GL.Vertex3(x, y, z);
x = rv * sy * cx;
y = rv * sy * sx;
z = rv * cy;
GL.Vertex3(x, y, z);
}
}
GL.End();
GL.LineWidth(1);
GL.Color3(Color.LightGray);
}
}
// Создает 2D-текстуру на базе растрового образа, загруженного в data
public void makeTxtr()
{
// Активизируем режим вывода текстуры
GL.Enable(EnableCap.Texture2D);
// Генерируем идентификатор текстуры
GL.GenTextures(1, out texture);
// Связываем текстуру с идентификатором
GL.BindTexture(TextureTarget.Texture2D, texture);
// Параметры текстуры
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
// Создаем текстуру
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0,
OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
}
// Модель освещенности с одним источником цвета
// Создает материал и источник света
public void makeMatAndLight()
{
float[] light_position = { 0, -30, 30, 0 }; // Координаты источника света
float[] lghtClr = { 1, 1, 1, 0 }; // Источник излучает белый цвет
float[] mtClr = { 0, 1, 0, 0 }; // Материал зеленого цвета
if (flat)
GL.ShadeModel(ShadingModel.Flat); // Вывод без интерполяции цветов
else
GL.ShadeModel(ShadingModel.Smooth); // Вывод с интерполяцией цветов
GL.Enable(EnableCap.Lighting); // Будем рассчитывать освещенность
GL.Light(LightName.Light0, LightParameter.Position, light_position);
GL.Light(LightName.Light0, LightParameter.Ambient, lghtClr); // Рассеивание
GL.Enable(EnableCap.Light0); // Включаем в уравнение освещенности источник GL_LIGHT0
// Диффузионная компонента цвета материала
GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, mtClr);
}
using System;
using System.Drawing; // Color
using System.Windows.Forms;
using OpenTK; // WindowState , Exit() and so on
using OpenTK.Graphics.OpenGL;
namespace WindowsFormsApplicationOpenTK
{
public partial class FormSphere : Form
{
bool showNormals = false; // Флаг вывода нормалей
double sphR = 20; // Радиус сферы
int nx = 16; // Число разбиений сферы по X и Y
int ny = 16;
public FormSphere()
{
InitializeComponent();
}
private void glControlSphere_Load(object sender, EventArgs e)
{
int Width = glControlSphere.Width;
int Height = glControlSphere.Height;
double crds = 1.45 * sphR;
radioButtonWire.Checked = true;
GL.Viewport(0, 0, Width, Height);
// Формируем матрицу проецирования
GL.MatrixMode(MatrixMode.Projection);
GL.LoadIdentity();
GL.Ortho(-crds, crds, -crds, crds, -crds, crds);
// Умножаем матрицу проецирования на матрицы поворотов вокруг осей Y и X
GL.Rotate(15, new Vector3d(0, 1, 0));
GL.Rotate(-55, new Vector3d(1, 0, 0));
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
glControlSphere.Invalidate();
}
private void glControlSphere_Resize(object sender, EventArgs e)
{
glControlSphere.Invalidate();
}
private void glControlSphere_Paint(object sender, PaintEventArgs e)
{
sphere(sphR, nx, ny);
}
private void radioButton_CheckedChanged(object sender, EventArgs e)
{
showNormals = !radioButtonWire.Checked;
glControlSphere.Invalidate();
}
private void numericUpDownRadius_ValueChanged(object sender, EventArgs e)
{
sphR = (double)numericUpDownRadius.Value;
glControlSphere.Invalidate();
}
private void buttonClose_Click(object sender, EventArgs e)
{
this.Close();
}
// Вывод сферы, формирование координат нормалей и текстуры
// r - радиус сферы
// nx - число полигонов (сегментов) по X
// ny - число полигонов (сегментов) по Y
void sphere(double r, int nx, int ny)
{
int ix, iy;
double x, y, z, sy, cy, sy1, cy1, sx, cx, piy, pix, ay, ay1, ax, tx, ty, ty1, dnx, dny, diy;
dnx = 1.0 / (double)nx;
dny = 1.0 / (double)ny;
// Заливка окна вывода и очистка буфера глубины
GL.ClearColor(Color.Beige);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.Color3(Color.Black);
// Рисуем полигональную модель сферы, формируем нормали и задаем коодинаты текстуры
// Каждый полигон - это трапеция. Трапеции верхнего и нижнего слоев вырождаются в треугольники
GL.Begin(PrimitiveType.QuadStrip);
piy = Math.PI * dny;
pix = Math.PI * dnx;
for (iy = 0; iy < ny; iy++)
{
diy = (double)iy;
ay = diy * piy;
sy = Math.Sin(ay);
cy = Math.Cos(ay);
ty = diy * dny;
ay1 = ay + piy;
sy1 = Math.Sin(ay1);
cy1 = Math.Cos(ay1);
ty1 = ty + dny;
for (ix = 0; ix <= nx; ix++)
{
ax = 2.0 * ix * pix;
sx = Math.Sin(ax);
cx = Math.Cos(ax);
x = r * sy * cx;
y = r * sy * sx;
z = r * cy;
tx = (double)ix * dnx;
// Координаты нормали в текущей вершине
GL.Normal3(x, y, z); // Нормаль направлена от центра
// Координаты текстуры в текущей вершине
GL.TexCoord2(tx, ty);
GL.Vertex3(x, y, z);
x = r * sy1 * cx;
y = r * sy1 * sx;
z = r * cy1;
GL.Normal3(x, y, z);
GL.TexCoord2(tx, ty1);
GL.Vertex3(x, y, z);
}
}
GL.End();
// Показываем нормали
if (showNormals)
{
double rv = 1.15 * r;
// Толщина линии, отображающей нормаль
GL.LineWidth(1.5f);
GL.Color3(Color.Red);
GL.Begin(PrimitiveType.Lines);
piy = Math.PI * dny;
pix = Math.PI * dnx;
for (iy = 0; iy < ny; iy++)
{
diy = (double)iy;
ay = diy * piy;
sy = Math.Sin(ay);
cy = Math.Cos(ay);
ay1 = ay + piy;
sy1 = Math.Sin(ay1);
cy1 = Math.Cos(ay1);
for (ix = 0; ix <= nx; ix++)
{
ax = 2.0 * ix * pix;
sx = Math.Sin(ax);
cx = Math.Cos(ax);
x = r * sy * cx;
y = r * sy * sx;
z = r * cy;
GL.Vertex3(x, y, z);
x = rv * sy * cx;
y = rv * sy * sx;
z = rv * cy;
GL.Vertex3(x, y, z);
}
}
GL.End();
GL.LineWidth(1);
GL.Color3(Color.LightGray);
}
glControlSphere.SwapBuffers();
}
}
}
using System;
using System.Drawing; // Color
using System.Drawing.Imaging; // BitmapData
using OpenTK; // WindowState , Exit() and so on
using OpenTK.Graphics.OpenGL;
using OpenTK.Input; // KeyboardKeyEventArgs
namespace SphereTest
{
public class SimpleWindow : GameWindow
{
double sphR = 2; // Радиус сферы
// Файл для текcтуры
static string path = "G:\\Шоя\\watch\\earth3.png"; // earth2 или 3 или 4.png
static Bitmap bitmap = new Bitmap(path);
BitmapData data;
int texture;
bool showTexture = false;
bool useMaterial = true;
bool showNormals = false;
bool flat = true;
// 300 * 300 - размер окна вывода
public SimpleWindow() : base(300, 300)
{
KeyDown += Keyboard_KeyDown; // Обработчик нажатий на клавиши клавиатуры
}
// Обработчик нажатий на клавиши клавиатуры
void Keyboard_KeyDown(object sender, KeyboardKeyEventArgs e)
{
if (e.Key == Key.Escape) this.Exit();
if (e.Key == Key.F11)
this.WindowState = (this.WindowState == WindowState.Fullscreen) ? WindowState.Normal : WindowState.Fullscreen;
if (e.Key == Key.F7)
if (showNormals)
{
showNormals = false;
showTexture = false;
}
else
{
showNormals = true;
showTexture = false;
}
if (e.Key == Key.F8)
if (flat)
{
flat = false;
useMaterial = true;
showTexture = false;
}
else
{
flat = true;
useMaterial = true;
showTexture = false;
}
if (e.Key == Key.F9)
if (showTexture)
{
showTexture = false;
showNormals = false;
useMaterial = true;
}
else
{
showTexture = true;
showNormals = false;
useMaterial = false;
}
}
protected override void OnLoad(EventArgs e)
{
if (showTexture)
GL.ClearColor(Color.Black);
else
GL.ClearColor(Color.DarkBlue);
Rectangle Rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
data = bitmap.LockBits(Rect, ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
double crds = 1.25 * sphR;
GL.Viewport(0, 0, Width, Height);
// Формируем матрицу проецирования
GL.MatrixMode(MatrixMode.Projection);
GL.LoadIdentity();
GL.Ortho(-crds, crds, -crds, crds, -crds, crds);
// Умножаем матрицу проецирования на матрицы поворотов вокруг осей Y и X
GL.Rotate(15, new Vector3d(0, 1, 0));
GL.Rotate(-55, new Vector3d(1, 0, 0));
}
// Вывод сферы, формирование координат нормалей и текстуры
// r - радиус сферы
// nx - число полигонов (сегментов) по X
// ny - число полигонов (сегментов) по Y
void sphere(double r, int nx, int ny)
{
int ix, iy;
double x, y, z, sy, cy, sy1, cy1, sx, cx, piy, pix, ay, ay1, ax, tx, ty, ty1, dnx, dny, diy;
dnx = 1.0 / (double)nx;
dny = 1.0 / (double)ny;
// Рисуем полигональную модель сферы, формируем нормали и задаем коодинаты текстуры
// Каждый полигон - это трапеция. Трапеции верхнего и нижнего слоев вырождаются в треугольники
GL.Begin(PrimitiveType.QuadStrip);
piy = Math.PI * dny;
pix = Math.PI * dnx;
for (iy = 0; iy < ny; iy++)
{
diy = (double)iy;
ay = diy * piy;
sy = Math.Sin(ay);
cy = Math.Cos(ay);
ty = diy * dny;
ay1 = ay + piy;
sy1 = Math.Sin(ay1);
cy1 = Math.Cos(ay1);
ty1 = ty + dny;
for (ix = 0; ix <= nx; ix++)
{
ax = 2.0 * ix * pix;
sx = Math.Sin(ax);
cx = Math.Cos(ax);
x = r * sy * cx;
y = r * sy * sx;
z = r * cy;
tx = (double)ix * dnx;
// Координаты нормали в текущей вершине
GL.Normal3(x, y, z); // Нормаль направлена от центра
// Координаты текстуры в текущей вершине
GL.TexCoord2(tx, ty);
GL.Vertex3(x, y, z);
x = r * sy1 * cx;
y = r * sy1 * sx;
z = r * cy1;
GL.Normal3(x, y, z);
GL.TexCoord2(tx, ty1);
GL.Vertex3(x, y, z);
}
}
GL.End();
// Показываем нормали
if (showNormals)
{
double rv = 1.15 * r;
// Толщина линии, отображающей нормаль
GL.LineWidth(2);
GL.Color3(Color.White);
GL.Begin(PrimitiveType.Lines);
piy = Math.PI * dny;
pix = Math.PI * dnx;
for (iy = 0; iy < ny; iy++)
{
diy = (double)iy;
ay = diy * piy;
sy = Math.Sin(ay);
cy = Math.Cos(ay);
ay1 = ay + piy;
sy1 = Math.Sin(ay1);
cy1 = Math.Cos(ay1);
for (ix = 0; ix <= nx; ix++)
{
ax = 2.0 * ix * pix;
sx = Math.Sin(ax);
cx = Math.Cos(ax);
x = r * sy * cx;
y = r * sy * sx;
z = r * cy;
GL.Vertex3(x, y, z);
x = rv * sy * cx;
y = rv * sy * sx;
z = rv * cy;
GL.Vertex3(x, y, z);
}
}
GL.End();
GL.LineWidth(1);
GL.Color3(Color.LightGray);
}
}
// Модель освещенности с одним источником цвета
// Создает материал и источник света
public void makeMatAndLight()
{
float[] light_position = { 0, -30, 30, 0 }; // Координаты источника света
float[] lghtClr = { 1, 1, 1, 0 }; // Источник излучает белый цвет
float[] mtClr = { 0, 1, 0, 0 }; // Материал зеленого цвета
if (flat)
GL.ShadeModel(ShadingModel.Flat); // Вывод без интерполяции цветов
else
GL.ShadeModel(ShadingModel.Smooth); // Вывод с интерполяцией цветов
GL.Enable(EnableCap.Lighting); // Будем рассчитывать освещенность
GL.Light(LightName.Light0, LightParameter.Position, light_position);
GL.Light(LightName.Light0, LightParameter.Ambient, lghtClr); // Рассеивание
GL.Enable(EnableCap.Light0); // Включаем в уравнение освещенности источник GL_LIGHT0
// Диффузионная компонента цвета материала
GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, mtClr);
}
// Создает 2D-текстуру на базе растрового образа, загруженного в data
public void makeTxtr()
{
// Активизируем режим вывода текстуры
GL.Enable(EnableCap.Texture2D);
// Генерируем идентификатор текстуры
GL.GenTextures(1, out texture);
// Связываем текстуру с идентификатором
GL.BindTexture(TextureTarget.Texture2D, texture);
// Параметры текстуры
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
// Создаем текстуру
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0,
OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
}
// Выполняется при воспроизведении кадра изображения
protected override void OnRenderFrame(FrameEventArgs e)
{
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.Viewport(0, 0, Width, Height);
GL.Color3(Color.LightGray);
GL.Disable(EnableCap.Lighting);
GL.Enable(EnableCap.DepthTest);
int nx, ny;
// Число сегментов в моддели сферы по X и Y
nx = 32;
ny = 32;
if (showTexture || useMaterial)
{
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
if (useMaterial && flat)
{
nx = 24;
ny = 24;
}
}
else
{
// Полигоны
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
if (showNormals) // Добавляем нормали
{
nx = 16;
ny = 16;
}
}
// Сооздаем текстуру
if (showTexture) makeTxtr();
// Сооздаем материал и источник света
if (useMaterial) makeMatAndLight();
// Выводим сферу
sphere(sphR, nx, ny);
if (showTexture) GL.Disable(EnableCap.Texture2D);
if (useMaterial) GL.Disable(EnableCap.Lighting);
this.SwapBuffers();
}
// Entry point
[STAThread]
public static void Main()
{
using (SimpleWindow sphere = new SimpleWindow())
{
sphere.Title = "Сфера";
sphere.Run(30.0, 0.0);
}
}
}
}