ARKit框架详细解析(十)—— 使用ARKit 2构建博物馆应用程序(一)

版本记录

版本号 时间
V1.0 2018.10.12 星期五

前言

苹果最近新出的一个API就是ARKit,是在2017年6月6日,苹果发布iOS11系统所新增框架,它能够帮助我们以最简单快捷的方式实现AR技术功能。接下来几篇我们就详细的对ARKit框架进行详细的解析。感兴趣的可以看上面几篇。
1. ARKit框架详细解析(一)—— 基本概览
2. ARKit框架详细解析(二)—— 关于增强现实和ARKit
3. ARKit框架详细解析(三)—— 开启你的第一个AR体验之旅
4. ARKit框架详细解析(四)—— 处理增强现实中的3D交互和UI控件
5. ARKit框架详细解析(五)—— 创建基于面部的AR体验
6. ARKit框架详细解析(六)—— 用Metal展示AR体验
7. ARKit框架详细解析(七)—— 使用AR Face Tracking和TrueDepth相机进行面部跟踪(一)
8. ARKit框架详细解析(八)—— 使用AR Face Tracking和TrueDepth相机进行面部跟踪(二)
9. ARKit框架详细解析(九)—— 使用AR Face Tracking和TrueDepth相机进行面部跟踪源码(三)

开始

首先看一下写作环境

Swift 4.2, iOS 12, Xcode 10

您是否曾参加过博物馆展览,想要了解更多有关艺术品或工艺品的信息,而不是小标语牌?应该有一个应用程序。好吧,你可以在ARKit 2中制作这样一个带有图像和物体检测和跟踪的应用程序!

为了让体验变得有趣和有意思,ARKit允许应用程序为现实世界的对象添加动态虚拟内容。它允许您为现实世界的地方和事物构建交互式指南应用程序。 ARKit通过内置图像和对象跟踪使其变得简单。这非常适合博物馆,画廊,游乐园和大学的配套应用。它可以为您提供任何想要提供动态或用户特定体验的地方。

在本文中,您将构建TriassicLoupe,这是一个自然历史博物馆恐龙展览的配套应用程序。此应用程序显示隐藏的详细信息,因为用户将其指向展览周围。如果你周围没有任何恐龙,请不要担心 - 你可以使用普通的家用物品作为替身。

最终应用程序在信息图像上显示简短动画。它还显示有关独立复制对象旁边的恐龙的信息。该应用程序还将为该对象添加一些可怕的声音效果。

该应用程序将ARKitSceneKit(iOS的3D图形框架)结合使用。 你会看到ARKit使用SceneKit完成所有繁重的工作。 对于图像和对象跟踪,您将只使用非常基本的SceneKit功能,了解有关SceneKit的更多信息将使您能够构建更丰富的应用程序,但这超出了本文的范围。

ARKit依赖于A9或更高版本处理器的内置功能。 它使用机器学习,后置摄像头,以及视频和图像处理。 这意味着ARKit应用程序需要在iPhone 6s或更新版本上运行,并且它们无法在模拟器中运行。

使用超长的Lightning线缆或者设置设备以通过Wi-Fi连接到Xcode非常方便。 ARKit需要移动一点才能获得一张好的世界地图。 世界地图是ARKit对物理空间的意识。 它是一系列特征点,测量,方向和锚点。

注意:稍后你会扫描一些图像。 这应该适用于您的显示器,但如果您在扫描图像时遇到问题,则可能需要打印图像以获得更好的效果。

打开入门项目。 应用程序本身非常简单,只需一个ViewController即可添加所有逻辑。 有一个辅助结构,DinosaurFacts,其中包含一些关于一些恐龙的基本信息。

如果你Build并运行,你会看到一个黑屏,因为你尚未连接ARKit会话。


Building Image Detection - 构建图像检测

首先要做的是构建图像检测。 图像检测可能是ARKit最简单的功能。 要构建图像检测器,您所要做的就是提供带有图像副本的图像跟踪会话。 这些提供的图像称为参考图像(reference images)

1. Adding Reference Images - 添加参考图像

TriassicLoupe使用来自信息标志的艺术作品作为参考图像。 当用户将应用程序指向其中一个标志时,该应用程序将添加恐龙图像叠加层。

与其他应用程序图像一样,增强现实(AR)参考图像存在于资源目录中。 参考图像有点特殊,因为它们需要专门为ARKit分组。

打开Assets.xcassets并单击底部的+按钮。

从弹出菜单中,选择New AR Resource Group以创建新组。 将其重命名为AR Images,因为该组将保留参考图像。

Finder中,从下载的材料中打开Dinosaur Images文件夹。 将每个图像文件逐个拖动到Xcode中的AR Images中。 完成后,您应该有三个带黄色警告三角形的图像。

如果参考图像不足以作为参考,Xcode会发出警告。 这可能是因为它们太小或者没有足够的功能或对比度。 具有大量空白空间,少量颜色或缺乏独特形状的图像难以检测。

在这种情况下,警告是Unsupported Configuration警告。 这是因为参考图像必须具有非零的正宽度。 AR参考图像需要您指定其实际尺寸!

在资产库中选择剑龙stegosaurus图像。 然后选择Attributes inspector

将单位更改为Inches英寸。 接下来,输入宽度4.15。 当你这样做时,根据纵横比,高度将自动变为2.5711! 这些字段是您桥接虚拟世界和真实世界的地方。

对于其他两个图像,请使用以下值:

  • trex: Inches, width: 6.3333, height: 4.75
  • triceratops: Inches, width: 5, height: 2.8125

输入尺寸后,警告消失。

这些图像对应于所包含的Dinosaurs.key Keynote文件的幻灯片。 每张幻灯片代表一个信息标语牌,旁边是博物馆展示。 在美国信纸大小的纸张上打印时,指定的尺寸是物理图像尺寸。

注意:某些图像实际上是在Keynote幻灯片中剪辑的。 通过保持大小和宽高比相同,ARKit能够有足够的信心来识别匹配。

这些图像都是不同的风格,以展示ARKit系列的一小部分。 这里有两件事情:1.图像中有足够的形状和对比度。 2.真实版本平坦,光线充足,不反光。

书页,壁纸或在镜子上打印是不好的候选者。 照片,绘画或插图将很好地工作。 如果图像不够好,Xcode会发出警告。 无需在运行时猜测!

现在,是时候继续编写代码来寻找这些图像了。

2. Adding Image Tracking - 添加图像跟踪

ViewController.swift中,在注释// Add configuration variables here:下添加一个新变量

private var imageConfiguration: ARImageTrackingConfiguration?

这将设置一个变量,以便在创建后保留图像跟踪配置。

现在,查找setupImageDetection()并添加以下代码:

imageConfiguration = ARImageTrackingConfiguration()

这会将该实例变量设置为新的ARImageTrackingConfiguration。 顾名思义,此类是一个ARKit配置,用于检测和跟踪图像。

在该行下,添加以下内容:

guard let referenceImages = ARReferenceImage.referenceImages(
  inGroupNamed: "AR Images", bundle: nil) else {
      fatalError("Missing expected asset catalog resources.")
  }
imageConfiguration?.trackingImages = referenceImages

这将使用您刚刚在资源目录中创建的AR Images组中的图像创建ARReferenceImage集。 然后,将它们作为要跟踪的图像列表添加到配置中。

注意:图像检测最适用于资源组中少于25个图像。 如果您的博物馆有超过25个展品,您可以创建多个资源组,并在用户在建筑物周围移动时切换它们。

要使用配置,请将以下内容添加到viewWillAppear(_ :)

if let configuration = imageConfiguration {
  sceneView.session.run(configuration)
}

这将使用imageConfiguration启动ARKit会话。 一旦运行,ARKit将处理相机数据以检测参考图像。

要确保全部启动,请将以下内容添加到viewDidLoad()的底部:

setupImageDetection()

最后,为了平衡会话运行,添加到viewWillDisappear(_ :)

sceneView.session.pause()

当视图消失时,这会暂停会话。 由于相机使用,视频处理和渲染,ARKit会话会耗尽电池。 在我们的单视图应用程序中,这不是什么大问题,但是尊重用户的设备并在没有显示时暂停会话总是一个好主意。

3. Handling Detected Images - 处理检测到的图像

一旦检测到图像,AR会话就会将ARImageAnchor添加到其世界地图中。 当发生这种情况时,您将获得回调renderer(_:didAdd:for :)

ViewController.swift的底部找到此函数并添加以下代码:

DispatchQueue.main.async { self.instructionLabel.isHidden = true }
if let imageAnchor = anchor as? ARImageAnchor {
  handleFoundImage(imageAnchor, node)
}

此代码检查是否为图像锚点添加了新添加的节点。 这意味着在现实世界中检测到图像。

handleFoundImage(_:_ :)方法体替换为:

let name = imageAnchor.referenceImage.name!
print("you found a \(name) image")

let size = imageAnchor.referenceImage.physicalSize
if let videoNode = makeDinosaurVideo(size: size) {
  node.addChildNode(videoNode)
  node.opacity = 1
}

这将从锚点的参考图像中获取图像的名称和大小。 您在资产目录中指定了这些值。 使用该大小,调用辅助函数以创建视频播放器以位于检测到的图像之上。

要制作视频节点,请将makeDinosaurVideo(size :)的内容替换为:

// 1
guard let videoURL = Bundle.main.url(forResource: "dinosaur",
                                     withExtension: "mp4") else {
  return nil
}

// 2
let avPlayerItem = AVPlayerItem(url: videoURL)
let avPlayer = AVPlayer(playerItem: avPlayerItem)
avPlayer.play()

// 3
NotificationCenter.default.addObserver(
  forName: .AVPlayerItemDidPlayToEndTime,
  object: nil,
  queue: nil) { notification in
    avPlayer.seek(to: .zero)
    avPlayer.play()
}

// 4
let avMaterial = SCNMaterial()
avMaterial.diffuse.contents = avPlayer

// 5
let videoPlane = SCNPlane(width: size.width, height: size.height)
videoPlane.materials = [avMaterial]

// 6
let videoNode = SCNNode(geometry: videoPlane)
videoNode.eulerAngles.x = -.pi / 2
return videoNode

此函数创建一个视频播放器并将其放入一个大小适合图像的SceneKit节点。它通过以下方式实现:

  • 1) 从资源包中抓取视频。这有一个用于所有恐龙的简单动画。但您可以使用图像锚点的名称为每种恐龙类型提供不同的视频。
  • 2) 为该视频创建和启动AVPlayer
  • 3) AVPlayer实例不会自动重复。此通知块通过监听播放器来完成视频循环。然后它回到开头并重新开始。
  • 4) SceneKit不使用UIViews,而是使用节点渲染场景。无法直接添加AVPlayer。相反,视频播放器可以用作节点的纹理或“材料”。这将视频帧映射到相关节点。
  • 5) 检测到的图像是扁平方形(即平面),因此与其重叠的节点是与检测到的图像大小相同的SCNPlane。这架平面装饰着视频作为其纹理。
  • 6) 创建将成为场景一部分的实际节点。它在x轴上翻转,以便正确显示给用户。

4. Tryin’ It Out - 试试吧

最后,在这之后,是时候构建和运行了! 但是,首先,打印至少一张Dinosaurs.key的幻灯片。 将其平放(垂直或水平)放置在光线充足的区域。

Build并运行应用程序。 接受相机权限并显示视频后,将其指向打印页面。 可能需要一两秒或两只稳定的手来检测图像。 正确完成后,您将在控制台中看到输出,并在屏幕上显示动画叠加层。

不幸的是,如果会话启动但它没有检测到图像,则没有错误消息。 大多数时候,ARKit并不一定找到图像,因此不会被视为错误。 只要资产目录中没有关于图像的警告,就应该最终检测到它。


Adding Object Detection and Tracking - 添加对象检测和跟踪

现在,您已经看到了图像检测功能。 接下来,您将向应用添加对象检测。 从开发人员的角度来看,对象检测的工作方式几乎相同。 主要区别在于它会寻找三维物体而不是平面图像。 设置对象检测稍微复杂一些。 对象引用创建也更复杂一些。

以下是使用图像和对象检测的步骤:

  • 1) 创建引用。
  • 2) 将引用放在资产目录中的AR Resources组中。
  • 3) 设置ARKit会话。
  • 4) 加载参考图像/对象并设置会话以检测它们。
  • 5) 开始会话。
  • 6) 添加锚点时等待回调。
  • 7) 将交互式节点添加到场景中,或采取其他操作。

Object Detection - 对象检测

另一个有用的ARKit功能是对象检测和跟踪。 TriassicLoupe检测已知对象并对其进行注释。 实际上,这些是恐龙或恐龙骨骼中的恐龙。 在本文中,您将使用手头的任何内容。

1. Selecting Reference Objects - 选择参考对象

为了检测对象,您需要的第一件事是参考对象。 通过图像检测,您可以创建,扫描或拍摄图像。 但是对于3D对象,引用更难构建。

ARKit提供了自己的API,可以通过iPhone扫描来创建参考对象。 TriassicLoupe不直接使用它,因为它只检测已知的一组对象。 您可以使用Apple提供的实用程序提前扫描它们。

如果可以,您应该下载Apple的Object Scanner project。 为方便起见,它还包含在ScanningAndDetecting3DObjects文件夹中的项目下载中。 请注意,在您阅读本文时,所包含的项目可能已过期。

此应用程序扫描对象,并允许您导出.arobject文件。 然后,您可以将此文件作为资产导入Xcode项目。 成功的扫描需要有适当的物体和良好的照明。 一个合适的对象是:

  • 固体。
  • 有很多细节(如形状和颜色)。
  • 不反光或透明。
  • 可能介于垒球大小和椅子之间。

您家中可能没有3D恐龙展示,因此您可以在本教程中使用任何家用物品。 好的物品是饼干罐,动作人物或植物。 将物体放置在平坦的表面上,在良好的照明下,周围有空间。

2. Creating the Reference Objects - 创建参考对象

Build并运行此应用程序到设备。 为获得最佳效果,请使用iPhone 8,8 +X,它具有足够的处理能力,可在执行扫描时保持良好的帧速率。

  • 1) 将手机的相机对准物体。 对象周围应出现一个黄色框。 一旦对象位于框的中间,请点击Next
  • 2) 通过移动边框并长按并拖动边缘来调整边界框的大小。 该框应仅包含该对象。 与新的Measure应用程序一样,此扫描仪使用ARKit来测量对象的真实世界大小。 一旦对象位于中间,请点击Scan按钮。
  • 3) 绕着物体走动,将手机对准物体。 一定要在几个角度,上方和两侧。 当扫描获得足够的信息来表示对象时,黄色框将填充。 尝试尽可能多地覆盖。
  • 4) 下一步设置锚点。 此点控制模型的节点几何与现实世界的相互作用。 对于本文,确切的位置并不重要。 尝试将它放在物体中间的底部平面上。 准备好后按Finish
  • 5) 点击Export按钮,然后通过AirDrop,文件共享或电子邮件将.arobject文件发送给自己。

对另外两个对象重复此过程。

3. Importing the Reference Objects - 导入参考对象

返回Assets.xcassets并创建一个新的AR Resource Group。 将其命名为AR Objects

将每个.arobject文件拖到此组中。 您将看到对象扫描时的一些照片预览。 重命名对象以匹配这些恐龙名称:brachiosaurus, iguanodon and velociraptor

与图像不同,您不必指定尺寸,因为它已经通过对象扫描过程进行了测量。

4. Looking for Objects - 寻找对象

下一步是设置配置以查找这些对象。 在ViewController.swift的顶部,在imageConfiguration定义下,添加:

private var worldConfiguration: ARWorldTrackingConfiguration?

这将创建一个变量来存储世界跟踪配置。 此配置对于对象检测是必需的。 与图像检测不同,不存在仅针对对象的配置。

接下来,将setupObjectDetection()的主体替换为:

worldConfiguration = ARWorldTrackingConfiguration()

guard let referenceObjects = ARReferenceObject.referenceObjects(
  inGroupNamed: "AR Objects", bundle: nil) else {
  fatalError("Missing expected asset catalog resources.")
}

worldConfiguration?.detectionObjects = referenceObjects

这将创建一个ARWorldTrackingConfiguration实例。 此配置是功能最全的ARKit配置。 它可以检测水平和垂直平面以及物体。 它使用后置摄像头和所有运动传感器来计算现实世界的虚拟表示。

创建配置后,从资源目录中加载引用对象,并将引用设置为配置的detectObjects。 一旦检测到,当ARKit将其锚点添加到场景时,您将获得适当的回调。

viewDidLoad中将最后一行更改为:

setupObjectDetection()

您刚刚使用调用设置对象检测替换了图像检测的设置。

要使用此新配置启动会话,请将viewWillAppear(_ :)的内容替换为:

super.viewWillAppear(animated)
if let configuration = worldConfiguration {
  sceneView.debugOptions = .showFeaturePoints
  sceneView.session.run(configuration)
}

这将启动具有新worldConfiguration的会话。

您还可以激活可选的ARSCNDebugOptions.showFeaturePoints调试选项。 这会在屏幕上为ARKit检测到的特征点放置黄点。 这有助于在再次运行应用程序时进行调试。 显示在对象上的点越多,检测就越容易。

注意:scene视图一次只能运行一个配置,但您可以随时替换该配置。 如果要更新选项或切换配置类型,只需运行新配置即可。 除非您明确清除它们,否则会话将保留具有任何检测到的要素和锚点的相同世界地图。 如果要切换一组检测对象,请执行此操作。 例如,如果用户从恐龙展览移动到天文展览,他们可以看到不同的一组对象。

5. Finding the Objects - 寻找对象

与图像检测一样,当ARKit检测到对象时,它会向世界地图添加锚点,并向场景添加节点。

修改renderer(_:didAdd:for:),将其内容替换为:

DispatchQueue.main.async { self.instructionLabel.isHidden = true }
if let imageAnchor = anchor as? ARImageAnchor {
  handleFoundImage(imageAnchor, node)
} else if let objectAnchor = anchor as? ARObjectAnchor {
  handleFoundObject(objectAnchor, node)
}

这样可以保留先前对图像锚点的处理,但会添加一个检查以查看新锚点是否为对象锚点。 如果它是一个对象锚点,那意味着检测到一个对象! 然后移交节点并锚定到辅助方法。

说到这个,用以下内容替换handleFoundObject(_:_ :)的内容:

// 1
let name = objectAnchor.referenceObject.name!
print("You found a \(name) object")

// 2
if let facts = DinosaurFact.facts(for: name) {
  // 3
  let titleNode = createTitleNode(info: facts)
  node.addChildNode(titleNode)

  // 4
  let bullets = facts.facts.map { "• " + $0 }.joined(separator: "\n")

  // 5
  let factsNode = createInfoNode(facts: bullets)
  node.addChildNode(factsNode)
}

此代码收集有关找到的对象的信息。 用户可以通过添加的文本节点获得关于由对象表示的恐龙的额外信息。 看看代码:

  • 1) referenceObject的名称在资产目录中设置并匹配该恐龙的名称。
  • 2) DinosaurFact是一种帮助类型,描述了每种已知的恐龙。 它有一个很酷的facts清单。
  • 3) 此辅助函数创建一个带有恐龙名称的文本节点,并将其添加到场景中。 此文本看起来好像浮在对象上方。
  • 4) 这个小字符串数学为每个事实添加一个子弹,将它们组合成一个单独的行分隔字符串。 SCNText节点可以有多行但需要单个String输入。
  • 5) 此帮助程序创建文本节点,该节点将显示在对象旁边并将其添加到场景中。

6. Displaying Text Nodes - 显示文本节点

现在,深入了解SceneKit文本节点。

添加辅助函数以在handleFoundObject(_:_ :)下创建文本节点:

private func createTitleNode(info: DinosaurFact) -> SCNNode {
  let title = SCNText(string: info.name, extrusionDepth: 0.6)
  let titleNode = SCNNode(geometry: title)
  titleNode.scale = SCNVector3(0.005, 0.005, 0.01)
  titleNode.position = SCNVector3(info.titlePosition.x, info.titlePosition.y, 0)
  return titleNode
}

这将创建一个带有恐龙名称的文本节点。 SCNText是描述字符串形状的几何体。 它允许您创建可放置在场景中的形状。 与对象相比,默认文本大小很大,因此缩放titleNode会将其缩小到合理的大小。

此处指定的position将文本对齐在对象的中心。 因为对象的大小可能因对象而异,所以需要为每个恐龙表示指定。 您可以为自己的对象调整DinosaurFacts.swift中的值。SceneKit默认以米为单位。

接下来,为有趣的事实添加其他帮助函数:

private func createInfoNode(facts: String) -> SCNNode {
  // 1
  let textGeometry = SCNText(string: facts, extrusionDepth: 0.7)
  let textNode = SCNNode(geometry: textGeometry)
  textNode.scale = SCNVector3(0.003, 0.003, 0.01)
  textNode.position = SCNVector3(0.02, 0.01, 0)

  // 2
  let material = SCNMaterial()
  material.diffuse.contents = UIColor.blue
  textGeometry.materials = [material]

  // 3
  let billboardConstraints = SCNBillboardConstraint()
  textNode.constraints = [billboardConstraints]
  return textNode
}

这与前一个助手类似,但有一些额外的东西:

  • 1) 首先,与前一个辅助函数一样,这将创建一个带有节点的文本几何体,并将其缩小到合理的大小。
  • 2) 这使文本变为蓝色。通过创建新材质并将其diffuse内容设置为蓝色来设置蓝色。节点的材质有助于scene渲染器确定对象如何响应光。diffuse属性就像基本外观。在这里,它被设置为蓝色,但可以改为图像或视频,如您之前所见。
  • 3) SCNBillboardConstraint是一个有用的约束,它保持面向节点,以便文本始终面向用户。这提高了可读性;当你四处移动时,你不必移动到一个尴尬的角度来看文本。

Build并运行应用程序,然后指向其中一个扫描对象。

检测可能需要多次尝试。在物体周围移动可能效果更好:向前和向后,在两侧等,以给予ARKit实现形状的最佳机会。一定要慢慢稳定地移动。

一旦ARKit检测到一个对象,该应用程序就会显示该对象旁边的信息。 请注意,标题浮动在对象上方。 如果您的对象高于几英寸,则必须在DinosaurFacts.swift中调整titlePosition.y。 此文本将沿扫描期间设置的原点定向。

与标题相反,信息子弹与相机一起移动,以便它们始终面向您。

7. Simultaneous Image and Object Tracking - 图像和对象跟踪

此时,您已使用对象跟踪替换了图像跟踪。 ARWorldTrackingConfiguration是一个超级配置;它支持与ARImageTrackingConfiguration相同的图像跟踪。

要恢复图像跟踪,请将以下行添加到setupObjectDetection的底部。

guard let referenceImages = ARReferenceImage.referenceImages(
  inGroupNamed: "AR Images", bundle: nil) else {
    fatalError("Missing expected asset catalog resources.")
}
worldConfiguration?.detectionImages = referenceImages

这将加载与以前相同的一组参考图像,但将它们设置为世界跟踪配置的detectionImages

再次Build并运行。 现在,您将看到恐龙海报上的动画和物体上的信息文本。


Adding Sound - 添加声音

打开任何应用程序的一种很酷的方式是使用一些定位音频。 是时候添加一些可怕的恐龙声了!

在文件的顶部,在配置下方添加一个新变量:

lazy var audioSource: SCNAudioSource = {
  let source = SCNAudioSource(fileNamed: "dinosaur.wav")!
  source.loops = true
  source.load()
  return source
}()

这将创建一个SCNAudioSource,它保存用于SceneKit的声音数据。 此数据从包含的声音文件加载。

接下来,在handleFoundObject(_:_ :)的最后,添加以下一行:

node.addAudioPlayer(SCNAudioPlayer(source: audioSource))

默认情况下,SCNAudioPlayer处理从其源创建3D声音效果的所有细节。

再次Build并运行。 将相机对准物体。 一旦识别出来,可怕的音频就会开始播放。 为了获得最佳体验,戴上耳机。 当您在物体周围走动时,您应该听到声音在您的耳朵之间调制。

后记

本篇主要讲述了使用ARKit 2构建博物馆应用程序,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容