向MFC嵌入OpenGL

搬运自CSDN博客:向MFC嵌入OpenGL

本人上计算机图形学课时要做的大作业是基于MFC框架用OpenGL实现一些功能。但是我一开始并不知道怎么在MFC中加入OpenGL模块。于是我在网上找了半天终于找到一篇文章,阅读之后受益匪浅。今天我准备把这篇文章翻译出来。

原文地址:Setting Up OpenGL in an MFC Control

翻译如下:
我在网上没有看到许多文章关于集成功能的,哪怕仅仅是在一个MFC控制结构中设置基本的OpenGL窗口的文章也没有。本教程的目的是一步一步地教大家如何在MFC控制结构(例如图片控制)中初始化一个OpenGL渲染上下文(rendering context),还有基于定时器、改变大小事件、基本相机功能等的基本绘制功能。在本教程中我将在Microsoft Visual Studio .NET 2003 的环境下操作(注:译者用的是VS2015),而且我将尽量让各个水平的人都能理解。我将为初学者增加一些特定的步骤,非初学者也可以跳过这些步骤去找自己所需要的部分。无论如何,欢迎大家的评论(无论好坏),我将近我全力去更新此文直到每个人都满意。谢谢大家。那么让我们开始吧!

第一部分:创建最初的OpenGL窗口

第1步:创建工程

首先我们先要创建一个MFC对话框应用程序项目。文件->新建->项目 选择MFC应用程序并将其命名为"oglMFCDialog"。把工程保存在哪里取决与你,你只要能记住你把它存到哪儿就行了。当MFC应用程序向导出现的时候,在应用程序类型中选择基于对话框然后点击完成按钮。其他附加的设置选项在用户界面功能中。比如说我就选择了最小化框。

第2步 创建控制模块

在资源浏览器标签中找到资源视图,展开Dialog文件夹,然后双击自动生成的IDD_OGLMFCDIALOG_DIALOG对话框。你需要添加一个控制组件用于OpenGL内容的渲染。一个简单的图片控制组件就可以,那么就在工具箱中选择Picture Control吧。
注意:如果工具箱没有出现,在菜单栏中找视图->工具箱把它调出来。
你可以直接将组建拽到你的对话窗口,也可以随时调整大小。
你将需要对这个Picture Control设置一些属性值。当这个Picture Control被选中时,它的属性在右下角,设定以下属性:
Visible:False
ID:IDC_OPENGL
你也许觉得很奇怪为什么将Visible属性设为False。当你在任何一个MFC中加载OpenGL渲染内容时,你能用到的只是这个控件的方形位置来辅助绘制OpenGL内容。由于一些奇怪的原因,如果Visible设为True,这个控件就会覆盖掉你的OpenGL内容。

第3步 添加OpenGL类

接下来,为了设置OpenGL,我选择为其添加一个单独的类。把工程里的主要部分分成单独的类,这是一个好习惯。因此我将把OpenGL和MFC分离。
要添加一个类,点击解决方案资源管理器标签,在树中右击oglMFCDialog工程,选择添加->添加类,再从模板中选择C++类然后点击添加。当一般C++类向导出现时,设置以下属性:
类名:COpenGLControl
基类:CWnd
访问(Access):public
勾选虚析构函数
然后点击完成,就创建成功了。

第4步 添加项目库(Adding Project Libraries)

由于你要用到OpenGL的渲染功能,你还需要向工程中链接一些库。在树中再次右击oglMFCDialog项目,选择属性,在属性页中选择链接器->输入,设置以下变量:
附加依赖项:opengl32.lib;glu32.lib
注意:这两个*.lib文件中间不要有任何空格

第5步 设置类的变量

在本工程的范围内,一些变量需要被添加到OpenGLControl.h中,公有的和私有的都需要,还需要添加#include语句,代码如下:

#include <gl/gl.h>  
#include <gl/glu.h>  
   
class COpenGLControl : public CWnd  
{  
   public:  
      /******************/  
      /* PUBLIC MEMBERS */  
      /******************/  
      // Timer  
      UINT_PTR m_unpTimer;  
   
   private:  
      /*******************/  
      /* PRIVATE MEMBERS */  
      /*******************/  
      // Window information  
      CWnd    *hWnd;  
      HDC     hdc;  
      HGLRC   hrc;  
      int     m_nPixelFormat;  
      CRect   m_rect;  
      CRect   m_oldWindow;  
      CRect   m_originalRect;  
      ...

特别注意:我把函数按照特定顺序排列,这是非常重要的。因为如果有些函数在别的函数之前被实例化,就会有一个编译错误,这个错误是protected成员引起的。所以,我把这些函数按照以下顺序排列:

  1. 构造和析构函数。
  2. 手动添加的函数。
  3. 自动添加的"afx_msg"函数。
  4. DECLARE_MESSAGE_MAP()调用。

第6步 添加oglCreate函数

一个新的函数将添加到OpenGLControl.hOpenGLControl.cpp中。该函数将负责搭建一些基本的窗口变量和对MFC来说很重要的函数调用。我把这个函数命名为oglCreate。添加的代码如下:

OpenGLControl.h
void oglCreate(CRect rect, CWnd *parent);

OpenGLControl.cpp

void COpenGLControl::oglCreate(CRect rect, CWnd *parent)  
{  
   CString className = AfxRegisterWndClass(CS_HREDRAW |  
      CS_VREDRAW | CS_OWNDC, NULL,  
      (HBRUSH)GetStockObject(BLACK_BRUSH), NULL);  
   
   CreateEx(0, className, "OpenGL", WS_CHILD | WS_VISIBLE |  
            WS_CLIPSIBLINGS | WS_CLIPCHILDREN, rect, parent, 0);  
   
   // Set initial variables' values  
   m_oldWindow    = rect;  
   m_originalRect = rect;  
   
   hWnd = parent;  
}  

第7步 添加OnPaint函数

接下来需要添加一个消息类函数。该函数与其他函数的区别也很好辨认。当程序中有事件发生时,MFC会调用特定的消息(以WM_为前缀),例如OnPaint,OnSize,OnCreate等。要通过Visual Studio添加消息,你可以在属性窗口中找消息按钮(闪电图标右边的那个)。如果你的光标在你选定的文件中,这就是你添加事件的地方。
说完了这些,首先你需要创建OnPaint事件,通过属性->消息窗口下找到WM_PAINT事件。然后再下拉列表中选择<Add>OnPaint。你注意到消息函数同时加到OpenGLControl.hOpenGLControl.cpp中。但是,与用户添加的函数不同的是,这个函数有"afx_msg"的前缀,还会在cpp文件的顶部加一个调用。我建议大家不要更改Visual Studio自动增加的东西,除非你知道它们是干什么的。
OnPaint函数内部,只有一小部分自动生成的代码需要更改。你自己渲染(或者说是绘制)OpenGL窗口的方式与MFC不同,不同在你是通过定时器来绘制的。以防万一接下来需要固定一个特定的帧率。由于要通过定时器来渲染窗口,OnPaint函数就不能按照原有的方式调用。因此就需要加上简单的一行来代替原有的调用。以下代码需要加到OpenGLControl.cpp文件中的OnPaint函数。

OpenGLControl.cpp

void COpenGLControl::OnPaint()  
{  
   //CPaintDC dc(this);    // device context for painting  
   ValidateRect(NULL);  
}  

第8步 添加OnCreate函数

接下来,按照之前的方法在添加一个消息类,这次选择WM_CREATE消息,选择<Add>OnCreate。和之前一样,新的类也会加到.h和.cpp文件中。当程序调用到它时,你只需要添加一行代码调用之后会增加的函数,oglInitialize函数。
注意:当然,你还没有添加这个函数,运行时会有编译错误。

OpenGLControl.cpp

int COpenGLControl::OnCreate(LPCREATESTRUCT lpCreateStruct)  
{  
   if (CWnd::OnCreate(lpCreateStruct) == -1)  
      return -1;  
   
   oglInitialize();  
   
   return 0;  
}  

第9步 添加oglInitialize函数

正如前文所说的那样,接下来我们将手动的添加这个函数,即oglInitialize函数,该函数在你的OpenGL类被创建的时候调用(通过OnCreate消息)。oglInitialize函数将负责设定所有OpenGL渲染所需要的信息。这些信息包括像素格式、渲染上下文以及清除颜色以确保程序正常交换缓存。
注意:下面的.cpp代码片段的最后一行电泳OnDraw*函数,该函数还没有创建,此时运行将有编译错误。

OpenGLControl.h
void oglInitialize(void)

OpenGLControl.cpp

void COpenGLControl::oglInitialize(void)  
{  
   // Initial Setup:  
   //  
   static PIXELFORMATDESCRIPTOR pfd =  
   {  
      sizeof(PIXELFORMATDESCRIPTOR),  
      1,  
      PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,  
      PFD_TYPE_RGBA,  
      32,    // bit depth  
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  
      16,    // z-buffer depth  
      0, 0, 0, 0, 0, 0, 0,  
   };  
   
   // Get device context only once.  
   hdc = GetDC()->m_hDC;  
   
   // Pixel format.  
   m_nPixelFormat = ChoosePixelFormat(hdc, &pfd);  
   SetPixelFormat(hdc, m_nPixelFormat, &pfd);  
   
   // Create the OpenGL Rendering Context.  
   hrc = wglCreateContext(hdc);  
   wglMakeCurrent(hdc, hrc);  
   
   // Basic Setup:  
   //  
   // Set color to use when clearing the background.  
   glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  
   glClearDepth(1.0f);  
   
   // Turn on backface culling  
   glFrontFace(GL_CCW);  
   glCullFace(GL_BACK);  
   
   // Turn on depth testing  
   glEnable(GL_DEPTH_TEST);  
   glDepthFunc(GL_LEQUAL);  
   
   // Send draw request  
   OnDraw(NULL);  
} 

第10步 添加OnDraw函数

接下来,我们将添加之前在oglInitialize函数中提到的OnDraw函数。这个函数将会扮演消息函数,但是需要手动添加。正如你归纳出来的那样,在该函数的声明语句中需要在前面添加"afx_msg"前缀。你将注意到在cpp代码片段中你实际上什么也不用做,只有一个TODO注释。在本教程后面部分会用它实现一些功能,例如摄像机控制等。
注意:如果你现在运行该程序,他并不会报错,但不幸的是在控件中什么也没有,这取决于你在OnPaint函数中做出的更改。

OpenGLControl.h
afx_msg void OnDraw(CDC *pDC);

OpenGLControl.cpp

void COpenGLControl::OnDraw(CDC *pDC)  
{  
   // TODO: Camera controls.  
}  

第11步 添加OnTimer函数

现在是时候用定时器来添加之前OnPaint函数的功能了。让我们添加一个新的消息类,选择WM_TIMER消息,然后在下拉列表中选择<Add>OnTimer来创建函数。
** OnTimer函数的代码十分的直白。这个类的定时器每次调用的时程序都会通过该函数执行。因此,函数自动传入的值是一个无符号整数,对应着你手动创建的定时器。使用select** 声明(译者认为应该是switch),我为定时器创建了相应的case。基本的"绘制并交换缓存"过程将在这个定时器里。在本教程的前半部分中你不用绘制任何图形,只需要正确的交换缓存即可。

OpenGLControl.cpp

void COpenGLControl::OnTimer(UINT nIDEvent)  
{  
   switch (nIDEvent)  
   {  
      case 1:  
      {  
         // Clear color and depth buffer bits  
         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
   
         // Draw OpenGL scene  
         // oglDrawScene();  
   
         // Swap buffers  
         SwapBuffers(hdc);  
   
         break;  
      }  
   
      default:  
         break;  
   }  
   
   CWnd::OnTimer(nIDEvent);  
}  

第12步 添加OnSize函数

你需要添加的最后一个消息函数是OnSize函数。和之前一样,选择WM_SIZE消息并在下拉列表中选择<Add>OnSize
OnSize函数在窗口大小改变时被调用。为了防止窗口大小改变造成的渲染窗口的失常,透视及视口等需要调整。在本教程中你其实不需要随窗口大小调整,但是在必要时可以通过以下函数来实现:

OpenGLControl.cpp

void COpenGLControl::OnSize(UINT nType, int cx, int cy)  
{  
   CWnd::OnSize(nType, cx, cy);  
   
   if (0 >= cx || 0 >= cy || nType == SIZE_MINIMIZED) return;  
   
   // Map the OpenGL coordinates.  
   glViewport(0, 0, cx, cy);  
   
   // Projection view  
   glMatrixMode(GL_PROJECTION);  
   
   glLoadIdentity();  
   
   // Set our current view perspective  
   gluPerspective(35.0f, (float)cx / (float)cy, 0.01f, 2000.0f);  
   
   // Model view  
   glMatrixMode(GL_MODELVIEW);  
}  

现在你已经完成了COpenGLControl类(至少是初始化)。接下来就是将他集成到你的主MFC窗口,即CoglMFCDialogDlg类。

第13步 定制主MFC对话框类

你的最后一步就是将你的OpenGL类集成到主MFC对话框中。这个过程非常简单。首先在oglMFCDialogDlg.h文件中将你的OpenGL类包含进去,然后添加一个COpenGLControl类作为本地变量。

oglMFCDialogDlg.h

#include "OpenGLControl.h"  
   
class CoglMFCDialogDlg : public CDialog  
{  
   private:  
      COpenGLControl m_oglWindow;  
   .  
   .  
   .  

接下来,在oglMFCDialogDlg.cpp文件中已经存在的OnInitDialog函数中return语句之前加上以下代码就可以在你创建的picture控件中启动定时器,渲染OpenGL内容。

注意:在出事测试中,我将定时器的间隔设为1毫秒。
oglMFCDialogDlg.cpp::OnInitDialog

.  
.  
.  
CRect rect;  
   
// Get size and position of the picture control  
GetDlgItem(IDC_OPENGL)->GetWindowRect(rect);  
   
// Convert screen coordinates to client coordinates  
ScreenToClient(rect);  
   
// Create OpenGL Control window  
m_oglWindow.oglCreate(rect, this);  
   
// Setup the OpenGL Window's timer to render  
m_oglWindow.m_unpTimer = m_oglWindow.SetTimer(1, 1, 0);  
.  
.  
. 

第14步 初始化 OpenGL 控件结果

现在你完成了!现在你应该看到黑色的背景。现在你可以在OnTimer中添加绘制函数。

第二部分:扩展OpenGL的功能(附加)

第15步 绘制几何图形

下一步就是在你的OpenGL空间中绘制一些测试图形。不幸的是,你需要设置摄像机系统,否则无法看到这些图形。如果没有摄像机直向特定点的话,你绘制的图形将被放置在摄像机原本的位置上,这样你就什么也看不见了。所以如果你想先看到你画的东西,请先看第16步:设置Maya风格的摄像机,在第16步中你将创建一个Alias|Wavefront Maya风格的摄像机(用鼠标进行旋转/缩放/平移)。但是我选择在下一步中做这些。
无论如何,本教程绘制的图形是十分简单的,就画一个立方体就行了(6个方形面)。如果你再次调用OnTimer函数,将会调用oglDrawScene函数,你将创建这个函数。

OpenGLControl.h

void oglDrawScene(void);  

** OpenGLControl.cpp**

void COpenGLControl::oglDrawScene(void)  
{  
   // Wireframe Mode  
   glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);  
   
   glBegin(GL_QUADS);  
      // Top Side  
      glVertex3f( 1.0f, 1.0f,  1.0f);  
      glVertex3f( 1.0f, 1.0f, -1.0f);  
      glVertex3f(-1.0f, 1.0f, -1.0f);  
      glVertex3f(-1.0f, 1.0f,  1.0f);  
   
      // Bottom Side  
      glVertex3f(-1.0f, -1.0f, -1.0f);  
      glVertex3f( 1.0f, -1.0f, -1.0f);  
      glVertex3f( 1.0f, -1.0f,  1.0f);  
      glVertex3f(-1.0f, -1.0f,  1.0f);  
   
      // Front Side  
      glVertex3f( 1.0f,  1.0f, 1.0f);  
      glVertex3f(-1.0f,  1.0f, 1.0f);  
      glVertex3f(-1.0f, -1.0f, 1.0f);  
      glVertex3f( 1.0f, -1.0f, 1.0f);  
   
      // Back Side  
      glVertex3f(-1.0f, -1.0f, -1.0f);  
      glVertex3f(-1.0f,  1.0f, -1.0f);  
      glVertex3f( 1.0f,  1.0f, -1.0f);  
      glVertex3f( 1.0f, -1.0f, -1.0f);  
   
      // Left Side  
      glVertex3f(-1.0f, -1.0f, -1.0f);  
      glVertex3f(-1.0f, -1.0f,  1.0f);  
      glVertex3f(-1.0f,  1.0f,  1.0f);  
      glVertex3f(-1.0f,  1.0f, -1.0f);  
   
      // Right Side  
      glVertex3f( 1.0f,  1.0f,  1.0f);  
      glVertex3f( 1.0f, -1.0f,  1.0f);  
      glVertex3f( 1.0f, -1.0f, -1.0f);  
      glVertex3f( 1.0f,  1.0f, -1.0f);  
   glEnd();  
}  

现在,你最后要做的是替换OnTimer函数的注释部分,调用oglDrawScene函数。

OpenGLControl.cpp::OnTimer

.  
.  
.  
// Clear color and depth buffer bits  
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
   
// Draw OpenGL scene  
oglDrawScene();  
   
// Swap buffers  
SwapBuffers(hdc);  
.  
.  
.  

第16步 设置Maya风格的摄像机

Maya风格的摄像机指的是在Alias|Wavefront Maya中使用的摄像机系统。鼠标左键旋转,右键缩放,中键平移。要实现这个功能,你需要创建一个OnMouseMove函数调用。
选择WM_MOUSEMOVE消息,在下拉菜单中选择<Add>OnMouseMove,创建函数。在新建的函数里,你需要为每个按键添加相应的操作。如果你回想起之前的内容,你创建一些成员变量用于旋转、缩放和平移。你将为这些变量设置新的值。

OpenGLControl.h
afx_msg void OnMouseMove(UINT nFlags, CPoint point);

OpenGLControl.cpp

void COpenGLControl::OnMouseMove(UINT nFlags, CPoint point)  
{  
   int diffX = (int)(point.x - m_fLastX);  
   int diffY = (int)(point.y - m_fLastY);  
   m_fLastX  = (float)point.x;  
   m_fLastY  = (float)point.y;  
   
   // Left mouse button  
   if (nFlags & MK_LBUTTON)  
   {  
      m_fRotX += (float)0.5f * diffY;  
   
      if ((m_fRotX > 360.0f) || (m_fRotX < -360.0f))  
      {  
         m_fRotX = 0.0f;  
      }  
   
      m_fRotY += (float)0.5f * diffX;  
   
      if ((m_fRotY > 360.0f) || (m_fRotY < -360.0f))  
      {  
         m_fRotY = 0.0f;  
      }  
   }  
   
   // Right mouse button  
   else if (nFlags & MK_RBUTTON)  
   {  
      m_fZoom -= (float)0.1f * diffY;  
   }  
   
   // Middle mouse button  
   else if (nFlags & MK_MBUTTON)  
   {  
      m_fPosX += (float)0.05f * diffX;  
      m_fPosY -= (float)0.05f * diffY;  
   }  
   
   OnDraw(NULL);  
   
   CWnd::OnMouseMove(nFlags, point);  
}  

此外,我在COpenGLControl类的构造函数中加一些初始化语句。

OpenGLControl.cpp

COpenGLControl::COpenGLControl(void)  
{  
   m_fPosX = 0.0f;    // X position of model in camera view  
   m_fPosY = 0.0f;    // Y position of model in camera view  
   m_fZoom = 10.0f;   // Zoom on model in camera view  
   m_fRotX = 0.0f;    // Rotation on model in camera view  
   m_fRotY = 0.0f;    // Rotation on model in camera view  
}  

现在最后要做的是改变OnDraw函数,将TODO注释改为以下:

OpenGLControl.cpp::OnDraw

.  
.  
.  
glLoadIdentity();  
glTranslatef(0.0f, 0.0f, -m_fZoom);  
glTranslatef(m_fPosX, m_fPosY, 0.0f);  
glRotatef(m_fRotX, 1.0f, 0.0f, 0.0f);  
glRotatef(m_fRotY, 0.0f, 1.0f, 0.0f);  
.  
.  
.  

第17步 正确地改变窗口的大小
改变MFC窗口的大小是一个十分繁琐的工作(或许和Visual Basic相比),不过也能轻松完成。而且一旦你做完一次,你就能一直重复使用这段代码。以下就是我摸索出来的,现在还在用的方法。这涉及到MFC和OpenGL的两个OnSize函数/功能。
首先,进入oglMFCDialogDlg.cpp文件中按照之前的方法创建OnSize消息函数。这回为你的MFC部分生成OnSize函数,但是你要加一些代码,整个Onsize函数是这样的。

oglMFCDialogDlg.cpp

void CoglMFCDialogDlg::OnSize(UINT nType, int cx, int cy)  
{  
   CDialog::OnSize(nType, cx, cy);  
   
   switch (nType)  
   {  
      case SIZE_RESTORED:  
      {  
         if (m_oglWindow.m_bIsMaximized)  
         {  
            m_oglWindow.OnSize(nType, cx, cy);  
            m_oglWindow.m_bIsMaximized = false;  
         }  
   
         break;  
      }  
   
      case SIZE_MAXIMIZED:  
      {  
         m_oglWindow.OnSize(nType, cx, cy);  
         m_oglWindow.m_bIsMaximized = true;  
   
         break;  
      }  
   }  
}  

你注意到了你改变了一些没有创建的变量的值。所以你现在要找到OpenGL窗口代码,在已经存在的OnSize函数后面添加以下代码。

OpenGLControl.cpp

.  
.  
.  
switch (nType)  
{  
   // If window resize token is "maximize"  
   case SIZE_MAXIMIZED:  
   {  
      // Get the current window rect  
      GetWindowRect(m_rect);  
   
      // Move the window accordingly  
      MoveWindow(6, 6, cx - 14, cy - 14);  
   
      // Get the new window rect  
      GetWindowRect(m_rect);  
   
      // Store our old window as the new rect  
      m_oldWindow = m_rect;  
   
      break;  
   }  
   
   // If window resize token is "restore"  
   case SIZE_RESTORED:  
   {  
      // If the window is currently maximized  
      if (m_bIsMaximized)  
      {  
         // Get the current window rect  
         GetWindowRect(m_rect);  
   
         // Move the window accordingly (to our stored old window)  
         MoveWindow(m_oldWindow.left,  
                    m_oldWindow.top - 18,  
                    m_originalRect.Width() - 4,  
                    m_originalRect.Height() - 4);  
   
         // Get the new window rect  
         GetWindowRect(m_rect);  
   
         // Store our old window as the new rect  
         m_oldWindow = m_rect;  
      }  
   
      break;  
   }  
}  
.  
.  
.  

以上就是这些。简单?不简单?请随意发表评论。感谢你的阅读,希望能帮到你。

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

推荐阅读更多精彩内容