Источник света внутри комнаты действует неожиданно - PullRequest
6 голосов
/ 22 июня 2011

Я написал несколько приложений для 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);

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

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

Ответы [ 2 ]

8 голосов
/ 22 июня 2011

Каждый объект в вашем трехмерном мире имеет нормальный , где он помогает 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];

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

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

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.

1 голос
/ 22 июня 2011

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

...