使用 Hasura 在 React Native 中构建 AR 应用,由 GraphQL 提供支持 工作原理 React Native 是移动应用的未来 在 Viro 中创建 AR 内容。数据如何?摘要

2025-06-07

使用 Hasura 在 React Native 中构建 AR 应用,由 GraphQL 提供支持

工作原理

React Native 是移动应用的未来

在 Viro 中创建 AR 内容。

数据怎么样

概括

现实 2.0。过去半年来,我倾向于这样称呼增强现实。为什么这么说呢?
我真诚地认为,增强现实已经脱离了“花哨”的科技领域,进入了企业认真考虑为消费者添加 AR 体验的阶段。基于位置的 AR、休闲游戏、零售、房地产,应有尽有。AR 已经发展到这样的阶段:它还没有准备好应用于我们的智能眼镜或“黑镜式”隐形眼镜,但借助 ARKit 和 ARCore,我们已经在手机上实现了它。

工作原理

如果您不熟悉后者,ARKit 和 ARCore 分别是 Apple 和 Google 开发的框架,它们将 AR 技术引入任何现代手机。简而言之,它具有基本的环境理解和运动追踪功能,使开发者不仅能够在现实世界中渲染 3D 内容,还能将其“锚定”到可识别的表面上。例如,将手机摄像头对准桌面,您就可以将办公桌识别为一个表面,并在其上渲染 3D 模型。更酷的是,由于运动追踪功能,当您在模型周围走动时,它看起来就像在物理世界中您渲染它的位置一样。它可以使您创建各种体验,将现实世界的情境带入您的应用,反之亦然。

React Native 是移动应用的未来

近年来,React Native 彻底改变了移动应用的开发体验,因此,专注于 React Native 的开发者自然对 ARKit 和 ARCore 解决方案的需求也随之增长。通常,创建 AR 解决方案的标准是使用 Unity3D 游戏引擎,并以相当简单的工作流程创建 AR 应用。开发者可以使用 Unity3D 出色的可视化编辑器来处理 3D 内容,并且他们编写的代码是跨平台的——兼容 iOS 和 Android。

那么,如果您需要在现有的 React Native 应用中嵌入 AR 内容,或者 AR 只是用 React Native 编写的更复杂的应用的一部分,该怎么办呢?有很多解决方案。在这篇博文中,我们将讨论的不是如何将 Unity 内容嵌入到我们的 React Native 应用中(这也是可行的),而是使用 React 组件开发简单的 AR 体验。我们将使用一个名为 ViroReact 的框架来实现这一点。

在 Viro 中创建 AR 内容。

ViroReact 为您提供由 ARKit 和 ARCore 驱动的渲染引擎,并提供一系列可用于布局 AR 应用的 React 组件和事件。除此之外,它还具有下图中描述的所有功能。

入门

首先,您需要访问 Viro 网站并注册以获取 API 密钥。按照那里的快速入门指南,即可启动并运行简单的 Hello World 应用程序。

简而言之,它将像跑步一样简单。

npm install -g react-viro-cli
react-viro init MyAwesomeApp
Enter fullscreen mode Exit fullscreen mode

完成后,您将拥有一个可以使用 Viro 测试平台应用运行的基本应用程序。不过,您需要在 App.js 文件中添加 API 密钥。

或者,如果您想在设备上原生运行它,您可以运行./setup-ide.sh- 此脚本将为您的 iOS、Android 或两者设置开发环境。您可以在此处了解有关 Xcode 或 Android 的更多信息。

构建块

任何 AR 场景的第一个也是最重要的构建块是<ViroARSceneNavigator>组件。您将 apiKey 和 prop 传递给这个组件initialScene。请看以下示例:


      <ViroARSceneNavigator 
        apiKey="1839C275-6929-45AF-B638-EF2DEE44C1D9"
        numberOfTrackedImages={this.props.numberOfTrackedImages || 1}
        initialScene={{
          scene: this.props.navigation.state.params.screenType === "portal" ? ARPortalsScene: MarkerScreen,
          passProps: this.props.navigation.state.params
        }}
      />

Enter fullscreen mode Exit fullscreen mode

在这里,我们将指定 AR 屏幕为ARPortalsScene场景或MarkerScreen。基本上,我们将创造这种体验:

标记屏幕是我们识别标记并显示每个标记的文本数据(带有轻微动画)的屏幕。门户屏幕具有自描述性,在这里你可以看到通往 VR 世界的大门,但你仍然可以从那里看到现实世界。

门户网站

让我们从传送门场景开始吧。在加载传送门之前,你会看到地面上渲染了一个灰色四边形。这个四边形基本上是使用ViroARPlaneSelector组件创建的,它使我们能够选择一个可识别的表面,当它被选中时,该组件会将其子对象渲染到表面上。

因此,渲染门户的代码将如下所示:

<ViroARPlaneSelector dragType="FixedToWorld">
   <ViroPortalScene passable={true} dragType="FixedDistance" onDrag={()=>{}}>
      <ViroPortal scale={[.5, .5, .5]}>
          <Viro3DObject onLoadStart={() => { alert("Loading portal") }}
              source={require('../res/portal_wood_frame.vrx')}
              resources={[require('../res/portal_wood_frame_diffuse.png'),
                                    require('../res/portal_wood_frame_normal.png'),
                                    require('../res/portal_wood_frame_specular.png')]}
              type="VRX"
          />
       </ViroPortal>
       <Viro360Image source={{ uri: data.portals_by_pk.portalMedia360 }} />
   </ViroPortalScene>
</ViroARPlaneSelector>
Enter fullscreen mode Exit fullscreen mode

如您所见,我们使用 Viro 提供的辅助组件来加载.fbx模型(.vrxFBX通过 Viro 压缩脚本传递的格式扩展)和 360 图像(也可以是视频)

标记

标记将使用ViroARImageMarker辅助组件为每个标记渲染特定的数据。代码如下。

              return (
                <ViroARImageMarker target={"businessCard"} 
                  onAnchorFound={
                    () => this.setState({
                        runAnimation: true
                    })}
                >
                <ViroNode key="card" onTouch={() => alert("twitter")}>
                  <ViroNode 
                    opacity={0} position={[0, -0.02, 0]} 
                    animation={{
                      name:'animateImage', 
                      run: this.state.runAnimation 
                      }}
                  >
                    <ViroFlexView 
                        rotation={[-90, 0, 0]}
                        height={0.03} 
                        width={0.05}
                        style={styles.card} 
                    >
                      <ViroFlexView 
                        style={styles.cardWrapper} 
                      >
                        <ViroImage
                          height={0.015}
                          width={0.015}
                          style={styles.image}
                          source={{uri: markerData.user.avatarUrl}}
                        />
                        <ViroText 
                          textClipMode="None"
                          text={`${markerData.user.name} ${markerData.user.lastname}`}
                          scale={[.015, .015, .015]}
                          style={styles.textStyle}
                        />
                      </ViroFlexView>
                      <ViroFlexView 
                        style={styles.subText} 
                      >
                        <ViroText 
                          width={0.01}
                          height={0.01}
                          textAlign="left"
                          textClipMode="None"
                          text={`@${markerData.user.twitterProfile}`}
                          scale={[.01, .01, .01]}
                          style={styles.textStyle}
                        />
                        <ViroAnimatedImage
                          height={0.01}
                          width={0.01}
                          loop={true}
                          source={require('../res/tweet.gif')}
                        />
                      </ViroFlexView>
                    </ViroFlexView>
                  </ViroNode>  
                  <ViroNode opacity={0} position={[0, 0, 0]} 
                    animation={{
                      name:'animateViro', 
                      run: this.state.runAnimation 
                    }}
                  >
                    { markerData.bottomBar && <ViroText text={markerData.bottomBar}
                      rotation={[-90, 0, 0]}
                      scale={[.01, .01, .01]}
                      style={styles.textStyle}
                  /> }
                  </ViroNode>
                </ViroNode>
                </ViroARImageMarker>
Enter fullscreen mode Exit fullscreen mode

这里你可以看到我们使用了许多其他组件,例如用于分组内容的 ViroNode 和 ViroFlexView,这样我们就可以使用 flexbox 在 AR 世界中布局文本。我们还创建了滑动动画等等。

数据怎么样

您可能已经注意到我们没有硬编码值,事实上,实际数据来自使用 Hasura 自动生成的 GraphQL API。

Hasura GraphQL

如果你是第一次听说 Hasura,我强烈建议你查看一下这里或这里的博客文章

简而言之,Hasura 可以从您的 Postgres DB(无论是新的还是现有的)自动生成 GraphQL API;它为您提供了令人印象深刻的管理控制台,用于管理数据、GraphQL API、身份验证、访问控制、业务逻辑等。它为您提供了各种添加业务逻辑的选项,无论您是喜欢更同步的方法,还是准备进一步使用3factor.app架构来构建您的现代应用程序。您可以在此处阅读有关添加业务逻辑的更多信息:

因此,对我们来说,在 AR 世界中获取实时数据至关重要。让我们先创建后端

使用 Hasura 创建后端

前往hasura.io,使用您喜欢的任何方式部署引擎。我将使用 Heroku。部署引擎后,我将看到一个空的 Hasura 控制台,我们将在其中开始添加数据

转到“数据”选项卡并创建下表:

  • model_resources - 将保存 3D 模型的资源(纹理等)

  • 模型 - 将保存 3D 模型 obj url、缩放、照明模型,并与 model_resources 表有关系


  • 标记 - 将保存我们的标记数据,并与用户和模型表有关系


  • 门户网站 - 将保存我们门户网站的数据

  • 用户 - 我们将在此应用程序中使用虚拟登录,但您可以通过查看以下博客文章来实现正确的身份验证:

在新创建的引擎中设置好所有这些之后,Hasura 将为您自动生成 GraphQL API,包括我们将在 AR 应用中使用的性能极佳的订阅。

设置您的客户端

现在是时候进入我们的 Viro 应用了,实际上,删除我们之前的所有示例内容,然后开始使用 GraphQL 创建基本的 React Native 应用。如果您是 React Native 和 GraphQL 的新手,我强烈建议您查看learn.hasura.io上的 React Native 教程。

我们的AppScreen代码App.js从仅仅是 ViroSample 变成了这样:



const mkWsLink = (uri) => {
  const splitUri = uri.split('//');
  const subClient = new SubscriptionClient(
    'wss://' + splitUri[1],
    { reconnect: true }
  );
  return new WebSocketLink(subClient);
}


const wsLink = mkWsLink(GRAPHQL_ENDPOINT)
const httpLink = new HttpLink({ uri: GRAPHQL_ENDPOINT });
const link = split(
  // split based on operation type
  ({ query }) => {
    const { kind, operation } = getMainDefinition(query);
    return kind === 'OperationDefinition' && operation === 'subscription';
  },
  wsLink,
  httpLink
);

// Creating a client instance
const client = new ApolloClient({
  link,
  cache: new InMemoryCache({
    addTypename: false
  })
});


export default class AppScreen extends React.Component {
  render() {
    return(
      <ApolloProvider client={client}>
        <RootNavigator
          client={client}
          session={this.props.sessionInfo}
        />
      </ApolloProvider>
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

正如您所看到的,我们已经在 React 上下文中设置了我们的 apollo 客户端,因此以后ApolloProvider可以通过组件访问它。Subscription

导航

让我们看看我们的RootNavigator


const AppStack = createBottomTabNavigator({
  Markers: createStackNavigator({ 
    Home: Home, 
    ARScreen: ARScreen 
  }),
  Portals: createStackNavigator({ 
    Portals: PortalsList, 
    ARPortalScreen: ARScreen 
  })
});
const AuthStack = createStackNavigator({ SignIn: SignIn }); 

export default createAppContainer(createSwitchNavigator(
  {
    AuthLoading: AuthLoadingScreen,
    App: AppStack,
    Auth: AuthStack,
    Upload
  },
  {
    initialRouteName: 'AuthLoading',
  }
));


Enter fullscreen mode Exit fullscreen mode

正如您在此处看到的,我们的初始屏幕将是 AuthLoading 屏幕,但Markers选项卡和Portals选项卡都会有一个堆栈,该堆栈将具有纯粹的反应原生屏幕以及标记/门户列表和ARScreen

我们之前已经见过 ARScreen,但它看起来像这样:

 getARView() {
    return this.state.arEnabled ? (
      <ViroARSceneNavigator 
        apiKey=API_KEY
        numberOfTrackedImages={this.props.numberOfTrackedImages || 1}
        initialScene={{
          scene: this.props.navigation.state.params.screenType === "portal" ? ARPortalsScene: MarkerScreen,
          passProps: this.props.navigation.state.params
        }}
      />
    ) : null
  }

  render() {
    return (
      <SafeAreaView style={styles.container}>
        {this.getARView()}
        <View style={styles.bottomTab}>
          <Button text="Go Back" onPress={this._goBack} />
        </View>
      </SafeAreaView>
    )
  }
Enter fullscreen mode Exit fullscreen mode

订阅在 AR 和移动世界中均可使用

现在我们唯一要做的就是前往我们的 MarkerScreen.js 并使用 Subscription 包装我们的 Marker。renderMarkerScreen函数如下所示:

  render() {
    return (
      <ViroARScene onTrackingUpdated={this._onInitialized} >
      <ViroDirectionalLight color="#777777"
        direction={[0, -1, -2]}
        shadowOrthographicPosition={[0, 8, -5]}
        shadowOrthographicSize={10}
        shadowNearZ={2}
        shadowFarZ={9}
        lightInfluenceBitMask={2}
        castsShadow={true} 
      />
        { this.state.isTracking ? this.getNoTrackingUI() : this.getARScene()}

      </ViroARScene>
    );
  }

Enter fullscreen mode Exit fullscreen mode

基本上,我们将场景包装成ViroARScene组件,利用 ViroReact 和 ARKit/ARCore 框架将 AR 功能引入 React Native。
我们之前已经了解了标记代码的样子,现在剩下要做的就是用 Subscription 来包装它。

const MARKER_DETAILS = gql`
subscription fetchMarker($markerId: uuid!) {
  markers_by_pk(id: $markerId) {
    user {
      name
      lastname,
      twitterProfile,
      avatarUrl
    }
    bottomBar
  }
}
`;


getARScene() {
    return (
      <ViroNode>
          <Subscription subscription={MARKER_DETAILS} variables={{ markerId: this.props.id }}>
            {({loading, error, data }) => {
              if (error) {
                alert("Error", "Could not fetch marker");
                return null;
              }

              if (loading) {
                return (
                  <LoadingComponent text="Loading Marker" />
                )
              }

              const markerData = data.markers_by_pk
              if (this.props.is3d) {
                return this.render3d()
              }
              return (
                <ViroARImageMarker target={"businessCard"} 
                  onAnchorFound={
                    () => this.setState({
                        runAnimation: true
                    })}
                >

// rest of marker code
Enter fullscreen mode Exit fullscreen mode

结果如下:

概括

正如您在本篇博文中所见,将 AR 内容添加到您的 React Native 并非难事,只需对 3D 环境有基本的了解即可轻松完成。此 AR 内容可以像常规 React Native 应用一样连接到 GraphQL API。Hasura 将使这一步变得非常简单直接。

我还没有介绍这个示例应用的全部代码,但你可以在这里
查看。 在过去的几个月里,我还就这个主题做了几次演讲,重点介绍了这个演示应用,并进一步解释了 AR 的各个部分,你可以去 DevDays Europe 看看:

幻灯片可在此处获取

如果您想了解有关 AR 和 GraphQL 组合的更多信息,我将在Chain React 2019会议上教授 AR 和 GraphQL 研讨会,因此如果您有兴趣获得更多指导性实践经验,欢迎您购买门票。

在结束这篇博文之前,最后要说的是,我或 Hasura.io 团队的其他人每周都会在twitch.tv/hasurahq上直播一些精彩内容,并上传到我们的YouTube 频道,所以请务必关注我们的后续活动。例如,昨天,我直播了我们刚刚看到的同一款 AR 应用的详细代码概述。

如果您有任何疑问,可以通过填写我网站上的联系表格联系我:vnovick.com或在 Twitter 上给我发送 DM @VladimirNovick

文章来源:https://dev.to/hasurahq/building-ar-apps-in-react-native-powered-by-graphql-using-hasura-h9m
PREV
使用 Hasura 在 ReasonML 中使用 GraphQL 和无服务器构建博客 CMS ReasonML 简介 ReasonReact 我们将创建什么 使用 Hasura 添加 GraphQL 后端 将 GraphQL 添加到我们的应用程序 添加查询和突变 添加订阅 总结和下一步
NEXT
将 Material-UI 4 迁移到 Mui-5