在 SCNProgram 传递的金属着色器中使用哪些正确的矩阵值以获得正确的 chrome 像反射

Which are the right Matrix Values to use in a metal shader passed by a SCNProgram to get a correct chrome like reflection

我正在开发一个应用程序,它应该在天空盒(使用六面立方体贴图)内渲染一个 chrome 风格的类似反射球体的对象。

我在 Swift 中通过不同的方法使用 Scenekit 来执行此操作。

只要我让 Scenekit 完成所有工作,一切都很好并且完美反映(参见下面的图 1)——换句话说,使用金属度 1.0、粗糙度 0.0 和颜色 UIColor.white 的标准 SCNMaterial(使用 .physicallyBased 作为光照模型)附加到节点几何体的 firstMaterial(包括定向光)。

但目标是使用 SCNProgram 而不是 ,(附加到节点的 material)具有自己的顶点和片段着色器 - 对应于关于它的苹果文档。我有一个工作场景,但是对象上的反射是错误的(如下图 2 所示)

主要问题是:scn_node 中哪些矩阵值 是正确的scn_frame(在 shaders.metal 文件中)使用,以在对象上获得与图 1 中 Scenekit 相同的反射。但是仅将 SCNProgram 与着色器一起使用(并且没有光)。不幸的是,Apple 没有提供很多关于 SCNProgram 提交给着色器的不同矩阵的信息,以及哪个矩阵用于什么目的 - 或者种类的例子。

这是我当前的顶点着色器,我假设其中使用了一些错误的矩阵(我留下了一些注释掉的代码,以显示已经测试过的内容,而不是注释掉的代码对应于 1:1 图 2) :

vertex SimpleVertexChromeOrig myVertexChromeOrig(MyVertexInput in [[ stage_in ]],
                              constant SCNSceneBuffer& scn_frame [[buffer(0)]],
                              constant MyNodeBuffer& scn_node [[buffer(1)]])
{

SimpleVertexChromeOrig OUT;

OUT.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
// OUT.position = scn_frame.viewProjectionTransform * float4(in.position, 1.0);

float4 eyeSpacePosition  = scn_frame.viewTransform * float4(in.position, 1.0);
float3 eyeSpaceEyeVector = normalize(-eyeSpacePosition).xyz;


// float3 eyeSpaceNormal  = normalize(scn_frame.inverseViewTransform * float4(in.normal, 1.0)).xyz;
float3 eyeSpaceNormal  = normalize(scn_node.normalTransform * float4(in.normal, 1.0)).xyz;

// Reflection and Refraction Vectors
float3 eyeSpaceReflection = reflect(-eyeSpaceEyeVector, eyeSpaceNormal);
OUT.worldSpaceReflection  = (scn_node.inverseModelViewTransform * float4(eyeSpaceReflection, 1.0)).xyz;
// OUT.worldSpaceReflection  = (scn_node.modelViewTransform * float4(eyeSpaceReflection, 1.0)).xyz;
// OUT.worldSpaceReflection  = (scn_node.modelTransform * float4(eyeSpaceReflection, 1.0)).xyz;

return OUT;
}

这是当前的片段着色器(立方体贴图采样器的默认设置):

fragment float4 myFragmentChromeOrig(SimpleVertexChromeOrig in [[stage_in]],
                texturecube<float, access::sample> cubeTexture [[texture(0)]],
                sampler cubeSampler [[sampler(0)]])
{

float3 reflection = cubeTexture.sample(cubeSampler, in.worldSpaceReflection).rgb;

float4 color;
color.rgb = reflection;
color.a   = 1.0;

return color;
}

这是我从 NodeBuffer 获得的矩阵(由 SCNProgram 自动提供)——它们必须只在着色器文件的结构中定义才能像这样访问:

struct MyNodeBuffer {
    float4x4 modelTransform;
    float4x4 inverseModelTransform;
    float4x4 modelViewTransform;
    float4x4 inverseModelViewTransform;
    float4x4 normalTransform;
    float4x4 modelViewProjectionTransform;
    float4x4 inverseModelViewProjectionTransform;
};

这是顶点输入结构:

typedef struct {
    float3 position [[ attribute(SCNVertexSemanticPosition) ]];
    float3 normal [[ attribute(SCNVertexSemanticNormal) ]]; // Phil
} MyVertexInput;

这是由顶点着色器填充的 Stuct:

struct SimpleVertexChromeOrig
{
    float4 position [[position]];
    float3 worldSpaceReflection;
};

(天空盒始终通过包含六张图像的 SCNMaterialContent 属性 提供并附加到 sceneView.scene.background.contents)

有许多可能的公式适用于此,但我在下面包含了一个似乎对我有用的公式。评论解释了每个步骤。

vertex SimpleVertexChromeOrig myVertexChromeOrig(MyVertexInput in [[stage_in]],
                                                 constant SCNSceneBuffer& scn_frame [[buffer(0)]],
                                                 constant MyNodeBuffer& scn_node [[buffer(1)]])
{
    float4 modelSpacePosition(in.position, 1.0f);
    float4 modelSpaceNormal(in.normal, 0.0f);

    // We'll be computing the reflection in eye space, so first we find the eye-space
    // position. This is also used to compute the clip-space position below.
    float4 eyeSpacePosition = scn_node.modelViewTransform * modelSpacePosition;

    // We compute the eye-space normal in the usual way.
    float3 eyeSpaceNormal = (scn_node.normalTransform * modelSpaceNormal).xyz;

    // The view vector in eye space is just the vector from the eye-space position.
    float3 eyeSpaceViewVector = normalize(-eyeSpacePosition.xyz);

    // To find the reflection vector, we reflect the (inbound) view vector about the normal.
    float4 eyeSpaceReflection = float4(reflect(-eyeSpaceViewVector, eyeSpaceNormal), 0.0f);

    // To sample the cubemap, we want a world-space reflection vector, so multiply
    // by the inverse view transform to go back from eye space to world space.
    float3 worldSpaceReflection = (scn_frame.inverseViewTransform * eyeSpaceReflection).xyz;

    SimpleVertexChromeOrig out;
    out.position = scn_frame.projectionTransform * eyeSpacePosition;
    out.worldSpaceReflection = worldSpaceReflection;
    return out;
}

fragment float4 myFragmentChromeOrig(SimpleVertexChromeOrig in [[stage_in]],
                                     texturecube<float, access::sample> cubeTexture [[texture(0)]],
                                     sampler cubeSampler [[sampler(0)]])
{
    // Since the reflection vector's length will vary under interpolation, we normalize it
    // and flip it from the assumed right-hand space of the world to the left-hand space
    // of the interior of the cubemap.
    float3 worldSpaceReflection = normalize(in.worldSpaceReflection) * float3(1.0f, 1.0f, -1.0f);

    float3 reflection = cubeTexture.sample(cubeSampler, worldSpaceReflection).rgb;
    float4 color;
    color.rgb = reflection;
    color.a   = 1.0;
    return color;
}