Всем привет. В этой небольшой статье я собираюсь рассказать о том, как можно выводить 3D графику на векторном дисплее. Конечно, практической ценности это не несет никакой, но зато как интересно!
Итак, мы собираемся рисовать 3D. И не просто рисовать, а рисовать на осциллографе. Я думаю, что будет достаточно изобразить вращающийся кубик, что бы понять принцип работы, а там и до трехмерной стрелялки не далеко.
Вся фишка заключается в том, что в векторных дисплеях(то есть для нас это экран осциллографа) в отличие от, тех к которым мы привыкли, изображение выводится не с помощью последовательного подсвечивания пикселов, а с помощью установки проецирующего луча в нужное место. То есть мы просто «обводим» контуры будущей картинки лучом по экрану, вместо того что бы отмечать точки.
Давайте посмотрим, что происходит внутри осциллографа. В самом простейшем случае осциллограф представляет собой электронную пушку, которая создает и фокусирует луч из электронов. Затем этот луч, проходя через два конденсатора, отклоняется горизонтально или вертикально, в зависимости от напряжения на них. Потом луч попадает на экран, покрытый люминофором, и происходит вспышка в том месте куда попал луч. Таким образом при достаточно быстром движении луча формируется картинка.
Напряжение на отклоняющих конденсаторах зависит от напряжения на входах осциллографа. У нас отклонением по оси X будет управлять один вход, а по оси Y другой. Ура, мы уже можем что-нибудь нарисовать подавая соответствующее напряжение осциллографу.
А подавать его мы будем с помощью звуковой карты компьютера, ведь от частоты звука зависит напряжение на ее выходах. Пусть правый канал у нас отвечает за движение по оси X, а левый по оси Y.
То есть просто присоединив правый канал звуковой карты к первому входу осциллографа, а левый ко второму можно будет управлять движением луча по экрану.
Для начала научимся просто ставить одну точку. Будем использовать всеми любимый Python и пару библиотек к нему. Посмотрим на такой код:
Общие слова
Итак, мы собираемся рисовать 3D. И не просто рисовать, а рисовать на осциллографе. Я думаю, что будет достаточно изобразить вращающийся кубик, что бы понять принцип работы, а там и до трехмерной стрелялки не далеко.
Вся фишка заключается в том, что в векторных дисплеях(то есть для нас это экран осциллографа) в отличие от, тех к которым мы привыкли, изображение выводится не с помощью последовательного подсвечивания пикселов, а с помощью установки проецирующего луча в нужное место. То есть мы просто «обводим» контуры будущей картинки лучом по экрану, вместо того что бы отмечать точки.
Как подружить компьютер и осциллограф?
Давайте посмотрим, что происходит внутри осциллографа. В самом простейшем случае осциллограф представляет собой электронную пушку, которая создает и фокусирует луч из электронов. Затем этот луч, проходя через два конденсатора, отклоняется горизонтально или вертикально, в зависимости от напряжения на них. Потом луч попадает на экран, покрытый люминофором, и происходит вспышка в том месте куда попал луч. Таким образом при достаточно быстром движении луча формируется картинка.
Напряжение на отклоняющих конденсаторах зависит от напряжения на входах осциллографа. У нас отклонением по оси X будет управлять один вход, а по оси Y другой. Ура, мы уже можем что-нибудь нарисовать подавая соответствующее напряжение осциллографу.
А подавать его мы будем с помощью звуковой карты компьютера, ведь от частоты звука зависит напряжение на ее выходах. Пусть правый канал у нас отвечает за движение по оси X, а левый по оси Y.
То есть просто присоединив правый канал звуковой карты к первому входу осциллографа, а левый ко второму можно будет управлять движением луча по экрану.
Ставим точку
Для начала научимся просто ставить одну точку. Будем использовать всеми любимый Python и пару библиотек к нему. Посмотрим на такой код:
import sys
import struct
import wave
from math import *
# Открываем и инициализируем wav-файл
WaveF = wave.open("output.wav", "wb")
WaveF.setnchannels(2)
WaveF.setsampwidth(2)
WaveF.setframerate(48000)
WaveF.writeframes(struct.pack("hh", 0.5 * 32767.0, 0.5 * 32767.0))
WaveF.close()
* This source code was highlighted with Source Code Highlighter.
Тут все просто: открываем wav-файл, и записываем туда информацию об одной точке(фрейме в терминологии wav файлов). Мы считаем что координаты точки бывают только от -1 до 1, поэтому смело домножаем их на максимальное значение в типе integer, что бы громкость, а соответственно и выходное напряжение были бы достаточно большими. Звук я вывожу в wav только потому что, так его можно залить на плеер и спокойно донести до осциллографа.
Так, мы научились ставить точку на двухмерной плоскости, но ведь мы хотим 3D. Поэтому применим немного простой математики и научимся проецировать точку из трехмерного пространства в двухмерное, вот так:
# Настройки перспективной прекции
xSize = 0.6 # Размер проекционного экрана по оси X
ySize = 0.4 # Размер проекционного экрана по оси Y
Dist = 1.5 # Расстояние от наблюдателя до экрана
# Проецируем точку, у нас уже три координаты
def PutPoint( x, y, z ):
# Специальные магические формулы
sx = (xSize / 2) + ((x * Dist) / (z + Dist))
sy = (ySize / 2) + ((y * Dist) / (z + Dist))
# Проверяем на корректность полученые значения
if (sx < -1):
sx = -1;
if (sx > 1):
sx = 1;
if (sy < -1):
sy = -1;
if (sy > 1):
sy = 1;
# И выводим в файл
WaveF.writeframes(struct.pack("hh", sy * 32767.0, sx * 32767.0))
* This source code was highlighted with Source Code Highlighter.
Супер, теперь можно ставить точку с тремя координатами и она будет правильно проецироваться. Самое важное сделано, осталось совсем немного.
Рисуем куб!
Тут все еще проще. Смотрим на интеллектуальный код:
def DrawCube():
DrawXLine(CPoint(0, 0, 0), CPoint(0.5, 0, 0), 0.0121)
DrawXLine(CPoint(0, 0.5, 0), CPoint(0.5, 0.5, 0), 0.0121)
DrawXLine(CPoint(0, 0, 0.5), CPoint(0.5, 0, 0.5), 0.0121)
DrawXLine(CPoint(0, 0.5, 0.5), CPoint(0.5, 0.5, 0.5), 0.0121)
DrawYLine(CPoint(0, 0, 0), CPoint(0, 0.5, 0), 0.0121)
DrawYLine(CPoint(0.5, 0, 0), CPoint(0.5, 0.5, 0), 0.0121)
DrawYLine(CPoint(0, 0, 0.5), CPoint(0, 0.5, 0.5), 0.0121)
DrawYLine(CPoint(0.5, 0, 0.5), CPoint(0.5, 0.5, 0.5), 0.0121)
DrawZLine(CPoint(0, 0, 0), CPoint(0, 0, 0.5), 0.0121)
DrawZLine(CPoint(0.5, 0, 0), CPoint(0.5, 0, 0.5), 0.0121)
DrawZLine(CPoint(0, 0.5, 0), CPoint(0, 0.5, 0.5), 0.0121)
DrawZLine(CPoint(0.5, 0.5, 0), CPoint(0.5, 0.5, 0.5), 0.0121)
* This source code was highlighted with Source Code Highlighter.
Что делаю функции фида DrawXLine, думаю, понятно? Последний параметр в них обозначает шаг луча и выбран на глаз=) Таким образом мы нарисовали статичный куб в начале координат, однако он слишком неподвижен, а это так скучно. Поэтому дабавим анимацию.
Все, что нам потребуется это совсем немного изменить код функции PutPoint.
def PutPoint( x, y, z ):
global AngleX, AngleY
# Магический поворот точки вокруг начала координат по оси Y
nx = x * cos(AngleY) + z * sin(AngleY)
nz = z * cos(AngleY) - x * sin(AngleY)
ny = y;
# И по оси X
nnx = nx
nny = ny * cos(AngleX) - nz * sin(AngleX)
nnz = ny * sin(AngleX) + nz * cos(AngleX)
# Дальше все по-старому
sx = (xSize / 2) + ((nnx * Dist) / (nnz + Dist))
sy = (ySize / 2) + ((nny * Dist) / (nnz + Dist))
if (sx < -1):
sx = -1;
if (sx > 1):
sx = 1;
if (sy < -1):
sy = -1;
if (sy > 1):
sy = 1;
WaveF.writeframes(struct.pack("hh", sy * 32767.0, sx * 32767.0))
* This source code was highlighted with Source Code Highlighter.
Мы добавили две глобальные переменные AngleX и AngleY для управления углами вращения, и просто применили соответствующие преобразования к исходным координатам точки. Вот так теперь выводим кубик:
AngleY = 0
AngleX = 0
for cnt in range(0, 1000):
DrawCube()
AngleY += radians(1)
AngleX += radians(1)
* This source code was highlighted with Source Code Highlighter.
Все предельно просто.
Смотрите на результат:
Ну как оно? Огорчают странные шумы в вершинах кубика, я надеюсь это из-за плохого соединения плеера и осциллографа.
Вот и все, спасибо за внимание, всем удачи=)