Я работал над созданием движка для учебного процесса и столкнулся с концепцией OpenGL, которую, как я думал, я понял, однако я не могу объяснить поведение, за которым я наблюдал. Проблема с буфером глубины. Кроме того, понимаю, что я исправил проблему, и в конце своего поста я объясню, что решило проблему, однако я не понимаю Зачем проблема была решена через мое решение.
Сначала я инициализирую GLUT & GLEW:
//Initialize openGL
glutInit(&argc, argv);
//Set display mode and window attributes
glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB);
//Size and position attributes can be found in constants.h
glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
glutInitWindowPosition(WINDOW_XPOS, WINDOW_YPOS);
//Create window
glutCreateWindow("Gallagher");
// Initialize GLEW
glewExperimental = true;
glewInit();
//Initialize Graphics program
Initialize();
Затем я инициализирую свою программу (оставляя сегменты для читабельности и отсутствия актуальности):
//Remove cursor
glutSetCursor(GLUT_CURSOR_NONE);
//Enable depth buffering
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LESS);
glDepthRange(0.0f, 1.0f)
//Set back color
glClearColor(0.0,0.0,0.0,1.0);
//Set scale and dimensions of orthographic viewport
//These values can be found in constants.h
//Program uses a right handed coordinate system.
glOrtho(X_LEFT, X_RIGHT, Y_DOWN, Y_UP, Z_NEAR, Z_FAR);
Все, что находится за пределами этой точки, включает в себя только инициализацию различных компонентов движка, загрузку файлов .obj и инициализацию экземпляров класса ModularGameObject, присоединение к ним сеток, ничего, что касалось бы любого существенного перенасыщения / отблеска.
Однако, прежде чем я продолжу, может быть важно указать следующие значения:
X_LEFT = -1000;
X_RIGHT = 1000;
Y_DOWN = -1000;
Y_UP = 1000;
Z_NEAR = -0.1;
Z_FAR = -1000;
Что заставляет мой видовой экран следовать правой системе координат.
Последний фрагмент кода, который, кажется, вовлечен в проблему — мой вершинный шейдер:
#version 330 core
//Position of vertices in attribute 0
layout(location = 0) in vec4 _vertexPosition;
//Vertex Normals in attribute 1
layout(location = 1) in vec4 _vertexNormal;
//Model transformations
//Uniform location of model transformation matrix
uniform mat4 _modelTransformation;
//Uniform location of camera transformations
//Camera transformation matrix
uniform mat4 _cameraTransformation;
//Camera perspective matrix
uniform mat4 _cameraPerspective;
//Uniform location of inverse screen dimensions
//This is used because GLSL normalizes viewport from -1 to 1
//So any vector representing a position in screen space must be multiplied by this vector before display
uniform vec4 _inverseScreenDimensions;
//Output variables
//Indicates whether a vertex is valid or not, non valid vertices will not be drawn.
flat out int _valid; // 0 = valid vertex
//Normal to be sent to fragment shader
smooth out vec4 _normal;
void main()
{
//Initiate transformation pipeline
//Transform to world space
vec4 vertexInWorldSpace = vec4(_modelTransformation *_vertexPosition);
//Transform to camera space
vec4 vertexInCameraSpace = vec4(_cameraTransformation * vertexInWorldSpace);
//Project to screen space
vec4 vertexInScreenSpace = vec4(_cameraPerspective * vertexInCameraSpace);
//Transform to device coordinates and store
vec4 vertexInDeviceSpace = vec4(_inverseScreenDimensions * vertexInScreenSpace);
//Store transformed vertex
gl_Position = vertexInScreenSpace;
}
Этот код приводит к тому, что все преобразования и обычные вычисления (не включены) должны выполняться правильно, однако каждое лицо моих моделей постоянно борется за то, чтобы быть выше всех остальных. Единственный раз, когда у меня нет проблем, это то, что я стою внутри нарисованной первой модели, тогда ничто не мерцает, и я могу видеть внутреннюю часть головы Сюзанны так, как должна.
После нескольких недель попыток чего-либо, о чем я мог подумать, я наконец нашел свой путь к решению, которое включает изменение / добавление всего лишь двух строк кода. Сначала я добавил эту строку в конец моей основной функции в моем вершинном шейдере:
gl_Position.z = 0.0001+vertexInScreenSpace.z;
Добавление этой строки кода привело к тому, что все биты z-борьбы исчезли, за исключением того, что теперь буфер глубины был полностью задом наперед, вершины, расположенные дальше, надежно рисовались поверх вершин впереди. Это мой первый вопрос, почему эта строка кода вызывает более надежное поведение?
Теперь, когда у меня было надежное поведение, и я больше не боролся с глубиной, нужно было поменять порядок прорисовки, поэтому я изменил свой вызов glDepthRange на следующий:
glDepthRange(1.0f, 0.0f);
Я предполагал, что glDepthRange (0.0f, 1.0f) приведет к тому, что объекты ближе к моему Z_NEAR (-0.1) будут ближе к 0, а объекты ближе к моему Z_FAR (-1000) — к 1. Затем, имея мой Проверка глубины, установленная в GL_LESS, будет иметь смысл, фактически это должно быть так, независимо от того, какие у меня Z_NEAR и Z_FAR, потому что glDepthRange отображает значения, если я не ошибаюсь.
Я, должно быть, ошибаюсь, потому что это изменение строки будет означать, что объекты, расположенные ближе ко мне, будут хранить значение ближе к 1 в буфере глубины, а объекты, кроме того, будут иметь значение 0, отображающее порядок отрисовки в обратном направлении, но, конечно, он работает как очарование.
Если кто-нибудь и может указать мне, почему мои предположения неверны и что я могу не учитывать в моем понимании glsl и глубинной буферизации. Я бы предпочел не двигаться дальше с развитием моего двигателя, пока я полностью не пойму функционирование его основания.
Редактировать:
Содержимое моей _cameraPerspective матрицы выглядит следующим образом:
Матрица перспективы
AspectX 0 0 0
0 AspectY 0 0
0 0 1 0
0 0 1/focalLength 0
Где AspectX равен 16, а AspectY равен 9. Фокусное расстояние по умолчанию равно 70, однако для изменения этого значения во время выполнения были добавлены элементы управления.
Указанный derhass, это не объясняет, каким образом любая информация, передаваемая в glOrtho (), учитывается шейдером. Размеры области просмотра из-за неиспользования стандартного конвейера & стек матрицы, рассматриваются с _inverseScreenDimensions. Это vec4, который содержит [1 / X_RIGHT, 1 / Y_UP, 1 / Z_Far, 1]. Или из-за отсутствия имен переменных, [1/1000, 1/1000, -1/1000, 1].
Умножение вектора координат экрана на это в моем вершинном шейдере приводит к значению X между -1 и 1, значению Y между -1 и 1, значению Z между 0 и 1 (если объект находился перед камерой, он имел отрицательная координата z для начала), и W из 1.
Если я не ошибаюсь, это будет последний шаг к достижению «координат устройства» с последующим рисованием сетки.
Пожалуйста, помните оригинальный вопрос: я знаю, что это не упрощено, я знаю, что я не использую GLM или все наиболее часто используемые библиотеки для этого, однако мой вопрос не «Эй, ребята, исправьте это!» Мой вопрос: почему это было исправлено внесенными мною изменениями?
Используя следующую матрицу в качестве матрицы проекции:
AspectX 0 0 0
0 AspectY 0 0
0 0 1 0
0 0 1/focalLength 0
собирается полностью уничтожить значение глубины.
Когда это применяется к вектору (x,y,z,w)^T
, ты получишь
z'=z
а также w'=z/focalLength
в качестве компонентов пространства клипа. После перспективного деления вы получите компонент NDC z z'/w'
который просто focaldepth
и совершенно не зависит от значения глазного пространства z. Таким образом, вы проецируете все на ту же глубину, которая полностью объясняет поведение, которое вы видели.
Эта страница объясняет, как обычно создаются матрицы проекций, и, в частности, предлагает много деталей о том, как отображается значение z.
С линией gl_Position.z = 0.0001+vertexInScreenSpace.z;
вы на самом деле получаете некоторую «рабочую» глубину с тех пор, координата Z NDC будет (0.0001+z')/w'
который focalLenght * (1+ 0.0001/z)
и, наконец, по крайней мере, функция глазного пространства z, как и должно быть. Можно рассчитать, что near
а также far
значения, которые на самом деле отображает отображение, но это вычисление совершенно бессмысленно для этого ответа. Вы должны ознакомиться с математикой для компьютерных графических проекций, особенно для линейной алгебры и проективных пространств.
Причина, по которой тест глубины является инвертированным, заключается в том, что ваша матрица проецирования отрицает координаты z. Обычно матрица вида построена так, что направление просмотра -z
и матрица проекции имеет (0 0 -1 0)
как последний ряд, пока у вас есть (0 0 1/focalLength 0)
, который в основном умножает z на -1 в действительности.
Ближний и дальний расстояния — это расстояние ближнего и дальнего плоскостей в направлении, которое вы смотрите, и поэтому обычно оба должны быть положительными числами. Отрицательные числа будут помещать плоскости отсечения за начало координат вида, это, вероятно, не то, что вы хотите.