Я совсем новичок в OpenGL ES и пытаюсь добавить тени к моей текущей сцене. Я решил сделать это с помощью кубической карты. Я использую OpenGL es 2.0, поэтому геометрический шейдер или переменная gl_FragDepth для меня недоступны. Я погуглил некоторые, так что я получил некоторое представление о теме и прочитав это (http://www.cg.tuwien.ac.at/courses/Realtime/repetitorium/2010/OmnidirShadows.pdf) оказался весьма полезным. В основном я полагаюсь на этот связанный документ.
Но что-то не так с моим кодом, потому что в визуализированной сцене каждый пиксель находится в тени. Я думаю, что проблема может быть найдена в моих шейдерах, но я вставляю сюда весь свой соответствующий код, чтобы все ясно видеть.
Настройте кадровый буфер и создайте кубическую карту:
GLuint FBO;
GLuint cubeTexture;
glGenFramebuffers(1, &FBO);
glBindFramebuffer(GL_FRAMEBUFFER, FBO);
// Depth texture
glGenTextures(1, &cubeTexture);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // no GL_TEXTURE_WRAP_R
// right, left, top, bottom, front, back
for (int face = 0; face < 6; ++face) {
// create space for the textures, content need not to be specified (last parameter is 0)
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_DEPTH_COMPONENT16, 1024, 768, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
}
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
В функции «Дисплей» я пытаюсь выполнить 6 проходов рендеринга, чтобы заполнить карты теней (6 сторон куба).
Rendering:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepthf(1.0f);
glm::vec3 camPos = ... // position of the camera, it is in world space
glm::vec4 lightPos = ... // position of the light source, it is in world space
// 1. render into texture
float zNear = 0.1f;
float zFar = 100.0f;
// or should I use ortho instead of perspective?
glm::mat4 projMatrix = glm::perspective(90.0f, (float)esContext->width / esContext->height, zNear, zFar);
// The 6 cameras have to be placed to the light source and they need the proper view matrices
glm::mat4 cubeMapMatrices[6]; // contains six basic and defined rotation matrices for the six directions
cubeMapMatrices[0] = glm::make_mat4(rotPositiveX);
cubeMapMatrices[1] = glm::make_mat4(rotNegativeX);
cubeMapMatrices[2] = glm::make_mat4(rotPositiveY);
cubeMapMatrices[3] = glm::make_mat4(rotNegativeY);
cubeMapMatrices[4] = glm::make_mat4(rotPositiveZ);
cubeMapMatrices[5] = glm::make_mat4(rotNegativeZ);
glm::vec4 translation = lightPos;
glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture);
glBindFramebuffer(GL_FRAMEBUFFER, FBO);
for (int face = 0; face < 6; ++face) {
glClear(GL_DEPTH_BUFFER_BIT); // do I need this here?
cubeMapMatrices[face][3] = translation; // the translation part is the same for all
cubeMapMatrices[face] = projMatrix * cubeMapMatrices[face]; // now it's an mvp matrix
glUniformMatrix4fv(cubeProjectionMatrixID, 1, GL_FALSE, glm::value_ptr(cubeMapMatrices[face]));
// Attach depth cubemap texture to FBO's depth attachment point
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, cubeTexture, 0);
int err = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (err != GL_FRAMEBUFFER_COMPLETE) {
std::cout << "Framebuffer error, status: " << err << std::endl;
}
RenderScene(); // do the drawing
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 2. render into screen
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
RenderScene(); // do the drawing
// swap the buffers
Итак, вот и разные шейдеры. У меня есть один вершинный шейдер и один фрагментный шейдер для вычисления глубины и один вершинный шейдер и один фрагментный шейдер для рендеринга экрана. Моя проблема в том, что я не уверен, как записать в кубическую карту, если кадровый буфер используется, тогда будет ли gl_Position определять координаты в данной грани куба?
вершинный шейдер для расчета глубины:
in vec3 vPosition;
uniform mat4 mModel;
uniform mat4 mCubeProjection;
uniform vec4 vLight;
out vec4 vFrag Position; // world space
out vec4 vLightPosition; // mvp transformed
void main()
{
vec4 position = vec4(vPosition, 1.0);
vFragPosition = mModel* position;
vLightPosition = vLight;
gl_Position = mCubeProjection * vFragPosition;
}
фрагментный шейдер для расчета глубины:
in vec4 vFragPosition; // world space
in vec4 vLightPosition; // world space
out float depthValue;
void main()
{
depthValue = distance(vFragPosition.xyz, vLightPosition.xyz); // need normalization?
}
вершинный шейдер для рендеринга на экран:
uniform mat4 mModel;
uniform mat4 mView;
uniform mat4 mProjection;
uniform vec4 vLight; // it is in world space
out vec3 lw; // light position in world space
out vec3 pw; // pixel position in world space
void main()
{
vec4 position = vec4(vPosition, 1.0);
lw = vLight.xyz;
pw = (mModel* position).xyz;
gl_Position = mProjection* mView * mModel* position;
}
фрагмент шейдера для рендеринга на экран:
in vec3 lw;
in vec3 pw;
uniform samplerCube cubeMap;
out vec4 outputColor;
void main()
{
vec3 lookup = pw - lw;
float smValue = texture(cubeMap, lookup).r; // retrieves texels from a texture (d, d, d, 1.0)
float distance = length(lookup); // dist from the fragment to the light
float eps = 0.1;
float shadowVal = 1.0;
if (smValue + eps < distance) {
shadowVal = 0.1; // in shadow
}
// here comes the lighting stuff
// ...
outputColor = outputColor * shadowVal;
}
Итак, еще раз, проблема в том, что каждый пиксель попадает под тень. Из кода я исключил некоторые единообразные проходы в шейдер, но они в порядке. Можете ли вы дать мне совет, что я должен исправить в коде? Правильны ли мои шейдеры (особенно для первого прохода), правильно ли я настроил преобразования для отображения куба? Спасибо.
П.С .: Это мой первый вопрос, надеюсь, он достаточно ясен и отвечает требованиям правильно поставленного вопроса.
По сути, ваша проблема довольно проста: вы используете привязку глубины для своей карты куба, а буфер глубины хранит глубину перспективы.
То, что ваш шейдер ожидает увидеть на карте теней, это расстояние без перспективы от источника света до ближайшего фрагмента. Вы на самом деле пошли дальше и рассчитали это в своем фрагментном шейдере для создания карты теней, но вы выводите его в буфер цвета (к которому ничего не прикреплено) вместо буфера глубины.
Существует как минимум три возможных решения этой проблемы (в порядке наихудший первый):
Написать в gl_FragDepth
вместо depthValue
(который на самом деле является целью цветового буфера).
Прикрепите свою кубическую карту к GL_COLOR_ATTACHMENT0
и использовать цвето-визуализируемый формат вместо GL_DEPTH_COMPONENT
,
Удалить depthValue
от вашего шейдера и ТОЛЬКО напишите глубину перспективы.
Опция 1 абсолютно ужасно, хотя я видел людей справочные руководства, которые предлагают сделать это. Если вы напишите gl_FragDepth
Вы отключите оптимизацию буфера глубины аппаратного обеспечения на большом количестве оборудования, и это сделает процесс генерации ваших теневых карт хуже.
Вариант 2 это лучше, потому что он выигрывает от аппаратной оптимизации Z-буфера, но все же требует большой пропускной способности памяти, потому что вы эффективно храните два разных значения глубины (одно в приложении цвета и другое в приложении глубины).
Вариант 3, в то время как самый сложный, также, как правило, лучше всего. Это потому, что вы можете сократить пропускную способность памяти, необходимую для вычисления ваших теневых карт, вдвое, только сохранив аппаратную глубину.
Если вы хотите узнать больше о Варианте 3, я бы посоветовал вам взглянуть на этот связанный вопрос. Вместо того, чтобы сравнивать расстояние, вы фактически вычисляете глубину перспективы, чтобы использовать ее для сравнения. Вы торгуете несколькими дополнительными вычислениями в фрагментном шейдере, когда применяете тени для намного меньшей пропускной способности памяти при создании теневых карт.
Теперь, чтобы решить вашу непосредственную проблему с наименьшим количеством работы, вы должны продолжать Вариант 2. Покидать Вариант 3 на столе для будущей оптимизации; Лучше не оптимизировать вещи, пока у вас не будет хотя бы чего-то работающего.
Других решений пока нет …