Índice:
- Etapa 1: iniciar um novo projeto do Unity
- Etapa 2: configurar a cena
- Etapa 3: vamos fazer algumas partículas
- Etapa 4: Retardar as partículas
- Etapa 5: Criando o Portal
- Etapa 6: sombreadores de partículas
- Etapa 7: crie o Skybox
- Etapa 8: Lógica do portal
- Etapa 9: Quase pronto
- Etapa 10: coloque o aplicativo no seu telefone
Vídeo: Portal de realidade aumentada para coisas estranhas: 10 etapas (com fotos)
2024 Autor: John Day | [email protected]. Última modificação: 2024-01-30 11:39
Este Instructable passará pela criação de um aplicativo móvel de realidade aumentada para o iPhone com um portal que leva de cabeça para baixo a Stranger Things. Você pode entrar no portal, caminhar ao redor e voltar para fora. Tudo dentro do portal só pode ser visto através do portal até você entrar. Uma vez lá dentro, tudo será renderizado em todos os lugares, até que você volte para o mundo real. Usaremos o mecanismo de videogame Unity 3D com o plugin Apple ARKit. Todo o software que usaremos pode ser baixado e usado gratuitamente. Você não precisa ser um especialista para acompanhar, nós seguiremos todas as etapas!
Etapa 1: iniciar um novo projeto do Unity
Primeiro, baixe Unity3D e certifique-se de instalar os arquivos de compilação para a plataforma IOS. Você também precisará baixar o Xcode e se inscrever para uma conta gratuita de desenvolvedor da Apple. Seu iPhone também deverá estar executando o IOS 11 ou superior. A partir de hoje, 5 de fevereiro de 2018, o IOS 11.3 foi lançado, mas o xCode 9.2 ainda não possui arquivos de suporte para ele. Portanto, se você estiver executando a versão mais recente do IOS, certifique-se de baixar a versão beta do Xcode mais recente em Apple. Developer.com.
Depois de ter todos os programas necessários, abra o Unity e comece um novo projeto, chame-o do que quiser. Vamos precisar do plugin Apple ARKit para que possamos usar a câmera do nosso telefone para detectar o solo e objetos no chão. Vamos importá-lo agora, indo para a guia Asset Store e pesquisando "ARKit". Você precisará criar uma conta Unity gratuita se ainda não tiver uma e clique em importar para obter o plug-in.
Navegue até a pasta de exemplos na pasta ARKit e encontre o "UnityARKitScene". Clique duas vezes para abri-lo. Vamos usar essa cena como ponto de partida e começar a partir daqui. Esta cena, por padrão, permitirá que você detecte o solo e quando você tocar na tela, um cubo será colocado nessa posição.
Vamos primeiro ajustar nossas configurações de construção para que não nos esqueçamos de fazer isso mais tarde. Clique em arquivo, crie configurações e remova todas as cenas dessa lista. Clique em adicionar cenas abertas para adicionar a atual. A última coisa que precisamos configurar aqui é nas configurações do player, ir até o identificador do pacote e o formato dessa string é com. YourCompanyName. YourAppName, portanto, no meu caso, eu faço algo como com. MatthewHallberg. PortalTest.
Etapa 2: configurar a cena
Primeiro, olhe para a esquerda e encontre o objeto do jogo chamado "GeneratePlanes". Com isso destacado, olhe para a direita agora e clique na caixa de seleção para desativá-lo. Dessa forma, não temos os quadrados azuis feios sendo gerados quando o ARKit detecta um plano de solo. Em seguida, exclua o objeto de jogo "RandomCube" porque não queremos ver isso em nossa cena.
Agora precisamos primeiro criar nossa porta de portal. Exclua o cubo que é filho de "HitCubeParent". Clique com o botão direito e escolha criar um objeto de jogo vazio. Renomeie-o como "Portal". Agora clique com o botão direito naquele objeto e crie um cubo, isso o tornará um filho do portal. Renomeie-o para "PostLeft" e este será o post esquerdo do nosso portal. Escale-o de forma que x seja 1 ey seja 28 e z seja um. Faça a mesma coisa para a postagem certa. Agora crie o poste superior e dimensione y até 14. Vire-o de lado e mova-o de forma que conecte os outros postes. Faça toda a escala do portal 1,3 x 1,4 x 1.
Vá para o google e digite textura de madeira ou casca. Baixe uma dessas imagens e arraste-a para sua pasta de ativos no Unity. Agora arraste essa imagem para todos os seus posts no portal.
Clique no objeto "Portal" novamente e clique em adicionar componente à direita. Adicione o script "UnityARHitTestExample" a ele. Há um slot vazio lá para "Hit Transform", arraste o objeto "HitCubeParent" para esse slot.
Etapa 3: vamos fazer algumas partículas
Agora vamos usar o sistema de Partículas de Unidade para fazer um efeito de fumaça e partículas flutuantes para dentro de nosso portal. Vá para Ativos na barra de menu superior, ativos padrão e importe sistemas de partículas.
Crie dois objetos de jogo vazios dentro de seu portal e chame um de "SmokeParticles" e o outro de "FloatingParticles".
Adicione um componente do sistema de partículas às partículas de fumaça.
Este componente tem várias opções, mas só precisamos mudar algumas.
Mude a cor inicial para algo azul escuro com cerca de 50% de transparência. Faça a taxa de emissão 100. Forma interna, faça o raio 0,01. Na parte do renderizador, na parte inferior, altere o tamanho mínimo para 0,8 e o tamanho máximo para 5. No componente de material, apenas escolha o material de fumaça da lista, mas vamos mudar isso mais tarde.
Adicione um sistema de partículas ao objeto de jogo de partículas flutuantes agora e defina a emissão para 500. Defina o tempo de vida inicial para 2, o raio para 10, o tamanho mínimo de partícula para 0,01 e o tamanho máximo de partícula para 0,015. Defina o material como partícula padrão por enquanto.
Por fim, pegue os dois objetos do jogo e gire-os 90 graus no xe eleve-os no ar para que eles sejam emitidos para a entrada do portal.
Etapa 4: Retardar as partículas
Como queremos que essas partículas cubram uma grande área, mas também se movam lentamente, precisamos criar nossa própria função de amostra. Então, clique com o botão direito na pasta de ativos e crie um novo script C # e chame-o de "Amostra de Partículas". Copie e cole neste código:
using System. Collections;
using System. Collections. Generic; using UnityEngine; public class ParticleSample: MonoBehaviour {private ParticleSystem ps; // Use para inicialização void Start () {ps = GetComponent (); StartCoroutine (SampleParticleRoutine ()); } IEnumerator SampleParticleRoutine () {var main = ps.main; main.simulationSpeed = 1000f; ps. Play (); rendimento retorna novo WaitForSeconds (.1f); main.simulationSpeed =.05f; }}
Agora arraste este script para cada um dos seus objetos de jogo do sistema de partículas.
Etapa 5: Criando o Portal
Agora precisamos criar o portal, então clique com o botão direito no objeto do jogo do portal e crie um quad. Escale o quadrante para que ele cubra todo o portal, esta vai se tornar a nossa janela do portal. A primeira coisa que precisamos adicionar a ele é o shader de portal, isso apenas renderizará objetos com outro shader específico neles. Clique com o botão direito na pasta de ativos e crie um novo sombreador apagado. Remova tudo lá e cole este código:
Shader "Portal / portalWindow"
{SubShader {Zwrite off Colormask 0 cull off Stencil {Ref 1 Passe substituir} Passe {}}}
Clique com o botão direito na hierarquia e crie um novo material, chame-o de PortalWindowMat, na lista suspensa deste material encontre a seção do portal e escolha a janela do portal. Arraste este material para o seu quadrante portal.
Etapa 6: sombreadores de partículas
Clique com o botão direito na pasta de ativos novamente e crie um novo sombreador. Precisamos fazer os shaders para as partículas que vão dentro do portal. Substitua todo o código por este:
Shader "Portal / Partículas" {
Propriedades {_TintColor ("Tint Color", Color) = (0.5, 0.5, 0.5, 0.5) _MainTex ("Particle Texture", 2D) = "white" {} _InvFade ("Soft Particles Factor", Range (0.01, 3.0)) = 1.0 _Stencil ("stencil", int) = 6} Categoria {Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane"} Blend SrcAlpha OneMinusSrcAlpha ColorMask RGB Cull Off Lighting Off ZWrite Off SubShader {Stencil {Ref 1 Comp [_Stencil]} Passe {CGPROGRAM #pragma vertex vert #pragma frag frag #pragma target 2.0 #pragma multi_compile_particles #pragma multi_compile_fog #include _MainTainTain2. fixed4 _TintColor; struct appdata_t {vértice float4: POSIÇÃO; fixed4 color: COLOR; float2 texcoord: TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID}; struct v2f {vértice float4: SV_POSITION; fixed4 color: COLOR; float2 texcoord: TEXCOORD0; UNITY_FOG_COORDS (1) #ifdef SOFTPARTICLES_ON float4 projPos: TEXCOORD2; #endif UNITY_VERTEX_OUTPUT_STEREO}; float4 _MainTex_ST; v2f vert (appdata_t v) {v2f o; UNITY_SETUP_INSTANCE_ID (v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO (o); o.vertex = UnityObjectToClipPos (v.vertex); #ifdef SOFTPARTICLES_ON o.projPos = ComputeScreenPos (o.vertex); COMPUTE_EYEDEPTH (o.projPos.z); #endif o.color = v.color * _TintColor; o.texcoord = TRANSFORM_TEX (v.texcoord, _MainTex); UNITY_TRANSFER_FOG (o, o.vertex); return o; } UNITY_DECLARE_DEPTH_TEXTURE (_CameraDepthTexture); float _InvFade; fixed4 frag (v2f i): SV_Target {#ifdef SOFTPARTICLES_ON float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ (_CameraDepthTexture, UNITY_PROJ_COORD (i.projPos))); float partZ = i.projPos.z; float fade = saturate (_InvFade * (sceneZ-partZ)); i.color.a * = fade; #endif fixed4 col = 2.0f * i.color * tex2D (_MainTex, i.texcoord); UNITY_APPLY_FOG (i.fogCoord, col); return col; } ENDCG}}}}
Crie dois novos materiais, um denominado portalSmoke e outro denominado portalParticles.
Para cada um escolha este shader, no menu suspenso, nos portais, partículas. Para as partículas de fumaça escolha uma textura de fumaça e para as partículas escolha a textura de partícula. Mude a cor da fumaça para um azul mais escuro com cerca de 50% de transparência. Vá para o componente renderizador de cada sistema de partículas em seu portal e escolha seus respectivos materiais que acabamos de criar.
Etapa 7: crie o Skybox
Agora, para realmente criar o tipo de aparência de cabeça para baixo, temos que tingir tudo de azul escuro. Para isso usaremos um skybox transparente, então faça um novo shader e cole neste código:
Shader "Portal / portalSkybox" {
Propriedades {_Tint ("Tint Color", Color) = (.5,.5,.5,.5) [Gamma] _Exposure ("Exposure", Range (0, 8)) = 1.0 _Rotation ("Rotation", Range (0, 360)) = 0 [NoScaleOffset] _Tex ("Cubemap (HDR)", Cube) = "cinza" {} _Stencil ("StencilNum", int) = 6} SubShader {Tags {"Queue" = "Background" "RenderType" = "Background" "PreviewType" = "Skybox"} Eliminar ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Stencil {Ref 1 Comp [_Stencil]} Passar {CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #include "UnityCG.cginc "samplerCUBE _Tex; half4 _Tex_HDR; half4 _Tint; metade _exposição; float _Rotation; float3 RotateAroundYInDegrees (vértice float3, float graus) {float alfa = graus * UNITY_PI / 180.0; float sina, cosa; sincos (alfa, sina, cosa); float2x2 m = float2x2 (cosa, -sina, sina, cosa); retornar float3 (mul (m, vertex.xz), vertex.y).xzy; } struct appdata_t {vértice float4: POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID}; struct v2f {vértice float4: SV_POSITION; float3 texcoord: TEXCOORD0; UNITY_VERTEX_OUTPUT_STEREO}; v2f vert (appdata_t v) {v2f o; UNITY_SETUP_INSTANCE_ID (v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO (o); float3 rotated = RotateAroundYInDegrees (v.vertex, _Rotation); o.vertex = UnityObjectToClipPos (girado); o.texcoord = v.vertex.xyz; return o; } frag fixo4 (v2f i): SV_Target {half4 tex = texCUBE (_Tex, i.texcoord); half3 c = DecodeHDR (tex, _Tex_HDR); c = c * _Tint.rgb * unity_ColorSpaceDouble.rgb; c * = _Exposição; retornar half4 (c,.5); } ENDCG}} Fallback Off}
Agora crie um novo material de skybox, chame-o de "PortalSkybox" e escolha este shader portalSkybox no menu do portal. Vá para Window, Lighting, no topo e escolha esta skybox que acabamos de criar. Vá para a câmera principal e defina sinalizadores claros para skybox. Enquanto estamos aqui, vamos adicionar alguns componentes em nossa câmera para que possamos detectar colisões. Adicione um componente de corpo rígido à câmera e desmarque usar gravidade. Adicione um colisor de caixa e verifique se ele é acionado. Faça o tamanho dos colliders de caixa 0,5 x 1 x 4. Defina o plano de recorte na câmera para 0,01.
Etapa 8: Lógica do portal
A última coisa que precisamos fazer é criar a lógica que controla nosso portal. Crie um novo script C # e chame-o de PortalController.
using System. Collections;
using System. Collections. Generic; using UnityEngine; namespace UnityEngine. XR.iOS {public class PortalController: MonoBehaviour {public Material materiais; public MeshRenderer meshRenderer; public UnityARVideo UnityARVideo; bool privado isInside = false; bool privado isOutside = true; // Use para inicialização void Start () {OutsidePortal (); } void OnTriggerStay (Collider col) {Vector3 playerPos = Camera.main.transform.position + Camera.main.transform.forward * (Camera.main.nearClipPlane * 4); if (transform. InverseTransformPoint (playerPos).z <= 0) {if (isOutside) {isOutside = false; isInside = true; InsidePortal (); }} else {if (isInside) {isInside = false; isOutside = true; OutsidePortal (); }}} void OutsidePortal () {StartCoroutine (DelayChangeMat (3)); } void InsidePortal () {StartCoroutine (DelayChangeMat (6)); } IEnumerator DelayChangeMat (int stencilNum) {UnityARVideo.shouldRender = false; rendimento retornar novo WaitForEndOfFrame (); meshRenderer.enabled = false; foreach (tapete de material em materiais) {mat. SetInt ("_Stencil", stencilNum); } rendimento retorna novo WaitForEndOfFrame (); meshRenderer.enabled = true; UnityARVideo.shouldRender = true; }}}
Arraste este novo script para a janela do portal. Isso nos fará entrar e sair do portal sempre que o colisor de nossa câmera colidir com a janela do portal. Agora, na função que muda todos os materiais, dizemos ao plugin ARkit para não renderizar o quadro, então vá para a câmera principal e abra o script UnityARVideo. Crie um bool público shouldRender no topo e defina-o como verdadeiro. Abaixo na função OnPreRender (), envolva tudo em uma instrução if, onde tudo dentro só será executado se shouldRender for true. Todo o script deve ser semelhante a este:
using System;
using System. Runtime. InteropServices; using UnityEngine; using UnityEngine. Rendering; namespace UnityEngine. XR.iOS {public class UnityARVideo: MonoBehaviour {public Material m_ClearMaterial; [HideInInspector] public bool shouldRender = true; private CommandBuffer m_VideoCommandBuffer; private Texture2D _videoTextureY; private Texture2D _videoTextureCbCr; Matrix4x4 _displayTransform privada; private bool bCommandBufferInitialized; public void Start () {UnityARSessionNativeInterface. ARFrameUpdatedEvent + = UpdateFrame; bCommandBufferInitialized = false; } void UpdateFrame (UnityARCamera cam) {_displayTransform = new Matrix4x4 (); _displayTransform. SetColumn (0, cam.displayTransform.column0); _displayTransform. SetColumn (1, cam.displayTransform.column1); _displayTransform. SetColumn (2, cam.displayTransform.column2); _displayTransform. SetColumn (3, cam.displayTransform.column3); } void InitializeCommandBuffer () {m_VideoCommandBuffer = new CommandBuffer (); m_VideoCommandBuffer. Blit (null, BuiltinRenderTextureType. CurrentActive, m_ClearMaterial); GetComponent (). AddCommandBuffer (CameraEvent. BeforeForwardOpaque, m_VideoCommandBuffer); bCommandBufferInitialized = true; } void OnDestroy () {GetComponent (). RemoveCommandBuffer (CameraEvent. BeforeForwardOpaque, m_VideoCommandBuffer); UnityARSessionNativeInterface. ARFrameUpdatedEvent - = UpdateFrame; bCommandBufferInitialized = false; } #if! UNITY_EDITOR public void OnPreRender () {if (shouldRender) {ARTextureHandles alças = UnityARSessionNativeInterface. GetARSessionNativeInterface (). GetARVideoTextureHandles (); if (handles.textureY == System. IntPtr. Zero || handles.textureCbCr == System. IntPtr. Zero) {return; } if (! bCommandBufferInitialized) {InitializeCommandBuffer (); } Resolução currentResolution = Screen.currentResolution; // Texture Y if (_videoTextureY == null) {_videoTextureY = Texture2D. CreateExternalTexture (currentResolution.width, currentResolution.height, TextureFormat. R8, false, false, (System. IntPtr) handles.textureY); _videoTextureY.filterMode = FilterMode. Bilinear; _videoTextureY.wrapMode = TextureWrapMode. Repeat; m_ClearMaterial. SetTexture ("_ textureY", _videoTextureY); } // Texture CbCr if (_videoTextureCbCr == null) {_videoTextureCbCr = Texture2D. CreateExternalTexture (currentResolution.width, currentResolution.height, TextureFormat. RG16, false, false, (System. IntPtr) handles.textureCbCr); _videoTextureCbCr.filterMode = FilterMode. Bilinear; _videoTextureCbCr.wrapMode = TextureWrapMode. Repeat; m_ClearMaterial. SetTexture ("_ textureCbCr", _videoTextureCbCr); } _videoTextureY. UpdateExternalTexture (handles.textureY); _videoTextureCbCr. UpdateExternalTexture (handles.textureCbCr); m_ClearMaterial. SetMatrix ("_ DisplayTransform", _displayTransform); }} #else public void SetYTexure (Texture2D YTex) {_videoTextureY = YTex; } public void SetUVTexure (Texture2D UVTex) {_videoTextureCbCr = UVTex; } public void OnPreRender () {if (! bCommandBufferInitialized) {InitializeCommandBuffer (); } m_ClearMaterial. SetTexture ("_ textureY", _videoTextureY); m_ClearMaterial. SetTexture ("_ textureCbCr", _videoTextureCbCr); m_ClearMaterial. SetMatrix ("_ DisplayTransform", _displayTransform); } #fim se } }
Etapa 9: Quase pronto
Finalmente, quando clicamos na tela e colocamos o portal, queremos que ele fique sempre de frente para nós. Para fazer isso, vá para o script "UnityARHitTestExample" no portal. Substitua tudo dentro deste:
using System;
using System. Collections. Generic; namespace UnityEngine. XR.iOS {public class UnityARHitTestExemplo: MonoBehaviour {public Transform m_HitTransform; public float maxRayDistance = 30.0f; public LayerMask collisionLayer = 1 <0) {foreach (var hitResult em hitResults) {Debug. Log ("Got hit!"); m_HitTransform.position = UnityARMatrixOps. GetPosition (hitResult.worldTransform); m_HitTransform.rotation = UnityARMatrixOps. GetRotation (hitResult.worldTransform); Debug. Log (string. Format ("x: {0: 0. ######} y: {1: 0. #######} z: {2: 0. ####### } ", m_HitTransform.position.x, m_HitTransform.position.y, m_HitTransform.position.z)); Vector3 currAngle = transform.eulerAngles; transform. LookAt (Camera.main.transform); transform.eulerAngles = new Vector3 (currAngle.x, transform.eulerAngles.y, currAngle.z); return true; } } retorna falso; } // A atualização é chamada uma vez por quadro void Update () {#if UNITY_EDITOR // usaremos este script apenas no lado do editor, embora não haja nada que o impeça de funcionar no dispositivo se (Input. GetMouseButtonDown (0)) {Ray ray = Camera.main. ScreenPointToRay (Input.mousePosition); RaycastHit hit; // tentaremos atingir um dos objetos de jogo do colisor de plano que foram gerados pelo plug-in // efetivamente semelhante a chamar HitTest com ARHitTestResultType. ARHitTestResultTypeExistingPlaneUsingExtent if (Physics. Raycast (ray, out hit, maxRayDistance, collisionLayer)) {// vamos obter a posição do ponto de contato m_HitTransform.position = hit.point; Debug. Log (string. Format ("x: {0: 0. ######} y: {1: 0. #######} z: {2: 0. ####### } ", m_HitTransform.position.x, m_HitTransform.position.y, m_HitTransform.position.z)); // e a rotação da transformação do colisor de plano m_HitTransform.rotation = hit.transform.rotation; }} #else if (Input.touchCount> 0 && m_HitTransform! = null) {var touch = Input. GetTouch (0); if (touch.phase == TouchPhase. Began || touch.phase == TouchPhase. Moved) {var screenPosition = Camera.main. ScreenToViewportPoint (touch.position); Ponto ARPoint = novo ARPoint {x = screenPosition.x, y = screenPosition.y}; // priorizar reults tipos ARHitTestResultType resultTypes = {ARHitTestResultType. ARHitTestResultTypeExistingPlaneUsingExtent, // se você quiser usar infinitas aviões usar este: //ARHitTestResultType. ARHitTestResultTypeExistingPlane, ARHitTestResultType. ARHitTestResultTypeHorizontalPlane, ARHitTestResultType. ARHitTestResultTypeFeaturePoint}; foreach (ARHitTestResultType resultType em resultTypes) {if (HitTestWithResultType (point, resultType)) {return; } } } } #fim se } } }
Etapa 10: coloque o aplicativo no seu telefone
Finalmente terminamos. Vá para o arquivo, crie as configurações e clique em construir. Abra o Xcode e escolha a pasta que foi criada a partir da construção. Escolha sua equipe de desenvolvimento e coloque o app no seu celular! Você pode querer alterar as cores das partículas e skybox para atender às suas necessidades. Deixe-me saber nos comentários se você tiver alguma dúvida e obrigado por olhar!
Recomendado:
Faça com que os cegos reconheçam as coisas tocando as coisas ao seu redor usando o MakeyMakey: 3 etapas
Faça com que os cegos reconheçam as coisas tocando as coisas ao seu redor usando o MakeyMakey: introduçãoEste projeto visa facilitar a vida dos cegos identificando as coisas ao seu redor por meio do sentido do tato. Eu e meu filho Mustafa pensamos em encontrar uma ferramenta para ajudá-los e no período que usamos o hardware MakeyMakey t
Exibições de lâmpadas reativas ao som + coisas estranhas : 8 etapas (com imagens)
Visores de lâmpadas reativas ao som + coisas estranhas …: Para mais fotos e atualizações do projeto: @capricorn_one
Parede de coisas estranhas em um quadro (escreva suas próprias mensagens!): 8 etapas (com fotos)
Parede de coisas estranhas em um quadro (Escreva suas próprias mensagens!): Há meses que pretendo fazer isso depois de ver um tutorial usando luzes de Natal (parecia ótimo, mas qual é o sentido em não mostrar nenhuma mensagem, certo?). Então eu fiz este Stranger Things Wall há algum tempo e demorou muito tempo
Realidade aumentada (AR) para Dragonboard410c ou Dragonboard820c usando OpenCV e Python 3.5: 4 etapas
Realidade aumentada (AR) para Dragonboard410c ou Dragonboard820c usando OpenCV e Python 3.5: Este instructables descreve como instalar OpenCV, Python 3.5 e dependências para Python 3.5 para executar o aplicativo de realidade aumentada
Demonstração de produtos de realidade aumentada (TfCD): 11 etapas (com fotos)
Demonstração de produtos de realidade aumentada (TfCD): Vender produtos durante o vôo está se tornando cada vez mais popular hoje em dia. No entanto, no avião, a primeira e quase única informação que o passageiro (possível comprador) vê é um folheto impresso. Este instrutível vai mostrar uma forma de inovar no airp