#ios #metal #metalkit
Вопрос:
Попытка создать отложенную визуализацию надписей на экранах в металле, следуя этой статье. Хотя, кажется, не могу этого понять…
Это границы деколи…
Фактический результат…
Потенциальная проблема
Так что, по-видимому, он не думает, что наклейка пересекает сетку, я правильно выбираю значение глубины, но затем при расчете фактического положения пикселя в 3D-пространстве что-то не складывается.
Код
vertex VertexOut vertex_decal(
const VertexIn in [[ stage_in ]],
constant DecalVertexUniforms amp;uniforms [[ buffer(2) ]]
) {
VertexOut out;
out.position = uniforms.projectionMatrix * uniforms.viewMatrix * uniforms.modelMatrix * in.position;
out.viewPosition = (uniforms.viewMatrix * uniforms.modelMatrix * in.position).xyz;
out.normal = uniforms.normalMatrix * in.normal;
out.uv = in.uv;
return out;
}
fragment float4 fragment_decal(
const VertexOut in [[ stage_in ]],
constant DecalFragmentUniforms amp;uniforms [[ buffer(3) ]],
depth2d<float, access::sample> depthTexture [[ texture(0) ]]
) {
constexpr sampler textureSampler (mag_filter::nearest, min_filter::nearest);
float2 resolution = float2(
depthTexture.get_width(),
depthTexture.get_height()
);
float2 textureCoordinate = in.position.xy / resolution;
float depth = depthTexture.sample(textureSampler, textureCoordinate);
float3 viewRay = in.viewPosition * (uniforms.farClipPlane / in.viewPosition.z);
float3 viewPosition = viewRay * depth;
float3 worldPositon = (uniforms.inverseViewMatrix * float4(viewPosition, 1)).xyz;
float3 objectPositon = (uniforms.inverseModelMatrix * float4(worldPositon, 1)).xyz;
float distX = 0.5 - abs(objectPositon.x);
float distY = 0.5 - abs(objectPositon.y);
float distZ = 0.5 - abs(objectPositon.z);
if(distX > 0 amp;amp; distY > 0 amp;amp; distZ > 0) {
return float4(1, 0, 0, 0.5);
} else {
discard_fragment();
}
}
Редактировать:
Немного продвинулся вперед, теперь он, по крайней мере, что-то визуализирует, он правильно закрепляет наклейку, как только она выходит за пределы некоторой сетки, но детали на сетке все еще не совсем правильные.. чтобы быть точным, он также отображает стороны коробки, которые перекрываются сеткой под наклейкой (вы можете видеть это на изображении ниже, так как красный цвет там немного темнее).
И чтобы добавить больше деталей, структура глубины передается из предыдущего «прохода», поэтому она содержит только icosphere, и шейдер куба деколи не записывает в структуру глубины, а просто считывает из нее.
и трафарет глубины определяется как…
let stencilDescriptor = MTLDepthStencilDescriptor()
stencilDescriptor.depthCompareFunction = .less
stencilDescriptor.isDepthWriteEnabled = false
и конвейер визуализации определяется как…
let renderPipelineDescriptor = MTLRenderPipelineDescriptor()
renderPipelineDescriptor.vertexDescriptor = vertexDescriptor
renderPipelineDescriptor.vertexFunction = vertexLibrary.makeFunction(name: "vertex_decal")
renderPipelineDescriptor.fragmentFunction = fragmentLibrary.makeFunction(name: "fragment_decal")
if let colorAttachment = renderPipelineDescriptor.colorAttachments[0] {
colorAttachment.pixelFormat = .bgra8Unorm
colorAttachment.isBlendingEnabled = true
colorAttachment.rgbBlendOperation = .add
colorAttachment.sourceRGBBlendFactor = .sourceAlpha
colorAttachment.destinationRGBBlendFactor = .oneMinusSourceAlpha
}
renderPipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm
renderPipelineDescriptor.depthAttachmentPixelFormat = .depth32Float
таким образом, текущая проблема заключается в том, что он отбрасывает только пиксели, которые находятся вне сетки, на которую он проецируется, вместо всех пикселей, которые находятся «над» поверхностью icosphere
Новый Код Шейдера
fragment float4 fragment_decal(
const VertexOut in [[ stage_in ]],
constant DecalFragmentUniforms amp;uniforms [[ buffer(3) ]],
depth2d<float, access::sample> depthTexture [[ texture(0) ]]
) {
constexpr sampler textureSampler (mag_filter::nearest, min_filter::nearest);
float2 resolution = float2(
depthTexture.get_width(),
depthTexture.get_height()
);
float2 textureCoordinate = in.position.xy / resolution;
float depth = depthTexture.sample(textureSampler, textureCoordinate);
float3 screenPosition = float3(textureCoordinate * 2 - 1, depth);
float4 viewPosition = uniforms.inverseProjectionMatrix * float4(screenPosition, 1);
float4 worldPosition = uniforms.inverseViewMatrix * viewPosition;
float3 objectPosition = (uniforms.inverseModelMatrix * worldPosition).xyz;
if(abs(worldPosition.x) > 0.5 || abs(worldPosition.y) > 0.5 || abs(worldPosition.z) > 0.5) {
discard_fragment();
} else {
return float4(1, 0, 0, 0.5);
}
}
Комментарии:
1. Здесь определенно недостаточно кода, чтобы понять, что происходит. Кроме того, привязка текстуры глубины, в которую вы рендерите, или это отдельная текстура?
2. Просто, парень, отредактировал вопрос, чтобы добавить больше деталей, помимо достижения еще большего прогресса, о котором там также упоминается… этого достаточно? Спасибо! что касается текстуры глубины, то это текстура глубины icosphere, шейдер деколи не записывает текстуру глубины
3. Я имею в виду, связана ли текстура глубины как вложение глубины, одновременно считываясь с нее?
4. да, это связано также с привязанностью к глубине
5. Вы не можете сделать это в металле, выборка из прикрепленной текстуры является неопределенным поведением. Он может работать на некоторых машинах, но на других он выйдет из строя непредсказуемым образом.
Ответ №1:
Наконец-то удалось заставить его работать должным образом, так что окончательный код шейдера таков…
проблемы, с которыми столкнулся последний шейдер, были…
- Перевернутая ось Y в положении экрана
- Не преобразует положение объекта в пространство NDC (
localPosition
)
fragment float4 fragment_decal(
const VertexOut in [[ stage_in ]],
constant DecalFragmentUniforms amp;uniforms [[ buffer(3) ]],
depth2d<float, access::sample> depthTexture [[ texture(0) ]],
texture2d<float, access::sample> colorTexture [[ texture(1) ]]
) {
constexpr sampler depthSampler (mag_filter::linear, min_filter::linear);
float2 resolution = float2(
depthTexture.get_width(),
depthTexture.get_height()
);
float2 depthCoordinate = in.position.xy / resolution;
float depth = depthTexture.sample(depthSampler, depthCoordinate);
float3 screenPosition = float3((depthCoordinate.x * 2 - 1), -(depthCoordinate.y * 2 - 1), depth);
float4 viewPosition = uniforms.inverseProjectionMatrix * float4(screenPosition, 1);
float4 worldPosition = uniforms.inverseViewMatrix * viewPosition;
float4 objectPosition = uniforms.inverseModelMatrix * worldPosition;
float3 localPosition = objectPosition.xyz / objectPosition.w;
if(abs(localPosition.x) > 0.5 || abs(localPosition.y) > 0.5 || abs(localPosition.z) > 0.5) {
discard_fragment();
} else {
float2 textureCoordinate = localPosition.xy 0.5;
float4 color = colorTexture.sample(depthSampler, textureCoordinate);
return float4(color.rgb, 1);
}
}
Окончательные результаты выглядят так (красные пиксели сохраняются, синие пиксели отбрасываются)…