2018-07-29

前言 ? GPUImage是iOS上一個基于OpenGL進行圖像處理的開源框架测蹲,內(nèi)置大量濾鏡,架構(gòu)靈活鬼吵,可以在其基礎(chǔ)上很輕松地實現(xiàn)各種圖像處理功能扣甲。本文主要向大家分享一下項目的核心架構(gòu)、源碼解讀及使用心得齿椅。 ? GPUImage有哪些特性 ? 1.豐富的輸入組件 攝像頭琉挖、圖片、視頻涣脚、OpenGL紋理粹排、二進制數(shù)據(jù)、UIElement(UIView, CALayer) ? 2.大量現(xiàn)成的內(nèi)置濾鏡(4大類) 1). 顏色類(亮度涩澡、色度顽耳、飽和度、對比度妙同、曲線射富、白平衡...) 2). 圖像類(仿射變換、裁剪粥帚、高斯模糊胰耗、毛玻璃效果...) 3). 顏色混合類(差異混合、alpha混合芒涡、遮罩混合...) 4). 效果類(像素化柴灯、素描效果卖漫、壓花效果、球形玻璃效果...) ? 3.豐富的輸出組件 UIView赠群、視頻文件羊始、GPU紋理、二進制數(shù)據(jù) ? 4.靈活的濾鏡鏈 濾鏡效果之間可以相互串聯(lián)查描、并聯(lián)突委,調(diào)用管理相當(dāng)靈活。 ? 5.接口易用 濾鏡和OpenGL資源的創(chuàng)建及使用都做了統(tǒng)一的封裝冬三,簡單易用匀油,并且內(nèi)置了一個cache模塊實現(xiàn)了framebuffer的復(fù)用。 ? 6.線程管理 OpenGLContext不是多線程安全的勾笆,GPUImage創(chuàng)建了專門的contextQueue敌蚜,所有的濾鏡都會扔到統(tǒng)一的線程中處理。 ? 7.輕松實現(xiàn)自定義濾鏡效果 繼承GPUImageFilter自動獲得上面全部特性窝爪,無需關(guān)注上下文的環(huán)境搭建钝侠,專注于效果的核心算法實現(xiàn)即可。 基本用法 // 獲取一張圖片 UIImage *inputImage = [UIImage imageNamed:@"sample.jpg"]; // 創(chuàng)建圖片輸入組件GPUImagePicture *sourcePicture = [[GPUImagePicture alloc] initWithImage:inputImage smoothlyScaleOutput:YES];? // 創(chuàng)建素描濾鏡 GPUImageSketchFilter *customFilter = [[GPUImageSketchFilter alloc] init]; // 把素描濾鏡串聯(lián)在圖片輸入組件之后 [sourcePicture addTarget:customFilter]; // 創(chuàng)建ImageView輸出組件GPUImageView *imageView = [[GPUImageView alloc] initWithFrame:mainScreenFrame]; [self.view addSubView:imageView]; // 把ImageView輸出組件串在濾鏡鏈末尾[customFilter addTarget:imageView]; // 調(diào)用圖片輸入組件的process方法酸舍,渲染結(jié)果就會繪制到imageView上[sourcePicture processImage]; ? 效果如圖: ? 整個框架的目錄結(jié)構(gòu) ? ? 核心架構(gòu) ? ? 基本上每個濾鏡都繼承自GPUImageFilter; 而GPUImageFilter作為整套框架的核心里初; 接收一個GPUImageFrameBuffer輸入啃勉; 調(diào)用GLProgram渲染處理; 輸出一個GPUImageFrameBuffer双妨; 把輸出的GPUImageFrameBuffer傳給通過targets屬性關(guān)聯(lián)的下級濾鏡淮阐; 直到傳遞至最終的輸出組件; 核心架構(gòu)可以整體劃分為三塊:輸入刁品、濾鏡處理泣特、輸出 接下來我們就深入源碼,看看GPUImage是如何獲取數(shù)據(jù)挑随、傳遞數(shù)據(jù)状您、處理數(shù)據(jù)和輸出數(shù)據(jù)的 ? 獲取數(shù)據(jù) ? GPUImage提供了多種不同的輸入組件,但是無論是哪種輸入源兜挨,獲取數(shù)據(jù)的本質(zhì)都是把圖像數(shù)據(jù)轉(zhuǎn)換成OpenGL紋理膏孟。這里就以視頻拍攝組件(GPUImageVideoCamera)為例,來講講GPUImage是如何把每幀采樣數(shù)據(jù)傳入到GPU的拌汇。 ? GPUImageVideoCamera里大部分代碼都是對攝像頭的調(diào)用管理柒桑,不了解的同學(xué)可以去學(xué)習(xí)一下AVFoundation(傳送門)。攝像頭拍攝過程中每一幀都會有一個數(shù)據(jù)回調(diào)噪舀,在GPUImageVideoCamera中對應(yīng)的處理回調(diào)的方法為: ? - (void)processVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer; ? iOS的每一幀攝像頭采樣數(shù)據(jù)都會封裝成CMSampleBufferRef魁淳; CMSampleBufferRef除了包含圖像數(shù)據(jù)飘诗、還包含一些格式信息、圖像寬高界逛、時間戳等額外屬性昆稿; 攝像頭默認的采樣格式為YUV420,關(guān)于YUV格式大家可以自行搜索學(xué)習(xí)一下(傳送門): ? YUV420按照數(shù)據(jù)的存儲方式又可以細分成若干種格式仇奶,這里主要是kCVPixelFormatType_420YpCbCr8BiPlanarFullRange和kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange兩種貌嫡; ? 兩種格式都是planar類型的存儲方式,y數(shù)據(jù)和uv數(shù)據(jù)分開放在兩個plane中该溯; 這樣的數(shù)據(jù)沒法直接傳給GPU去用岛抄,GPUImageVideoCamera把兩個plane的數(shù)據(jù)分別取出: ? - (void)processVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer { ? ?// 一大坨的代碼用于獲取采樣數(shù)據(jù)的基本屬性(寬、高狈茉、格式等等) ? ?...... ? ?if ([GPUImageContext supportsFastTextureUpload] && captureAsYUV) { ? ? ? ?CVOpenGLESTextureRef luminanceTextureRef = NULL; ? ? ? ?CVOpenGLESTextureRef chrominanceTextureRef = NULL; ? ? ? ?if (CVPixelBufferGetPlaneCount(cameraFrame) > 0) // Check for YUV planar inputs to do RGB conversion ? ? ? ?{ ?? ? ? ? ? ?...... // 從cameraFrame的plane-0提取y通道的數(shù)據(jù)夫椭,填充到luminanceTextureRef ? ? ? ? ? ?glActiveTexture(GL_TEXTURE4); ? ? ? ? ? ?err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], cameraFrame, NULL, GL_TEXTURE_2D, GL_LUMINANCE, bufferWidth, bufferHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0, &luminanceTextureRef); ? ? ? ? ? ?...... ? ? ? ? ? ? ? ? ? ? ? ?// 從cameraFrame的plane-1提取uv通道的數(shù)據(jù),填充到chrominanceTextureRef ? ? ? ? ? ?glActiveTexture(GL_TEXTURE5); ? ? ? ? ? ?err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], cameraFrame, NULL, GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, bufferWidth/2, bufferHeight/2, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 1, &chrominanceTextureRef); ? ? ? ? ? ?...... ? ? ? ? ? ? ?? ? ? ? ? ?// 把luminance和chrominance作為2個獨立的紋理傳入GPU ? ? ? ? ? ?[self convertYUVToRGBOutput]; ? ? ? ? ? ?...... ? ? ? ?} ? ?} else { ?? ? ? ?...... ? ?} ? } ? 注意CVOpenGLESTextureCacheCreateTextureFromImage中對于internalFormat的設(shè)置氯庆; 通常我們創(chuàng)建一般紋理的時候都會設(shè)成GL_RGBA蹭秋,傳入的圖像數(shù)據(jù)也會是rgba格式的; 而這里y數(shù)據(jù)因為只包含一個通道堤撵,所以設(shè)成了GL_LUMINANCE(灰度圖)仁讨; uv數(shù)據(jù)則包含2個通道,所以設(shè)成了GL_LUMINANCE_ALPHA(帶alpha的灰度圖)实昨; 另外uv紋理的寬高只設(shè)成了圖像寬高的一半洞豁,這是因為yuv420中,每個相鄰的2x2格子共用一份uv數(shù)據(jù)荒给; 數(shù)據(jù)傳到GPU紋理后丈挟,再通過一個顏色轉(zhuǎn)換(yuv->rgb)的shader(shader是OpenGL可編程著色器,可以理解為GPU側(cè)的代碼志电,關(guān)于shader需要一些OpenGL編程基礎(chǔ)(傳送門))曙咽,繪制到目標(biāo)紋理: ? ?// fullrange varying highp vec2 textureCoordinate; uniform sampler2D luminanceTexture; uniform sampler2D chrominanceTexture; uniform mediump mat3 colorConversionMatrix; void main() { ? ? 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); } ?// videorange varying highp vec2 textureCoordinate; uniform sampler2D luminanceTexture; uniform sampler2D chrominanceTexture; uniform mediump mat3 colorConversionMatrix; void main() { ? ? mediump vec3 yuv; ? ? lowp vec3 rgb; ? ? yuv.x = texture2D(luminanceTexture, textureCoordinate).r - (16.0/255.0); ? ? yuv.yz = texture2D(chrominanceTexture, textureCoordinate).ra - vec2(0.5, 0.5); ? ? rgb = colorConversionMatrix * yuv; ? ? gl_FragColor = vec4(rgb, 1); } ? 注意yuv420fullrange和yuv420videorange的數(shù)值范圍是不同的,因此轉(zhuǎn)換公式也不同挑辆,這里會有2個顏色轉(zhuǎn)換shader例朱,根據(jù)實際的采樣格式選擇正確的shader; 渲染輸出到目標(biāo)紋理后就得到一個轉(zhuǎn)換成rgb格式的GPU紋理鱼蝉,完成了獲取輸入數(shù)據(jù)的工作茉继; ? 傳遞數(shù)據(jù) ? GPUImage的圖像處理過程,被設(shè)計成了濾鏡鏈的形式蚀乔;輸入組件烁竭、效果濾鏡、輸出組件串聯(lián)在一起吉挣,每次推動渲染的時候派撕,輸入數(shù)據(jù)就會按順序傳遞婉弹,經(jīng)過處理,最終輸出终吼。 ? ? GPUImage設(shè)計了一個GPUImageInput協(xié)議镀赌,定義了GPUImageFilter之間傳入數(shù)據(jù)的方法: ? - (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex { ? ?firstInputFramebuffer = newInputFramebuffer; ? ?[firstInputFramebuffer lock]; } ? firstInputFramebuffer屬性用來保存輸入紋理; GPUImageFilter作為單輸入濾鏡基類遵守了GPUImageInput協(xié)議际跪,GPUImage還提供了GPUImageTwoInputFilter, GPUImageThreeInputFilter等多輸入filter的基類商佛。 ? 這里還有一個很重要的入口方法用于推動數(shù)據(jù)流轉(zhuǎn): ? - (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex { ? ?...... ? ? ? ?[self renderToTextureWithVertices:imageVertices textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]]; ? ?[self informTargetsAboutNewFrameAtTime:frameTime]; } ? 每個濾鏡都是由這個入口方法開始啟動,這個方法包含2個調(diào)用 1). 首先調(diào)用render方法進行效果渲染 2). 調(diào)用informTargets方法將渲染結(jié)果推到下級濾鏡 ? GPUImageFilter繼承自GPUImageOutput姆打,定義了輸出數(shù)據(jù)良姆,向后傳遞的方法: ? - (void)notifyTargetsAboutNewOutputTexture; ? 但是這里比較奇怪的是濾鏡鏈的傳遞實際并沒有用notifyTargets方法,而是用了前面提到的informTargets方法: ? - (void)informTargetsAboutNewFrameAtTime:(CMTime)frameTime { ? ?...... ? ? ? ?// Get all targets the framebuffer so they can grab a lock on it ? ?for (id currentTarget in targets) { ? ? ? ?if (currentTarget != self.targetToIgnoreForUpdates) { ? ? ? ? ? ?NSInteger indexOfObject = [targets indexOfObject:currentTarget]; ? ? ? ? ? ?NSInteger textureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue]; ? ? ? ? ? ?[self setInputFramebufferForTarget:currentTarget atIndex:textureIndex]; ? ? ? ? ? ?[currentTarget setInputSize:[self outputFrameSize] atIndex:textureIndex]; ? ? ? ?} ? ?} ? ? ? ?...... ? ? ? ?// Trigger processing last, so that our unlock comes first in serial execution, avoiding the need for a callback ? ?for (id currentTarget in targets) { ? ? ? ?if (currentTarget != self.targetToIgnoreForUpdates) { ? ? ? ? ? ?NSInteger indexOfObject = [targets indexOfObject:currentTarget]; ? ? ? ? ? ?NSInteger textureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue]; ? ? ? ? ? ?[currentTarget newFrameReadyAtTime:frameTime atIndex:textureIndex]; ? ? ? ?} ? ?} } ? GPUImageOutput定義了一個targets屬性來保存下一級濾鏡玛追,這里可以注意到targets是個數(shù)組,因此濾鏡鏈也支持并聯(lián)結(jié)構(gòu)闲延∪剩可以看到這個方法主要做了2件事情: 1). 對每個target調(diào)用setInputFramebuffer方法把自己的渲染結(jié)果傳給下級濾鏡作為輸入 2). 對每個target調(diào)用newFrameReadyAtTime方法推動下級濾鏡啟動渲染 濾鏡之間通過targets屬性相互銜接串在一起,完成了數(shù)據(jù)傳遞工作垒玲。 ? ? 處理數(shù)據(jù) ? 前面提到的renderToTextureWithVertices:方法便是每個濾鏡必經(jīng)的渲染入口陆馁。 每個濾鏡都可以設(shè)置自己的shader,重寫該渲染方法合愈,實現(xiàn)自己的效果: ? - (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates { ? ?...... ? ?[GPUImageContext setActiveShaderProgram:filterProgram]; ? ?outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO]; ? ?[outputFramebuffer activateFramebuffer]; ? ?...... ? ?[self setUniformsForProgramAtIndex:0]; ? ? ? ?glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha); ? ?glClear(GL_COLOR_BUFFER_BIT); ? ?glActiveTexture(GL_TEXTURE2); ? ?glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]); ? ?glUniform1i(filterInputTextureUniform, 2); ? ?glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices); ? ?glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates); ? ? ? ?glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); ? ? ? ?...... } ? 上面這個是GPUImageFilter的默認方法叮贩,大致做了這么幾件事情: 1). 向frameBufferCache申請一個outputFrameBuffer 2). 將申請得到的outputFrameBuffer激活并設(shè)為渲染對象 3). glClear清除畫布 4). 設(shè)置輸入紋理 5). 傳入頂點 6). 傳入紋理坐標(biāo) 7). 調(diào)用繪制方法 ? 再來看看GPUImageFilter使用的默認shader: ? ?// vertex shader attribute vec4 position; attribute vec4 inputTextureCoordinate; varying vec2 textureCoordinate; void main() { ? ? gl_Position = position; ? ? textureCoordinate = inputTextureCoordinate.xy; } ?// fragment shader varying highp vec2 textureCoordinate; uniform sampler2D inputImageTexture; void main() { ? ? gl_FragColor = texture2D(inputImageTexture, textureCoordinate); } ? 這個shader實際上啥也沒做,VertexShader(頂點著色器)就是把傳入的頂點坐標(biāo)和紋理坐標(biāo)原樣傳給FragmentShader想暗,F(xiàn)ragmentShader(片段著色器)就是從紋理取出原始色值直接輸出,最終效果就是把圖片原樣渲染到畫面帘不。 ? 輸出數(shù)據(jù) ? 比較常用的主要是GPUImageView和GPUImageMovieWriter说莫。 GPUImageView繼承自UIView,用于實時預(yù)覽寞焙,用法非常簡單 1). 創(chuàng)建GPUImageView 2). 串入濾鏡鏈 3). 插到視圖里去 UIView的contentMode储狭、hidden、backgroundColor等屬性都可以正常使用 里面比較關(guān)鍵的方法主要有這么2個: ? // 申明自己的CALayer為CAEAGLLayer+ (Class)layerClass ?{ ? ?return [CAEAGLLayer class]; } - (void)createDisplayFramebuffer { ? ?[GPUImageContext useImageProcessingContext]; ? ? ? ?glGenFramebuffers(1, &displayFramebuffer); ? ?glBindFramebuffer(GL_FRAMEBUFFER, displayFramebuffer); ? ?glGenRenderbuffers(1, &displayRenderbuffer); ? ?glBindRenderbuffer(GL_RENDERBUFFER, displayRenderbuffer); ? ?[[[GPUImageContext sharedImageProcessingContext] context] renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; ? ?GLint backingWidth, backingHeight; ? ?glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth); ? ?glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight); ? ? ? ?...... ? ?glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, displayRenderbuffer); ? ?...... } ? 創(chuàng)建frameBuffer和renderBuffer時把renderBuffer和CALayer關(guān)聯(lián)在一起捣郊; 這是iOS內(nèi)建的一種GPU渲染輸出的聯(lián)動方法辽狈; 這樣newFrameReadyAtTime渲染過后畫面就會輸出到CALayer。 ? GPUImageMovieWriter主要用于將視頻輸出到磁盤呛牲; 里面大量的代碼都是在設(shè)置和使用AVAssetWriter刮萌,不了解的同學(xué)還是得去看AVFoundation; 這里主要是重寫了newFrameReadyAtTime:方法: ? - (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex { ? ?...... ? ?GPUImageFramebuffer *inputFramebufferForBlock = firstInputFramebuffer; ? ?glFinish(); ? ?runAsynchronouslyOnContextQueue(_movieWriterContext, ^{ ? ? ? ?...... ? ? ? ? ? ? ? ?// Render the frame with swizzled colors, so that they can be uploaded quickly as BGRA frames ? ? ? ?[_movieWriterContext useAsCurrentContext]; ? ? ? ?[self renderAtInternalSizeUsingFramebuffer:inputFramebufferForBlock]; ? ? ? ? ? ? ? ?CVPixelBufferRef pixel_buffer = NULL; ? ? ? ? ? ? ? ?if ([GPUImageContext supportsFastTextureUpload]) { ? ? ? ? ? ?pixel_buffer = renderTarget; ? ? ? ? ? ?CVPixelBufferLockBaseAddress(pixel_buffer, 0); ? ? ? ?} else { ? ? ? ? ? ?CVReturn status = CVPixelBufferPoolCreatePixelBuffer (NULL, [assetWriterPixelBufferInput pixelBufferPool], &pixel_buffer); ? ? ? ? ? ?if ((pixel_buffer == NULL) || (status != kCVReturnSuccess)) { ? ? ? ? ? ? ? ?CVPixelBufferRelease(pixel_buffer); ? ? ? ? ? ? ? ?return; ? ? ? ? ? ?} else { ? ? ? ? ? ? ? ?CVPixelBufferLockBaseAddress(pixel_buffer, 0); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?GLubyte *pixelBufferData = (GLubyte *)CVPixelBufferGetBaseAddress(pixel_buffer); ? ? ? ? ? ? ? ?glReadPixels(0, 0, videoSize.width, videoSize.height, GL_RGBA, GL_UNSIGNED_BYTE, pixelBufferData); ? ? ? ? ? ?} ? ? ? ?} ? ? ? ? ? ? ? ?...... ?? ? ? ?[assetWriterPixelBufferInput appendPixelBuffer:pixel_buffer]; ?? ? ? ?...... ? ?}); } ? 這里有幾個地方值得注意: 1). 在取數(shù)據(jù)之前先調(diào)了一下glFinish娘扩,CPU和GPU之間是類似于client-server的關(guān)系着茸,CPU側(cè)調(diào)用OpenGL命令后并不是同步等待OpenGL完成渲染再繼續(xù)執(zhí)行的壮锻,而glFinish命令可以確保OpenGL把隊列中的命令都渲染完再繼續(xù)執(zhí)行,這樣可以保證后面取到的數(shù)據(jù)是正確的當(dāng)次渲染結(jié)果涮阔。 2). 取數(shù)據(jù)時用了supportsFastTextureUpload判斷猜绣,這是個從iOS5開始支持的一種CVOpenGLESTextureCacheRef和CVImageBufferRef的映射(映射的創(chuàng)建可以參看獲取數(shù)據(jù)中的 CVOpenGLESTextureCacheCreateTextureFromImage),通過這個映射可以直接拿到CVPixelBufferRef而不需要再用glReadPixel來讀取數(shù)據(jù)敬特,這樣性能更好掰邢。 最后歸納一下本文涉及到的知識點 ? 1.?AVFoundation 攝像頭調(diào)用、輸出視頻都會用到AVFoundation 2.?YUV420 視頻采集的數(shù)據(jù)格式 3.?OpenGL shader GPU的可編程著色器 4.?CAEAGLLayer iOS內(nèi)建的GPU到屏幕的聯(lián)動方法 5.?fastTextureUpload iOS5開始支持的一種CVOpenGLESTextureCacheRef和CVImageBufferRef的映射 ?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伟阔,一起剝皮案震驚了整個濱河市辣之,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌减俏,老刑警劉巖召烂,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異娃承,居然都是意外死亡奏夫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門历筝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酗昼,“玉大人,你說我怎么就攤上這事梳猪÷橄鳎” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵春弥,是天一觀的道長呛哟。 經(jīng)常有香客問我,道長匿沛,這世上最難降的妖魔是什么扫责? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮逃呼,結(jié)果婚禮上鳖孤,老公的妹妹穿的比我還像新娘。我一直安慰自己抡笼,他們只是感情好苏揣,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著推姻,像睡著了一般平匈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天吐葱,我揣著相機與錄音街望,去河邊找鬼。 笑死弟跑,一個胖子當(dāng)著我的面吹牛灾前,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播孟辑,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼哎甲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了饲嗽?” 一聲冷哼從身側(cè)響起炭玫,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎貌虾,沒想到半個月后吞加,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡尽狠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年衔憨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袄膏。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡践图,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沉馆,到底是詐尸還是另有隱情码党,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布斥黑,位于F島的核電站揖盘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏锌奴。R本人自食惡果不足惜兽狭,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缨叫。 院中可真熱鬧椭符,春花似錦荔燎、人聲如沸耻姥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琐簇。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間婉商,已是汗流浹背似忧。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留丈秩,地道東北人盯捌。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像蘑秽,于是被迫代替她去往敵國和親饺著。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

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