Какие значения матрицы следует использовать в металлическом шейдере, передаваемом программой SCN, чтобы получить правильное отражение, подобное хрому?

Я работаю над приложением, которое должно отображать хромированный отражающий сферический объект внутри скайбокса (с использованием шестигранной кубической карты).

Я делаю это в Swift, используя Scenekit с разными подходами.

Все в порядке и идеально отражается (см. Рисунок 1 ниже), пока я позволяю Scenekit выполнять всю работу - другими словами, используя стандартный SCNMaterial с металличностью 1.0, шероховатостью 0,0 и цветом UIColor.white (с использованием.physicallyBased в качестве модели освещения) прикрепленный к firstMaterial геометрии узла (включая направленный свет).

Но цель состоит в том, чтобы вместо этого использовать SCNProgram (прикрепленный к материалу узла) с собственными вершинными и фрагментными шейдерами, соответствующими документации Apple по этому поводу. У меня есть рабочий сценарий, но отражение на объекте неправильное (как вы можете видеть ниже на рисунке 2)

Главный вопрос: какие именно значения Matrix из scn_node или scn_frame (в файле shaders.metal) использовать, чтобы получить такое же отражение на объекте, как Scenekit на рисунке 1. Но использование программы SCN с шейдерами только (и без света). К сожалению, Apple предоставляет не так много информации о различных матрицах, которые загружаются в шейдер программой SCN, и о том, какую из них использовать для чего - или о каких-то примерах.

Вот мой текущий вершинный шейдер, в котором я предполагаю, что использовал некоторые неправильные матрицы (я оставил некоторый код без комментариев, чтобы показать, что уже было протестировано, код без комментариев соответствует 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;
};

Это структура Vertex Input:

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

Это Stuct, заполненный Vertex Shader:

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

(Skybox всегда предоставляется через свойство SCNMaterialContent, содержащее шесть изображений, и прикрепляется к sceneView.scene.background.contents)

1 ответ

Решение

Есть много возможных формулировок, которые подойдут для этого, но я включил одну, которая показалась мне подходящей, ниже. Комментарии объясняют каждый шаг.

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;
}
Другие вопросы по тегам