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