首页 > 解决方案 > 哪些是在 SCNProgram 传递的金属着色器中使用的正确矩阵值,以获得正确的镀铬,如反射

问题描述

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

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

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

图 1 - 正确的反射

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

图 2 - 错误反射

主要问题是:要使用来自scn_nodescn_frame (在 shaders.metal 文件中)的正确矩阵值,以获得与图 1 中 Scenekit 相同的对象反射。但是将 SCNProgram 与着色器一起使用只有(并且没有光)。不幸的是,Apple 没有提供很多关于 SCNProgram 提交给着色器的不同矩阵的信息,以及使用哪个矩阵或示例类型。

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

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

(Skybox 始终通过包含六个图像的 SCNMaterialContent 属性提供,并附加到 sceneView.scene.background.contents)

标签: reflectionshaderscenekitmetalscnprogram

解决方案


有许多可能的公式可以解决这个问题,但我在下面列出了一个似乎对我有用的公式。注释解释了每个步骤。

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

推荐阅读