Metal 系列教程(1)- Metal 介紹及基本使用

Demo 地址已更新

https://github.com/Danny1451/MetalLutFilter

Metal 介紹及基本使用

最近做的一個技術(shù)研究挟鸠,metal 的國內(nèi)相關(guān)資料很少掸鹅,所以整理了這一系列文章腐巢,希望能幫到有用的人永品。

什么是 Metal

Metal 是一個和 OpenGL ES 類似的面向底層的圖形編程接口症见,通過使用相關(guān)的 api 可以直接操作 GPU ,最早在 2014 年的 WWDC 的時候發(fā)布,并于今年發(fā)布了 Metal 2寂殉。
Metal 是 iOS 平臺獨有的村怪,意味著它不能像 OpenGL ES 那樣支持跨平臺,但是它能最大的挖掘蘋果移動設(shè)備的 GPU 能力,進行復(fù)雜的運算,像 Unity 等游戲引擎都通過 Metal 對 3D 能力進行了優(yōu)化铲敛, App Store 還有相應(yīng)的運用 Metal 技術(shù)的游戲?qū)n}。

Metal 具有特點

  • GPU 支持的 3D 渲染
  • 和 CPU 并行處理數(shù)據(jù) (深度學(xué)習(xí))
  • 提供低功耗接口
  • 可以和 CPU 共享資源內(nèi)存

這樣可能有些抽象,層級的關(guān)系大概如下宏多,我們平時更多的接觸的上面兩層筐喳。:
UIKit -> Core Graphics -> Metal/OpenGL ES -> GPU Driver -> GPU

GPU 相關(guān)知識

為了更好的理解 Metal 的工作流程和機制荣月,這里補充一些 GPU 工作相關(guān)流程管呵。

手機包含兩個不同的處理單元,CPU 和 GPU哺窄。CPU 是個多面手捐下,并且不得不處理所有的事情账锹,而 GPU 則可以集中來處理好一件事情,就是并行地做浮點運算坷襟。事實上奸柬,圖像處理和渲染就是在將要渲染到窗口上的像素上做許許多多的浮點運算。
通過有效的利用 GPU婴程,可以成百倍甚至上千倍地提高手機上的圖像渲染能力廓奕。如果不是基于 GPU 的處理,手機上實時高清視頻濾鏡是不現(xiàn)實档叔,甚至不可能的桌粉。
精細(xì)到屏幕繪制的每一幀上,每次準(zhǔn)備畫下一幀前衙四,屏幕會發(fā)出一個垂直同步信號(vertical synchronization)铃肯,簡稱 VSync
屏幕通常以固定頻率進行刷新,這個刷新率就是 VSync 信號產(chǎn)生的頻率传蹈。

一般來說押逼,計算機系統(tǒng)中 CPU、GPU惦界、屏幕是以上面這種方式協(xié)同工作的挑格。CPU 計算好顯示內(nèi)容提交到 GPU,GPU 渲染完成后將渲染結(jié)果放入幀緩沖區(qū)表锻,隨后視頻控制器會按照 VSync 信號逐行讀取幀緩沖區(qū)的數(shù)據(jù)恕齐,經(jīng)過可能的數(shù)模轉(zhuǎn)換傳遞給屏幕顯示。

基礎(chǔ)流程

這邊以通過 Metal 渲染一個三角形作為例子瞬逊,來介紹一下基本的使用显歧。

Xcode 版本 8.3.3 ,語言 Objective-C

需要注意的是 Metal 必須在真機上運行确镊,并且至少要是 A7 處理器士骤,就是 5s 或者以上。

初始化

新建一個普通的工程 Single View Application蕾域,在 VC 中導(dǎo)入 Metal Framework拷肌。

#import <Metal/Metal.h>
MTLDevice

都說是操作 GPU 了,當(dāng)然我們要拿到 GPU 對象旨巷,Metal 中提供了 MTLDevice 的接口巨缘,代表了 GPU。


//獲取設(shè)備
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
if (device == nil) {
    NSLog(@"don't support metal !");
    return;
}
    

當(dāng)設(shè)備不支持 Metal 的時候會返回空采呐。

MTLDevice 代表 GPU 的接口若锁,提供了如下的能力:

  • 查詢設(shè)備狀態(tài)
  • 創(chuàng)建 buffer 和 texture
  • 指令轉(zhuǎn)換和隊列化渲染進行指令的計算
MTLCommandQueue

有了 GPU 之后,我們需要一個渲染隊列 MTLCommandQueue斧吐,隊列是單一隊列又固,確保了指令能夠按順序執(zhí)行仲器,里面的是將要渲染的指令 MTLCommandBuffer,這是個線程安全的隊列仰冠,可以支持多個 CommandBuffer 同時編碼乏冀。
通過 MTLDevice 可以獲取隊列


id<MTLCommandQueue> queue = self.device.newCommandQueue;

MTKView

要用 Metal 來直接繪制的話,需要用特殊的界面 MTKView洋只,同時給它設(shè)置對應(yīng)的 device 為我們上面獲取到 MTLDevice辆沦,并把它添加到當(dāng)前的界面中。

_mtkView = [[MTKView alloc] initWithFrame:self.view.frame device:_device];
[self.view addSubview:_mtkView];

渲染

我們配置好 MTLDevice木张,MTLCommandQueue 和 MTKView 之后众辨,我們開始準(zhǔn)備需要渲染到界面上的內(nèi)容了,就是要塞進隊列中的緩沖數(shù)據(jù) MTLCommandBuffer 舷礼。
簡單的流程就是先構(gòu)造 MTLCommandBuffer 鹃彻,再配置 CommandEncoder ,包括配置資源文件妻献,渲染管線等蛛株,再通過 CommandEncoder 進行編碼,最后才能提交到隊列中去育拨。

MTLCommandBuffer

有了隊列之后谨履,我們開始構(gòu)建隊列中的 MTLCommandBuffer,一開始獲取的 Buffer 是空的熬丧,要通過 MTLCommandEncoder 編碼器來 Encode 笋粟,一個 Buffer 可以被多個 Encoder 進行編碼。

MTLCommandBuffer 是包含了多種類型的命令編碼 - 根據(jù)不同的 編碼器 決定 包含了哪些數(shù)據(jù)析蝴。 通常情況下害捕,app 的一幀就是渲染為一個單獨的 Command Buffer。MTLCommandBuffer 是不支持重用的輕量級的對象闷畸,每次需要的時候都是獲取一個新的 Buffer尝盼。

Buffer 有方法可以 Label ,用來增加標(biāo)簽佑菩,方便調(diào)試時使用盾沫。

臨時對象,在執(zhí)行之后殿漠,唯一有效的操作就是等到被執(zhí)行或者完成的時候的回調(diào)赴精,同步或者通過 block 回調(diào),檢查 buffer 的運行結(jié)果绞幌。

創(chuàng)建

  • MTLCommandQueue - commandBuffer 方法 蕾哟,只能加到創(chuàng)建它的隊列中。
  • 獲取 retain 的對象 commandBufferWithUnretainedReferences 能夠重用 一般不推薦

這里我們通過如下方法創(chuàng)建

//command buffer
    id<MTLCommandBuffer> commandBuffer = [_queue commandBuffer];
    

執(zhí)行

  • enqueue 順序執(zhí)行
  • commit 插隊盡快執(zhí)行 (如果前面有 commit 就還是排隊等著)

監(jiān)聽結(jié)果

commandBuffer.addCompletedHandler { (buffer) in
}
commandBuffer.waitUntilCompleted()
        
commandBuffer.addScheduledHandler { (buffer) in
}
commandBuffer.waitUntilScheduled()
創(chuàng)建 Metal 資源

接下來我需要把我們需要繪制的內(nèi)容 encode 到我們上面生成 MTLCommandBuffer 中。

現(xiàn)在我們要配置需要繪制的內(nèi)容渐苏,即資源。
在 Metal 中資源分為兩種:

  • MTLBuffer 代表著未格式化的內(nèi)存菇夸,可以是任何類型的數(shù)據(jù)琼富。 Buffer 用來做頂點著色和計算狀態(tài)。
  • MTLTexture 代表著有著特殊紋理類型和像素格式的格式化的圖像數(shù)據(jù)庄新。用來做頂點鞠眉,面和計算的源

我們這里是要畫一個三角形,所以要有三個頂點择诈,然后需要繪制三角形的圖片械蹋。
分別用 MTLBuffer 來讀入三個頂點。

在 Metal 中是歸一化的坐標(biāo)系羞芍,以屏幕中心為原點(0, 0, 0)哗戈,且是始終不變的。面對屏幕荷科,你的右邊是x正軸唯咬,上面是y正軸,屏幕指向你的為z正軸畏浆。長度單位這樣來定:窗口范圍按此單位恰好是(-1,-1)到(1,1)胆胰,即屏幕左下角坐標(biāo)為(-1,-1)刻获,右上角坐標(biāo)為(1,1)蜀涨。

所以我們要畫在中間一個正三角形的話,三個頂點分別為

(0.577, -0.25, 0.0, 1.0)
(-0.577, -0.25, 0.0, 1.0)
(0.0, 0.5, 0.0, 1.0)

在 Metal 里面代表頂點需要 4 個 float 蝎毡,代表 x厚柳,y,z顶掉,w草娜。最后二位我們繪制 2D 界面的時候默認(rèn)為0.0 和 1.0,w 是為了方便 3D 計算的痒筒。

我們要把頂點數(shù)據(jù)轉(zhuǎn)為字節(jié)宰闰,通過 MTLDevice 的 *- (id <MTLBuffer>)newBufferWithBytes:(const void )pointer length:(NSUInteger)length options:(MTLResourceOptions)options;
方法構(gòu)造為 MTLBuffer 。

static const float vertexArrayData[] = {
        // 前 4 位 位置 x , y , z ,w
        0.577, -0.25, 0.0, 1.0,
        -0.577, -0.25, 0.0, 1.0,
        0.0,  0.5, 0.0, 1.0,
    };
    
id<MTLBuffer> vertexBuffer = [_device newBufferWithBytes:vertexArrayData
                                         length:sizeof(vertexArrayData)
                                        options:0];

有了頂點 Vertex 之后簿透,我們來構(gòu)建面 Fragment移袍。這里我們用一張圖片作為我們的三角形的貼圖。
首先獲取圖片的 image 對象:

UIImage *image = [UIImage imageNamed:name];

接下來通過 MTKTextureLoader 來構(gòu)建 MTLTexture

 MTKTextureLoader *loader = [[MTKTextureLoader alloc]initWithDevice:self.device];
    NSError* err;
    id<MTLTexture> sourceTexture = [loader newTextureWithCGImage:image.CGImage options:nil error:&err];
    
    return sourceTexture;
Shader (著色器) 和 Pipeline (渲染管線)

資源有了老充,我們要告訴 GPU 怎么去使用這些數(shù)據(jù)葡盗,這里就需要 Shader 了,這部分代碼是在 GPU 中執(zhí)行的啡浊,所以要用特殊的語言去編寫觅够,即 Metal Shading Language胶背,它是 C++ 14的超集,封裝了一些 Metal 的數(shù)據(jù)格式和常用方法喘先。
你可以添加多個 Metal 文件钳吟,最后都會編譯到二進制文件default.metallib 中。
通過 Xcode 的 File - New - File 菜單窘拯,新建一個 Metal 文件红且。

meta

添加下面兩個函數(shù),分別代表頂點的處理函數(shù)涤姊,和 片段處理函數(shù)暇番。

#include <metal_stdlib>

using namespace metal;


typedef struct
{
    float4 position;
    float2 texCoords;
} VertexIn;


typedef struct
{
    float4 position [[position]];
    float2 texCoords;
}VertexOut;



vertex VertexOut myVertexShader(const device VertexIn* vertexArray [[buffer(0)]],
                                unsigned int vid  [[vertex_id]]){
    
    VertexOut verOut;
    verOut.position = vertexArray[vid].position;
    verOut.texCoords = vertexArray[vid].texCoords;
    return verOut;
    
}






fragment float4 myFragmentShader(
                                VertexOut vertexIn [[stage_in]],
                            texture2d<float,access::sample>   inputImage   [[ texture(0) ]],
                                 sampler textureSampler [[sampler(0)]]
                             )
{
    float4 color = inputImage.sample(textureSampler, vertexIn.texCoords);
    return color;

}

兩個結(jié)構(gòu)體
VertexIn 和 VertexOut
里面的 float4 和 float2 代表著 4 個和 2 個浮點數(shù)的向量。
可以通過如下方式構(gòu)造和取值思喊,具體的不展開可以查看相關(guān)文檔壁酬。

float4(1.0) = float4(1.0,1.0,1.0,1.0)
float4 test = float4(1,2,3,4)
test.x = test.r = 1
test.y = test.g = 2
test.z = test.b = 3
test.w = test.a = 4
...

myVertexShader 為方法名,vertex 代表是一個頂點函數(shù) VertexOut 代表返回值搔涝,該方法有兩個入?yún)ⅰ?/p>

  • vertexArray 后面的 buff(0) 代表去后面配置的 index 為 0 的 MTLBuffer 資源

  • vid 代表著進入的頂點的 id 即順序厨喂。
    其實還有很多入?yún)⑼ㄟ^查閱文檔可以看到

    • [[vertex_id]]
    • [[instance_id]]
    • [[base_vertex]]
    • [[base_instance]]

這里可以對頂點進行處理,如轉(zhuǎn)向庄呈,3D 場景下的光影的計算等等蜕煌,然后返回處理之后的頂點信息,這里直接返回诬留,并沒有做額外的處理斜纪。

myFragmentShader 同上,fragment 代表是一個處理片段的方法文兑,方法有三個入?yún)?/p>

  • VertexOut vertexIn [[stage_in]] 代表著從頂點返回的頂點信息

  • texture2d<float,access::sample> inputImage [[ texture(0) ]] 讀入的圖片資源

  • sampler textureSampler 采樣器

頂點著色器返回了 VertexOut 結(jié)構(gòu)體盒刚,通過 [[stage_in]] 入?yún)ⅲ闹禃歉鶕?jù)你的渲染的位置來插值绿贞。所以這個方法的主要內(nèi)容就是根據(jù)因块,之前返回的頂點信息,去圖像中采樣得到相應(yīng)位置的樣色涡上,并返回顏色。

渲染管線

著色器這邊的工作已經(jīng)完成拒名,下面我們需要把它和我們的 CommandBuffer 關(guān)聯(lián)起來吩愧,就需要我們的 PipelineState 渲染管線了。

渲染管線就好比是 CPU 和 GPU 直接的管道增显,通過它來配置運行在 GPU 中的頂點和段著色器雁佳,就是我們寫在 metal 中的編譯好的代碼,多個 c++ 函數(shù)的組合。

PipelineState 對象是線程安全的糖权,所以這個對象是可以復(fù)用的堵腹,不同的 CommandBuffer 都可以使用它,創(chuàng)建它是有性能消耗的星澳,建議和 Device 和 Queue 一起初始化并作為全局對象秸滴。

生成 PipelineState 對象需要獲取我們剛剛寫在 Metal 中的幾個函數(shù)。
通過下面的方法募判,我們可以得到代表整個 Metal 的函數(shù)庫 MTLLibrary 對象。

id<MTLLibrary> library = [_device newDefaultLibrary];

通過 MTLLibrary 的 newFunctionWithName 方法咒唆,可以得到對應(yīng)的方法届垫。

[library newFunctionWithName:@"myVertexShader"];

下面我們開始構(gòu)造我們的 MTLRenderPipelineState

    //構(gòu)造Pipeline
    MTLRenderPipelineDescriptor *des = [MTLRenderPipelineDescriptor new];
    
    
    //獲取 shader 的函數(shù)
    id<MTLLibrary> library = [_device newDefaultLibrary];
    des.vertexFunction = [library newFunctionWithName:@"myVertexShader"];
    des.fragmentFunction = [library newFunctionWithName:@"myFragmentShader"];
    des.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
    
    //生成 MTLRenderPipelineState
    NSError *error;
    _pipelineState = [_device newRenderPipelineStateWithDescriptor:des
                                                             error:&error];
    
    
MTLCommandEncoder 編碼器

有了資源文件,渲染管線之后全释,我們可以開始做最后的步驟了装处,構(gòu)造 MTLCommandEncoder 編碼器。
指令編碼器包括 渲染 計算 位圖復(fù)制三種編碼器浸船。

  • MTLRenderCommandEncoder 渲染 3D 編碼器
  • MTLComputeCommandEncoder 計算編碼器
  • MTLBlitCommandEncoder 位圖復(fù)制編碼器 拷貝 buffer texture 同時也能生成 mipmap

mipmap 指的是一種紋理映射技術(shù)妄迁,將低一級圖像的每邊的分辨率取為高一級圖像的每邊的分辨率的二分之一,而同一級分辨率的紋理組則由紅李命、綠登淘、藍(lán)三個分量的紋理數(shù)組組成。由于這一個查找表包含了同一紋理區(qū)域在不同分辨率下的紋理顏色值封字,因此被稱為 Mipmap黔州。比如一張 64x64 的圖片,會生成 32x32,16x16 等阔籽,需要 20x20 的話就會用 32x32 和 16x16 的進行計算流妻,大大的提高渲染的效率。

這里我們是為了渲染一個三角形笆制,所以這里用的是 MTLRenderCommandEncoder 绅这。
相關(guān)代碼如下

  1. 創(chuàng)建 MTLRenderPassDescriptor 描述符 配置一些基本參數(shù)
  2. 通過描述符構(gòu)建 Encoder
  3. 配置 VertexBuffer 后面的 index 就是 Shader 里面對應(yīng) [[buffer[0]]] 的 0 【index 最多是 31 個】
  4. 配置 FragmentTexture
  5. 設(shè)置渲染的頂點配置(這里設(shè)置為三角 從第一個頂點開始取 取 3 個)
  6. 編碼結(jié)束
 //render des
    MTLRenderPassDescriptor *renderDes = [MTLRenderPassDescriptor new];
    renderDes.colorAttachments[0].texture = drawable.texture;
    renderDes.colorAttachments[0].loadAction = MTLLoadActionClear;
    renderDes.colorAttachments[0].storeAction = MTLStoreActionStore;
    renderDes.colorAttachments[0].clearColor = MTLClearColorMake(0.5, 0.65, 0.8, 1); //background color
    

    //command encoder
    id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:renderDes];
    [encoder setCullMode:MTLCullModeNone];
    [encoder setFrontFacingWinding:MTLWindingCounterClockwise];
    [encoder setRenderPipelineState:self.pipelineState];
    [encoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:0];
    [encoder setFragmentTexture:textture atIndex:0];

   //set render vertex
    [encoder drawPrimitives:MTLPrimitiveTypeTriangle
                vertexStart:0
                vertexCount:3];

    [encoder endEncoding];

繪制

編碼結(jié)束之后,就可以開始準(zhǔn)備提交到 GPU 了在辆。
配置需要繪制的 Layer证薇,獲取 MTKView 的 Layer 就可以。

CAMetalLayer *metalLayer = (CAMetalLayer*)[_mtkView layer];
id<CAMetalDrawable> drawable = [metalLayer nextDrawable];
    
//commit
[commandBuffer presentDrawable:drawable];
[commandBuffer commit];

現(xiàn)在所有的工作就都完成了开缎,運行項目就可以看到如下的三角形了棕叫,里面填充的是我之前導(dǎo)入的圖片。

調(diào)試

如何進行調(diào)試和評估性能呢奕删?
這里 iOS 提供了兩個工具

  • Xcode 中的 Capute GPU Frame
  • Instruments 中的 Metal System Trace

Capute GPU Frame
第一個是用來 Debug 的工具俺泣,運行的時候點擊 Debug ,選擇 Capute GPU Frame,就會看到如下的界面伏钠,相關(guān)的說明我已經(jīng)附在圖上了横漏,用法和 Capute View Hierachy 很像。


比較強大的一個功能是點擊動態(tài)更新的按鈕可以在修改完之后直接應(yīng)用熟掂,避免了 app 編譯帶來的時間消耗缎浇。

Metal System Trace

  1. 打開 Instruments 之后選擇需要調(diào)試的應(yīng)用
  2. 點擊 record 之后開始錄制
  3. 完成之后點擊停止,分析之后會有如下界面

從上到下分別是 Application 在 CPU 中執(zhí)行赴肚,對應(yīng)的是 Buffer 和 Encoder 的初始化工作
隨著箭頭往下是 Graphic Driver Activity 素跺,在 GPU 驅(qū)動處理,這部分操作也是在 CPU 中誉券。
再往下就是進入到 GPU 了指厌,就部分才是真正的工作。
最后是到 Display 就是展示界面了踊跟,在 Display 下面是 Vsync 信號踩验,代表著同步信號,用來刷新界面商玫。


放大之后可以看到詳細(xì)的 Buffer / Render 箕憾,而且上面顯示的名字,正是 之前設(shè)置的 Label 的名字拳昌。


總結(jié)

流程總結(jié)

最后我們再來通過下面這個圖來梳理下的流程袭异。

  1. 配置 Device 和 Queue
  2. 獲取 CommandBuffer
  3. 配置 CommandBufferEncoder
  4. 配置 PipelineState
  5. 創(chuàng)建資源
  6. Encoder Buffer 【如有需要的話可以用 Threadgroups 來分組 Encoder 數(shù)據(jù)】
  7. 提交到 Queue 中


Metal 能力

根據(jù)不同的 CommandBufferEncoder 可以提供不同的能力,除了優(yōu)秀的 3D 渲染能力炬藤,Metal 還能提供強大的計算能力扁远。

在 WWDC 2015,蘋果發(fā)布了 Metal Performance Shaders (MPS) 框架刻像,iOS 9 上的一組高性能的圖像濾鏡畅买,其實就是邊寫好的 Shaders,提供了優(yōu)秀的圖像處理能力细睡。同時還提供了高性能的矩陣運算的 Shaders 谷羞,能用來做機器學(xué)習(xí)的運算,在 GPU 上運行卷積神經(jīng)網(wǎng)絡(luò)溜徙。

而且非常棒的是湃缎,今年的 WWDC 2017 上 Metal 也將開始支持 macOS 。
更多的實踐可以參考蘋果的官方文檔:
Metal 的最佳實踐

我們可以用來做什么蠢壹?

  • 圖片處理 濾鏡/調(diào)整
  • 視頻處理
  • 機器學(xué)習(xí)
  • 大計算工作 分擔(dān) CPU 壓力

參考

MetalProgrammingGuide : https://developer.apple.com/library/content/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40014221-CH1-SW1
metal-image-processing : https://www.invasivecode.com/weblog/metal-image-processing
Metal Shading Language : https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf#//apple_ref/doc/uid/TP40014364
the-metal-shading-language-in-practice : https://www.objc.io/issues/18-games/metal/#the-metal-shading-language-in-practice
metal-performance-shaders-in-swift : http://metalbyexample.com/metal-performance-shaders-in-swift/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嗓违,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子图贸,更是在濱河造成了極大的恐慌蹂季,老刑警劉巖冕广,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異偿洁,居然都是意外死亡撒汉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門涕滋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來睬辐,“玉大人,你說我怎么就攤上這事宾肺∷荻” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵锨用,是天一觀的道長瓣喊。 經(jīng)常有香客問我,道長黔酥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任洪橘,我火速辦了婚禮跪者,結(jié)果婚禮上熄求,老公的妹妹穿的比我還像新娘。我一直安慰自己弟晚,他們只是感情好忘衍,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布枚钓。 她就那樣靜靜地躺著,像睡著了一般瑟押。 火紅的嫁衣襯著肌膚如雪搀捷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天多望,我揣著相機與錄音嫩舟,去河邊找鬼。 笑死怀偷,一個胖子當(dāng)著我的面吹牛家厌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播椎工,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼饭于,長吁一口氣:“原來是場噩夢啊……” “哼蜀踏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起镰绎,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤脓斩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后畴栖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體随静,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年吗讶,在試婚紗的時候發(fā)現(xiàn)自己被綠了燎猛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡照皆,死狀恐怖重绷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膜毁,我是刑警寧澤昭卓,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站瘟滨,受9級特大地震影響候醒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杂瘸,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一倒淫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧败玉,春花似錦敌土、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晚顷。三九已至疗疟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間栓袖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工音榜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赠叼,地道東北人违霞。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓涧郊,卻偏偏與公主長得像眼五,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子批旺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

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