【OpenGL ES】EGL+FBO离屏渲染

1 前言

FBO离屏渲染 中使用 GLSurfaceView 来驱动 Renderer 渲染图片,为了隐藏 GLSurfaceView,将其设置为透明的,并且宽高都设置为1。本文将使用 EGL 代替 GLSurfaceView 生成 OpenGL ES 的渲染环境,实现离屏渲染,将渲染后的图片显示在 ImageView 上。

EGL 为 OpenGL ES 提供了绘制表面(或渲染画布),是 OpenGL ES 与显示设备的桥梁让 OpenGL ES 绘制的内容能够在呈现当前设备上。

EGL 环境创建分为以下5步:

1)创建EGLDisplay

EGLDisplay mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
int[] versions = new int[2];
EGL14.eglInitialize(mEGLDisplay, versions,0, versions, 1);

2)创建EGLConfig

int[] mEGLConfigAttrs = {
 EGL14.EGL_RED_SIZE, 8,
 EGL14.EGL_GREEN_SIZE, 8,
 EGL14.EGL_BLUE_SIZE, 8,
 EGL14.EGL_ALPHA_SIZE, 8,
 EGL14.EGL_DEPTH_SIZE, 8,
 EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
 EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] configNum = new int[1];
EGL14.eglChooseConfig(mEGLDisplay, mEGLConfigAttrs, 0, configs, 0,1,  configNum, 0);
EGLConfig mEGLConfig = configs[0];

3)创建EGLContext

int[] mEGLContextAttrs = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
EGLContext mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, mEGLContextAttrs, 0);</pre>

4)创建EGLSurface

int[] eglSurfaceAttrs = {EGL14.EGL_WIDTH, mWidth, EGL14.EGL_HEIGHT, mHeight, EGL14.EGL_NONE};
EGLSurface mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, eglSurfaceAttrs, 0);

5)绑定EGLSurface和EGLContext到显示设备(EGLDisplay)

EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);

读者如果对 OpenGL ES 不太熟悉,请回顾以下内容:

本文完整代码资源见→EGL+FBO离屏渲染

项目目录如下:

2 案例

本案例实现了将彩色图片转换为灰色,并且使用 ImageView 显示转换后的图片。

MainActivity.java

package com.zhyan8.egl.activity;

import android.graphics.Bitmap;
import android.os.Bundle;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import com.zhyan8.egl.R;
import com.zhyan8.egl.model.Model;
import com.zhyan8.egl.opengl.MyEGLSurface;
import com.zhyan8.egl.opengl.MyRender;

public class MainActivity extends AppCompatActivity implements Model.Callback {
 private ImageView mImageView;
 private MyEGLSurface mEGlSurface;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mImageView = findViewById(R.id.imageView);
 initEGLSurface();
 mEGlSurface.requestRender();
 }

 private void initEGLSurface() {
 mEGlSurface = new MyEGLSurface(this);
 MyRender render = new MyRender(getResources());
 render.setCallback(this);
 mEGlSurface.init(render);
 }

 @Override
 public void onCall(final Bitmap bitmap) {
 runOnUiThread(() -> {
 mImageView.setImageBitmap(bitmap);
 });
 }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="#556688">

 <ImageView
 android:id="@+id/imageView"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:scaleType="fitCenter"/>

</FrameLayout></pre>

BaseEGLSurface.java

package com.zhyan8.egl.opengl;

import android.content.Context;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.util.DisplayMetrics;
import android.view.WindowManager;

public class BaseEGLSurface {
 protected EGLDisplay mEGLDisplay;
 protected EGLConfig mEGLConfig;
 protected EGLContext mEGLContext;
 protected EGLSurface mEGLSurface;
 protected Context mContext;
 protected Renderer mRenderer;
 protected EglStatus mEglStatus = EglStatus.INVALID;
 protected int mWidth;
 protected int mHeight;

 public BaseEGLSurface(Context context) {
 mContext = context;
 WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
 DisplayMetrics displayMetrics = new DisplayMetrics();
 mWindowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
 mWidth = displayMetrics.widthPixels;
 mHeight = displayMetrics.heightPixels;
 }

 public BaseEGLSurface(Context context, int width, int height) {
 mContext = context;
 mWidth = width;
 mHeight = height;
 }

 // 设置渲染器
 public void setRenderer(Renderer renderer) {
 mRenderer = renderer;
 }

 // EGLDisplay宽高发生变化
 public void onSurfaceChanged(int width, int height) {
 mWidth = width;
 mHeight = height;
 EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
 createSurface();
 mEglStatus = EglStatus.CREATED;
 }

 // 请求渲染
 public void requestRender() {
 if (mEglStatus == mEglStatus.INVALID) {
 return;
 }
 if (mEglStatus == EglStatus.INITIALIZED) {
 mRenderer.onSurfaceCreated();
 mRenderer.onSurfaceChanged(mWidth, mHeight);
 mEglStatus = EglStatus.CREATED;
 }
 if (mEglStatus == EglStatus.CREATED || mEglStatus == EglStatus.DRAW) {
 mRenderer.onDrawFrame();
 mEglStatus = EglStatus.DRAW;
 }
 }

 // 创建EGL环境
 public void createEGLEnv() {
 createDisplay();
 createConfig();
 createContext();
 createSurface();
 makeCurrent();
 }

 // 销毁EGL环境
 public void destroyEGLEnv() {
 // 与显示设备解绑
 EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
 // 销毁 EGLSurface
 EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
 // 销毁EGLContext
 EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
 // 销毁EGLDisplay(显示设备)
 EGL14.eglTerminate(mEGLDisplay);
 mEGLContext = null;
 mEGLSurface = null;
 mEGLDisplay = null;
 }

 // 1.创建EGLDisplay
 private void createDisplay() {
 mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
 int[] versions = new int[2];
 EGL14.eglInitialize(mEGLDisplay, versions,0, versions, 1);
 }

 // 2.创建EGLConfig
 private void createConfig() {
 EGLConfig[] configs = new EGLConfig[1];
 int[] configNum = new int[1];
 EGL14.eglChooseConfig(mEGLDisplay, mEGLConfigAttrs, 0, configs, 0,1,  configNum, 0);
 if (configNum[0] > 0) {
 mEGLConfig = configs[0];
 }
 }

 // 3.创建EGLContext
 private void createContext() {
 if (mEGLConfig != null) {
 mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, mEGLContextAttrs, 0);
 }
 }

 // 4.创建EGLSurface
 private void createSurface() {
 if (mEGLContext != null && mEGLContext != EGL14.EGL_NO_CONTEXT) {
 int[] eglSurfaceAttrs = {EGL14.EGL_WIDTH, mWidth, EGL14.EGL_HEIGHT, mHeight, EGL14.EGL_NONE};
 mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, eglSurfaceAttrs, 0);
 }
 }

 // 5.绑定EGLSurface和EGLContext到显示设备(EGLDisplay)
 private void makeCurrent() {
 if (mEGLSurface != null && mEGLSurface != EGL14.EGL_NO_SURFACE) {
 EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);
 mEglStatus = EglStatus.INITIALIZED;
 }
 }

 // EGLConfig参数
 private int[] mEGLConfigAttrs = {
 EGL14.EGL_RED_SIZE, 8,
 EGL14.EGL_GREEN_SIZE, 8,
 EGL14.EGL_BLUE_SIZE, 8,
 EGL14.EGL_ALPHA_SIZE, 8,
 EGL14.EGL_DEPTH_SIZE, 8,
 EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
 EGL14.EGL_NONE
 };

 // EGLContext参数
 private int[] mEGLContextAttrs = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};

 // EGL状态
 enum EglStatus {
 INVALID, INITIALIZED, CREATED, DRAW
 }

 // 渲染器接口
 interface Renderer {
 void onSurfaceCreated();
 void onSurfaceChanged(int width, int height);
 void onDrawFrame();
 }
}

MyEGLSurface.java

package com.zhyan8.egl.opengl;

import android.content.Context;

public class MyEGLSurface extends BaseEGLSurface {
 public MyEGLSurface(Context context) {
 super(context);
 }

 public MyEGLSurface(Context context, int width, int height) {
 super(context, width, height);
 }

 public void init(Renderer renderer) {
 setRenderer(renderer);
 createEGLEnv();
 }
}

MyRender.java

package com.zhyan8.egl.opengl;

import android.content.res.Resources;
import android.opengl.GLES30;
import com.zhyan8.egl.model.Model;

public class MyRender implements BaseEGLSurface.Renderer {
 private Model mModel;

 public MyRender(Resources resources) {
 mModel = new Model(resources);
 }

 @Override
 public void onSurfaceCreated() {
 //设置背景颜色
 GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
 //启动深度测试
 GLES30.glEnable(GLES30.GL_DEPTH_TEST);
 //创建程序id
 mModel.onModelCreate();
 }

 @Override
 public void onSurfaceChanged(int width, int height) {
 mModel.onModelChange(width, height);
 }

 @Override
 public void onDrawFrame() {
 GLES30.glClearColor(0.5f, 0.7f, 0.3f, 1.0f);
 // 将颜色缓存区设置为预设的颜色
 GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
 // 启用顶点的数组句柄
 GLES30.glEnableVertexAttribArray(0);
 GLES30.glEnableVertexAttribArray(1);
 // 绘制模型
 mModel.onModelDraw();
 // 禁止顶点数组句柄
 GLES30.glDisableVertexAttribArray(0);
 GLES30.glDisableVertexAttribArray(1);
 }

 public void setCallback(Model.Callback callback) {
 mModel.setCallback(callback);
 }
}

Model.java

package com.zhyan8.egl.model;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.opengl.GLES30;
import com.zhyan8.egl.R;
import com.zhyan8.egl.utils.ArraysUtils;
import com.zhyan8.egl.utils.ShaderUtils;
import com.zhyan8.egl.utils.TextureUtils;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

public class Model {
 private static final int TEXTURE_DIMENSION = 2; // 纹理坐标维度
 private static final int VERTEX_DIMENSION = 3; // 顶点坐标维度
 private Callback mCallback;
 private Resources mResources;
 private float mVertex[] = {-1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f};
 private float[] mFboTexture = {0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f};
 protected FloatBuffer mVertexBuffer;
 protected FloatBuffer mFboTextureBuffer;
 // 帧缓冲对象 - 颜色、深度、模板附着点,纹理对象可以连接到帧缓冲区对象的颜色附着点
 private int[] mFrameBufferId = new int[1];
 private int[] mTextureId = new int[2];
 private int mProgramId;
 private Point mBitmapSize = new Point();

 public Model(Resources resources) {
 mResources = resources;
 mVertexBuffer = ArraysUtils.getFloatBuffer(mVertex);
 mFboTextureBuffer = ArraysUtils.getFloatBuffer(mFboTexture);
 }

 // 模型创建
 public void onModelCreate() {
 mProgramId = ShaderUtils.createProgram(mResources, R.raw.vertex_shader, R.raw.fragment_shader);
 TextureUtils.loadTexture(mResources, R.raw.xxx, mBitmapSize, mTextureId, mFrameBufferId);
 }

 // 模型参数变化
 public void onModelChange(int width, int height) {
 GLES30.glViewport(0, 0, mBitmapSize.x, mBitmapSize.y);
 }

 // 模型绘制
 public void onModelDraw() {
 GLES30.glUseProgram(mProgramId);
 // 准备顶点坐标和纹理坐标
 GLES30.glVertexAttribPointer(0, VERTEX_DIMENSION, GLES30.GL_FLOAT, false, 0, mVertexBuffer);
 GLES30.glVertexAttribPointer(1, TEXTURE_DIMENSION, GLES30.GL_FLOAT, false, 0, mFboTextureBuffer);
 // 激活纹理
 GLES30.glActiveTexture(GLES30.GL_TEXTURE);
 // 绑定纹理
 GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureId[0]);
 // 绑定缓存
 GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBufferId[0]);
 // 绘制贴图
 GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
 showBitmap();
 }

 private void showBitmap() {
 // 分配字节缓区大小, 一个像素4个字节
 ByteBuffer byteBuffer = ByteBuffer.allocate(mBitmapSize.x * mBitmapSize.y * Integer.BYTES);
 GLES30.glReadPixels(0, 0, mBitmapSize.x, mBitmapSize.y, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, byteBuffer);
 Bitmap bitmap = Bitmap.createBitmap(mBitmapSize.x, mBitmapSize.y, Bitmap.Config.ARGB_8888);
 // 从缓存区读二进制缓冲数据
 bitmap.copyPixelsFromBuffer(byteBuffer);
 // 回调
 mCallback.onCall(bitmap);
 }

 public void setCallback(Callback callback) {
 mCallback = callback;
 }

 public interface Callback{
 void onCall(Bitmap bitmap);
 }
}

ShaderUtils.java

package com.zhyan8.egl.utils;

import android.content.res.Resources;
import android.opengl.GLES30;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

public class ShaderUtils {
 //创建程序id
 public static int createProgram(Resources resources, int vertexShaderResId, int fragmentShaderResId) {
 final int vertexShaderId = compileShader(resources, GLES30.GL_VERTEX_SHADER, vertexShaderResId);
 final int fragmentShaderId = compileShader(resources, GLES30.GL_FRAGMENT_SHADER, fragmentShaderResId);
 return linkProgram(vertexShaderId, fragmentShaderId);
 }

 //通过外部资源编译着色器
 private static int compileShader(Resources resources, int type, int shaderId){
 String shaderCode = readShaderFromResource(resources, shaderId);
 return compileShader(type, shaderCode);
 }

 //通过代码片段编译着色器
 private static int compileShader(int type, String shaderCode){
 int shader = GLES30.glCreateShader(type);
 GLES30.glShaderSource(shader, shaderCode);
 GLES30.glCompileShader(shader);
 return shader;
 }

 //链接到着色器
 private static int linkProgram(int vertexShaderId, int fragmentShaderId) {
 final int programId = GLES30.glCreateProgram();
 //将顶点着色器加入到程序
 GLES30.glAttachShader(programId, vertexShaderId);
 //将片元着色器加入到程序
 GLES30.glAttachShader(programId, fragmentShaderId);
 //链接着色器程序
 GLES30.glLinkProgram(programId);
 return programId;
 }

 //从shader文件读出字符串
 private static String readShaderFromResource(Resources resources, int shaderId) {
 InputStream is = resources.openRawResource(shaderId);
 BufferedReader br = new BufferedReader(new InputStreamReader(is));
 String line;
 StringBuilder sb = new StringBuilder();
 try {
 while ((line = br.readLine()) != null) {
 sb.append(line);
 sb.append("\n");
 }
 br.close();
 } catch (Exception e) {
 e.printStackTrace();
 }
 return sb.toString();
 }
}

TextureUtils.java

package com.zhyan8.egl.utils;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.opengl.GLES30;
import android.opengl.GLUtils;

public class TextureUtils {
 // 加载纹理贴图
 public static void loadTexture(Resources resources, int resourceId, Point bitmapSize, int[] textureId, int[] frameBufferId) {
 BitmapFactory.Options options = new BitmapFactory.Options();
 options.inScaled = false;
 Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId, options);
 bitmapSize.set(bitmap.getWidth(), bitmap.getHeight());
 // 生成纹理id
 GLES30.glGenTextures(2, textureId, 0);
 for (int i = 0; i < 2; i++) {
 // 绑定纹理到OpenGL
 GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId[i]);
 GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
 GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
 GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
 GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
 if (i == 0) {
 // 第一个纹理对象给渲染管线(加载bitmap到纹理中)
 GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, bitmap, 0);
 } else {
 // 第二个纹理对象给帧缓冲区
 GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, bitmap.getWidth(), bitmap.getHeight(),
 0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
 }
 // 取消绑定纹理
 GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, GLES30.GL_NONE);
 }
 // 创建帧缓存id
 GLES30.glGenFramebuffers(1, frameBufferId, 0);
 // 绑定帧缓存
 GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBufferId[0]);
 // 将第二个纹理附着在帧缓存的颜色附着点上
 GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, textureId[1], 0);
 // 取消绑定帧缓存
 GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);
 }
}

ArraysUtils.java

package com.zhyan8.egl.utils;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

public class ArraysUtils {
 public static FloatBuffer getFloatBuffer(float[] floatArr) {
 FloatBuffer fb = ByteBuffer.allocateDirect(floatArr.length * Float.BYTES)
 .order(ByteOrder.nativeOrder())
 .asFloatBuffer();
 fb.put(floatArr);
 fb.position(0);
 return fb;
 }
}

vertex_shader.glsl

#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec2 aTextureCoord;
out vec2 vTexCoord;
void main() {
 gl_Position  = vPosition;
 vTexCoord = aTextureCoord;
}

fragment_shader.glsl

#version 300 es
precision mediump float;
uniform sampler2D uTextureUnit;
in vec2 vTexCoord;
out vec4 fragColor;
void main() {
 vec4 color = texture(uTextureUnit, vTexCoord);
 float rgb = color.g;
 vec4 c = vec4(rgb, rgb, rgb, color.a);
 fragColor = c;
}

3 运行效果

原图:

处理后:

声明:本文转自【OpenGL ES】EGL+FBO离屏渲染

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容