Scenekit: перекрасить диффузную текстуру с помощью шейдера
Я хочу написать некоторый шейдер (поверхность / фрагмент /...), чтобы перекрасить мою диффузную текстуру в новый цвет. В настоящее время у меня есть эта версия шейдера (я пытаюсь перекрасить текстуру в режиме реального времени):
//sm_surf
uniform vec4 colorTarget; (used in full version)
uniform vec4 colorTint; (used in full version)
vec4 colorTexture = texture2D(u_diffuseTexture, _surface.diffuseTexcoord);
//vec4 colorTexture = _sample.diffuse;//the same result
vec4 tinted = colorTexture;
tinted.r = max(0.0, colorTexture.r - 0.2);
_surface.diffuse = tinted;
И это код в OpenCV (я просто перекрасил текстуру заранее и использовал ее как новую диффузную текстуру):
image = cv::imread([path UTF8String], cv::IMREAD_UNCHANGED);
for (int i = 0; i < image.rows; i++) {
for (int j = 0; j < image.cols; j++) {
cv::Vec4b pixel = image.at<cv::Vec4b>(i,j);
pixel[2] = fmin(255, fmax(0, pixel[2] - 50));
image.at<cv::Vec4b>(i,j) = pixel;
}
}
cv::imwrite([newPath UTF8String], image);
Для этого теста я просто хочу уменьшить R компонента цвета. Результаты:
OpenCV (правильно)
SceneKit (неверно)
диффузная текстура содержит альфа-канал.
(Решено mnuages) Также похоже, что после перекраски шейдерный альфа-канал не работает. С этим шейдером:
tinted.r = 1;
tinted.g = 0;
tinted.b = 0;
Как я могу просто перекрасить диффузную текстуру, как в openCV?
ОБНОВЛЕНИЕ: Это результат для шейдера SceneKit и OpenCV (я удалил все прозрачные пиксели из изображения):
шейдер:
vec4 colorTexture = _surface.diffuse;
vec3 tinted = colorTexture.a > 0.0 ? colorTexture.rgb / colorTexture.a : colorTexture.rgb;
if (colorTexture.a == 1) {
tinted.r = max(0.0, colorTexture.r - 0.2);
} else {
colorTexture.a = 0;
}
_surface.diffuse = vec4(tinted, 1.0) * colorTexture.a;
и код OpenCV:
pixel[2] = fmax(0, pixel[2] - 50);//2 because it's bgr in OpenCV
if (pixel[3] != 255) {
pixel[3] = 0;
}
Несколько более странных вещей: я изменил свой код OpenCV на этот, чтобы генерировать новую текстуру
pixel[0] = 255 - (j % 4) * 30;//b
pixel[1] = 0;//g
pixel[2] = 0;//r
pixel[3] = 255;
Если я изменю эту текстуру следующим образом:
if (pixel[0] == 255) {
pixel[0] = 255;pixel[1] = 255;pixel[2] = 255;
} else {
pixel[0] = 0;pixel[1] = 0;pixel[2] = 0;
}
Я получаю что-то вроде этого:
С этим шейдером SceneKit должно быть то же самое:
vec4 colorTexture = _surface.diffuse;
vec3 tinted = colorTexture.rgb; // colorTexture.a == 1
if (tinted.b > 0.99) {
tinted = vec3(0,0,0);
} else {
tinted = vec3(1,1,1);
}
_surface.diffuse = vec4(tinted, 1.0) * colorTexture.a;
Но я получаю это:
Есть некоторые белые полосы, но слишком тонкие.
Я могу увеличить их, изменив условие на tinted.b > 0.85, но это уже ошибка, потому что цвет в _surface.diffuse не такой, как в текстуре. Похоже, SceneKit интерполирует текстуру или что-то в этом роде.
UPDATE2:
Я добавил исходный код (1,5 МБ) с этой проблемой. Есть 3 сферы:
1 верх) С оригинальной текстурой
2 слева) С текстурой, перекрашенной шейдером (newR = r - 0.2) (float)
3 справа) С текстурой, перекрашенной OpenCV (newR = r - 51) (uint8)
и они разные! Сцена не содержит света /env/... только 3 сферы.
1 ответ
SceneKit использует предварительно умноженную альфу. Следующее должно работать:
vec3 tinted = colorTexture.a > 0.f ? colorTexture.rgb / colorTexture.a : colorTexture.rgb;
tinted.r = max(0.0, tinted.r - 0.2);
_surface.diffuse = vec4(tinted, 1.f) * colorTexture.a;
редактировать
Этот код хорошо работает в примере, который вы приложили, но требует, чтобы некоторые из этих методов были изменены на 100%. Как объяснено в этом другом потоке SO, шейдеры SceneKit работают в линейном цветовом пространстве.
Это означает, что 51
в коде вашего процессора не отображается на 0.2
в вашем коде GPU. Чтобы получить тот же результат, вам необходимо преобразовать выбранный цвет (линейный) в окрашенный цвет (нелинейный), применить операцию оттенка, а затем преобразовать обратно в линейный.
Что касается примера с полосами, это ожидаемое поведение. Mipmapping приведет к значениям в градациях серого. Если вы подойдете достаточно близко к объекту, так что 1 тексель проецируется на ~1 пиксель на экране, тогда вы снова получите только черно-белые значения.