OpenGL ES學(xué)習(xí)實戰(zhàn)(360全景視頻播放器)

對OpenGL ES學(xué)習(xí)了一段時間,今天實現(xiàn)一個360度的全景視頻播放器娩井。本博客的代碼可在我的github倉庫下載暇屋,但如果感覺可以就start一下你們的支持是我將博客寫下去的動力!本博客的demo是我之前在面試一家做VR視頻的公司時寫的洞辣,雖然我拿著做好的demo去面試了但還是沒讓我見技術(shù)人員人事就把我打發(fā)了率碾。

注意:本博客已假定你對OpenGL ES已經(jīng)具備基本知識,很多OpenGL ES基礎(chǔ)理論不再解釋屋彪,如果感覺有不理解的地方可以找別的博客進行學(xué)習(xí)所宰。我建議對于同一個知識最好看不同的博客有比較的學(xué)習(xí),這樣才會有不一樣的收獲畜挥。

全景視頻原理

一仔粥、拍攝設(shè)備

全景視頻在拍攝時是多個攝像機同時在一個點向四面八方拍攝。下面在網(wǎng)上找了一個拍攝設(shè)備的圖片蟹但。

攝像機.jpg
攝像機.jpeg

我在面試的那家做VR視頻的公司見到了他們的拍攝設(shè)備發(fā)面只有2個攝像頭躯泰,類似下面的圖片里的設(shè)備,但通過只有2個攝像頭的設(shè)備來拼接成的全景視頻在移動視角時會有強烈的拉伸感华糖,我在觀看一些小公司的App里的全景視頻時會有這樣的體驗麦向,具體視頻質(zhì)量的好壞這里就不深入討論了大家可以自己在各平臺對比體驗一下就知道了。

拍攝設(shè)備.png

如果要拍攝的是VR視頻每個方向上會有2個攝像頭(區(qū)分左右眼)客叉,不過這種視頻很少見诵竭。個人感覺很多公司都是通過全景視頻做一下處理來生成VR視頻的,所以沒有很強的立體感兼搏,這也是個人的感覺卵慰,如果有對VR視頻了解更多的大神可以在下面評論處說明以供大家共同學(xué)習(xí)。

二佛呻、視頻拼接

后期視頻的制作會將視頻目標當作一個球來制作成視頻裳朋,也就是說最后的視頻是要渲染到一個球上面的∠胖可以想像如果用平常的播放器來播放的效果圖像的上面和下面是被拉伸的鲤嫡,下面是一個在網(wǎng)上下載的全景視頻在平常的播放器播放的效果,可以看出下面的路面被嚴重拉伸绑莺,上面其實也被拉伸了因為是黑色的所以看著不太明顯暖眼。

2017-10-09 14_49_11.gif

最后這個視頻是要渲染到一個球上面的,而我們的視角在是在球的中心點紊撕,這樣就可以向四面八方去觀看了玫镐。下面是一個我做好的播放器里播放的效果捍岳,有對比才能看出圖像被拉伸的情況咖气。

全景效果.gif

代碼實現(xiàn)

一、生成頂點數(shù)據(jù)

從上面的介紹可知我們只要生成一個球體并將視頻的每一幀渲染到球上面就可以了惭缰。怎么生成球體可以看我另一篇博客OpenGL ES學(xué)習(xí)筆記之四(創(chuàng)建球體),這里只簡單介紹一下。生成球的頂點信息:


/**
 繪制一個球的頂點
 
 @param num 傳入要生成的頂點的一層的個數(shù)(最后生成的頂點個數(shù)為 num * num)
 @return 返回生成后的頂點
 */
- (Vertex *)getBallDevidNum:(GLint) num{
    
    if (num % 2 == 1) {
        return 0;
    }
    
    GLfloat delta = 2 * M_PI / num; // 分割的份數(shù)
    GLfloat ballRaduis = 0.3; // 球的半徑
    GLfloat pointZ;
    GLfloat pointX;
    GLfloat pointY;
    GLfloat textureY;
    GLfloat textureX;
    GLfloat textureYdelta = 1.0 / (num / 2);
    GLfloat textureXdelta = 1.0 / num;
    GLint layerNum = num / 2.0 + 1; // 層數(shù)
    GLint perLayerNum = num + 1; // 要讓點再加到起點所以num + 1
    
    Vertex * cirleVertex = malloc(sizeof(Vertex) * perLayerNum * layerNum);
    memset(cirleVertex, 0x00, sizeof(Vertex) * perLayerNum * layerNum);
    
    // 層數(shù)
    for (int i = 0; i < layerNum; i++) {
        // 每層的高度(即pointY)笼才,為負數(shù)讓其從下向上創(chuàng)建
        pointY = -ballRaduis * cos(delta * i);
        
        // 每層的半徑
        GLfloat layerRaduis = ballRaduis * sin(delta * i);
        // 每層圓的點,
        for (int j = 0; j < perLayerNum; j++) {
            // 計算
            pointX = layerRaduis * cos(delta * j);
            pointZ = layerRaduis * sin(delta * j);
            textureX = textureXdelta * j;
            // 解決圖片上下顛倒的問題
            textureY = 1 - textureYdelta * i;
            
            cirleVertex[i * perLayerNum + j] = (Vertex){pointX, pointY, pointZ, textureX, textureY};
        }
    }
    
    return cirleVertex;
}

/**
 生成球體的頂點索引數(shù)組

 @param num 每一層頂點的個數(shù)
 @return 返回生成好的數(shù)組
 */
- (GLuint *)getBallVertexIndex:(GLint)num{
    
    // 每層要多原點兩次
    GLint sizeNum = sizeof(GLuint) * (num + 1) * (num + 1);
    
    GLuint * ballVertexIndex = malloc(sizeNum);
    memset(ballVertexIndex, 0x00, sizeNum);
    GLint layerNum = num / 2 + 1;
    GLint perLayerNum = num + 1; // 要讓點再加到起點所以num + 1
    
    for (int i = 0; i < layerNum; i++) {
        
        if (i + 1 < layerNum) {
            
            for (int j = 0; j < perLayerNum; j++) {
                
                // i * perLayerNum * 2每層的下標是原來的2倍
                ballVertexIndex[(i * perLayerNum * 2) + (j * 2)] = i * perLayerNum + j;
                // 后一層數(shù)據(jù)
                ballVertexIndex[(i * perLayerNum * 2) + (j * 2 + 1)] = (i + 1) * perLayerNum + j;
            }
        } else {
            
            for (int j = 0; j < perLayerNum; j++) {
                // 后最一層數(shù)據(jù)單獨處理
                ballVertexIndex[i * perLayerNum * 2 + j] = i * perLayerNum + j;
            }
        }
    }
    return ballVertexIndex;
}

/**
 設(shè)置VBO
 */
- (void)setupVertexVBO {
    
    // 生成頂點數(shù)據(jù)(包括紋理頂點)
    Vertex * vertex = [self getBallDevidNum:kDivisionNum];
    // 生成頂點數(shù)據(jù)對應(yīng)的索引數(shù)據(jù)
    GLuint * indexes = [self getBallVertexIndex:kDivisionNum];
    
    // 設(shè)置頂點數(shù)據(jù)的VBO緩存
    GLuint vertexBufferVBO;
    glGenBuffers(1, &vertexBufferVBO);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * (kDivisionNum + 1) * (kDivisionNum / 2 + 1), vertex, GL_STATIC_DRAW);
    
    // 設(shè)置頂點索引數(shù)據(jù)的VBO緩存
    GLuint indexBufferVBO;
    glGenBuffers(1, &indexBufferVBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferVBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * (kDivisionNum + 1) * (kDivisionNum + 1), indexes, GL_STATIC_DRAW);
    
    // 設(shè)置頂點數(shù)據(jù)在從VBO中讀取和傳遞的指針設(shè)置
    glVertexAttribPointer(_myPositionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLvoid *)NULL);
    glEnableVertexAttribArray(_myPositionSlot);
    
    glVertexAttribPointer(_myTextureCoordsSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
    glEnableVertexAttribArray(_myTextureCoordsSlot);

    free(vertex);
    free(indexes);
}

這里提一下漱受,在我另一篇博客里生成的球體視角是在球體的外面,想讓視角在球體內(nèi)可將projectionMatrix傳的值中讓其沿Z軸偏移的代碼刪除即可骡送,因為我們生成的球體默認視角就在球體的中心昂羡,我將projectionMatrix沿Z軸偏移了-3才讓視角變成了外面。

二摔踱、生成紋理數(shù)據(jù)

首先我們要知道視頻其實就是一幀一幀的圖片虐先,我們只要將每一幀圖片在相應(yīng)時間點渲染到球體上就可以實現(xiàn)視頻播放了。首先我們獲取視頻數(shù)據(jù):

/**
 設(shè)置播放數(shù)據(jù)
 */
- (void)setupPlayerData{
    
    NSString * path = [[NSBundle mainBundle] pathForResource:@"demo.mp4" ofType:nil];
    // 獲取視頻資源信息
    _myAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:path]];
    
    _myPlayerItem = [[AVPlayerItem alloc] initWithAsset:_myAsset];
    // 創(chuàng)建視頻播放器
    _myPlyaer = [[AVPlayer alloc] initWithPlayerItem:_myPlayerItem];
    // 播放視頻
    [_myPlyaer play];
    
    // 設(shè)置視頻格式信息
    NSDictionary * dic = [NSDictionary dictionaryWithObjectsAndKeys:@(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange), kCVPixelBufferPixelFormatTypeKey, nil];
    // 創(chuàng)建視頻輸出派敷,后面會從_myPlayerOutput里讀取視頻的每一幀圖像信息
    _myPlayerOutput = [[AVPlayerItemVideoOutput alloc] initWithOutputSettings:dic];
    [_myPlayerItem addOutput:_myPlayerOutput];
}

通過上面的步驟我們可以獲取視頻每一幀的CVPixelBufferRef信息蛹批,原來我是用普通的圖片轉(zhuǎn)紋理的方式實現(xiàn)的,但運行的時候總出錯后來查了一下資料發(fā)現(xiàn)CoreVideo庫里有專門轉(zhuǎn)OpenGL紋理的方法(CVOpenGLESTextureCacheCreateTextureFromImage)篮愉,后來就用該方法解決了問題腐芍。方法如下,由于學(xué)習(xí)時間不長下面的參數(shù)注解只是我個人見解试躏,可能會有錯誤這里供大家做個參考吧猪勇。

CVOpenGLESTextureCacheCreateTextureFromImage(
    CFAllocatorRef CV_NULLABLE allocator,  // 分配紋理對象,可能為NULL颠蕴,
    CVOpenGLESTextureCacheRef CV_NONNULL textureCache, // 紋理緩存
    CVImageBufferRef CV_NONNULL sourceImage, // 傳入的圖像數(shù)據(jù)泣刹,用于生成相應(yīng)紋理
    CFDictionaryRef CV_NULLABLE textureAttributes, // 創(chuàng)建紋理的屬字典,可傳NULL
    GLenum target, // 渲染的目標裁替,可為GL_TEXTURE_2D和GL_RENDERBUFFER
    GLint internalFormat, // 圖片的色彩空間信息项玛,如GL_RGBA貌笨、GL_LUMINANCE弱判、GL_RGBA8_OES、 GL_RG和 GL_RED 
    GLsizei width, // 圖片的寬
    GLsizei height, // 圖片的高
    GLenum format, // 每個像素數(shù)據(jù)的色彩空間锥惋,如GL_RGBA昌腰、GL_LUMINANCE
    GLenum type, // 每個像素數(shù)據(jù)的類型
    size_t planeIndex, // 視頻數(shù)據(jù)buffer里哪一個平面的數(shù)據(jù)
    CVOpenGLESTextureRef  * CV_NONNULL textureOut // 最終輸出的紋理信息
) 

我們獲取的視頻數(shù)據(jù)是YUV(其實是YCbCr的色彩空間)格式的,該種格式主要是用于圖像數(shù)據(jù)的的壓縮膀跌。該種格式將像素信息分為1個亮度通道和2個色度通道遭商。由于人眼對色度的感知不太敏感而對亮度感知很敏感,當我們減弱色度通道的信息時人眼也很難察覺到前后圖像的變化捅伤。通過這一原理就可以通過刪除部分像素的色度通道而使用相鄰像素色度數(shù)據(jù)的方式來顯示圖像劫流,這樣就能實現(xiàn)圖像的壓縮。更詳細的解釋可查看其他資料,這里只做簡單說明祠汇。YUV的buffer數(shù)據(jù)一般會有兩個平面仍秤,一個是亮度一個是色度(2個色度通道混在一個平面里),我們都要將其傳給著色器:

/**
 設(shè)置視頻數(shù)據(jù)轉(zhuǎn)紋理
 */
- (void)setupVideoTexture{
    
    CMTime time = [_myPlayerItem currentTime];
    
    // 通過時間獲取相應(yīng)幀的圖片數(shù)據(jù)
    CVPixelBufferRef pixelBuffer = [_myPlayerOutput copyPixelBufferForItemTime:time itemTimeForDisplay:nil];
    
    if (pixelBuffer == nil) {
        return;
    }
    
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);
    
    CVReturn result;
    
    GLsizei textureWidth = (GLsizei)CVPixelBufferGetWidth(pixelBuffer);
    GLsizei textureHeight = (GLsizei)CVPixelBufferGetHeight(pixelBuffer);
    
    if (_cache == nil) {
        
        NSLog(@"no video texture cache");
    }
    
    _lumaTexture = nil;
    _chromaTexture = nil;
    // 刷新緩沖區(qū)保證上次數(shù)據(jù)正常提交
    CVOpenGLESTextureCacheFlush(_cache, 0);
    
    glActiveTexture(GL_TEXTURE0);
    result = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                          _cache,
                                                          pixelBuffer,
                                                          nil,
                                                          GL_TEXTURE_2D,
                                                          GL_RED_EXT,
                                                          textureWidth,
                                                          textureHeight,
                                                          GL_RED_EXT,
                                                          GL_UNSIGNED_BYTE,
                                                          0,
                                                          &_lumaTexture);
    if (result != 0) {
        NSLog(@"create CVOpenGLESTextureCacheCreateTextureFromImage failure 1 %d", result);
    }
    // 綁定紋理
    glBindTexture(CVOpenGLESTextureGetTarget(_lumaTexture), CVOpenGLESTextureGetName(_lumaTexture));
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    // UV
    glActiveTexture(GL_TEXTURE1);
    result = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                          _cache,
                                                          pixelBuffer,
                                                          nil,
                                                          GL_TEXTURE_2D,
                                                          GL_RG_EXT,
                                                          textureWidth/2,
                                                          textureHeight/2,
                                                          GL_RG_EXT,
                                                          GL_UNSIGNED_BYTE,
                                                          1,
                                                          &_chromaTexture);
    if (result != 0) {
        NSLog(@"create CVOpenGLESTextureCacheCreateTextureFromImage failure 2 %d", result);
    }
    // 綁定紋理
    glBindTexture(CVOpenGLESTextureGetTarget(_chromaTexture), CVOpenGLESTextureGetName(_chromaTexture));
    
    CFRelease(_lumaTexture);
    CFRelease(_chromaTexture);
    
    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
    CVPixelBufferRelease(pixelBuffer);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
三可很、YUV轉(zhuǎn)RGBA

圖像顯示的數(shù)據(jù)是RGBA格式的而我們傳的并不是RGBA格式的而是YUV诗力,所以我們還要將YUV數(shù)據(jù)轉(zhuǎn)成RGBA數(shù)據(jù)。在網(wǎng)上找了一個YUV轉(zhuǎn)RGB的公式我抠,在查找資料的時候發(fā)現(xiàn)公式有好幾個苇本,其中只是系數(shù)的細微差別。公式已在下面列出菜拓,其中公式一和公式二都可正常使用瓣窄,對于公式三可適用于普通的圖片資料的轉(zhuǎn)換對于視頻格式是不適用的,因為視頻類的色彩通道的取值范圍不同與圖片詳情可看音視頻開發(fā):RGB與YUV相互轉(zhuǎn)換問題這篇博客纳鼎。

// YUV轉(zhuǎn)GRB公式一康栈、
R = 1.164 * (Y - 16) + 1.596 * (V - 128)
G = 1.164 * (Y - 16) - 0.39 * (U - 128) - 0.813 * (V - 128)
B = 1.164 * (Y - 16) + 2.018 * (U - 128)

// YUV轉(zhuǎn)GRB公式二、
R = 1.164 * (Y - 16) + 1.793 * (V - 128)
G = 1.164 * (Y - 16) - 0.213 * (U - 128) - 0.533 * (V - 128)
B = 1.164 * (Y - 16) + 2.112 * (U - 128)

// YUV轉(zhuǎn)GRB公式三喷橙、
R = Y + 1.402V
G = Y - 0.344U - 0.714V
B = Y + 1.772U

對于色彩空間的轉(zhuǎn)換要在著色器里進行啥么,我們將片元著色器的代碼寫成如下形式:

precision mediump float;
// 亮度通道紋理
uniform sampler2D myTexture;
// 色度通道紋理
uniform sampler2D samplerUV;

varying vec2 myTextureCoordsOut;

void main()
{
    mediump vec3 yuv;
    lowp vec3 rgb;
    
    yuv.x = texture2D(myTexture, myTextureCoordsOut).r;;
    yuv.yz = texture2D(samplerUV, myTextureCoordsOut).rg;
    
    rgb.r = 1.164 * (yuv.x - 16.0 / 255.0) + 1.793 * (yuv.z - 128.0 / 255.0);
    rgb.g = 1.164 * (yuv.x - 16.0 / 255.0) - 0.213 * (yuv.y - 128.0 / 255.0) - 0.533 * (yuv.z - 128.0 / 255.0);
    rgb.b = 1.164 * (yuv.x - 16.0 / 255.0) + 2.112 * (yuv.y - 128.0 / 255.0);
    
    gl_FragColor = vec4(rgb, 1.0);
}

上面的代碼我們還可以簡化一下,將上面的公式轉(zhuǎn)換成矩陣乘法因為GUP更適合矩陣的計算贰逾,代碼修改如下:

precision mediump float;
// 亮度通道紋理
uniform sampler2D myTexture;
// 色度通道紋理
uniform sampler2D samplerUV;

varying vec2 myTextureCoordsOut;

void main()
{
    // 用一個矩陣來簡化后面YUV轉(zhuǎn)GRB的計算公式
    mat3 conversionColor = mat3(1.164, 1.164, 1.164,
                                 0.0, -0.213, 2.112,
                                 1.793, -0.533, 0.0);
    
    mediump vec3 yuv;
    lowp vec3 rgb;
    
    yuv.x = texture2D(myTexture, myTextureCoordsOut).r - (16.0/255.0);
    yuv.yz = texture2D(samplerUV, myTextureCoordsOut).rg - vec2(0.5, 0.5);
    
    rgb = conversionColor * yuv;
    
    gl_FragColor = vec4(rgb, 1.0);
}

到這里播放器的主要代碼基本已經(jīng)完成其他細節(jié)可在我github代碼里查看悬荣,上面需要一個定時器來不停的調(diào)用渲染方法以實現(xiàn)播放不同的幀數(shù)據(jù),播放效果的圖以在上面給出過這里不再占地方了疙剑。

小優(yōu)化

這里存在一個問題,球體的外面和里面都會渲染我們從外面看一下效果言缤。

球體的外面.gif

在播放全景視頻的時候我們是不會看外面的圖像管挟,這樣就會造成性能消耗上的浪費轿曙。所以我們要把外面剔除不讓其渲染,在原代碼里添加以下代碼:

// 在渲染方法這前寫以下代碼
// 面剔除以提高性能
    glEnable(GL_CULL_FACE); // 開啟面剔除
    glCullFace(GL_BACK); // 剔除背面
    glFrontFace(GL_CW); // 設(shè)置順時針為前面

其他的方法都很簡單這里就不啰嗦了僻孝。這里已把主要的實現(xiàn)說明了其他細節(jié)可以在我的代碼里查看。寫的有點倉促如果有不妥的地方還請各位大神指正您单。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市荞雏,隨后出現(xiàn)的幾起案子虐秦,更是在濱河造成了極大的恐慌平酿,老刑警劉巖悦陋,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叨恨,死亡現(xiàn)場離奇詭異,居然都是意外死亡痒钝,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門蚕甥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來栋荸,“玉大人,你說我怎么就攤上這事爱沟〈冶常” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵括享,是天一觀的道長珍促。 經(jīng)常有香客問我,道長娇斩,這世上最難降的妖魔是什么沐悦? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任藏否,我火速辦了婚禮充包,結(jié)果婚禮上遥椿,老公的妹妹穿的比我還像新娘淆储。我一直安慰自己,他們只是感情好碴裙,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布点额。 她就那樣靜靜地躺著,像睡著了一般载慈。 火紅的嫁衣襯著肌膚如雪珍手。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天寡具,我揣著相機與錄音稚补,去河邊找鬼。 笑死拯钻,一個胖子當著我的面吹牛撰豺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播亩歹,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凡橱,長吁一口氣:“原來是場噩夢啊……” “哼稼钩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坝撑,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抚笔,沒想到半個月后殊橙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡叠纹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年鸽疾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冒窍。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡豺鼻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出儒飒,到底是詐尸還是另有隱情,我是刑警寧澤附帽,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布井誉,位于F島的核電站颗圣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏在岂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一易茬、第九天 我趴在偏房一處隱蔽的房頂上張望祠丝。 院中可真熱鬧除嘹,春花似錦岸蜗、人聲如沸叠蝇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽犁柜。三九已至堂淡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绢淀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工覆履, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留硝全,地道東北人楞抡。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像赂鲤,于是被迫代替她去往敵國和親柱恤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容