Scenekit:使用着色器为漫反射纹理重新着色

时间:2018-08-05 16:34:12

标签: ios shader scenekit scnmaterial

我想编写一些着色器(表面/碎片/ ...),以便以某种新颜色重新着色漫反射纹理。当前,我具有此版本的着色器(我正在尝试实时重新着色纹理):

//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 image

SceneKit(不正确

SceneKit image

漫反射纹理包含Alpha通道。

(按手稿解决) 此外,似乎在重新着色着色器后,alpha通道已损坏。使用此着色器:

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

我的结果是:scenekit

代替:opencv

原始纹理:original

如何像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;

但是我收到了:

enter image description here

有一些白色条纹,但是太细了。

我可以通过将条件更改为tinted.b> 0.85来增加它们,但这已经是一个错误,因为_surface.diffuse中的颜色与纹理中的颜色不同。好像SceneKit这样插值纹理或污秽。


UPDATE2:

我添加了source code(1.5Mb)这个问题。有3个领域:

1顶部)具有原始纹理

2左)通过着色器重新着色纹理(newR = r-0.2)(浮动)

3对)通过OpenCV重新着色纹理(newR = r-51)(uint8)

,它们是不同的!场景不包含任何灯光/环境/ ...仅3个球体。

1 个答案:

答案 0 :(得分:2)

SceneKit使用预乘alpha。以下应该起作用:

(a + a) << a;

修改

此代码在您附加的示例中效果很好,但是需要对这两种技术进行一些更改以匹配100%。如this other SO thread中所述,SceneKit着色器在线性色彩空间中运行。

这意味着您的CPU代码中的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; 不会映射到您的GPU代码中的51。 为了获得相同的结果,您需要将采样的颜色(线性)转换为有色的颜色(非线性),应用着色操作,然后再转换回线性。


对于带有条纹的示例,这是预期的行为。 Mipmapping将导致灰度值。如果您移动到离对象足够近的位置,从而在屏幕上〜1像素上投影了1个纹理像素,那么您只会再次获得黑白值。