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 (правильно)

OpenCV изображение

SceneKit (неверно)

Изображение SceneKit

диффузная текстура содержит альфа-канал.

(Решено mnuages) Также похоже, что после перекраски шейдерный альфа-канал не работает. С этим шейдером:

tinted.r = 1;
tinted.g = 0;
tinted.b = 0;

Мой результат: scenekit

вместо: OpenCV

Оригинальная текстура: оригинал

Как я могу просто перекрасить диффузную текстуру, как в openCV?


ОБНОВЛЕНИЕ: Это результат для шейдера SceneKit и OpenCV (я удалил все прозрачные пиксели из изображения):

OpenCV: OpenCV

SceneKit: SceneKit

шейдер:

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;
}

Я получаю что-то вроде этого:

OpenCV

С этим шейдером 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 пиксель на экране, тогда вы снова получите только черно-белые значения.

Другие вопросы по тегам