- Soy demasiado joven para morir.
SceneKit es un marco de gráficos 3D de alto nivel en iOS que ayuda a crear escenas y efectos animados. Incluye un motor físico, un generador de partículas y un conjunto de acciones simples para objetos 3D que le permiten describir la escena en términos de contenido (geometría, materiales, iluminación, cámaras) y animarla a través de una descripción de los cambios para estos objetos.

Hoy veremos SceneKit con una mirada atenta y ligeramente severa, pero primero, repasemos lo básico y veamos cómo es la escena 3D y qué se debe hacer para crearla.

La escena más simple de tres nodos con geometría en ellos.
Primero debe crear la estructura básica de la escena, que consiste en nodos o nodos de la escena. Cada nodo puede contener geometría y otros nodos. La geometría puede ser simple, como una bola, cubo o pirámide, o más compleja, creada en editores externos.

Materiales de recubrimiento
Luego, para esta geometría, debe especificar materiales que determinarán la representación básica de los objetos. Cada material establece su propio modelo de iluminación y, dependiendo de él, utiliza un conjunto diferente de propiedades . Cada una de estas propiedades suele ser un color o textura, pero además de estas opciones de uso común, también existe la opción de usar CALayer , AVPlayer y SKScene .

Agregar fuentes de iluminación
Después de eso, es necesario agregar fuentes de luz que determinen qué tan bien los objetos son visibles en una u otra parte de la escena. Ellos, por analogía con la geometría, deben estar dentro de un nodo. SceneKit admite muchos tipos diferentes de iluminación , así como varios tipos de sombras .

Fuera de la caja Efecto Boke
Luego, debe crear una cámara (y colocarla en un nodo separado) y establecer los parámetros básicos para ella. Hay muchos, pero con la ayuda de ellos puedes crear efectos geniales. Fuera de la caja, se admiten bokeh (o desenfoque), HDR con adaptación, brillo, SSAO y modificaciones de tono / saturación.

Animaciones simples en SceneKit
Finalmente, SceneKit incluye un conjunto simple de acciones para objetos 3D que le permiten establecer cambios de escena con el tiempo. SceneKit también admite acciones descritas en JavaScript , pero este es un tema para un artículo separado.

¡La interacción de un generador de partículas con un motor físico puede provocar un tornado!
Además de los gráficos, las características principales de SceneKit son el generador de partículas y un motor físico avanzado que le permite establecer propiedades físicas reales para los objetos ordinarios y las partículas del generador.
Se ha escrito una gran cantidad de tutoriales detallados sobre todos estos chips. Pero en el proceso de desarrollo, prácticamente no usamos estas oportunidades ...
Oye, no muy rudo
Una vez que escribí un modelo de iluminación para juegos en 3D mejor que la luz solar real, que proporciona un FPS aceptable en el Nvidia 8800, pero decidí no lanzar el motor, porque Dios es amable conmigo y no quiero mostrar su incompetencia en este asunto.
- John Carmack
Comenzaremos un estudio detallado con una tarea bastante simple que surge para casi todos los que trabajan bastante en serio con SceneKit: ¿cómo cargar un modelo con geometría compleja y materiales conectados, iluminación e incluso animaciones?
Hay varias formas, y todas tienen sus pros y sus contras:
SCNScene (named :) - obtiene una escena de un paquete,
SCNScene (url: opciones :): carga la escena por URL,
SCNScene (mdlAsset :): convierte una escena de diferentes formatos,
SCNReferenceNode (url :) - carga perezosamente la escena.
Obtén la escena del paquete
Puede usar el método estándar : coloque nuestro modelo en formato dae o scn en el paquete scnassets y cárguelo desde allí por analogía con UIImage (llamado :).
Pero, ¿qué sucede si desea controlar la actualización de los modelos usted mismo sin publicar una actualización en la App Store cada vez que necesite cambiar un par de texturas? O suponga que necesita admitir mapas y modelos creados por el usuario. O bien, que simplemente no desea aumentar el tamaño de la aplicación, ya que los gráficos en 3D no son la funcionalidad principal.
Cargando la escena por URL
Puede usar el constructor de escenas desde la URL del archivo scn. Este método admite la descarga no solo del sistema de archivos, sino también de la red, pero en este último caso, puede olvidarse de la compresión. Además, debe convertir el modelo al formato scn de antemano. Puede, por supuesto, usar dae, pero viene con un conjunto de restricciones. Por ejemplo, la falta de renderizado basado físicamente.
La principal ventaja de este método es que le permite configurar de manera flexible la configuración de importación . Puede, por ejemplo, modificar el ciclo de vida de las animaciones y hacer que se repitan sin cesar. Puede especificar explícitamente la fuente para cargar recursos externos como texturas, puede convertir la orientación y la escala de la escena, crear normales faltantes para la geometría, fusionar toda la geometría de la escena en un nodo grande o descartar todos los elementos de la escena que no se ajustan al formato estándar.
La tercera opción es usar el constructor con MDLAsset . Es decir, primero creamos un MDLAsset , disponible en el marco ModelIO, y luego lo pasamos al constructor de la escena.
Esta opción es buena porque le permite descargar muchos formatos diferentes. Oficialmente, MDLAsset puede cargar los formatos obj, ply, stl y usd, pero después de ejecutar una lista de todos los formatos posibles, al menos de alguna manera relacionados con los gráficos de computadora, encontré cuatro más: abc, bsp, vox y md3, pero es posible que no sean totalmente compatibles o no en todos los sistemas, y para ellos debe verificar la corrección de la importación.
También es necesario tener en cuenta que este método tiene una sobrecarga para la conversión y usarlo con mucho cuidado.
Estos métodos tienen una trampa común: devuelven SCNScene, no SCNNode. La única forma de agregar contenido a una escena existente es copiar todos los nodos secundarios y, puede omitir fácilmente este paso, animaciones desde el nodo raíz (por ejemplo, pueden aparecer allí cuando se trabaja con dae). Además, debe tener en cuenta que en la escena solo puede haber un entorno de textura (si no utiliza sombreadores personalizados para los reflejos).
Perezosamente cargando la escena
La cuarta opción es usar SCNReferenceNode . No devuelve una escena, sino un nodo, que puede perezosamente (o bajo pedido) cargar en sí mismo toda la jerarquía de la escena. Por lo tanto, este método es similar al primero, pero oculta dentro de sí todos los problemas de copia.
Tiene una cosa más: los parámetros globales de la escena se pierden.
Resulta que esta es la forma más fácil y rápida de descargar su modelo, pero si necesita ajustar archivos, el primer método será mejor.
Como resultado, nos decidimos por la primera opción, ya que era más conveniente para nosotros trabajar en formato SCN, y para los diseñadores: convertir el formato DAE a este. Además, necesitábamos animaciones de ajuste de archivos en el arranque.
Nada de optimizaciones prematuras
Después de haber jugado con este proceso durante mucho tiempo, puedo darte algunos consejos.
El consejo más importante es convertir los archivos a scn por adelantado. Luego, al abrir el archivo en el editor de escenas incorporado en Xcode, puede ver cómo se verá su objeto en SceneKit.
Además, de hecho, el archivo scn es solo una representación binaria de la escena, por lo que cargarlo tomará menos tiempo. Para el mismo día, primero debe analizar el xml, luego convertir todas las mallas, animaciones y materiales. Además, la conversión de animaciones y materiales es una fuente potencial de problemas. Recordamos la falta de soporte PBR en dae: resulta que si desea usarlo, tendrá que cambiar el tipo de todos los materiales después de la conversión y colocar manualmente las texturas apropiadas.
Con esta operación, puede obtener un efecto secundario muy útil: compresión de textura significativa. Es suficiente abrirlos en la "Vista" y exportarlos, cambiando el formato a heic. En promedio, esta operación simple ahorró 5 megabytes por modelo.
Además, si está descargando una escena de Internet, puedo aconsejarle que la descargue en el archivo, lo desempaquete y transfiera la URL del archivo SCN desempaquetado. Esto le ahorrará a usted y al usuario megabytes adicionales, lo que, a su vez, acelerará la descarga y también reducirá el número de puntos de falla. De acuerdo: realizar una solicitud por separado para cada recurso externo, e incluso en Internet móvil, no es la mejor manera de aumentar la confiabilidad.
Lastimame mucho
Cuando conduzco un automóvil, a menudo escucho el disco duro del universo crujir, cargando la siguiente calle.
- John Carmack
Entonces, cuando el trabajo de cargar e importar modelos se pone en marcha, surge una nueva tarea: agregar varios efectos y características a la escena. Y créeme, hay algo que contar. Comenzamos por las diferentes constantes en SceneKit.

Las restricciones en SceneKit se consideran inmediatamente después de la física. Y antes de renderizar el marco
¿Restricciones, dices? ¿Cuáles son las constantes? Pocas personas lo saben, y aún más lo hablan, pero SceneKit tiene su propio conjunto de constantes. Y aunque no son tan flexibles como las constantes en UIkit, aún puedes hacer muchas cosas interesantes con ellos.

SCNReplicatorConstraint
Comencemos con una constante simple: SCNReplicatorConstraint . Todo lo que hace es duplicar la posición, rotación y tamaño de otro objeto con desplazamientos adicionales. Como con todas las demás constantes, puede cambiar la fuerza y establecer la bandera de incrementalidad. Ambos parámetros se pueden mostrar mejor en esta constante.

Fuerza reducida 10 veces
La fuerza afecta la cantidad de transformación que se aplica al objeto. Y dado que la posición del objeto objetivo cambia cada cuadro, el objeto sombra se acerca a una décima parte de la diferencia de distancia. Debido a esto, aparece un efecto de retraso.

Incremento incrementado y fuerza reducida en 10 veces
La incrementalidad , a su vez, afecta si la constante se cancela después de la representación. Supongamos que lo apagamos. Luego vemos que en cada cuadro, la constante se aplica antes de la representación, y después de la representación se cancela, por lo que se repite cada cuadro. Como resultado, combinando estos dos parámetros, puede obtener un efecto bastante interesante de las manecillas del reloj.

El avión siempre mira a la cámara.
Pasemos a una constante más interesante: la llamada cartelera.
Supongamos que es necesario que algún objeto esté siempre "frente a nosotros". Para hacer esto, simplemente use SCNBillboardConstraint , indique qué ejes puede rotar el objeto. Además, antes de calcular cada cuadro (después de un paso con la física), las posiciones y orientaciones de todos los objetos se actualizarán para satisfacer todas las constantes.
Aquí puede mencionar Look Restricción : es similar a una valla publicitaria, solo el objeto se puede configurar frente a cualquier otro objeto en la escena en lugar de la cámara actual.
¿Qué se puede hacer con su ayuda? Por supuesto, con mayor frecuencia estas constantes se usan para dibujar árboles u objetos pequeños. También crean efectos especiales como fuego o explosión. Además, con su ayuda, puede hacer que la cámara siga al objeto en el escenario.

Mantiene la distancia entre los objetos.
SCNDistanceConstraint le permite establecer la distancia mínima y / o máxima a la posición de otro objeto. Y sí, puedes usarlo para hacer una serpiente. :) Esta restricción también se puede usar para unir la cámara al personaje, aunque la posición de la cámara suele ser más complicada, y describirla solo con restricciones no es una tarea fácil. El mismo efecto se puede lograr agregando un resorte en el motor físico, pero este resorte se puede complementar con una tensión en caso de que necesite evitar problemas con el estiramiento o la compresión excesiva del resorte.
Muchos han visto en algunos Hitman, Fallout o Skyrim: arrastras un cuerpo contigo, toca un obstáculo y comienza a comportarse como si un demonio hubiera entrado en él. Esta constante ayudaría a evitar tales errores.

SCNSliderConstraint
SCNSliderConstraint le permite establecer la distancia mínima entre un objeto determinado y cuerpos físicos con una máscara de colisión adecuada. Constante bastante divertido, pero de nuevo, intentan simularlo mediante la interacción física. La idea principal es establecer el radio de la zona muerta con cuerpos físicos para un objeto que no tiene un cuerpo físico.

Cinemática inversa en el trabajo
SCNIKConstraint es la constante más interesante, pero también la más compleja, que utiliza la llamada cinemática inversa. Utilizando una cadena de nodos principales, la cinemática inversa intenta iterativamente llevar la posición del nodo a la que aplica esta constante al punto necesario. De hecho, le permite no pensar en qué posición deberían estar el hombro y el antebrazo, sino simplemente establecer la posición de la mano y los posibles ángulos de rotación de los nodos de conexión. El resto será contado por ti. El principal inconveniente de esta restricción es que le permite establecer solo la posición de la mano, pero no su orientación, y las restricciones en los ángulos pueden hacerse globales, sin romper los ejes.
Entonces, nos reunimos en detalle con las constantes y con lo que saben hacer. Sigamos explorando efectos interesantes. Nos ocuparemos del efecto de las sombras.

Hay un avión, pero no es
¿Parecería que podría ser más fácil en un motor que admite sombras que crear sombras? Pero a veces las sombras deben proyectarse en un plano completamente transparente. Esto es muy útil en ARKit, ya que la imagen de la cámara se muestra detrás del avión, y la sombra debe proyectarse en algún lugar. El truco resulta ser bastante simple: primero debe habilitar las sombras diferidas y desactivar la grabación en todos los componentes del plano en la pestaña de material, y la sombra continuará solapándose. El único problema es que este plano se superpondrá a los objetos detrás de él.
Pero las sombras no son el único efecto poco estudiado en SceneKit. Tratemos con los espejos ahora.

SCNFloor mirror: lo que podría ser más simple
Todos los que jugaron con SceneKit probablemente sepan sobre scnfloor, que agrega reflejos de espejo al piso. Pero por alguna razón, muy pocos lo usan para reflexiones honestas de espejos, porque puedes poner tu modelo en la geometría del piso, inclinarlo un poco y convertirlo ... en un espejo ordinario.


Goteo sobre vidrio y espejo curvo
Pero, lo que es aún menos conocido, se puede establecer un mapa normal para este género. Debido a esto, a su vez, puede crear muchos efectos interesantes diferentes, como el efecto de rayas o un espejo curvo.
Ultravioleta
Una vez besé a una chica con los ojos abiertos. La niña se cortó la cara con el plano de recorte. Desde entonces beso solo con los ojos cerrados.
- John Carmack
Sombras, espejos: efectos interesantes. Pero hay un efecto que, cuando se usa con habilidad, puede resultar aún más interesante: las texturas de video.


Videos ordinarios y de altura
Es posible que los necesites solo para mostrar el video dentro del juego. Pero es mucho más interesante que con la ayuda de texturas de video pueda modificar la geometría. Para hacer esto, debe colocar la textura del video con un mapa de altura en la propiedad de desplazamiento de su material y usar el material en un plano con un número suficientemente grande de segmentos . Queda por entender cómo ponerlo allí.
Mencioné en la descripción del proceso de creación de escenas que puede usar SKScene como una propiedad material, y esta es una escena de SpriteKit. SpriteKit es como SceneKit, pero para gráficos 2D. Tiene soporte para mostrar videos usando SKVideoNode . Solo necesita poner SKVideoNode en SKScene y SKScene en SCNMaterialProperty, y ya está.
Pero después de exportar la escena 3D resultante y abrirla en otro lugar, veremos un cuadrado negro. Revolviendo el archivo scn, encontré la razón. Resulta que al guardar un código de video, no guarda la URL del video. Parece que tomas y gobiernas. Pero no todo es tan simple: el archivo scn es un llamado plist binario, que contiene el resultado de NSKeyedArchiver. Y el material, que es la escena SpriteKit, es el mismo plist binario, que resulta que ya se encuentra dentro de otro plist binario. Es bueno que solo haya dos niveles de anidamiento.
Bueno, ahora incluso pasaremos al efecto, pero a una herramienta que te permite crear cualquier tipo de efectos. Estos son modificadores de sombreador.
Antes de modificar algo, debe comprender lo que estamos modificando. Un sombreador, por definición, es un programa para la GPU que se ejecuta para cada vértice y para cada píxel. Por lo tanto, un sombreador es un programa que determina cómo se ve un objeto en la pantalla.
Bueno, los modificadores de sombreado le permiten cambiar los resultados de sombreadores estándar a GLSL o lenguaje de sombreado de metal. También están disponibles en un editor visual, que le permite ver los cambios en el modificador en tiempo real.


Mapeo de pieles y paralaje
Con la ayuda de modificadores de sombreador, puede crear efectos visuales complejos. Por ejemplo, algunos de los efectos más famosos: mapeo de pieles y paralaje .
#pragma arguments texture2d bg; texture2d height; float depth; float layers; #pragma transparent #pragma body constexpr sampler sm = sampler(filter::linear, s_address::repeat, t_address::repeat); float3 bitangent = cross(_surface.tangent, _surface.normal); float2 direction = float2(-dot(_surface.view.rgb, _surface.tangent), dot(_surface.view.rgb, _surface.bitangent)); _output.color.rgba = float4(0); for(int i = 0; i < int(floor(layers)); i++) { float coeff = float(i) / floor(layers); float2 defaultCoords = _surface.diffuseTexcoord + direction * (1 - coeff) * depth; float2 adjustment = float2(scn_frame.sinTime + defaultCoords.x, scn_frame.cosTime) * depth * coeff * 0.1; float2 coords = defaultCoords + adjustment; _output.color.rgb += bg.sample(sm, coords).rgb * coeff * (height.sample(sm, coords).r + 0.1) * (1.0 - coeff); _output.color.a += (height.sample(sm, coords).r + 0.1) * (1.0 - coeff); } return _output;

Ray Casting con cáusticos en tiempo real
Más interesante aún, nadie se molesta en tirar por completo los resultados de su trabajo y escribir su propio renderizador. Por ejemplo, puede intentar implementar Ray Casting en sombreadores. Y todo esto funciona lo suficientemente rápido como para proporcionar 30 FPS incluso en cálculos tan complejos. Pero este es un tema para un informe separado. ¡Vamos Mobius !
Pesadilla!
No me gusta parpadear, porque los párpados cerrados cargan bruscamente la GPU para BDPT debido a la falta de iluminación.
- John Carmack
Entonces, tenemos un montón de objetos con efectos geniales. Ahora queda por aprender cómo grabarlos. Para hacer esto, pasemos a un tema más complejo: cómo aprendimos a grabar videos directamente desde SceneKit sin una IU externa y cómo optimizamos esta grabación decenas de veces.
Primero veamos la solución más simple: ReplayKit . Descubre por qué no encaja. En términos generales, esta solución le permite crear una entrada de pantalla en varias líneas de código y guardarla a través de la vista previa del sistema. Pero Tiene un gran inconveniente: registra todo, la interfaz de usuario completa, incluidos todos los botones en la pantalla. Esta fue nuestra primera decisión, pero por razones obvias fue imposible dejarla en producción: los usuarios tenían que compartir el video y no compartirlo desde la vista previa del sistema.
Nos encontramos en una situación en la que la solución necesitaba ser escrita desde cero. Absolutamente desde cero. Entonces, veamos cómo en iOS puede crear su propio video y grabar sus cuadros allí. Todo es bastante simple:

Proceso de grabación
Necesitamos crear una entidad que registre archivos: AVAssetWriter , agregar una transmisión de video, AVAssetWriterInput , y crear un adaptador para esta transmisión que convierta nuestro búfer de píxeles en el formato requerido por la transmisión: AVAssetWriterPixelBufferAdaptor .
Por si acaso, le recuerdo que el búfer de píxeles es una entidad, que es una pieza de memoria donde los datos de los píxeles se escriben de alguna manera. Esto es esencialmente una representación de bajo nivel de la imagen.
Pero, ¿cómo obtener este búfer de píxeles? La solución es simple. SCNView tiene una maravillosa función .snapshot () que devuelve un UIImage. Solo necesitamos crear un búfer de píxeles a partir de este UIImage.
var unsafePixelBuffer: CVPixelBuffer? CVPixelBufferPoolCreatePixelBuffer(NULL, self.pixelBufferPool, &unsafePixelBuffer) guard let pixelBuffer = maybePixelBuffer else { return } CVPixelBufferLockBaseAddress(pixelBuffer, 0) let data = CVPixelBufferGetBaseAddress(pixelBuffer) let rgbColorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) let rowBytes = NSUInteger(CVPixelBufferGetBytesPerRow(pixelBuffer)) let context = CGContext( data: data, width: image.width, height: image.height, bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer), space: rgbColorSpace, bitmapInfo: bitmapInfo.rawValue ) context?.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height)) CVPixelBufferUnlockBaseAddress(pixelBuffer, 0) self.appendPixelBuffer(pixelBuffer, withPresentationTime: presentationTime)
Simplemente asignamos un lugar en la memoria, describimos qué formato tienen estos píxeles, bloqueamos el búfer para cambiar, obtenemos la dirección de memoria, creamos un contexto en la dirección recibida, donde describimos cómo se empaquetan los píxeles, cuántas líneas hay en la imagen y qué espacio de color usamos. Luego copiamos los píxeles de UIImage allí, conociendo el formato final, y desbloqueamos el cambio.

Ahora necesitas hacer esto cada cuadro. Para hacer esto, creamos un enlace de pantalla que llamará a una devolución de llamada para cada fotograma, donde, a su vez, llamaremos al método de instantánea y crearemos un búfer de píxeles a partir de la imagen. ¡Todo es simple!

Pero no Tal solución, incluso en teléfonos potentes, provoca retrasos terribles y reducciones de FPS. Hagamos la optimización.

Digamos que no necesitamos 60 FPS. Incluso estaremos encantados con el 25. Pero, ¿cuál es la forma más fácil de lograr este resultado? Por supuesto, solo necesita poner todo esto en un hilo de fondo. Además, según los desarrolladores, esta función es segura para subprocesos.

Hmm, el retraso se ha vuelto menos, pero el video ha dejado de grabar ...
Todo es simple Como dicen, si tiene un problema y lo resolverá con la ayuda de varios hilos, tendrá 2 problemas.
Si intenta grabar un búfer de píxeles con una marca de tiempo inferior a la última grabada, el video completo no será válido.

Entonces, no escribamos un nuevo búfer hasta que finalice la escritura anterior.

Hmm, se puso mucho mejor. Pero de todos modos, ¿por qué aparecieron los retrasos inicialmente?

Resulta que la función .snapshot () , con la que obtenemos una imagen de la pantalla, crea un nuevo renderizador para cada llamada, dibuja un marco desde cero y lo devuelve, no la imagen que está en la pantalla. Esto lleva a efectos divertidos. Por ejemplo, la simulación física es dos veces más rápida.
Pero espere, ¿por qué tratamos de renderizar un nuevo marco cada vez? Seguramente en algún lugar puede encontrar el búfer que se muestra en la pantalla. De hecho, hay acceso a ese búfer, pero es muy no trivial. Necesitamos obtener CAMetalDrawable de Metal.
Desafortunadamente, llegar a Metal directamente desde SCNView no es tan fácil por una razón bastante comprensible: en SceneKit puede elegir el tipo de API usted mismo, pero si mira debajo del capó y mira la capa , puede ver que actúa como tal, en el caso de Metal, CAMetalLayer .
Pero aquí también nos espera el fracaso: en CAMetalLayer, la única forma de interactuar con la vista es la función nextDrawable, que devuelve un CAMetalDrawable desocupado. Se entiende que escribirá datos en él y llamará a la función presente en él, que lo mostrará en la pantalla.
La solución realmente existe. El hecho es que después de desaparecer de la pantalla, el búfer no se desasigna, sino que solo se vuelve a colocar en el grupo. De hecho, ¿por qué asignar memoria cada vez si dos o tres buffers son suficientes ?: uno se muestra en la pantalla, el segundo para renderizar y el tercero, por ejemplo, para el postprocesamiento, si tiene uno.
Resulta que después de mostrar el búfer, los datos de él no desaparecen en ningún lugar y puede acceder a ellos de manera segura.
Y si en el sucesor comenzamos en respuesta a cada llamada a nextDrawable () para guardarlo, obtenemos casi lo que necesitamos. El problema es que el CAMetalDrawable guardado es aquel en el que se está dibujando la imagen en este momento.
El salto a la solución real es muy simple: guardamos tanto el Drawable actual como el anterior.
Y aquí está, listo: acceso directo a la memoria a través de CAMetalDrawable.
var unsafePixelBuffer: CVPixelBuffer? CVPixelBufferPoolCreatePixelBuffer(NULL, self.pixelBufferPool, &unsafePixelBuffer) guard let pixelBuffer = maybePixelBuffer else { return } CVPixelBufferLockBaseAddress(pixelBuffer, 0) let data = CVPixelBufferGetBaseAddress(pixelBuffer) let width: NSUInteger = lastDrawable.texture.width let height: NSUInteger = lastDrawable.texture.height let rowBytes: NSUInteger = NSUInteger(CVPixelBufferGetBytesPerRow(pixelBuffer) lastDrawable.texture.getBytes( data, bytesPerRow: rowBytes, fromRegion: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0 ) CVPixelBufferUnlockBaseAddress(pixelBuffer, 0) self.appendPixelBuffer(pixelBuffer, withPresentationTime: presentationTime)
Entonces, ahora no creamos un contexto y dibujamos un UIImage en él, sino que copiamos una pieza de memoria a otra. Surge la pregunta: ¿qué pasa con el formato de píxeles? ..
No coincide con deviceColorSpace ... Y no coincide con espacios de color de uso común ...
Este es exactamente el punto donde el autor de uno de los hogares públicos, que realiza la misma tarea, se averió . Todos los demás ni siquiera llegaron aquí.

Bueno, todos estos trucos, ¿por un filtro espeluznante?
Pues no! En el artículo sobre ARKit, puede encontrar una mención de que la imagen de la cámara no utiliza el espacio de color estándar, sino que se expande. E incluso se presenta una matriz de transformación del espacio de color. Pero, ¿por qué participar en la transformación, si puede intentar grabar directamente en este formato? Queda por descubrir de qué formato está disponible entre 60 ...

Y luego comencé a reventar. Grabé tres videos en diferentes transmisiones con diferentes formatos, reemplazándolos con cada grabación.
Como resultado, en aproximadamente el cuadragésimo formato, obtenemos su nombre. Resulta que no es otro que kCVPixelFormatType_30RGBLEPackedWideGamut . ¿Cómo no adiviné?

Pero mi alegría duró hasta el primer probador. No tuve palabras Como? Acabo de pasar mucho tiempo buscando el formato correcto. Es bueno que el problema se haya localizado rápidamente: el error se reprodujo de manera estable y solo en 6s y 6s Plus. Casi inmediatamente después de eso, recordé que las pantallas con soporte de amplia gama comenzaron a instalarse solo en séptimos iPhones.
¡Cambiando la amplia gama al viejo 32RGBA, obtengo un registro de trabajo! Queda por comprender cómo determinar que el dispositivo admite una amplia gama. Hay iPads con diferentes tipos de pantalla, y pensé que seguramente puede obtener el tipo de pantalla ENUM del sistema. Hurgando en la documentación, la encontré: esta es displayGamut en UITraitCollection .
Después de entregar la asamblea a los probadores, recibí buenas noticias de ellos: ¡todo funcionó sin retrasos, incluso en dispositivos viejos!
En conclusión, quiero decirte: ¡haz gráficos en 3D! En nuestra aplicación, para la cual la realidad aumentada no es el caso de uso principal, durante el fin de semana, ¡la gente viajó más de 2,000 kilómetros, vio más de 3,000 objetos y grabó más de 1,000 videos con ellos! Imagina lo que puedes hacer si lo haces tú mismo.