Източник на светлина в стая, който действа неочаквано

Написах няколко приложения за 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)


Всеки обект във вашия 3D свят има нормален, където помага на OpenGL за да се определи колко светлина трябва да отразява даден обект. Вероятно сте забравили да посочите нормалните стойности за вашите повърхности. Без да ги посочва, OpenGL ще освети всички обекти във вашия свят по един и същи начин.

За да получите нормала на една повърхност в 3D, имате нужда от поне три върха, което означава, че тя е поне триъгълник.

Примерни неща:

За да изчислите нормалата на повърхността, имате нужда от два вектора. Тъй като имате три върха в 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