音視頻開發(fā)之旅(15) OpenGL ES粒子系統(tǒng) - 噴泉

目錄

  1. 粒子和粒子系統(tǒng)
  2. 實(shí)踐:噴泉效果
  3. 遇到的問題
  4. 資料
  5. 收獲

通過該篇的實(shí)踐實(shí)現(xiàn)如下效果


一、什么是粒子和粒子系統(tǒng)

如何定義粒子面徽?
一個粒子有位置信息(x,y,z)佩研、運(yùn)動方向荔茬、顏色、生命值(開始和結(jié)束的時間)等屬性

什么粒子系統(tǒng)有额?
通過渲染繪制出大量 位置般又、形狀、方向巍佑、顏色不同的物體(粒子)茴迁,從而形成大量粒子運(yùn)動的效果。

明確了概念我們來逐步實(shí)現(xiàn)煙花效果

二句狼、實(shí)踐:噴泉效果

面對一個比較大或者沒有嘗試過的項(xiàng)目或內(nèi)容笋熬,害怕、怯懦時常出現(xiàn)腻菇,這時要理清目標(biāo)胳螟,抓住主線,運(yùn)用結(jié)構(gòu)化思維筹吐,拆解流程糖耸,然后逐步實(shí)現(xiàn)每個環(huán)節(jié),解決每個環(huán)節(jié)以中的問題丘薛,這也是打怪升級的過程嘉竟,下面一起來享受這個過程吧。

目標(biāo):了解和運(yùn)用粒子系統(tǒng)洋侨,實(shí)現(xiàn)比較炫酷的效果舍扰,從粒子噴泉著手。

流程拆解

  1. 梳理粒子特性(坐標(biāo)希坚、顏色边苹、運(yùn)動矢量、開始時間)以及 重力和阻力的影響裁僧、持續(xù)時間
  2. 粒子發(fā)生器如何發(fā)射粒子(反射點(diǎn)个束、方向慕购、數(shù)量)
  3. 編寫著色器glsl代碼
  4. 編寫Program
  5. 編寫GLSurfaceView的Render進(jìn)行著色器加載、編譯茬底、鏈接沪悲、使用、渲染

下面開始我們的具體實(shí)踐

2.1. 定義粒子系統(tǒng)對象

定義粒子的屬性以及提供添加粒子的方法

public class ParticleSystem {
    //位置 xyz
    private final int POSITION_COMPONENT_COUNT = 3;
    //顏色 rgb
    private final int COLOR_COMPONENT_COUNT = 3;
    //運(yùn)動矢量 xyz
    private final int VECTOR_COMPONENT_COUNT = 3;
    //開始時間
    private final int PARTICLE_START_TIME_COMPONENT_COUNT = 1;


    private final int TOTAL_COMPONENT_COUNT = POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT
            + VECTOR_COMPONENT_COUNT + PARTICLE_START_TIME_COMPONENT_COUNT;

    //步長
    private final int STRIDE = TOTAL_COMPONENT_COUNT * VertexArray.BYTES_PER_FLOAT;


    //粒子游標(biāo)
    private int nextParticle;
    //粒子計(jì)數(shù)
    private int curParticleCount;
    //粒子數(shù)組
    private final float[] particles;
    //最大粒子數(shù)量
    private final int maxParticleCount;
    //VBO
    private final VertexArray vertexArray;

    public ParticleSystem(int maxParticleCount) {
        this.particles = new float[maxParticleCount * TOTAL_COMPONENT_COUNT];
        this.maxParticleCount = maxParticleCount;
        this.vertexArray = new VertexArray(particles);
    }

    /**
     * 添加粒子到FloatBuffer
     *
     * @param position        位置
     * @param color           顏色
     * @param direction       運(yùn)動矢量
     * @param particStartTime 開始時間
     */
    public void addParticle(Geometry.Point position, int color, Geometry.Vector direction, float particStartTime) {
        final int particleOffset = nextParticle * TOTAL_COMPONENT_COUNT;
        int currentOffset = particleOffset;
        nextParticle++;
        if (curParticleCount < maxParticleCount) {
            curParticleCount++;
        }
        //重復(fù)使用阱表,避免內(nèi)存過大
        if (nextParticle == maxParticleCount) {
            nextParticle = 0;
        }
        //填充 位置坐標(biāo) xyz
        particles[currentOffset++] = position.x;
        particles[currentOffset++] = position.y;
        particles[currentOffset++] = position.z;

        //填充 顏色 rgb
        particles[currentOffset++] = Color.red(color) / 255;
        particles[currentOffset++] = Color.green(color) / 255;
        particles[currentOffset++] = Color.blue(color) / 255;

        //填充 運(yùn)動矢量
        particles[currentOffset++] = direction.x;
        particles[currentOffset++] = direction.y;
        particles[currentOffset++] = direction.z;

        //填充粒子開始時間
        particles[currentOffset++] = particStartTime;

        //把新增的粒子添加到頂點(diǎn)數(shù)組FloatBuffer中
        vertexArray.updateBuffer(particles, particleOffset, TOTAL_COMPONENT_COUNT);
    }

}

2.2. 定義粒子發(fā)射器對象

public class ParticleShooter {

    //發(fā)射粒子的位置
    private final Geometry.Point position;
    //發(fā)射粒子的顏色
    private final int color;
    //發(fā)射粒子的方法
    private final Geometry.Vector direction;

    public ParticleShooter(Geometry.Point position, int color, Geometry.Vector direction) {
        this.position = position;
        this.color = color;
        this.direction = direction;
    }

    /**
     * 調(diào)用粒子系統(tǒng)對象添加粒子
     *
     * @param particleSystem
     * @param currentTime
     */
    public void addParticles(ParticleSystem particleSystem, float currentTime) {
        particleSystem.addParticle(position, color, direction, currentTime);
    }
}

2.3. 編寫頂點(diǎn)和片元著色器

uniform float u_Time;

attribute vec3 a_Position;
attribute vec3 a_Color;
attribute vec3 a_Direction;
attribute float a_PatricleStartTime;

varying vec3 v_Color;
varying float v_ElapsedTime;

void main(){
    v_Color = a_Color;
    //粒子已經(jīng)持續(xù)時間  當(dāng)前時間-開始時間
    v_ElapsedTime = u_Time - a_PatricleStartTime;
    //重力或者阻力因子殿如,隨著時間的推移越來越大
    float gravityFactor = v_ElapsedTime * v_ElapsedTime / 9.8;
    //當(dāng)前的運(yùn)動到的位置 粒子起始位置+(運(yùn)動矢量*持續(xù)時間)
    vec3 curPossition = a_Position + (a_Direction * v_ElapsedTime);
    //減去重力或阻力的影響
    curPossition.y -= gravityFactor;
    
    //把當(dāng)前位置通過內(nèi)置變量傳給片元著色器
    gl_Position =  vec4(curPossition,1.0);
    gl_PointSize = 10.0;
}
precision mediump float;

varying vec3 v_Color;
varying float v_ElapsedTime;

void main(){
    //粒子顏色隨著顏色的推移變化
    gl_FragColor = vec4(v_Color/v_ElapsedTime, 1.0);
}

2.4. 編寫Program封裝著色器

public class ParticleShaderProgram {

    private final String U_TIME ="u_Time";

    private final String A_POSITION="a_Position";
    private final String A_COLOR="a_Color";
    private final String A_DIRECTION="a_Direction";
    private final String A_PATRICLE_START_TIME="a_PatricleStartTime";

    private final int program;

    private final int uTimeLocation;
    private final int aPositionLocation;
    private final int aColorLocation;
    private final int aDirectionLocation;
    private final int aPatricleStartTimeLocation;



    public ParticleShaderProgram(Context context) {
        //生成program
        String vertexShaderCoder = ShaderHelper.loadAsset(context.getResources(), "particle_vertex_shader.glsl");
        String fragmentShaderCoder = ShaderHelper.loadAsset(context.getResources(), "particle_fragment_shader.glsl");
        this.program = ShaderHelper.loadProgram(vertexShaderCoder,fragmentShaderCoder);

        //獲取uniform 和attribute的location

        uTimeLocation = GLES20.glGetUniformLocation(program,U_TIME);

        aPositionLocation = GLES20.glGetAttribLocation(program,A_POSITION);
        aColorLocation = GLES20.glGetAttribLocation(program,A_COLOR);
        aDirectionLocation = GLES20.glGetAttribLocation(program,A_DIRECTION);
        aPatricleStartTimeLocation = GLES20.glGetAttribLocation(program,A_PATRICLE_START_TIME);
    }

    /**
     * 設(shè)置 始終如一的Uniform變量
     * @param curTime
     */
    public void setUniforms(float curTime){
        GLES20.glUniform1f(uTimeLocation,curTime);
    }

    public int getProgram() {
        return program;
    }

    public int getaPositionLocation() {
        return aPositionLocation;
    }

    public int getaColorLocation() {
        return aColorLocation;
    }

    public int getaDirectionLocation() {
        return aDirectionLocation;
    }

    public int getaPatricleStartTimeLocation() {
        return aPatricleStartTimeLocation;
    }

    public void useProgram(){
        GLES20.glUseProgram(program);
    }
}

2.5. 編寫Render加載、渲染

public class ParticlesRender implements GLSurfaceView.Renderer {
    
    private final Context mContext;
    
    private ParticleShaderProgram mProgram;
    private ParticleSystem mParticleSystem;
    private long mSystemStartTimeNS;
    private ParticleShooter mParticleShooter;

    public ParticlesRender(Context context) {
        this.mContext = context;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES20.glClearColor(0f,0f,0f,0f);

        mProgram = new ParticleShaderProgram(mContext);

        //定義粒子系統(tǒng) 最大包含1w個粒子捶枢,超過最大之后復(fù)用最前面的
        mParticleSystem = new ParticleSystem(10000);

        //粒子系統(tǒng)開始時間
        mSystemStartTimeNS = System.nanoTime();
        
        //定義粒子發(fā)射器
        mParticleShooter = new ParticleShooter(new Geometry.Point(0f, -0.9f, 0f),
                Color.rgb(255, 50, 5),
                new Geometry.Vector(0f, 0.3f, 0f));
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0,0,width,height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        
        //當(dāng)前(相對)時間 單位秒
        float curTime = (System.nanoTime() - mSystemStartTimeNS)/1000000000f;
        //粒子發(fā)生器添加粒子
        mParticleShooter.addParticles(mParticleSystem,curTime);
        //使用Program
        mProgram.useProgram();
        //設(shè)置Uniform變量
        mProgram.setUniforms(curTime);
        //設(shè)置attribute變量
        mParticleSystem.bindData(mProgram);
        //開始繪制粒子    
        mParticleSystem.draw();
    }
}



ParticleSystem添加bindData和draw方法握截,如下

public class ParticleSystem {

...

public void bindData(ParticleShaderProgram program) {
        int dataOffset = 0;
        vertexArray.setVertexAttributePointer(dataOffset,
                program.getaPositionLocation(),
                POSITION_COMPONENT_COUNT, STRIDE);
        dataOffset +=POSITION_COMPONENT_COUNT;

        vertexArray.setVertexAttributePointer(dataOffset,
                program.getaColorLocation(),
                COLOR_COMPONENT_COUNT, STRIDE);
        dataOffset +=COLOR_COMPONENT_COUNT;

        vertexArray.setVertexAttributePointer(dataOffset,
                program.getaDirectionLocation(),
                VECTOR_COMPONENT_COUNT, STRIDE);
        dataOffset +=VECTOR_COMPONENT_COUNT;

        vertexArray.setVertexAttributePointer(dataOffset,
                program.getaPatricleStartTimeLocation(),
                PARTICLE_START_TIME_COMPONENT_COUNT, STRIDE);
    }

    public void draw() {
        GLES20.glDrawArrays(GLES20.GL_POINTS,0,curParticleCount);
    }

}

VertexArray添加方法 setVertexAttributePointer 用于給頂點(diǎn)著色器的Attribute屬性的變量賦值

public class VertexArray {
   
   ...

    public void setVertexAttributePointer(int dataOffset, int location, int count, int stride) {
        floatBuffer.position(dataOffset);
        GLES20.glVertexAttribPointer(location,count,GLES20.GL_FLOAT,false,stride,floatBuffer);
        GLES20.glEnableVertexAttribArray(location);
        floatBuffer.position(0);
    }
}   

2.6. 在GLSurfaceView中使用Render

public class ParticleActivity extends Activity{

    private GLSurfaceView glSurfaceView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_particle_layout);
        glSurfaceView = findViewById(R.id.glSurfaceView);

        glSurfaceView.setEGLContextClientVersion(2);
        glSurfaceView.setRenderer(new ParticlesRender(this));
    }

    @Override
    protected void onResume() {
        super.onResume();
        glSurfaceView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        glSurfaceView.onPause();
    }
}

效果如下

是不是和預(yù)期的效果差別很大,別著急烂叔,只要在正確的路上谨胞,遇到問題就分析解決,最重要的是堅(jiān)持前行蒜鸡。

三胯努、問題

可以看到有如下幾個問題

  1. 粒子發(fā)射沒有方向變化
  2. 新發(fā)射的和下落的重疊

下面我們來一一解決。

問題1. 隨機(jī)改變發(fā)射粒子的方向

粒子的發(fā)射方向是有發(fā)射器決定的逢防,我們現(xiàn)在是一個固定的向上發(fā)送 Vector(0f, 0.3f, 0f)叶沛。

    mParticleShooter = new ParticleShooter(new Geometry.Point(0f, -0.9f, 0f),
                Color.rgb(255, 50, 5),
                new Geometry.Vector(0f, 0.3f, 0f));


public class ParticleShooter {
...
public void addParticles(ParticleSystem particleSystem, float currentTime) {
particleSystem.addParticle(position, color, direction, currentTime);
    }
...
}

如果想改變這個為隨機(jī)方向,我們需要修改這個這個direction
我們通過隨機(jī)數(shù)以及矩陣變換來實(shí)現(xiàn)方向的隨機(jī)

public class ParticleShooter {
...
  private float[] rotationMatrix = new float[16];
    private final Random random = new Random();
    final float angleVarianceInDegrees = 20f;

public void addParticles(ParticleSystem particleSystem, float currentTime) {

        Matrix.setRotateEulerM(rotationMatrix, 0,
                (random.nextFloat() -0.5f) * angleVarianceInDegrees,
                (random.nextFloat()-0.5f) * angleVarianceInDegrees,
                (random.nextFloat()-0.5f) * angleVarianceInDegrees);

        float[] tmpDirectionFloat = new float[4];

        Matrix.multiplyMV(tmpDirectionFloat,0,
                rotationMatrix,0,
                new float[]{direction.x,direction.y,direction.z,1f},0);

        Geometry.Vector newDirection = new Geometry.Vector(tmpDirectionFloat[0], tmpDirectionFloat[1], tmpDirectionFloat[2]);

        particleSystem.addParticle(position, color, newDirection, currentTime);
    }
...
}

效果如下:


問題2. 重疊覆蓋

通過修改粒子的發(fā)射方向忘朝,看起來重疊效果也沒有了灰署,真的是這樣嗎,還是不容易觀察到了局嘁?

為了驗(yàn)證問題我們同一時刻多加些粒子溉箕,即同時發(fā)送多批粒子粒子系統(tǒng)。
修改也比較簡單悦昵,加個for循環(huán)即可肴茄。

  public void addParticles(ParticleSystem particleSystem, float currentTime, int count) {

        for (int i = 0; i < count; i++) {
            Matrix.setRotateEulerM(rotationMatrix, 0,
                    (random.nextFloat() - 0.5f) * angleVarianceInDegrees,
                    (random.nextFloat() - 0.5f) * angleVarianceInDegrees,
                    (random.nextFloat() - 0.5f) * angleVarianceInDegrees);

            float[] tmpDirectionFloat = new float[4];

            Matrix.multiplyMV(tmpDirectionFloat, 0,
                    rotationMatrix, 0,
                    new float[]{direction.x, direction.y, direction.z, 1f}, 0);

            Geometry.Vector newDirection = new Geometry.Vector(tmpDirectionFloat[0], tmpDirectionFloat[1], tmpDirectionFloat[2]);

            particleSystem.addParticle(position, color, newDirection, currentTime);

        }
    }

我們給這個count傳20,效果如下


可以看到重疊覆蓋又出現(xiàn)了但指,為什么會出現(xiàn)這種情況寡痰?

同一時刻,同一位置棋凳,既有下落的粒子拦坠,又有上升的粒子,如果下落的粒子后渲染剩岳,就會覆蓋上升的粒子贪婉。

那么該如何解決吶?

OpenGL提供了累加混合技術(shù) GL_BLEND_卢肃,公式如下

輸出 = (源因子 * 源片段)+(目標(biāo)因子 * 目標(biāo)片段)

具體實(shí)現(xiàn)為疲迂,在Render的onSurfaceCrate中設(shè)置

   @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES20.glClearColor(0f,0f,0f,0f);

        GLES20.glEnable(GLES20.GL_BLEND);
//采用累加混合 輸出= (GLES20.GL_ONE * 源片段)+(GLES20.GL_ONE * 目標(biāo)片段)
        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE);

....
}

效果如下:


可以看到?jīng)]有再出現(xiàn)重疊覆蓋的情況了。

還有一個問題是莫湘,每個粒子都是一個點(diǎn)尤蒿,大小通過gl_PointSize設(shè)置為10,看到的是正方形粒子幅垮。能不能修改為圓形或者指定樣式吶腰池?_

問題3. 把點(diǎn)修改為紋理圖片

下面我們就通過紋理圖片來把每個點(diǎn)繪制為一個點(diǎn)精靈
關(guān)于紋理的使用如果不熟悉,請先閱讀[音視頻開發(fā)之旅(12) OpenGL ES之紋理]
首先 修改片元著色器忙芒,添加2D紋理

precision mediump float;

uniform sampler2D u_TextureUnit;

varying vec3 v_Color;
varying float v_ElapsedTime;

void main(){
    //粒子顏色隨著顏色的推移變化
    //gl_FragColor = vec4(v_Color/v_ElapsedTime, 1.0);

    //改為:通過內(nèi)置函數(shù)texture2D和原來的fragcolor相乘
    gl_FragColor = vec4(v_Color/v_ElapsedTime, 1.0) * texture2D(u_TextureUnit, gl_PointCoord);

}

然后修改Program示弓,解析sample2D以及賦值

public class ParticleShaderProgram {

...
    private final String U_TEXTURE_UNIT ="u_TextureUnit";
    private final int uTextureUnit;

    public ParticleShaderProgram(Context context) {
     
        ...
        uTextureUnit = GLES20.glGetUniformLocation(program,U_TEXTURE_UNIT);

        ...
    }


   public void setUniforms(float curTime, int textureId){
        GLES20.glUniform1f(uTimeLocation,curTime);

        //激活紋理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        //綁定紋理id
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureId);
        //賦值
        GLES20.glUniform1i(uTextureUnit,0);

    }
....


}

最后在Render中定義textureId以及在onDrawFrame中傳值

public class ParticlesRender {
    
    ...
    private int mTextureId;
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    ...
        mTextureId = TextureHelper.loadTexture(mContext, R.drawable.particle_texture);
    ...

   @Override
    public void onDrawFrame(GL10 gl) {
        ...
        //設(shè)置Uniform變量
        mProgram.setUniforms(curTime,mTextureId);
        ...
    }

}
    
    ...

}

效果如下:


最后我們修改頂點(diǎn)著色器中定義的點(diǎn)的大小以及運(yùn)動矢量

    gl_PointSize = 25.0;
//定義粒子發(fā)射器
        mParticleShooter = new ParticleShooter(new Geometry.Point(0f, -0.9f, 0f),
                Color.rgb(255, 50, 5),
                new Geometry.Vector(0f, 0.8f, 0f));

就是開篇展示的效果


這篇就到這里了,通過實(shí)踐呵萨,一步步實(shí)現(xiàn)最終的噴泉效果奏属。
下一篇我們繼續(xù)來學(xué)習(xí)實(shí)踐粒子系統(tǒng),實(shí)現(xiàn)煙花空中爆炸的效果潮峦。

四囱皿、資料

《OpenGL ES 3.0 編程指南》
《OpenGL ES應(yīng)用開發(fā)實(shí)踐指南》

[粒子系統(tǒng)--煙花 [OpenGL-Transformfeedback]]
[Android制作粒子爆炸特效]
[OpenGL進(jìn)階(六)-粒子系統(tǒng)]
[【OpenGL】Shader實(shí)例分析(七)- 雪花飄落效果]

五、收獲

  1. 了解粒子屬性和粒子系統(tǒng)
  2. 通過任務(wù)拆解逐步實(shí)現(xiàn)噴泉效果
  3. 解決遇到的問題(重力忱嘹、發(fā)射方向嘱腥、重疊覆蓋、點(diǎn)精靈)

感謝你的閱讀拘悦。

下一篇我們繼續(xù)來學(xué)習(xí)實(shí)踐粒子系統(tǒng)齿兔,實(shí)現(xiàn)煙花空中爆炸的效果。歡迎關(guān)注公眾號“音視頻開發(fā)之旅”础米,一起學(xué)習(xí)成長分苇。

歡迎交流

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市椭盏,隨后出現(xiàn)的幾起案子组砚,更是在濱河造成了極大的恐慌,老刑警劉巖掏颊,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糟红,死亡現(xiàn)場離奇詭異,居然都是意外死亡乌叶,警方通過查閱死者的電腦和手機(jī)盆偿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來准浴,“玉大人事扭,你說我怎么就攤上這事±趾幔” “怎么了求橄?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵今野,是天一觀的道長。 經(jīng)常有香客問我罐农,道長条霜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任涵亏,我火速辦了婚禮宰睡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘气筋。我一直安慰自己拆内,他們只是感情好嬉挡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布泉手。 她就那樣靜靜地躺著,像睡著了一般霜医。 火紅的嫁衣襯著肌膚如雪光稼。 梳的紋絲不亂的頭發(fā)上或南,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機(jī)與錄音艾君,去河邊找鬼采够。 笑死,一個胖子當(dāng)著我的面吹牛冰垄,可吹牛的內(nèi)容都是我干的蹬癌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼虹茶,長吁一口氣:“原來是場噩夢啊……” “哼逝薪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蝴罪,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤董济,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后要门,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虏肾,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年欢搜,在試婚紗的時候發(fā)現(xiàn)自己被綠了封豪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡炒瘟,死狀恐怖吹埠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤缘琅,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布粘都,位于F島的核電站,受9級特大地震影響胯杭,放射性物質(zhì)發(fā)生泄漏驯杜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一做个、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滚局,春花似錦居暖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嘁圈,卻和暖如春省骂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背最住。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工钞澳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涨缚。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓轧粟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脓魏。 傳聞我的和親對象是個殘疾皇子兰吟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

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