渲染物體raycast picking拾取交互(three.js webgl_interactive_cube例子)

Three.js中的webgl_interactive_cube例子展示了picking-拾取物體(使用點(diǎn)擊等方式選中渲染物體)的行為访锻。在這里借助ray casting方式實(shí)現(xiàn)了用戶交互。用戶可以在一大群隨機(jī)立方體上準(zhǔn)確選擇出一個具體的立方體健蕊。

實(shí)現(xiàn)過程

首先渲染出立方體群匠题。一開始使用隨機(jī)數(shù)生成特定范圍的位置边涕,伸縮尺寸,色彩等信息衍锚,隨后使用這些信息渲染出立方體群友题。我們還會加上眼睛圍繞立方體群做圓周運(yùn)動,產(chǎn)生不斷變化的視圖构拳。

立方體群渲染完成后咆爽,我們使用raycaster在鼠標(biāo)點(diǎn)擊處投射出屏幕射線梁棠,如果屏幕射線和被渲染物體相交,則認(rèn)為這個物體被選中斗埂。

這個例子比較復(fù)雜符糊,包含多個子過程:立方體幾何模型數(shù)據(jù)生成,單個立方體渲染呛凶,立方體群渲染男娄,眼睛幀圍繞場景中心做圓周運(yùn)動,用戶拾取機(jī)制等漾稀。

這里模闲,我們模仿webgl_interactive_cube例子,在application端計(jì)算射線與物體相交的方式來決定被選中的立方體崭捍,使用C++和OpenGL ES 3.0獲得了如下的渲染效果尸折,iOS版本實(shí)現(xiàn)源碼可以從github上獲取。

interactive_cubes_picked.gif

實(shí)現(xiàn)立方體群的渲染

基本立方體的繪制

要實(shí)現(xiàn)立方體的繪制殷蛇,首先需要準(zhǔn)備立方體模型的頂點(diǎn)數(shù)據(jù)实夹。在webgl_interactive_cube例子中并沒有使用硬編碼的頂點(diǎn)數(shù)據(jù),而是實(shí)現(xiàn)了CubeGeometry對象粒梦,可以用于靈活控制生成幾何體頂點(diǎn)數(shù)據(jù)的數(shù)量亮航。CubeGeometry對象生成的幾何頂點(diǎn)數(shù)據(jù)包含頂點(diǎn)和索引。下面為C++實(shí)現(xiàn)的代碼局部:

//構(gòu)建立方體每個面的數(shù)據(jù)
    buildPlane(2,1,0, - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px
    buildPlane(2,1,0, 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx
    buildPlane(0,2,1, 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py
    buildPlane(0,2,1, 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny
    buildPlane(0,1,2, 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz
    buildPlane(0,1,2, - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz
    
 //本函數(shù)生成立方體每個面上的頂點(diǎn)后索引數(shù)據(jù)
void CubeGeometry::buildPlane(int idx1,int idx2,int idx3,int udir,int vdir,float width,float height,float depth,int gridX,int gridY,int materialIndex){
    float segmentWidth = width / gridX;
    float segmentHeight = height / gridY;
    
    float widthHalf = width / 2;
    float heightHalf = height / 2;
    float depthHalf = depth / 2;
    
    int gridX1 = gridX + 1;
    int gridY1 = gridY + 1;
    
    int vertexCounter = 0;
    int groupCount = 0;
    
    // generate vertices, normals and uvs
    for (int iy = 0; iy < gridY1; iy ++){
        
        float y = iy * segmentHeight - heightHalf;
        
        for (int ix = 0; ix < gridX1; ix ++) {
            
            float x = ix * segmentWidth - widthHalf;
            
            Cvec3 vertice;
            // set values to correct vector component
            vertice[idx1] = x * udir;
            vertice[idx2] = y * vdir;
            vertice[idx3] = depthHalf;
            
            // now apply vertice to vertex buffer
            vertices.push_back(vertice);
            
            Cvec3 normal;
            // set values to correct vector component
            normal[idx1] = 0;
            normal[idx2] = 0;
            normal[idx3] = depth > 0 ? 1 : - 1;
            
            normals.push_back(normal);
            
            // uvs
            uvs.push_back(Cvec2(ix/gridX,1-(iy/gridY)));
            
            // counters
            vertexCounter += 1;
        }
        
    }
    
    // indices
    // 1. you need three indices to draw a single face
    // 2. a single segment consists of two faces
    // 3. so we need to generate six (2*3) indices per segment
    for (int iy = 0; iy < gridY; iy ++ ) {
        
        for (int ix = 0; ix < gridX; ix ++ ) {
            
            int a = numberOfVertices + ix + gridX1 * iy;
            int b = numberOfVertices + ix + gridX1 * ( iy + 1 );
            int c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 );
            int d = numberOfVertices + ( ix + 1 ) + gridX1 * iy;
            
            // faces
            indices.push_back(a);
            indices.push_back(b);
            indices.push_back(d);
            
            indices.push_back(b);
            indices.push_back(c);
            indices.push_back(d);
            
            // increase counter
            groupCount += 6;
            
        }
        
    }
    
    ... ...
}   

立方幾何體數(shù)據(jù)生成后匀们,使用phong shader借助GL_TRIANGLES進(jìn)行基本立方體的渲染缴淋。

渲染立方體群

要實(shí)現(xiàn)立方體群的渲染,重點(diǎn)是每個立方體的位置泄朴,方位重抖,以及色彩等屬性的確定,以便于生成的每個基本立方體都可以辨別區(qū)分叼旋。

這些屬性信息的生成代碼如下:

... 
int count = 1000;
    
for (int i = 0; i < count; i ++ ) {
    double x = rand()%50 - 25;
    double y = rand()%50 - 25;
    double z = rand()%50 - 25;
    mInstancePos.push_back(Cvec3(x,y,z));
    mInstanceOrientation.push_back(Cvec3(rand()%360,rand()%360,rand()%360));
    mInstanceScale.push_back(Cvec3((rand()%100)/100.0+0.5,(rand()%100)/100.0+0.5,
                               (rand()%100)/100.0+0.5));
    
    //random diffuse color
    int cInt = 0xffffff * (rand()%100/(100.0));
    std::stringstream stream;
    stream << std::hex << cInt;
    std::string hexString( stream.str() );
    Cvec3 diffuseColor = hexStringToRGB(hexString);
    mInstanceDiffuseColor.push_back(diffuseColor);
    
}
...

當(dāng)然還有一個主要問題仇哆,就是大量的幾何體如何實(shí)現(xiàn)有效的渲染,比如怎樣保證幾千個立方體在較高的幀率(FPS)下渲染》蛑玻基本動作包括避免無效的API調(diào)用開銷,避免圖形緩存(vbo,ibo,vao)的無效損耗油讯,避免紋理的過多加載详民。在渲染立方體群時,每個立方體都可以使用相同的vbo/ibo緩存陌兑,使用相同的渲染管線(program)沈跨,借助vao管理vertex array設(shè)置等等。同時兔综,盡量在application端避免不必要的矩陣計(jì)算饿凛,比如每個立方體的位置和方位信息狞玛,不必要每次渲染時都執(zhí)行計(jì)算。

眼睛(相機(jī))視野的變化

當(dāng)立方幾何體群按照隨機(jī)位置涧窒、方位心肪、縮放程度等屬性生成后,保持不變纠吴。我們通過移動眼睛(相機(jī))的方式硬鞍,保持畫面的變換。當(dāng)移動眼睛(眼睛幀)時戴已,我們保持眼睛持續(xù)看向場景的原點(diǎn)固该,同時圍繞球體不斷移動眼睛的位置。所有的立方體在渲染時使用相同的view matrix糖儡。示意代碼如下:

      
      angle+=0.1;
    float radius = 10;
    float eyeX = radius * sin(angle*M_PI/180);
    float eyeY = radius * sin(angle*M_PI/180);
    float eyeZ = radius * cos(angle*M_PI/180);
    Matrix4 mat_eye = Matrix4::makeLookAtEyeMatrix(Cvec3(eyeX,eyeY,eyeZ), Cvec3(0,0,0), Cvec3(0,1,0));
    
    Matrix4 viewMat = inv(mat_eye);
    
    cubeModel->mat_view=viewMat;

實(shí)現(xiàn)raycast picking(射線投射拾确セ怠)

webgl_interactive_cube例子中采用射線拾取(raycast picking)方式。這種方式通過發(fā)出一條屏幕射線(screen ray)握联,然后檢測屏幕射線是否和被渲染物體(boudning volumes)相交著淆。如果相交,則物體被選中拴疤,反之永部,則未選中。

屏幕射線(Screen Ray)

屏幕射線的定義:用戶點(diǎn)擊屏幕像素(x_s,y_s)呐矾,發(fā)出一條screen ray苔埋,方向?yàn)椋?,0蜒犯,1)组橄。
所有的屏幕空間射線都是平行線,方向都是朝向正z軸(0,0,1)罚随,和物體進(jìn)入眼睛的光線正好相反玉工。 當(dāng)然這是一種默認(rèn)設(shè)置,OpenGL API中淘菩,默認(rèn)的NDC空間是left handed coordinate system遵班。屏幕射線總是射向物體。

這個定義跟通常所理解的稍有不同潮改,我最開始認(rèn)為屏幕射線會從原點(diǎn)出發(fā)狭郑,方向?yàn)閺脑c(diǎn)到這個屏幕點(diǎn)。實(shí)際上,屏幕射線只是起點(diǎn)反向延伸到原點(diǎn)。

屏幕射線這么定義的原因在于笛谦,從頂點(diǎn)到原點(diǎn)的射線從clip space轉(zhuǎn)化為ndc space后,變?yōu)槠叫芯€亩鬼。所以這個反向的屏幕射線這么定義是完全準(zhǔn)確的殖告。

Screen像素點(diǎn)和screen ray的坐標(biāo)變換

screen點(diǎn)(x_s,y_s,0)和screen-ray(0,0,1)通過反轉(zhuǎn)的viewport矩陣和投射矩陣,被變換為(x_c,y_c,-n)雳锋,注意這種投射剛好將屏幕點(diǎn)投射到近平面之上黄绩,所以這個z=n是固定的。通過這個變換可以確定出clip space中近平面上的點(diǎn)(x_c,y_c,-n)的計(jì)算公式魄缚。當(dāng)這個點(diǎn)確定后宝与,用這個點(diǎn)減去原點(diǎn),就得到screen-ray在clip space的方向矢量冶匹,然后由于z軸比為n习劫,可以消除n的存在。

隨后應(yīng)用eye matrix(view matrix的反轉(zhuǎn))嚼隘,將它們轉(zhuǎn)化為world coordinate诽里。然后再根據(jù)需要將其轉(zhuǎn)換為對應(yīng)的object coordinate。

屏幕射線的計(jì)算步驟

  1. 首先確定相機(jī)(eye frame)的原點(diǎn)(相機(jī)在world frame中的位置)
  2. 隨后計(jì)算點(diǎn)擊像素點(diǎn)坐標(biāo)在world frame中的坐標(biāo)
  3. 最后飞蛹,在world frame中使用點(diǎn)擊的像素點(diǎn)的世界坐標(biāo)減去相機(jī)位置谤狡,標(biāo)準(zhǔn)化后得到方向矢量。

屏幕射線在應(yīng)用中的設(shè)置

我們通常會生成一個RayCaster對象卧檐,這個對象用于生成屏幕射線墓懂。在應(yīng)用中,我們通常在點(diǎn)擊或者鼠標(biāo)事件中初始化RayCaster對象霉囚,RayCaster對象利用被點(diǎn)擊屏幕像素的坐標(biāo)初始化出一條屏幕射線捕仔。一般情況下,屏幕射線會以世界坐標(biāo)來表示盈罐,隨后所進(jìn)行的射線和幾何體相交檢測就會利用這條射線進(jìn)行榜跌。生成屏幕射線的部分代碼如下:

    
    //借助相機(jī)數(shù)據(jù)生成world frame中的screen-ray
    //屏幕點(diǎn)先是被對應(yīng)為near plane上的點(diǎn),然后再轉(zhuǎn)換為world coordinate
    //也就是說先進(jìn)行反轉(zhuǎn)viewport計(jì)算盅粪,然后在應(yīng)用unporjection矩陣钓葫,最后應(yīng)用eye matrix(inverse view matrix)
    void setFromCamera(Cvec3 screenPos,shared_ptr<PerspectiveCamera> camera){
            //反轉(zhuǎn)viewport計(jì)算,由于窗口坐標(biāo)y軸的原點(diǎn)在窗口上方票顾,所以需要反轉(zhuǎn)符號
        float rayOriginX = (screenPos[0]/camera->view.width) * 2 - 1;
        float rayOriginY = -(screenPos[1]/camera->view.height) * 2 + 1;
        
        if(camera){
            Matrix4 eyeMat = camera->eyeMat;
            Matrix4 projMat = camera->projMat;
            Cvec3 camPosition = vec3(eyeMat(0,3),eyeMat(1,3),eyeMat(2,3));
            Cvec4 screenPosWorld= (eyeMat*inv(projMat)) * vec4(rayOriginX,rayOriginY,1.0f,1.0f);
            //反轉(zhuǎn)投射矩陣應(yīng)用后的坐標(biāo)仍為同質(zhì)坐標(biāo)础浮,需要執(zhí)行除法以獲得放射坐標(biāo)
            screenPosWorld = screenPosWorld/screenPosWorld[3];
            //screen ray方向矢量需要標(biāo)準(zhǔn)化
            Cvec3 rayDi =normalize(vec3(screenPosWorld) -camPosition);
            
            ray = new Ray(camPosition,rayDi);
        }
        
    }

射線和幾何體相交檢測(ray-geometry intersection test)

射線和幾何體相交檢測實(shí)際是要計(jì)算出屏幕射線和構(gòu)成幾何體的所有三角形是否相交,只要射線和其中至少一個三角形相交库物,我們就認(rèn)為射線和幾何體相交霸旗。但是由于構(gòu)成場景的幾何體以及幾何體本身的三角形數(shù)目很多,所以直接進(jìn)行射線三角形(ray-triangle)相交檢測的開銷很大戚揭。通常的方式是使用綁定容積(bounding volume)進(jìn)行保守檢測計(jì)算。bounding volume通常為圍繞幾何體的球體(sphere)或者方形體(box)撵枢,這些sphere或者box一般根據(jù)幾何體的頂點(diǎn)數(shù)據(jù)近似獲得民晒。

使用幾何體的頂點(diǎn)表達(dá)來獲得Bounding Volume精居,不管是sphere還是box,比較容易確定潜必。box一般是最大/最小頂點(diǎn)所構(gòu)成的AABB box靴姿。sphere一般先使用AABB box確定中心點(diǎn)的坐標(biāo),然后計(jì)算這個中心點(diǎn)和各個頂點(diǎn)的最大距離作為球體半徑磁滚,這樣所獲得的球體一般為更緊湊的球體(tighter sphere)佛吓。

ray-geometry相交檢測的原理

屏幕射線和幾何體相交檢測通常先執(zhí)行ray-sphere或者ray-box保守檢測,如果檢測不通過垂攘,則后續(xù)不會再執(zhí)行開銷大的ray-triangle檢測维雇。基本的執(zhí)行邏輯如下:

  1. 首先生成一個幾何體的bounding sphere晒他,執(zhí)行ray-sphere相交保守檢測吱型,如果不相交則舍棄整個檢查。
  2. 之后再執(zhí)行幾何體的ray-box相交檢測陨仅,再一次保守檢測津滞,逐漸精確。
  3. 最后執(zhí)行ray-triangle相交灼伤,這涉及到屏幕射線和每個三角形的相交檢測触徐,在cpu上執(zhí)行。ray-triangle要先計(jì)算barycentric坐標(biāo)u,v,w,其中u+v+q=1狐赡,u,v,w要都大于0撞鹉,射線才會和三角形相交,一般射線和幾何體相交猾警,相交點(diǎn)可能會大于1個孔祸,最小的t值為最先相交點(diǎn)。此處所計(jì)算出的相交點(diǎn)的精度是比較高的发皿。

ray-bv和ray-triangle檢測的部分移值代碼:

vector<vec3> Ray::intersectSphere(Sphere* sphere){
    
    vec3 v1;
    //兩個矢量相減產(chǎn)生新矢量v1崔慧,球體原點(diǎn)和射線原點(diǎn)的矢量
    v1 = sphere->center - origin;
    
    //使用標(biāo)準(zhǔn)矢量和非標(biāo)準(zhǔn)矢量的點(diǎn)積來計(jì)算余弦邊。
    float tca = dot(v1,direction);

    //d2是正弦邊的平方穴墅,v1平方構(gòu)成從相機(jī)位置和球體中心為最長邊平方惶室,tca2為余弦邊的平方,
    float d2 = dot(v1,v1) - tca * tca;
    //當(dāng)d2和radius2剛好相等時玄货,屏幕射線為球體切線皇钞,d2>raidus2時,屏幕射線和球體不相交
    float radius2 = sphere->radius * sphere->radius;
    
    //screen-ray和球體不相交
    if ( d2 > radius2 ) return {};
    
    float thc = sqrt( radius2 - d2 );
    
    // t0*ray方向就等于從相機(jī)原點(diǎn)到球體表面相交點(diǎn)的距離
    // t0 = first intersect point - entrance on front of sphere
    float t0 = tca - thc;
    // t1為到遠(yuǎn)距離點(diǎn)的距離
    // t1 = second intersect point - exit point on back of sphere
    float t1 = tca + thc;
    
    // test to see if both t0 and t1 are behind the ray - if so, return null
    if ( t0 < 0 && t1 < 0 ) return {};
    
    // test to see if t0 is behind the ray:
    // if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
    // in order to always return an intersect point that is in front of the ray.
    if ( t0 < 0 ) return {this->at(t1)};
    
    // else t0 is in front of the ray, so return the first collision point scaled by t0
    return {this->at(t0)};
}

vector<vec3> Ray::intersectBox(Box* box){
    
    float tmin, tmax, tymin, tymax, tzmin, tzmax;
    
    float invdirx = 1 / direction[0],
        invdiry = 1 / direction[1],
        invdirz = 1 / direction[2];
    
    if(invdirx >= 0){
        tmin = (box->min[0] - origin[0]) * invdirx;
        tmax = (box->max[0] - origin[0]) * invdirx;
    }else{
        tmin = (box->max[0] - origin[0]) * invdirx;
        tmax = (box->min[0] - origin[0]) * invdirx;
    }
    
    if(invdiry >= 0 ) {
        tymin = (box->min[1] - origin[1]) * invdiry;
        tymax = (box->max[1] - origin[1]) * invdiry;
    } else {
        tymin = (box->max[1] - origin[1]) * invdiry;
        tymax = (box->min[1] - origin[1]) * invdiry;
    }
    
    if ( ( tmin > tymax ) || ( tymin > tmax ) ) return {};
    
    // These lines also handle the case where tmin or tmax is NaN
    // (result of 0 * Infinity). x !== x returns true if x is NaN
    if ( tymin > tmin || tmin != tmin ) tmin = tymin;
    
        if ( tymax < tmax || tmax != tmax ) tmax = tymax;
            
            if ( invdirz >= 0 ) {
                tzmin = ( box->min[2] - origin[2] ) * invdirz;
                tzmax = ( box->max[2] - origin[2] ) * invdirz;
            } else {
                tzmin = ( box->max[2] - origin[2] ) * invdirz;
                tzmax = ( box->min[2] - origin[2] ) * invdirz; 
            }
    
    if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return {};
    
    if ( tzmin > tmin || tmin != tmin ) tmin = tzmin;
        
        if ( tzmax < tmax || tmax != tmax ) tmax = tzmax;
            
            //return point closest to the ray (positive side)
            if ( tmax < 0 ) return {};
    
    return {this->at( tmin >= 0 ? tmin : tmax)};
}

vector<vec3> Ray::intersectTriangle(vec3 a,vec3 b,vec3 c,bool backfaceCulling){
    
    // Compute the offset origin, edges, and normal.
    vec3 diff,edge1,edge2,normal;
    
    // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h
    edge1 = b-a;
    edge2 = c-a;
    normal = cross(edge1, edge2);
        
    //cross prodcut可以十分方便地應(yīng)用于determiant的計(jì)算
    // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
    // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
    //   |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
    //   |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
    //   |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) 
    //確定符號松捉,是因?yàn)橐?jì)算位于正反面
    float DdN = dot(direction,normal);
    int sign;
        
    if (DdN > 0){
        if (backfaceCulling) return {};
        sign = 1;
    }else if( DdN < 0 ){
        sign = - 1;
        DdN = - DdN;
    } else {
        return {};
    }
        
    //此處Q指向相機(jī)原點(diǎn)夹界,于korea text方向相反,故而后面需要負(fù)值隘世。
    diff = origin-a;
    
    float DdQxE2 = sign * dot(direction,cross(diff, edge2));
        
    // b1 < 0, no intersection
    if ( DdQxE2 < 0 ) {
        return {};
    }
        
    float DdE1xQ = sign * dot(direction,cross(edge1,diff));
        
    // b2 < 0, no intersection
    if ( DdE1xQ < 0 ) {
        return {};
    }
        
    // b1+b2 > 1, no intersection
    if ( DdQxE2 + DdE1xQ > DdN ) {
        return {};
    }
        
    // Line intersects triangle, check if ray does.
    float QdN = - sign * dot(diff,normal);
        
    // t<0,則位于射線的反方向上可柿?
    // t < 0, no intersection
    if ( QdN < 0 ) {
        return {};
    }
        
    // t值確定后鸠踪,就可以確定相交點(diǎn)。
    // Ray intersects triangle.
    return {this->at(QdN/DdN)};

}

real-time rendering書中對于具體的射線幾何體相交實(shí)現(xiàn)算法有十分詳細(xì)的講解复斥。

每條屏幕射線可能和場景中的多個物體相交营密。在本例中,我們只選擇最近相交的物體給予展示目锭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末评汰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子痢虹,更是在濱河造成了極大的恐慌被去,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件世分,死亡現(xiàn)場離奇詭異编振,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)臭埋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進(jìn)店門踪央,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瓢阴,你說我怎么就攤上這事畅蹂。” “怎么了荣恐?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵液斜,是天一觀的道長。 經(jīng)常有香客問我叠穆,道長少漆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任硼被,我火速辦了婚禮示损,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嚷硫。我一直安慰自己检访,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布仔掸。 她就那樣靜靜地躺著脆贵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪起暮。 梳的紋絲不亂的頭發(fā)上卖氨,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼双泪。 笑死持搜,一個胖子當(dāng)著我的面吹牛密似,可吹牛的內(nèi)容都是我干的焙矛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼残腌,長吁一口氣:“原來是場噩夢啊……” “哼村斟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抛猫,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蟆盹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后闺金,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逾滥,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年败匹,在試婚紗的時候發(fā)現(xiàn)自己被綠了寨昙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡掀亩,死狀恐怖舔哪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情槽棍,我是刑警寧澤捉蚤,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站炼七,受9級特大地震影響缆巧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜豌拙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一陕悬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧姆蘸,春花似錦墩莫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至推捐,卻和暖如春裂问,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工堪簿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痊乾,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓椭更,卻偏偏與公主長得像哪审,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子虑瀑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評論 2 348

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