OpenGL — Рисуем простые объекты
Точки, линии, треугольники, четырехугольники, многоугольники — простые объекты, из которых состоят любые сложные фигуры. В предыдущей главе мы рисовали сферу, конус и тор. OpenGL непосредственно не поддерживает функций для создания таких сложных объектов, т.е. таких функций нет в opengl32.dll. Эти функции есть в библиотеки утилит glu32.dll, и устроены они следующим образом. Для того чтобы нарисовать сферу функция auxSolidSphere использует функции из библиотеки glu32.dll, а те в свою очередь, используют базовую библиотеку opengl32.dll и из линий или многоугольников строят сферу. Примитивы создаются следующим образом:
glBegin(GLenum mode); // указываем, что будем рисовать glVertex[2 3 4][s i f d](. ); // первая вершина . // тут остальные вершины glVertex[2 3 4][s i f d](. ); // последняя вершина glEnd(); // закончили рисовать примитив
Сначала вы говорите, что будете рисовать — glBegin с соответствующим параметром. Возможные значения mode перечислены ниже в таблице. Далее вы указываете вершины, определяющие объекты указанного типа. Обычно вы будете задавать вершину одним из четырех способов.
glVertex2d(x,y); // две переменных типа double glVertex3d(x,y,z); // три переменных типа double glVertex2dv(array); // массив из двух переменных типа double glVertex3d(array); // массив из трех переменных типа double
И наконец, вы вызываете glEnd, чтобы указать, что вы закончили рисовать объекты типа, указанного в glBegin. Далее мы подробно разберем создание всех примитивов.
| Значение mode | Описание |
| GL_POINTS | Каждый вызов glVertex задает отдельную точку. |
| GL_LINES | Каждая пара вершин задает отрезок. |
| GL_LINE_STRIP | Рисуется ломанная. |
| GL_LINE_LOOP | Рисуется ломанная, причем ее последняя точка соединяется с первой. |
| GL_TRIANGLES | Каждые три вызова glVertex задают треугольник. |
| GL_TRIANGLE_STRIP | Рисуются треугольники с общей стороной. |
| GL_TRIANGLE_FAN | Тоже самое, но по другому правилу соединяются вершины, вряд ли понадобится. |
| GL_QUADS | Каждые четыре вызова glVertex задают четырехугольник. |
| GL_QUAD_STRIP | Четырехугольники с общей стороной. |
| GL_POLYGON | Многоугольник. |
3.2 Точки
Вы можете нарисовать столько точек, сколько вам нужно. Вызывая glVertex3d, вы устанавливает новую точку. При создании точек вы можете изменять следующие параметры. Вы можете вызывать glColor3d внутри glBegin/glEnd. Размер точки можно устанавливать с помощью функции:
void glPointSize(GLfloat size)
Режим сглаживания можно устанавливать вызовом функции
glEnable(GL_POINT_SMOOTH)
Отключается соответственно вызовом glDisable() c этим параметром. Последние функции — glPointSize и glEnable/glDisable надо вызывать вне glBegin/glEnd, иначе они будут проигнорированы. Функции glEnable/glDisable включают/выключают множество опций, но вы должны учитывать, что некоторые опции влекут за собой большие вычисления и, следовательно, изрядно затормаживают ваше приложение, поэтому без надобности не стоит их включать. Очевидно, что совершенно не к чем включать освещение, наложение текстуру и сглаживания цветов при рисовании точек. Пока вы с этими возможностями OpenGL не познакомились, поэтому запомните это на будущее.
// рисуем точки glPointSize(2); glBegin(GL_POINTS); glColor3d(1,0,0); glVertex3d(-4.5,4,0); // первая точка glColor3d(0,1,0); glVertex3d(-4,4,0); // вторая точка glColor3d(0,0,1); // третья glVertex3d(-3.5,4,0); glEnd(); glPointSize(5); glBegin(GL_POINTS); glColor3d(1,0,0); glVertex3d(-2,4,0); // первая точка glColor3d(0,1,0); glVertex3d(-1,4,0); // вторая точка glColor3d(0,0,1); // третье glVertex3d(0,4,0); glEnd(); glPointSize(10); glEnable(GL_POINT_SMOOTH); glBegin(GL_POINTS); glColor3d(1,0,0); glVertex3d(2,4,0); // первая точка glColor3d(0,1,0); glVertex3d(3,4,0); // вторая точка glColor3d(0,0,1); // третья glVertex3d(4,4,0); glEnd(); glDisable(GL_POINT_SMOOTH);
3.3 Линии
Для линий вы также можете изменять ширину, цвет, размер сглаживание. Если вы зададите разные цвета для начала и конца линии, то ее цвет будет переливающемся. OpenGL по умолчанию делает интерполяцию. Так же вы можете рисовать прерывистые линии, делается это путем наложения маски при помощи следующей функции:
void glLineStipple(GLint factor, GLushort pattern );
Второй параметр задает саму маску. Например, если его значение равно 255(0x00FF), то чтобы вычислить задаваемую маску воспользуемся калькулятором. В двоичном виде это число выглядит так: 0000000011111111, т.е. всего 16 бит. Старшие восемь установлены в ноль, значит тут линии не будет. Младшие установлены в единицу, тут будет рисоваться линия. Первый параметр определяет, сколько раз повторяется каждый бит. Скажем, если его установить равным 2, то накладываемая маска будет выглядеть так:
00000000000000001111111111111111
Далее приведен исходный текст с комментариями для наглядной демонстрации что к чему.
glLineWidth(1); // ширину линии // устанавливаем 1 glBegin(GL_LINES); glColor3d(1,0,0); // красный цвет glVertex3d(-4.5,3,0); // первая линия glVertex3d(-3,3,0); glColor3d(0,1,0); // зеленый glVertex3d(-3,3.3,0); // вторая линия glVertex3d(-4,3.4,0); glEnd(); glLineWidth(3); // ширина 3 glBegin(GL_LINE_STRIP); // см. ниже glColor3d(1,0,0); glVertex3d(-2.7,3,0); glVertex3d(-1,3,0); glColor3d(0,1,0); glVertex3d(-1.5,3.3,0); glColor3d(0,0,1); glVertex3d(-1,3.5,0); glEnd(); glLineWidth(5); glEnable(GL_LINE_SMOOTH); glEnable(GL_LINE_STIPPLE); // разрешаем рисовать // прерывистую линию glLineStipple(2,58360); // устанавливаем маску // пояснения см. ниже glBegin(GL_LINE_LOOP); glColor3d(1,0,0); glVertex3d(1,3,0); glVertex3d(4,3,0); glColor3d(0,1,0); glVertex3d(3,2.7,0); glColor3d(0,0,1); glVertex3d(2.5,3.7,0); glEnd(); glDisable(GL_LINE_SMOOTH); glDisable(GL_LINE_STIPPLE);
3.4 Треугольники
Для треугольника можно задавать те же параметры, что и для линии плюс есть еще одна функция glPolygonMode. Она устанавливает опции для отрисовки многоугольника. Первый параметр может принимать значения — GL_FRONT, GL_BACK и GL_FRONT_AND_BACK. Второй параметр указывает, как будет рисоваться многоугольник. Он принимает значения — GL_POINT(рисуются только точки), GL_LINE(рисуем линии) и GL_FILL(рисуем заполненный многоугольник). Первый параметр указывает: к лицевой, тыльной или же к обеим сторонам применяется опция, заданная вторым параметром. Треугольники можно рисовать, передав GL_TRIANGLE_STRIP или GL_TRIANGLE_FAN в glBegin. В первом случае первая, вторая и третья вершины задают первый треугольник. Вторая, третья и четвертая вершина — второй треугольник. Третья, четвертая и пятая вершина — третий треугольник и т.д. Вершины n, n+1 и n+2 определят n-ый треугольник. Во втором случае первая, вторая и третья вершина задают первый треугольник. Первая, третья и четвертая вершины задают второй треугольник и т.д. Вершины 1, n+1, n+2 определяют n-ый треугольник. Далее следует пример с комментариями.
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // см. выше glBegin(GL_TRIANGLES); glColor3d(1,0,0); // рисуем треугольник glVertex3d(-4,2,0); glVertex3d(-3,2.9,0); glVertex3d(-2,2,0); glEnd(); glLineWidth(2); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); //рисуем // проволочные треугольники glBegin(GL_TRIANGLE_STRIP); // обратите внимание на порядок // вершин glColor3d(0,1,0); glVertex3d(1,2,0); glVertex3d(0,2.9,0); glVertex3d(-1,2,0); glVertex3d(0,1.1,0); glEnd(); glEnable(GL_LINE_STIPPLE); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glBegin(GL_TRIANGLE_FAN); glColor3d(0,0,1); glVertex3d(4,2,0); glVertex3d(2.6,2.8,0); glVertex3d(2,2,0); glVertex3d(3,1.1,0); glEnd(); glDisable(GL_LINE_STIPPLE);
3.5 Четырехугольники и многоугольники
Четырехугольники рисуются вызовом функции glBegin с параметром GL_QUADS или GL_QUAD_STRIP. Для первого случая каждые четыре вершины определяют свой четырехугольник. Во втором случае рисуются связанные четырехугольники. Первая, вторая, третья и четвертая вершина определяют первый четырехугольник. Третья, четвертая, пятая и шестая вершина — второй четырехугольник и т.д. (2n-1), 2n, (2n+1) и (2n+2) вершины задают n-ый четырехугольник. Многоугольники задаются вызовом glBegin с параметром GL_POLYGON. Все вершины определяют один многоугольник. Для многоугольников можно задавать стили при помощи выше описанной функции glPolygonMode, толщину линии, толщину точек и цвет.
3.6 Упражнение: «Примитивы»
Изобразите точки, линии, треугольники, многоугольники в одном окне, как показано ниже.
Исходный файл смотрите здесь. Исполняемый файл здесь.
3.7 Упражнение: «Многогранники»
Реализуйте проволочные многогранники с помощью проволочных треугольников, многоугольников и линий.
3.8 Резюме
Ну вот, вы еще на один шаг продвинулись в изучение библиотеки OpenGL. Теперь вы имеете представление о том, как рисовать элементарные фигуры. Из примитивов вы можете составить фигуры любой сложности.
Как нарисовать сферу в opengl python
Существует набор функций для построения сфер, цилиндров и дисков. Эти функции представляют очень мощный контроль за построением трехмерных объектов. Непосредственно рисовать вы будете, используя следующие функции: gluSphere, gluCylinder, gluDisk и gluPartialDisk. В начале книги вы научились строить трехмерные объекты с помощью функций из библиотеки Auxilary Library. Функции aux[Solid/Wire]Sphere, aux[Solid/Wire]Cylinder и aux[Solid/Wire]Cone просто вызывают gluSphere и gluCylinder. Как я уже ранее говорил, в aux[Solid/Wire]Cylinder и aux[Solid/Wire]Cone фирмы Microsoft имеются баги. Здесь будет подробно рассмотрено построение сфер и цилиндров, так что потребность в aux[Solid/Wire]Cylinder и aux[Solid/Wire]Cone отпадет.
Первым параметром для gluSphere, gluCylinder, gluDisk является указатель на объект типа GLUquadricObj. Далее следуют параметры непосредственно создаваемой фигуры. Для сферы — это радиус; для цилиндра — радиус нижнего основания, радиус верхнего основания и высота; для диска — внутренний радиус, внешний радиус и для частичного диска — внутренний радиус, внешний радиус, угол, с которого начинать рисовать, длина дуги в градусах, которую рисовать. Последние два параметра у всех этих функций одинаковы. Это число разбиений вокруг оси Z и число разбиений вдоль оси Z. Как я уже говорил, все сложные объекты состоят из простых: точек, линий и многоугольников. Вы понимаете, что нарисовать/создать идеально гладкую сферу или цилиндр невозможно. Поэтому строится приближение из плоскостей. Для этого и нужно указать количество разбиений. Чем больше разбиение, тем лучше будет выглядеть ваша сфера. Однако, задавать здесь число с шестью нулями не стоит. Это лишено всякого смысла. Оптимальным, на мой взгляд, является число от 10 до 20. Чем больше объект, тем больше нужно разбиений. Число разбиений вдоль и поперек я выставляю одинаковыми. Сначала вы должны создать объект типа GLUquadricObj с помощью функции gluNewQuadric. Теперь устанавливаете свойства с помощью функции gluQuadricDrawStyle. Доступны стили: GLU_FILL — рисуется сплошной объект, GLU_LINE — проволочный объект, GLU_POINT — рисуются только точки. Рисуете то, что хотите. И не забудьте удалить созданный объект, воспользовавшись gluDeleteQuadric.
Настало время поэкспериментировать. Создайте очередное консольное приложение и отредактируйте функцию display следующим образом.
void CALLBACK display(void) < GLUquadricObj *quadObj; quadObj = gluNewQuadric(); // создаем новый объект // для создания сфер и цилиндров glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glPushMatrix(); glColor3d(1,0,0); gluQuadricDrawStyle(quadObj, GLU_FILL); // устанавливаем // стиль: сплошной gluSphere(quadObj, 0.5, 10, 10); // рисуем сферу // радиусом 0.5 glTranslated(-2,0,0); // сдвигаемся влево glRotated(45, 1,0,0); // поворачиваем glColor3d(0,1,0); gluQuadricDrawStyle(quadObj, GLU_LINE); // устанавливаем // стиль: проволочный gluCylinder(quadObj, 0.5, 0.75, 1, 15, 15); glPopMatrix(); gluDeleteQuadric(quadObj); auxSwapBuffers(); >
4.2 Упражнение: «Сфера, цилиндр и диски»
Доработайте код, приведенный выше, чтобы в первой строке показывались три сферы. Цвет и стили(GLU_POINT, GLU_LINE и GLU_FILL) должны быть разными. В следующих трех строках должны быть цилиндры, диски и частичные диски.

Исходный файл смотрите здесь. Исполняемый файл здесь.
4.3 Интерполяция цветов
Когда вы создаете многоугольник, вы можете задать цвет для каждой его вершины. Если разрешено сглаживание цветов, то многоугольник будет переливаться. Поясню на примере. Режим сглаживания по умолчанию разрешен. Он переключается функцией glShadeModel с аргументами GL_FLAT и GL_SMOOTH. GL_FLAT запрещает сглаживание. На мой взгляд, сглаживание редко нужно. Вещь красивая, но бесполезная. Я в своих неучебных программах этот режим никогда не использовал. Поэтому советую его отключать, особенно, при программировании анимированного приложения. Создайте очередной проект. В функцию main добавьте
glShadeModel(GL_SMOOTH);
Функцию display отредактируйте следующим образом:
void CALLBACK display(void)

Исходный файл смотрите здесь. Исполняемый файл здесь.
4.4 Прозрачность
С помощью четвертого компонента цвета можно получать различные эффекты наложения объектов друг на друга, наложения цветов и т.п. Здесь я расскажу о наиболее нужном и распространенном эффекте — прозрачности объектов. Для того, чтобы разрешить обрабатывать четвертый компонент цвета вы должны вызвать функцию glEnable с аргументом GL_ALPHA_TEST. Для получения требуемого эффекта прозрачности нужно разрешить наложение цветов — glEnable(GL_BLEND). И установить алгоритм, по которому будут смешиваться два цвета — glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA). Учтите, что эти режимы очень затормаживают вывод изображения, поэтому я не рекомендую устанавливать эти режимы глобально, для воспроизведения всех объектов. Выделите из ваших объектов те, которым требуется этот режим, включайте и отключайте его своевременно. Именно поэтому, эти тесты я разместил в функции display. Создайте новый проект с именем transperence и отредактируйте функцию display, как показано ниже.
void CALLBACK display(void)
- Создаем сферу.
- Выполняется тест глубины успешно, т.к. цилиндра пока нет.
- В буфере рисуется сфера.
- Создаем цилиндр.
- Выполняется тест глубины успешно, т.к. стенка цилиндра ближе, чем сфера.
- В буфере рисуется цилиндр, он закрывает сферу с учетом прозрачности.
- Создаем цилиндр.
- Выполняется тест глубины успешно.
- В буфере рисуется цилиндр.
- Создаем сферу.
- Выполняется тест глубины аварийно, т.к. сфера создается за цилиндром.
- В буфере ничего не рисуется.
В последнем случае вы увидите один цилиндр.

Исходный файл смотрите здесь. Исполняемый файл здесь.
4.5 Упражнение: «Снег»
Ранее рассматривалось приложение «Снеговик». Анимацию создавать вы тоже уже научились. Добавьте косой снег, только сделайте снеговика прозрачным, чтобы снежинки как бы пролетали сквозь него.
4.6 Плоскости отсечения
Если вам требуется нарисовать сферу или любой другой объект урезанным, то плоскости отсечения это то, что вам надо. Плоскостей отсечения может быть шесть штук. По умолчанию они все запрещены. Плоскость отсечения включается командой glEnable(GL_CLIP_PLANE0). Ноль на конце GL_CLIP_PLANE означает нулевую плоскость; можно указать один, два, три и т.д. Сама плоскость устанавливается функцией glClipPlane. Первый аргумент этой функции — это плоскость, второй — это массив из четырех элементов, в котором хранятся коэффициенты уравнения плоскости. Для тех, кто не помнит уравнения плоскости в трехмерном пространстве, напоминаю: A*x+B*y+C*z+D = 0. Вот эти самые A,B,C,D и являются теми четырьмя коэффициентами. Создайте новый проект с именем ClipPlane и отредактируйте функцию display, как показано ниже.
void CALLBACK display(void) < GLdouble equation[4] = ; glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glEnable(GL_CLIP_PLANE0); glClipPlane(GL_CLIP_PLANE0, equation); glColor3d(1,0,0); auxSolidSphere( 3 ); glDisable(GL_CLIP_PLANE0); auxSwapBuffers(); >

Исходный файл смотрите здесь. Исполняемый файл здесь.
4.7 Упражнение: «Три плоскости»
Добавьте еще две плоскости. Расположите их так, чтобы они отсекали двумерный угол [(p)/2].
4.8 Трафарет I
Зачем это может понадобиться, я не очень представляю. Рисовать прерывистую линию вы уже умеете. Трафарет — это то же самое, только для многоугольников. Вы разрешаете тест трафарета командой glEnable(GL_POLYGON_STIPPLE). Аналогично, как и в случае с линями, нужно загружать массив, который задает битовую маску. Размер трафарета строго оговорен — 32х32 пикселя. 32х32 равняется 1024. 1024 делим на восемь бит, получаем 128 байт, т.е., чтобы закодировать трафарет, нужен массив из 128-ми байт. Тут уже, как в случае с линиями, калькулятором не посчитаешь. Поэтому я написал утилиту — pcx_2bpp, которая конвертирует pcx-файл в формате два бита на пиксель в массив на языке Си. Запускается она так:
>pcx_2bpp filename.pcx >array.c
Утилиту можете взять на моем сервере в архиве с исходными текстами программ(см. приложения А). Знак «больше» означает перенаправление стандартного вывода в файл array.c. Эта утилита абсолютно переносима. Нарисовав в каком-нибудь графическом пакете рисунок в формате 32х32х2bpp, сохраните его и обработайте моей утилитой. Получите массив, который вставьте в свою программу. В функции main добавьте следующий код:
glEnable(GL_POLYGON_STIPPLE); glPolygonStipple(array);
4.9 Упражнение: «Совершенствуем Arcanoid»
Не знаю, правильно ли сказал. Мне кажется, что уродуем, а не совершенствуем. В общем, наложите трафарет на подставку и шар. Когда увидите трафарет на шаре, то поймете, что «Уродуем».

Исходный файл смотрите здесь. Исполняемый файл здесь.
4.10 Туман
Еще одна мелочь, которую мы рассмотрим — это включение тумана. За основу возьмем нашего снеговика. Надо сказать, что ведет туман себя довольно странно. Если его цвет установить белым, то при увеличении плотности тумана снеговик становится полностью белым, причем даже те его места, которые изначально были черными, т.к. не освещались. Когда я установил цвет тумана темно-серым — (0.25, 0.25, 0.25), то при увеличении плотности не освещенные его части как им и положено оставались черными, а видимые теряли яркость. Я скорее охарактеризовал бы средство тумана, как средство для изменения яркости, потому как на реальный туман, это к сожалению, не похоже. Итак, создайте новый проект с именем fog, скопируйте snowman.c в fog.c. Для того чтобы включить туман и поэкспериментировать с его влиянием на изображение мы добавим две функции, которые будут обрабатывать события от стрелок вверх\вниз на клавиатуре. Код этих функций выглядит так, включите его сразу после включения заголовочных файлов:
float density; void CALLBACK Key_UP(void ) < density+=(float)0.1; glFogf(GL_FOG_DENSITY, density); >void CALLBACK Key_DOWN(void )
Я также ввел глобальную переменную density, в ней хранится плотность тумана. При нажатие стрелок вверх\вниз будет вызываться соответствующая функция и будет изменено значение тумана. Снеговик у нас все время перерисовывается, поэтому при следующей отрисовке кадра значение тумана обновится. Теперь надо отредактировать функцию main, добавленный код выделен серым фоном:
void main() < float pos[4] = ; float dir[3] = ; float fogcolor[4] = ; // цвет тумана auxInitPosition( 50, 10, 400, 400); auxInitDisplayMode( AUX_RGB | AUX_DEPTH | AUX_DOUBLE ); auxInitWindow( "Snowman" ); auxIdleFunc(display); auxReshapeFunc(resize); auxKeyFunc(AUX_UP, Key_UP); // устанавливаем обработчик auxKeyFunc(AUX_DOWN, Key_DOWN); // стрелок вверх/вниз glEnable(GL_FOG); // разрешаем туман glGetFloatv(GL_FOG_DENSITY, &density); // получаем значение плотности glFogfv(GL_FOG_COLOR, fogcolor); // устанавливаем цвет тумана glEnable(GL_ALPHA_TEST); glEnable(GL_DEPTH_TEST); glEnable(GL_COLOR_MATERIAL); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glLightfv(GL_LIGHT0, GL_POSITION, pos); glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, dir); auxMainLoop(display); >
Я полагаю здесь все ясно, прокомментирую тут только одну функцию:
glGetFloatv(GL_FOG_DENSITY, &density);
С помощью этой функции я получаю значение плотности тумана, установленное по умолчанию. Первый аргумент этой функции указывает OpenGL значение какого параметра мы хотим получить. Второй аргумент — это адрес в памяти куда будет записано значение данного параметра.
4.11 Логические операции
Логические операции позволяют вам складывать цвет фрагмента находящегося в буфере с цветом, который туда поступает. Этот режим разрешается и запрещается вызовом функций glEnable\glDisable с параметром GL_COLOR_LOGIC_OP. Правило, по которому будут складываться цвета задается функцией glLogicOp. У нее один параметр — одна из следующих констант определенных в файле gl.h.
/* LogicOp */ #define GL_CLEAR 0x1500 #define GL_AND 0x1501 #define GL_AND_REVERSE 0x1502 #define GL_COPY 0x1503 #define GL_AND_INVERTED 0x1504 #define GL_NOOP 0x1505 #define GL_XOR 0x1506 #define GL_OR 0x1507 #define GL_NOR 0x1508 #define GL_EQUIV 0x1509 #define GL_INVERT 0x150A #define GL_OR_REVERSE 0x150B #define GL_COPY_INVERTED 0x150C #define GL_OR_INVERTED 0x150D #define GL_NAND 0x150E #define GL_SET 0x150F
Мне не удалось найти разумного применения этой функции поэтому я приведу здесь лишь текст моей программы с комментариями, которая позволяет перебрать и посмотреть действие всех логических операций. Как и в предыдущем пункте у меня определена глобальная переменная и две функции реагирующие на нажатие стрелок на клавиатуре.
/* * (c) Copyright 1995-2000, Igor Tarasov * FidoNet: 2:5020/370.2 620.20 1103.5 * email: igor@itsoft.miem.edu.ru itarasov@rtuis.miem.edu.ru * Phone: (095)916-89-51 916-89-63 */ #include #include #include #include int logicop = GL_CLEAR; void CALLBACK Key_UP(void ) < if(logicopvoid CALLBACK Key_DOWN(void ) < if(logicop>GL_CLEAR) logicop--; > void CALLBACK resize(int width,int height) < glViewport(0,0,width,height); glMatrixMode( GL_PROJECTION ); glLoadIdentity(); glOrtho(-5,5, -5,5, 2,12); gluLookAt( 0,0,5, 0,0,0, 0,1,0 ); glMatrixMode( GL_MODELVIEW ); >void CALLBACK display(void) < glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glColor4d(1,0,0, 1); auxSolidSphere( 1 ); glEnable(GL_COLOR_LOGIC_OP); glLogicOp(logicop); glColor4d(0,1,0, 0.7); auxSolidCylinder(2,3); glDisable(GL_COLOR_LOGIC_OP); auxSwapBuffers(); >void main() < float pos[4] = ; float dir[3] = ; GLfloat mat_specular[] = < 1,1,1,1>; auxInitPosition( 50, 10, 400, 400); auxInitDisplayMode( AUX_RGB | AUX_DEPTH | AUX_DOUBLE ); auxInitWindow( "Glaux Template" ); auxIdleFunc(display); auxReshapeFunc(resize); auxKeyFunc(AUX_UP, Key_UP); auxKeyFunc(AUX_DOWN, Key_DOWN); glEnable(GL_DEPTH_TEST); glEnable(GL_COLOR_MATERIAL); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); // glLightfv(GL_LIGHT0, GL_POSITION, pos); // glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, dir); glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialf(GL_FRONT, GL_SHININESS, 128); auxMainLoop(display); >
4.12 Трафарет II
Ранее вы познакомились с наложением трафарета состоящего из массива бит 32х32 точки. Недостатки такого трафарета в его небольшой площади и то, что каждые его элемент является битом, т.е. у такого трафарета всего две зоны(рисуем и не рисуем). Далее вы познакомитесь с полноценным трафаретом.
В OpenGL есть буфер трафарета, который предоставляет огромные возможности для творчества. Тест трафарета на мой взгляд бесспорно полезная вещь. С ее помощью реализуются самые разнообразные эффекты, начиная от простого вырезания одной фигуры из другой до реализации теней, отражений и прочих нетривиальных функций, требующих от вас уже не только знакомство с библиотекой OpenGL, но и понимания алгоритмов машинной графики. Здесь мы рассмотрим самое простое применение буфера трафарета. Пусть у нас есть два объекта на экране — сфера и куб, и пусть сфера находится внутри куба и немного из него выходит за его грани.

Мы поставим себе задачу изобразить три рисунка: куб минус сфера, сфера минус куб, пересечение куба и сферы. Как вы понимаете объединение куба и сферы выводится без всякого буфера трафарете по умолчанию. Собственно говоря, оно(объединение) изображено на рисунке 4.12.1. Теперь о том, что такое трафарет и как им пользоваться. Трафарет это двумерный массив целых переменных(тип int). Каждому пикселю в окне соответствует один элемент массива. Использование буфера трафарета происходит в два этапа. Сначала вы его заполняете, потом основываясь на его содержимом рисуете ваши объекты. Буфер трафарета заполняется следующим образом. Вы делите окно вывода на зоны и каждой зоне присваиваете свое значение. Например, для рисунка 4.12.1 область, где нет ничего будет заполнена нулями, область, где выведен куб заполнена единицами и область, где видна сфера двойками. Обратите внимание, что буфер трафарета — это двумерный массив, а не трехмерный. Теперь вы легко можете представить себе эти области, они собственно и изображены на приведенном рисунке. Также заметьте, что цвет здесь роли не играет. Я бы мог вывести все черным цветом, а буфер трафарета заполнился в зависимости от геометрических размеров фигур и от их пересечения. Далее мы рассмотрим функции библиотеки OpenGL для работы с трафаретом. Тест трафарета разрешается при помощи функций glEnable\glDisable с параметром GL_STENCIL_TEST. Очищается буфер трафарета при помощи функции glClear с параметром GL_STENCIL_BUFFER_BIT. Заполнение буфера трафарета происходит при помощи следующих двух функций:
void glStencilFunc(GLenum func, GLint ref, GLuint mask) void glStencilOp(GLenum fail, GLenum zfail, GLenum zpass)
Первая функция задает правило, по которому будет определяться пройден тест трафарета или нет. Переменная func может принимать одно из следующих значений:
| GL_NEVER | Не проходит |
| GL_LESS | Проходит if ( ref & mask) < ( stencil & mask) |
| GL_LEQUAL | Проходит if ( ref & mask) ? ( stencil & mask) |
| GL_GREATER | Проходит if ( ref & mask) > ( stencil & mask) |
| GL_GEQUAL | Проходит if ( ref & mask) ? ( stencil & mask) |
| GL_EQUAL | Проходит if ( ref & mask) = ( stencil & mask) |
| GL_NOTEQUAL | Проходит if ( ref & mask) ? ( stencil & mask) |
| GL_ALWAYS | Всегда проходит |
Если тест трафарета не пройден, то фрагмент (пиксели) фигуры не прорисовываются в данном месте, т.е. они не попадают в буфер кадра. Если тест пройден, то фигура рисуется. Вторая функция позволяет задать, как будет инициализироваться буфер трафарета. Параметры fail, zfail и zpass могут принимать одно из следующих значений:
| GL_KEEP | Сохранить текущее значение в буфере трафарета |
| GL_ZERO | Установить значение буфера трафарета в ноль |
| GL_REPLACE | Заменить значение буфера трафарета на значение переменной ref, заданной функцией glStencilOp |
| GL_INCR | Увеличить на единицу |
| GL_DECR | Уменьшить на единицу |
| GL_INVERT | Поразрядно инвертировать |
В случае непрохождения теста трафарета над фрагментом выполняется действие определенной параметром fail. Например, если мы хотим заполнить область трафарета, где рисуется куб единицами, то можно использовать следующий код:
glStencilFunc(GL_NEVER, 1, 0); // значение mask не используется glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP); auxSolidCube(2.5);
Объясняю подробней, первая функция говорит о том, что тест трафарета всегда проходит неудачно. Вторая функция задает, что в случае неудачного теста трафарета заменить значение хранящееся в буфере трафарета на значение переменной ref, а его мы задали равным единице. В результате, на экране ничего не нарисуется т.к. тест трафарета завершался неудачно, но в буфере трафарета мы получили проекцию куба из единичек, т.е. буфер трафарета заполнен не только нулями. Теперь мы хотим заполнить двойками область, где прорисовывается сфера. Здесь мы уже должны учитывать буфер глубины, иначе мы заполним двойками всю область, где у нас рисуется сфера. Для того чтобы учитывать буфер глубины тест трафарета должен завершиться положительно. Третий параметр zpass функции glStencilOp как раз указывает, что делать, если тест трафарета прошел, а тест глубины нет. Поэтому код выглядит так:
glStencilFunc(GL_ALWAYS, 2, 0); // значение mask не используется glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); auxSolidSphere(1.5);
В результате получаем буфер трафарета заполненный нулями, где ничего не было, единицами, где виден куб и двойками, где видна сфера. В последнем примере тест трафарета прошел успешно, поэтому на экране была нарисована сфера. Но это нам не мешает мы очистим буфер глубины и буфер цвета, но не буфер трафарета.
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Теперь дело техники нарисовать тот же самый куб без сферы. Надо установить, что тест трафарета проходит, если значение находящееся в буфере трафарета совпадает со значением второго параметра функции glStencilFunc. Как вы помните куб в буфере трафарета имел значение единицы. Поэтому получаем:
glStencilFunc(GL_EQUAL, 1, 255); // куб рисуется только там, где // в буфере трафарета лежат единицы glColor3d(1,1,1); auxSolidCube(2.5);
Создайте новый проект с именем stencil. Скопируйте glaux.c в stencil.c и отредактируйте функцию display следующим образом:
void CALLBACK display(void) < // очищаем все буферы glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // разрешаем тест трафарета glEnable(GL_STENCIL_TEST); // рисуем куб и заполняем буфер трафарета единицами // в том месте, где рисуется куб // тут у меня немного по другому, чем я выше было разобрано, // но действие выполняется аналогичное glStencilFunc(GL_ALWAYS, 1, 0); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); auxSolidCube(2.5); // заполняем буфер трафарета двойками // в том месте, где сфера закрывает куб glStencilFunc(GL_ALWAYS, 2, 0); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); auxSolidSphere(1.5); // очищаем буферы цвета и глубины glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glStencilFunc(GL_EQUAL, 1, 255); glColor3d(1,1,1); auxSolidCube(2.5); // вращаем сцену glRotated(3, 1,0,0); glRotated(5, 0,1,0); glRotated(7, 0,0,1); auxSwapBuffers();>

Теперь давайте немного приукрасим нашу программу. Давайте внутри куба нарисуем красный шар, который будут опоясывать зеленый и синий тор. Делается это следующим образом. Как и в предыдущей программе вы составляете буфер трафарета, после чего очищаете буфер глубины и цвета, запрещаете тест трафарета, т.к. нам надо просто вывести фигуры без трафарета, выводите шар с торами, и, наконец, включает тест трафарета и выводите куб с отверстиями. Вот код функции display:
void CALLBACK display(void) < // очищаем все буферы glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // разрешаем тест трафарета glEnable(GL_STENCIL_TEST); // рисуем куб и заполняем буфер трафарета единицами // в том месте, где рисуется куб // тут у меня немного по другому, чем я выше было разобрано, // но действие выполняется аналогичное glStencilFunc(GL_ALWAYS, 1, 0); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); auxSolidCube(2.5); // заполняем буфер трафарета двойками // в том месте, где сфера закрывает куб glStencilFunc(GL_ALWAYS, 2, 0); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); auxSolidSphere(1.5); // очищаем буферы цвета и глубины glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // запрещаем тест трафарета и рисуем красную сферу glDisable(GL_STENCIL_TEST); glColor3d(1,0,0); auxSolidSphere(0.5); //синий тор glColor3d(0,0,1); auxSolidTorus(0.15, 0.6); // зеленый тор, повернутый на 90 градусов относительно синего glColor3d(0,1,0); glPushMatrix(); glRotated(90, 1,0,0); auxSolidTorus(0.15, 0.6); glPopMatrix(); //снова разрешаем тест трафарета glEnable(GL_STENCIL_TEST); glStencilFunc(GL_EQUAL, 1, 255); glColor3d(1,1,1); auxSolidCube(2.5); // вращаем сцену glRotated(3, 1,0,0); glRotated(5, 0,1,0); glRotated(7, 0,0,1); auxSwapBuffers();>

Исходный файл смотрите здесь. Исполняемый файл здесь.
4.13 Упражнение «сфера минус куб»
Напишите программу, в которой будет крутиться сфера минус куб.
Нарисовать 1/8 сферы
Написать код, чтобы нарисовать сферы различных форм
Господа специалисты помогите написать код "Чайнику" в матлабе чтобы нарисовать сферы различных.
Найти высоту h, соответствующую положению равновесия шарика относительно сферы, и реакцию сферы N
Вот задача — сфера радиусом R = 2 м равномерно вращается вокруг оси симметрии с частотой (n) 30.
Нарисовать флаг, нарисовать график y=ctg x Нарисовать анимацию
Дело таково у меня сейчас практика, где я очень не успиваю сделать все свои задания. Большая.
Сферы применения с++
Всем привет) Подскажите пожалуйста где можно применять c++?Веб?Низкоуровневые задачи? Заранее.
3804 / 2358 / 414
Регистрация: 09.09.2017
Сообщений: 10,273
Примерно так же, как полную сферу, но развертка по оси не 360, а 45 градусов — получится «ломтик». Либо по другой оси на 180, а 22,5 градуса — получится «шапка». Либо по обеим половина — получится выпуклый равносторонний прямоугольный треугольник. А можно просто отрисовывать только каждый восьмой полигон — получится «ситечко».
Если подробнее: средствами OpenGL можно нарисовать сферу по отдельным «поясам» изменяющегося диаметра и с разными z-координатами. Нарисуйте на бумажке разбиение плоского кольца на треугольники — будет нагляднее.
Регистрация: 06.06.2012
Сообщений: 46
А есть пример кода с регулировкой развертки?
5146 / 2759 / 464
Регистрация: 05.10.2013
Сообщений: 7,281
Записей в блоге: 145
niksn13, покажите ваш код, которые рисует треугольник в 3D пространстве. На самом деле, это чисто математическая задача. Если вы на бумаге можете решить эту задачу математически, то перенести в 3D не составит особого труда, если вы знаете, как рисовать объекты в 3D. Проблема может быть если у вас нет никакого опыта рисования с помощью OpenGL, тогда нужно решать сначала эту проблему. Основы OpenGL можно начать изучать здесь: https://habr.com/ru/post/310790/
3804 / 2358 / 414
Регистрация: 09.09.2017
Сообщений: 10,273
Сообщение от niksn13 
А есть пример кода с регулировкой развертки?
Покажите сначала ваш код для полной сферы
1471 / 826 / 140
Регистрация: 12.10.2013
Сообщений: 5,456
1)Координаты треугольникам задавать через параметрическое уравнение сферы
“Параметрическое уравнение сферы с центром в точке”
https://ru.wikipedia.org/wiki/Сфера
2)можно нарисовать плоский треугольник из N треугольников, задать центр и радиус сферы затем нормализовать вектора из (центр сферы и точка треугольника) и умножить на радиус сферы, получим треугольники повторят кусок сферы.
3)можно воспользоваться что уже 2019 год и таких задач решали N раз и гуглить
half sphere opengl
https://stackoverflow.com/ques. -glusphere
4)можно схалтурить и нарисовать всю сферу отрезав кусок пирамидой видимости.
Регистрация: 06.06.2012
Сообщений: 46
Спасибо ребят, уже сделал, код кидать смысла не вижу, реализовал через старый добрый шарп, в связке с gl, привычнее
5146 / 2759 / 464
Регистрация: 05.10.2013
Сообщений: 7,281
Записей в блоге: 145
Сообщение от niksn13 
код кидать смысла не вижу, реализовал через старый добрый шарп, в связке с gl, привычнее
Есть смысл. Без кода данная тема будет бесполезная потеря времени. На OpenGL нет никакой разницы на каком языке написана программа. Я писал на Си, C++, Java и даже Python. А теперь пишу на C# и TypeScript (WebGL). Так что любой OpenGL-программист поймёт код на любом языке, даже если он не использовал этот язык никогда. Для С++ программиста не составит труда перевести OpenGL-код из C#, даже если он никогда не писал на C#. Может вам кто-то что-то подскажет полезного по коду. Лучше дополнительно архив с проектом прикрепить, чтобы легко было скачать и запустить в VS. С помощью чего делали связку C# с OpenGL? OpenTK, Tao или что-то другое?
Добавлено через 18 минут
Раз вы используете C#, то скину вам очень полезную информацию. Выше я кинул ссылку на знаменитые уроки LearnOpenGL в переводе на русский. Если перевод данных уроков на C#: LearnOpenTK. Правда, переведено только две главы и текст на английском, но если вы изучаете английский, то проблемы нет. Тогда далее вы сможете изучать оригинальные уроки: https://learnopengl.com/, переводя их на C#/OpenTK (на территории РФ этот сайт недоступен, нужно ставить VPN, например, плагин для браузеров Browsec)
3804 / 2358 / 414
Регистрация: 09.09.2017
Сообщений: 10,273
Сообщение от niksn13 
Спасибо ребят, уже сделал
Интереса ради — какую именно четверть отрисовываете?
Регистрация: 06.06.2012
Сообщений: 46

Сообщение было отмечено 8Observer8 как решение
Решение
Сообщение от COKPOWEHEU 
Интереса ради — какую именно четверть отрисовываете?
Отрисовываю «ломтик», но не имеет значения, ограничения цикла меняются и отрисовывается любая часть
Сообщение от 8Observer8 
С помощью чего делали связку C# с OpenGL?
Изменил готовый проект домика, под свои нужды, архив прикрепил
Если нужно само построение:
Кликните здесь для просмотра всего текста
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
for (alpha = 0.0; alpha GL_PI; alpha += PI / gradation) { gl.Begin(OpenGL.GL_TRIANGLES); for (beta = 0.0; beta (2.01 * GL_PI)/8; beta += PI / gradation) // деление на восемь позволяет нарисовать 1/8 { x = (radius * Math.Cos(beta) * Math.Sin(alpha))/20; y = (radius * Math.Sin(beta) * Math.Sin(alpha)) / 20; z = (radius * Math.Cos(alpha)) / 20; gl.Color(0, 0, 0); gl.Vertex(x, y, z); x = (radius * Math.Cos(beta) * Math.Sin(alpha + PI / gradation) )/ 20; y = (radius * Math.Sin(beta) * Math.Sin(alpha + PI / gradation) )/ 20; z = (radius * Math.Cos(alpha + PI / gradation)) / 20; gl.Vertex(x, y, z); } gl.End(); }
Вложения
| 8 сферы.rar (1.36 Мб, 24 просмотров) |
Регистрация: 06.06.2012
Сообщений: 46
Сообщение от 8Observer8 
Раз вы используете C#, то скину вам очень полезную информацию
Спасибо, однако графикой в основном не занимаюсь, материал интересный, можно изучить, однако пока на это не хватает времени, много других забот и других задач
3804 / 2358 / 414
Регистрация: 09.09.2017
Сообщений: 10,273
Сообщение от niksn13 
gl.Color(0, 0, 0);
зачем оно в цикле?
Сообщение от niksn13 
x = (radius * Math.Cos(beta) * Math.Sin(alpha))/20;
y = (radius * Math.Sin(beta) * Math.Sin(alpha)) / 20;
z = (radius * Math.Cos(alpha)) / 20;
Вместо деления на 20 каждой координаты лучше ввести константу, на которую умножать координаты:
1 2 3
x = (radius * Math.Cos(beta) * Math.Sin(alpha)) * scale; y = (radius * Math.Sin(beta) * Math.Sin(alpha)) * scale; z = (radius * Math.Cos(alpha)) * scale;
А еще лучше — glScale
.
Не совсем понятно зачем вы во внутреннем цикле считаете две точки, сдвинутые по (x,y), когда можно рисовать отрезками радиуса:
1 2 3 4 5 6
x = (radius * Math.Cos(beta) * Math.Sin(alpha)) * scale; y = (radius * Math.Sin(beta) * Math.Sin(alpha)) * scale; z = (radius * Math.Cos(alpha)) * scale; gl.Vertex(x, y, z); z = (radius * Math.Cos(alpha + PI / gradation)) * scale; gl.Vertex(x, y, z);
Регистрация: 06.06.2012
Сообщений: 46
COKPOWEHEU, как говорится, работает, не трогай��
Даже не смею предположить что мой код, будет хотя бы смахивать на хороший, код написанный в торопях на коленке в принципе не может быть таковым
5146 / 2759 / 464
Регистрация: 05.10.2013
Сообщений: 7,281
Записей в блоге: 145
А я читернул. Создал стандартную сферу Blender 2.8, обрезал всё лишнее (как Микеланджело писал), экспортировал 1/8 в Collada (.dae) формат и загрузил в программу на OpenGL. Прикрепил для сравнения ваш и мой результат.
Мне очень понравилось, как вы быстро решили задачу. Далеко не все могут довести начатое дело до конца. Первый раз нужно делать, как получится. Главное — быстро и до конца. А потом уже можно рефакторить по мере получения навыков и новых знаний. Надеюсь, вы не бросите изучать геометрию и линейную алгебру через практику на OpenGL.
Регистрация: 06.06.2012
Сообщений: 46
8Observer8, интересный подход, но ещё раз упомяну свой ничтожный опыт в графике ( думаю пока), есть куда расти и развиваться, алгоритм совсем не проблема, просто с OpenGL опыта маловато, вот и решил узнать совета более опытных, по реализации)
3804 / 2358 / 414
Регистрация: 09.09.2017
Сообщений: 10,273
Сообщение от 8Observer8 
А я читернул
Вы-то знаете недостатки своего метода, но ТСу пояснить стоит. Метод 8Observer8‘а не универсален: если захочется сделать другую часть сферы, или изменить количество полигонов, или размер — придется делать новую модель и заново ее экспортировать. И объем: код генерации куска сферы «на лету» значительно меньше, чем код импорта трехмерной модели плюс сама модель.
То есть этот метод хорош для сложных моделей, которые с ходу не опишешь формулой, но вот для простых лучше процедурная генерация.
5146 / 2759 / 464
Регистрация: 05.10.2013
Сообщений: 7,281
Записей в блоге: 145
Сообщение от COKPOWEHEU 
И объем: код генерации куска сферы «на лету» значительно меньше, чем код импорта трехмерной модели плюс сама модель.
Если с нуля писать, то да, чтобы повторить мой код, времени у TC уйдёт значительно больше, потому что ему ещё нужно будет освоить формат Collada, работу с XML в C#. Но у меня уже был пример, в котором я изменил только одну строку — путь к модели. Я не писал никакого кода для решения задачи. Я лишь открыл Blender 2.8 (осваиваю новый Blender), отсёк в этом редакторе лишнее. По объёму работы я сделал значительно меньше усилий в совокупности. Есть такие модели, которые невозможно сгененировать на лету, либо это будет очень сложно и займёт много времени. К тому же Collada позволяет хранить скелетную анимацию, что может помочь решать определённые задачи в будущем. Я думаю, что TC сам понимает разницу между этими двумя подходами и когда какой выгоднее применять. Я применил этот метод потому что у меня была универсальная заготовка, которая может отображать модели, вращать камеру с помощью мыши и приближать/отдалять объекты колёсиком. В рамках поставленных условий не говориться, что нужно менять что-то. Но с другой стороны я, конечно, понимаю, что этот метод не подходит TC, потому что цель поставлена преподавателем и она математическая, то есть подразумевает использование тригонометрии и только поэтому моё решение не подходит TC.
3804 / 2358 / 414
Регистрация: 09.09.2017
Сообщений: 10,273
Сообщение от 8Observer8 
времени у TC уйдёт значительно больше, потому что ему ещё нужно будет освоить формат Collada, работу с XML в C#. Но у меня уже был пример
Я писал про объем, а не про время его написания. Даже если вы вхардкодите все вершины в массив, он займет больше места, чем код для генерации «на лету».
Сообщение от 8Observer8 
Есть такие модели, которые невозможно сгененировать на лету, либо это будет очень сложно и займёт много времени.
Именно про это я написал:
Сообщение от COKPOWEHEU 
тот метод хорош для сложных моделей, которые с ходу не опишешь формулой, но вот для простых лучше процедурная генерация.
Сообщение от 8Observer8 
Но с другой стороны я, конечно, понимаю
Решить исходную задачу — хорошо. Научить ТСа решать подобные задачи — отлично. Но и предложить нестандартные решения для общего расширения кругозора (в том числе, своего) тоже прекрасно. Даже если ТС не воспользуется вашим решением, он будет знать, что такое возможно.
сфера в OpenGL помогите нарисовать сферу, которая будет двигаться по оси Х от точки -4 до +4
тута сферическими координатами воспользоваться удобно. вроде как-то примерно так будет:
x=r*sin(b)*cos(fi)
y=r*sin(b)*sin(fi)
z=r*cos(b)
0ну или расширения использовать. или готовую сферу из файла читать.
а от -4 до 4,это уж сам изволь сообразить)
Похожие вопросы
Ваш браузер устарел
Мы постоянно добавляем новый функционал в основной интерфейс проекта. К сожалению, старые браузеры не в состоянии качественно работать с современными программными продуктами. Для корректной работы используйте последние версии браузеров Chrome, Mozilla Firefox, Opera, Microsoft Edge или установите браузер Atom.
