OpenGL渲染原理

站在巨人的肩膀上,通常装逼都能成功!!!

OpenGL 渲染原理

01-自定义图层类型
02-初始化CAEAGLLayer图层属性
03-创建EAGLContext
04-创建渲染缓冲区
05-创建帧缓冲区
06-创建着色器
07-创建着色器程序
08-创建纹理对象
09-YUV转RGB绘制纹理
10-渲染缓冲区到屏幕
11-清理内存

12DD7A93A60C6222148CE8B55ACBED57.jpg

属性定义以及需要的相关代码

// 定义枚举用于获取定义变量的下标
typedef NS_ENUM(NSUInteger, IJSOpenGLViewEnum) {
    ATTRIB_POSITION,
    ATTRIB_TEXCOORD,
};

// #: 把参数包装成c语言的字符串
#define STRINGIZE(x) #x
#define STRINGIZE2(x) STRINGIZE(x)
#define SHADER_STRING(name) @STRINGIZE(name)

// 顶点着色器代码
NSString *const kVertexShaderString = SHADER_STRING
(
 // 图片信息传递的变量
 attribute vec4 position; // 4个顶点
 attribute vec2 inputTextureCoordinate;  // 两个定点

 varying vec2 textureCoordinate;
 
 void main()
 {
     gl_Position = position;
     textureCoordinate = inputTextureCoordinate;
 }
 );

// 片段着色器代码
NSString *const kYUVFullRangeConversionForLAFragmentShaderString = SHADER_STRING
(
 // 定义全局变量
 varying highp vec2 textureCoordinate;
 precision mediump float;
 uniform sampler2D luminanceTexture;
 uniform sampler2D chrominanceTexture;
 uniform mediump mat3 colorConversionMatrix;
 void main()
 {  // 把yuv 变成rgb
     mediump vec3 yuv;
     lowp vec3 rgb;
     
     yuv.x = texture2D(luminanceTexture, textureCoordinate).r;
     yuv.yz = texture2D(chrominanceTexture, textureCoordinate).ra - vec2(0.5, 0.5);
     rgb = colorConversionMatrix * yuv;
     
     gl_FragColor = vec4(rgb, 1);
 }
 );

static const GLfloat kColorConversion601FullRange[9] = {
    1.0,    1.0,         1.0,
    0.0,   -0.343,     1.765,
    1.4,     -0.711,    0.0,
};
// 属性相关
{
    GLuint _frameBuffer;
    GLuint _vertShader;
    GLuint _fragShader;
    GLuint _program;
    GLint  _luminanceTextureAtt;   // 亮度属性
    GLint _chrominanceTextureAtt;  // 色度属性
    GLsizei _bufferWidth;
    GLsizei _bufferHeight;
    CVOpenGLESTextureRef _luminanceTextureRef; // 亮度纹理
    CVOpenGLESTextureRef _chrominanceTextureRef;  //色度纹理
    CVOpenGLESTextureCacheRef _textureCacheRef;  // 纹理缓存
    GLuint _luminanceTexture;
    GLuint _chrominanceTexture;
    GLint _colorConversionMatrixAtt;
    GLfloat *_preferredConversion;
}
@property(nonatomic,strong) CAEAGLLayer *openGLLayer;  // 图层
@property(nonatomic,strong) EAGLContext *context;  // 图层
@property (nonatomic, assign) GLuint colorRenderBuffer;
@property (nonatomic, assign) GLuint frameBuffer;

正文:

继承自UIView自定义一个View

pragma mark - 1.自定义图层类型

/*
 修改当前的layer, 支持opengl 渲染
 */
+ (Class)layerClass
{
    return [CAEAGLLayer class];
}

pragma mark - 2.初始化图层

- (void)setupLayer
{
    CAEAGLLayer *openGLLayer = (CAEAGLLayer *)self.layer;
    self.openGLLayer = openGLLayer;
    openGLLayer.opaque = YES; // 设置不透明,CALayer 默认是透明的,透明性能不好,最好设置为不透明.
    // 设置绘图属性drawableProperties
    // kEAGLDrawablePropertyRetainedBacking: 是否缓存之前的数据
    // kEAGLColorFormatRGBA8 : red、green、blue、alpha共8位
    openGLLayer.drawableProperties = @{
                                       kEAGLDrawablePropertyRetainedBacking :[NSNumber numberWithBool:NO],
                                       kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8
                                       };
}

pragma mark - 3、创建OpenGL上下文,并且设置上下文

- (void)setupContext
{
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;  // 指定OpenGL 渲染 API 的版本,目前都使用 OpenGL ES 2.0
    self.context = [[EAGLContext alloc] initWithAPI:api];   // 创建EAGLContext上下文
    [EAGLContext setCurrentContext:_context];    // 设置为当前上下文,所有的渲染默认渲染到当前上下文
}

pragma mark - 4、创建渲染缓存---分配内存

- (void)setupRenderBuffer
{
    // OpgnGL 通过一些索引,去获取索引 分配n个未使用的渲染缓存对象,并将它存储到renderbuffers中。注意:返回的 id不会为0,0是OpenGL ES 保留的,我们也不能使用 id 为0的 renderbuffer
    /*
     n: 总数
     renderbuffers:渲染缓存区索引
     */
    glGenRenderbuffers(1, &_colorRenderBuffer);
    // 创建并绑定渲染缓存。当第一次来绑定某个渲染缓存的时候,它会分配这个对象的存储空间并初始化,此后再调用这个函数的时候会将指定的渲染缓存对象绑定为当前的激活状态: 操作索引就可以操作地址 -- 使用 GL_RENDERBUFFER 相当于是指针
    /*
     参数renderbuffer就是使用glGenRenderbuffers生成的id
       当指定id的renderbuffer第一次被设置为当前renderbuffer时,会初始化该 renderbuffer对象,其初始值为
             width 和 height:像素单位的宽和高,默认值为0;
             internal format:内部格式,三大 buffer 格式之一 -- color,depth or stencil;
             Color bit-depth:仅当内部格式为 color 时,设置颜色的 bit-depth,默认值为0;
             Depth bit-depth:仅当内部格式为 depth时,默认值为0;
             Stencil bit-depth: 仅当内部格式为 stencil,默认值为0
     */
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
    // 把渲染缓存绑定到渲染图层上CAEAGLLayer,并为它分配一个共享内存。
    // 并且会设置渲染缓存的格式,和宽度  为 color renderbuffer 分配存储空间
    /*
     把渲染缓存(renderbuffer)绑定到渲染图层(CAEAGLLayer)上,并为它分配一个共享内存。
     参数target,为哪个renderbuffer分配存储空间
     参数drawable,绑定在哪个渲染图层,会根据渲染图层里的绘图属性生成共享内存
     */
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_openGLLayer];
}

pragma mark - 5、创建帧缓冲区
它相当于buffer(color, depth, stencil)的管理者,三大buffer可以附加到一个framebuffer上
本质是把framebuffer内容渲染到屏幕

- (void)setupFrameBuffer
{
    // 分配n个未使用的帧缓存对象,并将它存储到framebuffers中
    glGenFramebuffers(1, &_frameBuffer);
     // 设置为当前 framebuffer 设置一个可读可写的帧缓存。当第一次来绑定某个帧缓存的时候,它会分配这个对象的存储空间并初始化,此后再调用这个函数的时候会将指定的帧缓存对象-----绑定为当前的激活状态
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    // 把颜色渲染缓存 添加到 帧缓存的GL_COLOR_ATTACHMENT0上,就会自动把渲染缓存的内容填充到帧缓存,在由帧缓存渲染到屏幕
  
    //该函数是将相关的 buffer(三大buffer之一)attach到framebuffer上(如果 renderbuffer不为 0,知道前面为什么说glGenRenderbuffers 返回的id 不会为 0 吧)或从 framebuffer上detach(如果 renderbuffer为 0)。参数 attachment 是指定 renderbuffer 被装配到那个装配点上,其值是GL_COLOR_ATTACHMENTi, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT中的一个,分别对应 color,depth和 stencil三大buffer
    /*
     将 _colorRenderBuffer 装配到 GL_COLOR_ATTACHMENT0 这个装配点上
     GL_FRAMEBUFFER: 当前帧缓存
     GL_COLOR_ATTACHMENT0: 缓存区有很多层,我们需要添加到颜色层 添加到那一层
     GL_RENDERBUFFER : 之前的渲染缓存
     _colorRenderBuffer :
     */
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer);
}

通常用来处理纹理对象,并且把处理好的纹理对象渲染到帧缓存上,从而显示到屏幕上
着色器分为顶点着色器,片段着色器
1,顶点着色器用来确定图形形状,顶点着色器 是一个可编程的处理单元,执行顶点变换、纹理坐标变换、光照、材质等顶点的相关操作,每顶点执行一次。替代了传统渲染管线中顶点变换、光照以及纹理坐标的处理。
2,片段着色器用来确定图形渲染颜色,是一个处理片元值及其相关联数据的可编程单元,片元着色器可执行纹理的访问、颜色的汇总、雾化等操作,每片元执行一次。
pragma mark - 06、创建着色器

- (void)setupShader
{
    // 创建顶点着色器 -- GL_VERTEX_SHADER
    _vertShader = [self loadShader:GL_VERTEX_SHADER withString:kVertexShaderString];

    // 创建片段着色器 -- GL_FRAGMENT_SHADER
    _fragShader = [self loadShader:GL_FRAGMENT_SHADER withString:kYUVFullRangeConversionForLAFragmentShaderString];
}
// 加载着色器
- (GLuint)loadShader:(GLenum)type withString:(NSString *)shaderString
{
    // 创建着色器
    GLuint shader = glCreateShader(type);
    if (shader == 0)
    {
        NSLog(@"创建失败");
        return 0;
    }
    // 加载着色器源代码
    const char * shaderStringUTF8 = [shaderString UTF8String];
    /*
     shader: 指向着色器对象的句柄
     count: 着色器源代码字符串的数量。着色器可以由多个源字符串组成,但是每个着色器只能有一个main函数
     string: 指向着色器源代码的字符串指针
     length: 指向保存着多个(如果有多个)源代码字符串大小的整型数组指针
     */
    glShaderSource(shader, 1, &shaderStringUTF8, NULL);
    glCompileShader(shader); // 编译着色器
    GLint compiled = 0;     // 检查是否完成
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);  // 获取完成状态
    if (compiled == 0)
    {
        glDeleteShader(shader);   // 没有完成就直接删除着色器
        return 0;
    }
    return shader;
}

pragma mark - 7、创建着色器程序

- (void)setupProgram
{
    _program = glCreateProgram(); //1 创建着色器程序
    glAttachShader(_program, _vertShader); // 2, 绑定着色器  / 绑定顶点着色器
    glAttachShader(_program, _fragShader);    // 3, 绑定片段着色器
    
    // 绑定着色器属性,方便以后获取,以后根据角标获取
    // 一定要在链接程序之前绑定属性,否则拿不到
    /*
     给属性绑定ID,通过ID获取属性,方便以后使用
     参数program 程序
     参数index 属性ID
     参数name 属性名称
     */
    glBindAttribLocation(_program, ATTRIB_POSITION , "position");  // 4个定点
    glBindAttribLocation(_program,ATTRIB_TEXCOORD, "inputTextureCoordinate");  //
    
    glLinkProgram(_program);      //4,链接程序

    // 5, 获取全局参数的索引值,注意 一定要在连接完成后才行,否则拿不到
    _luminanceTextureAtt = glGetUniformLocation(_program, "luminanceTexture");
    _chrominanceTextureAtt = glGetUniformLocation(_program, "chrominanceTexture");
    _colorConversionMatrixAtt = glGetUniformLocation(_program, "colorConversionMatrix");
    
    glUseProgram(_program);   //6 启动程序
}

pragma mark - 8、创建纹理对象,渲染采集图片到屏幕

采集的是一张一张的图片,可以把图片转换为OpenGL中的纹理, 然后再把纹理画到OpenGL的上下文中
什么是纹理?一个纹理其实就是一幅图像。
纹理映射,我们可以把这幅图像的整体或部分贴到我们先前用顶点勾画出的物体上去.
比如绘制一面砖墙,就可以用一幅真实的砖墙图像或照片作为纹理贴到一个矩形上,这样,一面逼真的砖墙就画好了。如果不用纹理映射的方法,则墙上的每一块砖都必须作为一个独立的多边形来画。另外,纹理映射能够保证在变换多边形时,多边形上的纹理图案也随之变化。
纹理映射是一个相当复杂的过程,基本步骤如下:

1)激活纹理单元、2)创建纹理 、3)绑定纹理 、4)设置滤波
注意:纹理映射只能在RGBA方式下执行

- (void)setupTexture:(CMSampleBufferRef)sampleBuffer
{
    // 获取图片信息
    CVImageBufferRef imageBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer);
    // CVPixelBufferRef == CVImageBufferRef
    // 获取图片宽度
    GLsizei bufferWidth = (GLsizei)CVPixelBufferGetWidth(imageBufferRef);
    _bufferWidth = bufferWidth;
    GLsizei bufferHeight = (GLsizei)CVPixelBufferGetHeight(imageBufferRef);
    _bufferHeight = bufferHeight;
    
    // 创建亮度纹理
    // 激活纹理单元0, 不激活,创建纹理会失败
    glActiveTexture(GL_TEXTURE0);
    
    // 创建纹理对象
    CVReturn err;
    /*
     根据图片生成纹理
     allocator, 参数allocator kCFAllocatorDefault,默认分配内存
     参数textureCache 纹理缓存
     参数sourceImage 图片
     参数textureAttributes NULL
     参数target , GL_TEXTURE_2D(创建2维纹理对象)
     参数internalFormat GL_LUMINANCE,亮度格式
     参数width 图片宽
     参数height 图片高
     参数format GL_LUMINANCE 亮度格式
     参数type 图片类型 GL_UNSIGNED_BYTE
     参数planeIndex 0,切面角标,表示第0个切面
     参数textureOut 输出的纹理对象
     */
    err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                       _textureCacheRef, imageBufferRef,
                                                       NULL,
                                                       GL_TEXTURE_2D,
                                                       GL_LUMINANCE,
                                                       bufferWidth,
                                                       bufferHeight,
                                                       GL_LUMINANCE,
                                                       GL_UNSIGNED_BYTE,
                                                       0,
                                                       &_luminanceTextureRef);
    if (err)
    {
        NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
    }
    // 获取纹理对象
    _luminanceTexture = CVOpenGLESTextureGetName(_luminanceTextureRef);
    
    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, _luminanceTexture);
    
    // 设置纹理滤波 -- 固定写法
    /*
     控制滤波,滤波就是去除没用的信息,保留有用的信息
     一般来说,纹理图像为正方形或长方形。但当它映射到一个多边形或曲面上并变换到屏幕坐标时,纹理的单个纹素很少对应于屏幕图像上的像素。根据所用变换和所用纹理映射,屏幕上单个象素可以对应于一个纹素的一小部分(即放大)或一大批纹素(即缩小)
     */
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    // 激活单元1
    glActiveTexture(GL_TEXTURE1);
    
    // 创建色度纹理
    /*
     根据图片生成纹理
     参数allocator kCFAllocatorDefault,默认分配内存
     参数textureCache 纹理缓存
     参数sourceImage 图片
     参数textureAttributes NULL
     参数target , GL_TEXTURE_2D(创建2维纹理对象)
     参数internalFormat GL_LUMINANCE,亮度格式
     参数width 图片宽
     参数height 图片高
     参数format GL_LUMINANCE 亮度格式
     参数type 图片类型 GL_UNSIGNED_BYTE
     参数planeIndex 0,切面角标,表示第0个切面
     参数textureOut 输出的纹理对象
     */
    err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                       _textureCacheRef,
                                                       imageBufferRef,
                                                       NULL,
                                                       GL_TEXTURE_2D,
                                                       GL_LUMINANCE_ALPHA,
                                                       bufferWidth / 2,
                                                       bufferHeight / 2,
                                                       GL_LUMINANCE_ALPHA,
                                                       GL_UNSIGNED_BYTE,
                                                       1,
                                                       &_chrominanceTextureRef);
    if (err)
    {
        NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
    }
    // 获取纹理对象
    _chrominanceTexture = CVOpenGLESTextureGetName(_chrominanceTextureRef);
    
    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, _chrominanceTexture);
    
    // 设置纹理滤波
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

pragma mark 9 :YUV 转 RGB

- (void)convertYUVToRGBOutput
{
    // 在创建纹理之前,有激活过纹理单元,就是那个数字.GL_TEXTURE0,GL_TEXTURE1
    // 指定着色器中亮度纹理对应哪一层纹理单元
    // 这样就会把亮度纹理,往着色器上贴
    /*
     指定着色器中亮度纹理对应哪一层纹理单元
     参数location:着色器中纹理坐标
     参数x:指定那一层纹理
     */
    glUniform1i(_luminanceTextureAtt, 0);
    
    // 指定着色器中色度纹理对应哪一层纹理单元
    glUniform1i(_chrominanceTextureAtt, 1);
    
    // YUV转RGB矩阵
    glUniformMatrix3fv(_colorConversionMatrixAtt, 1, GL_FALSE, _preferredConversion);
    
    // 计算顶点数据结构
    CGRect vertexSamplingRect = AVMakeRectWithAspectRatioInsideRect(CGSizeMake(self.bounds.size.width, self.bounds.size.height),self.layer.bounds);
    
    CGSize normalizedSamplingSize = CGSizeMake(0.0, 0.0);
    CGSize cropScaleAmount = CGSizeMake(vertexSamplingRect.size.width/self.layer.bounds.size.width, vertexSamplingRect.size.height/self.layer.bounds.size.height);
    
    if (cropScaleAmount.width > cropScaleAmount.height)
    {
        normalizedSamplingSize.width = 1.0;
        normalizedSamplingSize.height = cropScaleAmount.height/cropScaleAmount.width;
    }
    else
    {
        normalizedSamplingSize.width = 1.0;
        normalizedSamplingSize.height = cropScaleAmount.width/cropScaleAmount.height;
    }
    
    // 确定顶点数据结构
    GLfloat quadVertexData [] ={
        -1 * normalizedSamplingSize.width, -1 * normalizedSamplingSize.height,
        normalizedSamplingSize.width, -1 * normalizedSamplingSize.height,
        -1 * normalizedSamplingSize.width, normalizedSamplingSize.height,
        normalizedSamplingSize.width, normalizedSamplingSize.height,
    };
    
    // 确定纹理数据结构
    GLfloat quadTextureData[] =  { // 正常坐标
        0, 0,
        1, 0,
        0, 1,
        1, 1
    };
    
    // 激活ATTRIB_POSITION顶点数组开启顶点属性数组,只有开启顶点属性,才能给顶点属性信息赋值
    glEnableVertexAttribArray(ATTRIB_POSITION);
    // 给ATTRIB_POSITION顶点数组赋值
    /*
     设置顶点着色器属性,描述属性的基本信息
     参数indx:属性ID,给哪个属性描述信息
     参数size:顶点属性由几个值组成,这个值必须位1,2,3或4;
     参数type:表示属性的数据类型
     参数normalized:GL_FALSE表示不要将数据类型标准化
     参数stride 表示数组中每个元素的长度;
     参数ptr 表示数组的首地址
     */
    glVertexAttribPointer(ATTRIB_POSITION, 2, GL_FLOAT, 0, 0, quadVertexData);
    
    // 激活ATTRIB_TEXCOORD顶点数组开启顶点属性数组,只有开启顶点属性,才能给顶点属性信息赋值
    glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, 0, 0, quadTextureData);
    // 给ATTRIB_TEXCOORD顶点数组赋值
    glEnableVertexAttribArray(ATTRIB_TEXCOORD);
    
    // 渲染纹理数据,注意一定要和纹理代码放一起
    /*
     作用:使用当前激活的顶点着色器的顶点数据和片段着色器数据来绘制基本图形
     mode:绘制方式 一般使用GL_TRIANGLE_STRIP,三角形绘制法
     first:从数组中哪个顶点开始绘制,一般为0
     count:数组中顶点数量,在定义顶点着色器的时候,就定义过了,比如vec4,表示4个顶点
     */
            // 注意点,如果要绘制着色器上的点和片段,必须和着色器赋值代码放在一个代码块中,否则找不到绘制的信息,就绘制不上去,造成屏幕黑屏。
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

pragma mark - 10.渲染帧缓存

- (void)displayFramebuffer:(CMSampleBufferRef)sampleBuffer
{
    // 因为是多线程,每一个线程都有一个上下文,只要在一个上下文绘制就好,设置线程的上下文为我们自己的上下文,就能绘制在一起了,否则会黑屏.
    if ([EAGLContext currentContext] != _context)
    {
        [EAGLContext setCurrentContext:_context];
    }
    [self cleanUpTextures]; // 清空之前的纹理,要不然每次都创建新的纹理,耗费资源,造成界面卡顿
    [self setupTexture:sampleBuffer];  // 创建纹理对象
    [self convertYUVToRGBOutput];    // YUV 转 RGB
    glViewport(0, 0, self.bounds.size.width, self.bounds.size.height);   // 设置窗口尺寸 设置OpenGL渲染窗口的尺寸大小,一般跟图层尺寸一样
    [_context presentRenderbuffer:GL_RENDERBUFFER];   // 把上下文的东西渲染到屏幕 是将指定renderbuffer呈现在屏幕上上
}

pragma mark - 11.清理内存

- (void)dealloc
{
    [self destoryRenderAndFrameBuffer];    // 清空缓存
    [self cleanUpTextures];   // 清空纹理
}

#pragma mark - 销毁渲染和帧缓存
- (void)destoryRenderAndFrameBuffer
{
    glDeleteRenderbuffers(1, &_colorRenderBuffer);
    _colorRenderBuffer = 0;
    
    glDeleteBuffers(1, &_frameBuffer);
    _frameBuffer = 0;
}

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

推荐阅读更多精彩内容