OpenGL Pixel Perfect 2D рисунок
Я работаю над 2d движком. Это уже работает довольно хорошо, но я продолжаю получать пиксельные ошибки.
Например, мое окно размером 960x540 пикселей, я рисую линию от (0, 0) до (959, 0). Я ожидал бы, что каждый пиксель в строке развертки 0 будет установлен на цвет, но нет: самый правый пиксель не прорисован. Та же проблема, когда я рисую вертикально в пиксель 539. Мне действительно нужно нарисовать в (960, 0) или (0, 540), чтобы нарисовать его.
Поскольку я родился в эпоху пикселов, я убежден, что это неверный результат. Когда мой экран был размером 320х200 пикселей, я мог рисовать от 0 до 319 и от 0 до 199, и мой экран был бы заполнен. Теперь я получаю экран с правым / нижним пикселем, который не рисуется.
Это может происходить из-за разных вещей: где я ожидаю, что примитив opengl рисуется от пикселя к пикселю включительно, этот последний пиксель на самом деле является эксклюзивным? Это оно? моя матрица проекции неверна? У меня ложное предположение, что когда у меня есть буфер 960x540, то это на один пиксель больше? Что-то другое?
Может кто-нибудь, пожалуйста, помогите мне? Я давно изучал эту проблему, и каждый раз, когда я думал, что это нормально, через некоторое время я видел, что на самом деле это не так.
Вот часть моего кода, я постарался сократить его как можно больше. Когда я вызываю свою строковую функцию, каждая координата добавляется с 0,375, 0,375, чтобы сделать ее правильной на адаптерах ATI и nvidia.
int width = resX();
int height = resY();
for (int i = 0; i < height; i += 2)
rm->line(0, i, width - 1, i, vec4f(1, 0, 0, 1));
for (int i = 1; i < height; i += 2)
rm->line(0, i, width - 1, i, vec4f(0, 1, 0, 1));
// when I do this, one pixel to the right remains undrawn
void rendermachine::line(int x1, int y1, int x2, int y2, const vec4f &color)
{
... some code to decide what std::vector the coordinates should be pushed into
// m_z is a z-coordinate, I use z-buffering to preserve correct drawing orders
// vec2f(0, 0) is a texture-coordinate, the line is drawn without texturing
target->push_back(vertex(vec3f((float)x1 + 0.375f, (float)y1 + 0.375f, m_z), color, vec2f(0, 0)));
target->push_back(vertex(vec3f((float)x2 + 0.375f, (float)y2 + 0.375f, m_z), color, vec2f(0, 0)));
}
void rendermachine::update(...)
{
... render target object is queried for width and height, in my test it is just the back buffer so the window client resolution is returned
mat4f mP;
mP.setOrthographic(0, (float)width, (float)height, 0, 0, 8000000);
... all vertices are copied to video memory
... drawing
if (there are lines to draw)
glDrawArrays(GL_LINES, (int)offset, (int)lines.size());
...
}
// And the (very simple) shader to draw these lines
// Vertex shader
#version 120
attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;
uniform mat4 mP;
varying vec4 vColor;
void main(void) {
gl_Position = mP * vec4(aVertexPosition, 1.0);
vColor = aVertexColor;
}
// Fragment shader
#version 120
#ifdef GL_ES
precision highp float;
#endif
varying vec4 vColor;
void main(void) {
gl_FragColor = vColor.rgb;
}
2 ответа
В OpenGL линии растеризуются с использованием правила "Алмазный выход". Это почти то же самое, что сказать, что конечная координата является исключительной, но не совсем...
Вот что говорит спецификация OpenGL: http://www.opengl.org/documentation/specs/version1.1/glspec1.1/node47.html
Также посмотрите FAQ по OpenGL, http://www.opengl.org/archives/resources/faq/technical/rasterization.htm, пункт "14.090 Как получить точную пикселизацию линий?". В нем говорится: "Спецификация OpenGL допускает широкий спектр аппаратных средств рендеринга, поэтому точная пикселизация может быть невозможна вообще".
Многие утверждают, что вы не должны использовать строки в OpenGL вообще. Их поведение основано на том, как работало древнее оборудование SGI, а не на том, что имеет смысл. (И линии с шириной>1 почти невозможно использовать так, чтобы это выглядело хорошо!)
Обратите внимание, что в координатном пространстве OpenGL нет понятия целых чисел, все является плавающей точкой, и "центр" пикселя OpenGL действительно находится на уровне 0,5,0,5 вместо его верхнего левого угла. Поэтому, если вы хотите линию шириной 1px от 0,0 до 10,10 включительно, вам действительно нужно было нарисовать линию от 0,5,0,5 до 10,5,10,5.
Это будет особенно очевидно, если вы включите сглаживание, если у вас есть сглаживание и вы пытаетесь нарисовать от 50,0 до 50,100, вы можете увидеть размытую линию шириной 2px, потому что линия попала между двумя пикселями.