Я столкнулся с проблемой, когда шейдер GLSL генерирует неправильное изображение на следующих графических процессорах:
GT 430
GT 770
GTX 570
GTX 760
Но работает нормально на этих:
Intel HD Graphics 2500
Intel HD 4000
Intel 4400
GTX 740M
Radeon HD 6310M
Radeon HD 8850
Код шейдера выглядит следующим образом:
bool PointProjectionInsideTriangle(vec3 p1, vec3 p2, vec3 p3, vec3 point)
{
vec3 n = cross((p2 - p1), (p3 - p1));
vec3 n1 = cross((p2 - p1), n);
vec3 n2 = cross((p3 - p2), n);
vec3 n3 = cross((p1 - p3), n);
float proj1 = dot((point - p2), n1);
float proj2 = dot((point - p3), n2);
float proj3 = dot((point - p1), n3);
if(proj1 > 0.0)
return false;
if(proj2 > 0.0)
return false;
if(proj3 > 0.0)
return false;
return true;
}
struct Intersection
{
vec3 point;
vec3 norm;
bool valid;
};
Intersection GetRayTriangleIntersection(vec3 rayPoint, vec3 rayDir, vec3 p1, vec3 p2, vec3 p3)
{
vec3 norm = normalize(cross(p1 - p2, p1 - p3));
Intersection res;
res.norm = norm;
res.point = vec3(rayPoint.xy, 0.0);
res.valid = PointProjectionInsideTriangle(p1, p2, p3, res.point);
return res;
}
struct ColoredIntersection
{
Intersection geomInt;
vec4 color;
};
#define raysCount 15
void main(void)
{
vec2 radius = (gl_FragCoord.xy / vec2(800.0, 600.0)) - vec2(0.5, 0.5);
ColoredIntersection ints[raysCount];
vec3 randomPoints[raysCount];
int i, j;for(int i = 0; i < raysCount; i++)
{
float theta = 0.5 * float(i);
float phi = 3.1415 / 2.0;
float r = 1.0;
randomPoints[i] = vec3(r * sin(phi) * cos(theta), r * sin(phi)*sin(theta), r * cos(phi));
vec3 tangent = normalize(cross(vec3(0.0, 0.0, 1.0), randomPoints[i]));
vec3 trianglePoint1 = randomPoints[i] * 2.0 + tangent * 0.2;
vec3 trianglePoint2 = randomPoints[i] * 2.0 - tangent * 0.2;
ints[i].geomInt = GetRayTriangleIntersection(vec3(radius, -10.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, 0.0), trianglePoint1, trianglePoint2);
if(ints[i].geomInt.valid)
{
float c = length(ints[i].geomInt.point);
ints[i].color = vec4(c, c, c, 1.0);
}
}
for(i = 0; i < raysCount; i++)
{
for(j = i + 1; j < raysCount; j++)
{
if(ints[i].geomInt.point.z < ints[i].geomInt.point.z - 10.0)
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
ColoredIntersection tmp = ints[j];
ints[j] = ints[i];
ints[i] = tmp;
}
}
}
vec4 resultColor = vec4(0.0, 0.0, 0.0, 0.0);
for(i = 0; i < raysCount + 0; i++)
{
if(ints[i].geomInt.valid)
resultColor += ints[i].color;
}
gl_FragColor = clamp(resultColor, 0.0, 1.0);
}
Upd: я заменил векторную нормализацию встроенными функциями и добавил на всякий случай gl_FragColor claming.
Код представляет собой упрощенную версию фактического шейдера, ожидаемое изображение:
Но то, что я получаю, это:
Случайные повороты кода полностью удаляют артефакты. Например, если я изменю строку
if(ints[i].geomInt.valid) //1
в
if(ints[i].geomInt.valid == true) //1
который, по-видимому, никоим образом не должен влиять на логику или полностью удалять двойной цикл, который ничего не делает (помеченный как 2). Обратите внимание, что двойной цикл ничего не делает, так как условие
if(ints[i].geomInt.point.z < ints[i].geomInt.point.z - 10.0)
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
return;
ColoredIntersection tmp = ints[j];
ints[j] = ints[i];
ints[i] = tmp;
}
Никогда не может быть удовлетворен (левая и правая стороны имеют индекс i, а не i, j), и там нет NaN. Этот код абсолютно ничего не делает, но как-то производит артефакты.
Вы можете самостоятельно протестировать шейдер и демо-версию, используя этот проект (полный проект MSVS 2010 + исходные коды + скомпилированный двоичный файл и шейдер, использующий включенный SFML): https://dl.dropboxusercontent.com/u/25635148/ShaderTest.zip
Я использую sfml в этом тестовом проекте, но это на 100% не имеет значения, потому что в настоящем проекте, с которым я столкнулся, эта библиотека не используется.
То, что я хочу знать, — то, почему эти артефакты появляются и как надежно избежать их.
Я не думаю, что с вашим шейдером что-то не так. Конвейер openGL рендерится в кадровый буфер. Если вы используете этот кадровый буфер до завершения рендеринга, вы часто будете получать то, что видели. Пожалуйста, имейте в виду, что glDrawArrays и подобные являются асинхронными (функция возвращается до того, как графический процессор завершил рисование вершин).
Чаще всего эти квадратные артефакты используются, когда в качестве текстуры используется результирующий кадровый буфер, который затем используется для дальнейшей визуализации.
Предполагается, что драйвер OpenGL отслеживает зависимости и должен знать, как ожидать выполнения зависимостей.
Если вы делите фрейм-буфер между потоками, однако все ставки отключены, вам может понадобиться использовать такие вещи, как синхронизация забора (glFenceSync), чтобы гарантировать, что один поток ожидает рендеринга, который происходит в другом потоке.
В качестве обходного пути вы можете обнаружить, что вызов glFinish или даже glReadPixels (с одним пикселем) решает проблему.
Также имейте в виду, что эта проблема связана со временем, и упрощение шейдера может очень хорошо решить эту проблему.
Если кому-то все еще интересно, я задавал этот вопрос на многочисленных специализированных сайтах, включая opengl.org и devtalk.nvidia.com. Я не получил никакого конкретного ответа о том, что не так с моим шейдером, только некоторые предложения, как обойти мою проблему. Например, используйте if (условие == true) вместо if (условие), используйте как можно более простые алгоритмы и тому подобное. В конце концов я выбрал одну из самых простых ротаций своего кода, которая избавляет от проблемы: я просто заменил
struct Intersection
{
vec3 point;
vec3 norm;
bool valid;
};
с
struct Intersection
{
bool valid;
vec3 point;
vec3 norm;
};
Было множество других поворотов кода, из-за которых артефакты исчезали, но я выбрал этот, потому что я смог протестировать большинство других систем, с которыми у меня были проблемы раньше.
Я видел, как именно это происходит в GLSL, когда переменные не инициализированы. Например, vec3 будет (0,0,0) по умолчанию на некоторых видеокартах, но на других видеокартах это будет другое значение. Вы уверены, что не используете переменную без предварительного присвоения ей значения? В частности, вы не инициализируете ColoredIntersection.color, если Insersection.valid имеет значение false, но я думаю, что вы используете его позже.