Источник света внутри комнаты ведет себя неожиданно

Я написал несколько приложений для Android, но это мой первый опыт 3D-программирования.

Я создал комнату (4 стены, потолок и пол) с парой объектов внутри и могу перемещать камеру вокруг нее, как будто хожу. Я текстурировал все поверхности различными изображениями, и все работало, как и ожидалось.

Для контекста: ширина комнаты 14 единиц, глубина 16 единиц (с центром в исходной точке), высота 3 единицы (1 над исходной точкой и 2 под ней). В середине комнаты есть 2 объекта, куб и перевернутая пирамида на нем.

Затем я добавил источник света, чтобы затенить куб и пирамиду. Я прочитал и следил за парой портов NeHe, поэтому я взял то, что у меня было на уроке по освещению, и применил это к своему новому коду.

gl.glEnable(GL10.GL_LIGHTING);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, new float[] { 0.1f, 0.1f, 0.1f, 1f }, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, new float[] { 1f, 1f, 1f, 1f }, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, new float[] { -4f, 0.9f, 6f, 1f }, 0);
gl.glEnable(GL10.GL_LIGHT0);

В результате куб и пирамида не заштрихованы. Они выглядят одинаково на сторонах, противоположных свету, и на сторонах, обращенных к нему. Когда камера направлена ​​прямо в сторону от источника света, комната выглядит так, как она выглядела до того, как я добавил код освещения. Когда я поворачиваю камеру к источнику света, вся комната (включая объекты) становится темнее, пока не станет полностью черной, когда камера направлена ​​прямо на источник.

Что здесь происходит? Я читал много статей об освещении и о том, как оно работает, но я не видел ничего, что указывало бы на то, почему это не освещало бы все стороны комнаты, а куб и пирамиду затеняли в зависимости от положения источника света. Есть ли какое-то ожидаемое поведение света, потому что он находится «внутри» комнаты? Я просто пропустил что-то легкое, потому что я новичок?


person atheaos    schedule 22.06.2011    source источник


Ответы (2)


Каждый объект в вашем трехмерном мире имеет нормаль, где это помогает OpenGL чтобы определить, сколько света объект должен отражать. Вероятно, вы забыли указать нормали для своих поверхностей. Без их указания OpenGL будет одинаково освещать все объекты в вашем мире.

Чтобы получить нормаль поверхности в 3D, вам нужно как минимум три вершины, что означает, что это как минимум треугольник.

Примеры:

Чтобы вычислить нормаль поверхности, вам нужны два вектора. Поскольку у вас есть три вершины в трехмерном пространстве, это означает, что эти точки выборки могут содержать треугольник:

// Top triangle, three points in 3D space.
vertices = new float[] {
   -1.0f, 1.0f, -1.0f,
   1.0f, 1.0f, -1.0f,
   0.0f, 1.0f, -1.0f,
}

Учитывая эти три точки, теперь вы можете определить два вектора следующим образом:

// Simple vector class, created by you.
Vector3f vector1 = new Vector3f();
Vector3f vector2 = new Vector3f();

vector1.x = vertices[0] - vertices[3];
vector1.y = vertices[1] - vertices[4];
vector1.z = vertices[2] - vertices[5];

vector2.x = vertices[3] - vertices[6];
vector2.y = vertices[4] - vertices[7];
vector2.z = vertices[5] - vertices[8];

Теперь, когда у вас есть два вектора, вы, наконец, можете получить нормаль поверхности, используя перекрестное произведение. Вкратце, перекрестное произведение — это операция, результатом которой является новый вектор, содержащий угол, перпендикулярный входным векторам. Это норма, которая нам нужна.

Чтобы получить перекрестный продукт в своем коде, вы должны написать свой собственный метод, который его вычисляет. Теоретически вы вычисляете векторный продукт по этой формуле:

A X B = (A.y * B.z - A.z * B.y, A.z * B.x - A.x * B.z, A.x * B.y - A.y * B.x)

В коде (с использованием приведенных выше векторов):

public Vector3f crossProduct(Vector3f vector1, Vector3f vector2) {
    Vector3f normalVector = new Vector3f();

    // Cross product. The normalVector contains the normal for the
    // surface, which is perpendicular both to vector1 and vector2.
    normalVector.x = vector1.y * vector2.z - vector1.z * vector2.y;
    normalVector.y = vector1.z * vector2.x - vector1.x * vector2.z;
    normalVector.z = vector1.x * vector2.y - vector1.y * vector2.x;

    return normalVector;
}

Перед комментариями; вы можете указать свои нормали в массиве и просто поместить их в OpenGL, когда это необходимо, но ваше понимание этой темы будет намного лучше, если вы покопаетесь в ней, и ваш код будет намного более гибким.

Итак, теперь у нас есть нормаль, которую вы можете пройти в цикле, присвоить значения вектора вашему массиву нормалей (как порты NeHe, но динамически) и настроить OpenGL на использование GL_NORMAL_ARRAY, чтобы OpenGL правильно отражал свет на объекте:

gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);

// I'm assuming you know how to put it into a FloatBuffer.
gl.glNormalPointer(GL10.GL_FLOAT, 0, mNormalsBuffer);

// Draw your surface...

Еще один последний комментарий; если вы используете другие значения вершин (например, 5.0f, 10.0f или больше), вы можете нормализовать вектор, который возвращается из метода crossProduct(), чтобы повысить производительность. В противном случае OpenGL должен вычислить новый вектор, чтобы получить единичный вектор, и это может привести к снижению производительности.

Кроме того, ваше new float[] {-4f, 0.9f, 6f, 1f} для GL_POSITION не совсем корректно. Когда четвертое значение установлено на 1.0f, это означает, что положение источника света равно 0, 0, 0, независимо от первых трех значений. Чтобы указать вектор для положения источника света, измените четвертое значение на 0.0f.

person Wroclai    schedule 22.06.2011
comment
Ах! Хорошо, я постоянно пропускал строки, вызывающие glNormal3f в образце NeHe, потому что он использовал все простые поверхности и не нуждался в их вычислении, как вы показали. Так как я сделал комнату полностью из полос и вееров, мне нужно будет что-то нормализовать, прежде чем я смогу протестировать. Я ценю подробное объяснение расчета нормалей. - person atheaos; 22.06.2011

Вам нужно перезагружать положение света в каждом кадре, иначе источник света будет двигаться вместе с камерой, что, вероятно, не то, что вам нужно. Кроме того, затенение, которое вы описываете, полностью соответствует освещению с интерполяцией вершин. Если вы хотите что-то лучшее, вам придется делать это для каждого пикселя (что означает реализацию собственного шейдера) или же подразделять вашу геометрию.

person Mikola    schedule 22.06.2011
comment
Интересно, что ни одна информация, которую я читал о свете, не касалась движущейся камеры, поэтому они не упомянули, что положение должно обновляться вместе с движением. - person atheaos; 22.06.2011