WebGL繪制基本圖形--線

前言

地圖的渲染其實可以分解為線棘钞、面、紋理干毅、文字的渲染宜猜。為了了解地圖渲染的實現(xiàn)原理并實際練習(xí)WebGL,進行了這個系列的練習(xí)硝逢,線是第一步姨拥。

本文不贅述WebGL的基本知識,只對運用到的知識點進行一下簡單的回顧:

著色器

WebGL需要兩種著色器:頂點著色器和片元著色器趴捅,以O(shè)penGL ES著色器語言進行編寫垫毙,本文中使用的著色器如下:

var VSHADER_SOURCE = 
  'attribute vec4 a_Position;\n' +  // 頂點坐標
  'uniform mat4 u_MvpMatrix;\n' +   // 模型視圖投影矩陣
  'void main() {\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\n' +
  '}\n';
var FSHADER_SOURCE = 
  'precision mediump float;\n' +
  'uniform vec4 u_Color;\n' +
  'void main() {\n' +
  '  gl_FragColor = u_Color;\n' +   // 顏色
  '}\n';

考慮到繪制一條線使用同一種顏色霹疫,與頂點無關(guān)拱绑,所以在片元著色器中定義了一個uniform變量u_Color。

三角形

WebGL繪制模型的基本單位是三角形丽蝎,繪制一條有寬度的線并不能像Canvas2D那樣設(shè)置strokeStyle之后調(diào)用stroke()即可猎拨,而是需要將整條線拆分成多個小三角形膀藐,這個過程稱為三角剖分。



線段本身的三角剖分是很簡單的红省,即矩形剖分為兩個三角形额各。但是折線有拐角(lineJoin)和端頭(lineCap),且需要支持不同的樣式吧恃,這部分的剖分會稍微復(fù)雜一點虾啦,后文會詳細分析。

WebGL的drawArrays方法支持多種模式進行多個三角形的繪制痕寓,如下所示:

drawArrays模式

矢量

三角剖分的計算過程中使用到了矢量和矩陣的一些基本運算傲醉,涉及到了矢量的加減法、乘法呻率、單位化硬毕、旋轉(zhuǎn)等,這些讀者應(yīng)自行了解和掌握礼仗。本文封裝了二維矢量的相關(guān)計算方法到Vector2類中吐咳。

/**
 * Constructor of Vector2
 * If opt_src is specified, new vector is initialized by opt_src.
 * @param opt_src source vector(option)
 */
function Vector2(opt_src) {
  var v = new Float32Array(2);
  if (opt_src && typeof opt_src === 'object') {
    v[0] = opt_src[0]; v[1] = opt_src[1];
  } 
  this.elements = v;
}

/**
 * Vector2.prototype.normalize 單位化
 * Vector2.prototype.scalarProduct 與標量相乘
 * Vector2.prototype.dotProduct 與矢量點乘
 * Vector2.prototype.add 與矢量相加
 * Vector2.prototype.minus 與矢量相減
 * Vector2.prototype.rotate 旋轉(zhuǎn)角度
 * Vector2.prototype.copy 復(fù)制
 * Vector2.prototype.getVertical 獲取單位法向量
 * /

繪制目標

線這里專指折線,使用線段將一組離散的坐標點依次連接而形成元践。由于地圖是呈現(xiàn)在z=0平面上韭脊,本文也只探討在同一平面上延伸的線(扁平的),所以線的坐標點不用關(guān)心z坐標单旁,使用二維矢量(x, y)即可乾蓬。后文以coords表示線的坐標數(shù)組。

除了coords慎恒,線的樣式也是其重要的屬性任内。如下例所示,線可設(shè)置寬度融柬、顏色死嗦,同時可設(shè)置邊線的寬度和顏色;端頭以canvas為標準粒氧,可支持三種樣式:butt-平頭越除,square-方頭,round-圓頭外盯;拐角以canvas為標準摘盆,支持三種樣式:bevel-平角,miter-尖角饱苟,round-圓角孩擂。

defaultLineStyle = {
  strokeColor: new WebglColor(0.5, 0.5, 1, 1), // 邊線顏色
  strokeWidth: 5,  // 邊線寬度
  fillColor: new WebglColor(0.9, 0.9, 1, 1),  // 線顏色
  fillWidth: 20,  // 線寬度
  lineCap: 'butt',  // 端頭樣式
  lineJoin: 'bevel'  // 拐角樣式
}

[站外圖片上傳中...(image-c011bf-1545711490022)]

為了之后的一系列練習(xí),本文封裝了一個Shape類用于WebGL繪制基本圖形箱熬,抽象出了一個構(gòu)造的接口和通用的方法类垦、屬性如下:

  • 構(gòu)造函數(shù):new Shape(opts)狈邑,參數(shù)說明如下
字段名 類型 說明
type String 圖形類型:polyline, polygon, circle
glCtx WebGLRenderingContext WebGL繪圖上下文
camera Matrix4 視圖投影矩陣
coords Array.<Number> 坐標
style Object 樣式(不同圖形類型支持的樣式字段不同)
  • 方法
方法 返回值 說明
setCamera(camera: Matrix4) None 設(shè)置視圖投影矩陣
setCoords(coords: Array.<Number>) None 設(shè)置坐標
setStyle(style: Object) None 設(shè)置樣式

另外還封裝了WebglColorMatrix4蚤认、Vector2米苹,最終使用示例如下:

/**
 * 創(chuàng)建Camera矩陣
 * @param {Number} width 畫布寬度
 * @param {Number} height 畫布高度
 * @param {Number} pitch 視線俯仰角
 */
function createCamera(width, height, pitch) {
  var camera = new Matrix4();
  var fov = 60;
  var distance = height / 2 / Math.tan(fov / 2 / 180 * Math.PI);
  var near = 1;
  var far = 1.5 * distance;
  var aspect = width / height;
  camera.setPerspective(fov, aspect, near, far);
  camera.lookAt(0, 0, distance, 0, 0, 0, 0, 1, 0);

  camera.rotate(pitch, 1, 0, 0);
  return camera;
}

var canvas = document.getElementById('webgl');
var gl = canvas.getContext('webgl');
var camera = createCamera(canvas.clientWidth, canvas.clientHeight, -30);  // 構(gòu)建視圖投影矩陣

var polyline = new Shape({
  type: 'polyline',
  glCtx: gl,
  camera: camera,
  coords: [100,100,-100,100,-100,0,100,0,100,-100,-100,-100],
  style: {
    strokeColor: new WebglColor(0.5, 0.5, 1, 1),
    strokeWidth: 5,
    fillColor: new WebglColor(0.9, 0.9, 1, 1),
    fillWidth: 20
  }
});
// 構(gòu)造完成或重置屬性之后會自動繪制圖形

具體實現(xiàn)

繪制流程

我們先了解一下繪制的整體流程,然后依次詳解每個步驟砰琢。

function drawSolidLine(gl, camera, coords, style) {
  var mvpMatrix = camera;
  var color = style.color;
  
  // 三角剖分
  var triangulation = getLineTriangulation(coords, style);

  // 創(chuàng)建并初始化著色器蘸嘶,獲取變量存儲位置
  var locations = initUColorShader(gl);
  if (!locations) {
    return;
  }

  // 創(chuàng)建緩沖區(qū)并傳入數(shù)據(jù)
  var vertices = triangulation.vertices;
  if (!initVertexBuffers(gl, vertices)) {
    return;
  }
  
  // 變量賦值
  gl.uniformMatrix4fv(locations.u_MvpMatrix, false, mvpMatrix.elements);
  gl.uniform4f(locations.u_Color, color.r, color.g, color.b, color.a);
  gl.vertexAttribPointer(locations.a_Position, 3, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(location.a_Position);

  // 執(zhí)行繪制任務(wù)
  var tasks = triangulation.tasks;
  tasks.forEach(function(task) {
    gl.drawArrays(gl[task.mode], task.start, task.cnt);
  });
}

如代碼所示:

  1. 三角剖分:不同圖形的剖分過程不同,最終返回剖分后的頂點數(shù)組陪汽、繪制任務(wù)亏较。每個繪制任務(wù)指明了頂點索引范圍及繪制模式。
triangulation = {
  vertices: [x0, y0, z, x1, y1, z, ...]
  tasks: [task0, task1, ...]
}
  1. 創(chuàng)建并初始化著色器掩缓,獲取變量存儲位置:
    initUColorShader創(chuàng)建一個單一顏色的著色器雪情,然后創(chuàng)建、使用程序你辣,獲取并返回著色器中每個變量的存儲位置巡通。
locations = {
  a_Position: ..,
  u_MvpMatrix: ..,
  u_Color: ..
}
  1. 創(chuàng)建緩沖區(qū)并傳入數(shù)據(jù):
    進行緩沖區(qū)的創(chuàng)建、綁定等操作舍哄,將三角剖分后得到的頂點數(shù)組triangulation.vertices寫入緩沖區(qū)
  2. 變量賦值:
    為著色器中的變量賦值宴凉,向存儲位置locations寫入數(shù)據(jù)
  3. 執(zhí)行繪制任務(wù):
    遍歷triangulation.tasks,按指定的模式表悬、索引范圍進行繪制

下文詳細講解每個步驟的具體實現(xiàn)弥锄。

三角剖分

線的剖分可以分解為三個部分,一是線段蟆沫,二是端頭籽暇,三是拐角。

1. 準備工作

轉(zhuǎn)換coords為二維點饭庞,并計算每個線段的單位法向量戒悠。因為需要在路徑上進行垂直擴寬,且寬度與線段長度無關(guān)舟山,所以法向量取單位長度即可绸狐。

// 將坐標轉(zhuǎn)換為點、線段矢量累盗、線段單位法向量
var path = [],
    segments = [],
    verticalVectors = [],
    pathLength = 0;
for (let index = 0; index < coords.length; index += 2) {
  let x = coords[index];
  let y = coords[index + 1];
  let pathPoint = new Point2([x, y]);
  path.push(pathPoint);

  if (pathLength) {
    // 相鄰兩點相減得到線段矢量
    let prePoint = path[pathLength - 1];
    let segment = pathPoint.minus(prePoint);
    segments.push(segment);
    verticalVectors.push(segment.getVertical());
  }

  pathLength++;
}
準備工作

2. 線段剖分

線段剖分比較簡單寒矿,在路徑點坐標上加擴寬的法向量即可,需注意連接兩個線段的路徑點需要根據(jù)兩條線段的法向量若债,拓展出4個頂點符相。

path.forEach((pathPoint, index) => {
  // basePoints為擴寬后的頂點坐標
  var width = style.width / 2;
  var v0 = index == 0 ? null : verticalVectors[index-1].copy().scalarProduct(width);
  var v1 = index == pathLength - 1 ? null : verticalVectors[index].copy().scalarProduct(width);
  if (v0) {
    basePoints.push(pathPoint.add(v0));
    basePoints.push(pathPoint.minus(v0));
  }
  if (v1) {
    basePoints.push(pathPoint.add(v1));
    basePoints.push(pathPoint.minus(v1));
  }
});
basePoints
TRIANGLE_STRIP方式繪制

3. 端頭剖分

端頭只需要在首尾路徑點上進行擴展。端頭支持三種樣式:butt不需要增加坐標點拆座,square需要擴展出半個正方形主巍,邊長為線寬,round需要擴展出半個圓形垛耳,直徑為線寬江场。
square端頭剖分需要找到正方形的頂點嘁捷,只需將線段法向量旋轉(zhuǎn)90度,即可得到偏移向量offsetVector搞旭,示意圖如下:

square端頭

round端頭剖分需要在圓形弧線上找到等距且密集的點,只需將線段法向量以小角度旋轉(zhuǎn)n次直到2*PI菇绵,即可得到弧線上的頂點肄渗,最終將圓心與頂點以TRIANGLE_FAN的方式繪制即可實現(xiàn)圓形,示意圖如下:
round端頭

function getLineCapTrigl(pathPoint, verticalVector, style, isHead) {
  var subPoints = [];
  var mode = "TRIANGLE_STRIP";
  var width = style.width / 2;
  var v = verticalVector.copy().scalarProduct(width);
  switch (style.lineCap) {
    case 'butt':
      break;
    case 'square':
      var offsetVector = v.getVertical().scalarProduct(width);
      if (isHead) {
        subPoints.push(pathPoint.add(v).add(offsetVector));
        subPoints.push(pathPoint.minus(v).add(offsetVector));
      } else {
        subPoints.push(pathPoint.add(v).minus(offsetVector));
        subPoints.push(pathPoint.minus(v).minus(offsetVector));
      }
      subPoints.push(pathPoint.add(v));
      subPoints.push(pathPoint.minus(v));
      break;
    case 'round':
      subPoints.push(pathPoint);
      var rotateVector;
      for (let angle = 0; angle < 2.1 * Math.PI; angle += Math.PI/16) {
        rotateVector = v.rotate(angle);
        subPoints.push(pathPoint.add(rotateVector));
      }
      mode = "TRIANGLE_FAN";
      break;
    default:
      console.error('Invalid lineCap:' + style.lineCap);
  }
  return {
    points: subPoints,
    mode: mode
  };
}

4. 拐角剖分

拐角是在除去首尾兩端的路經(jīng)點上進行擴展咬最。支持三種樣式:bevel不需要增加坐標點(線段剖分后連接處自然形成了平角)翎嫡,miter需要填補線段延長線交匯出的尖角,round需要填補扇形永乌,直徑為線寬惑申。
miter的剖分相對來說比較復(fù)雜一點,如下圖所示翅雏,并非是一個菱形圈驼,而是兩個以線段法向量為直角邊的直角三角形拼接而成,計算公式如下:

miter拐角
function getLineJoinTrigl(pathPoint, v0, v1, style) {
  var subPoints = [];
  var mode = "TRIANGLE_STRIP";
  var width = style.width / 2;
  var v0_scale = v0.copy().scalarProduct(width);
  var v1_scale = v1.copy().scalarProduct(width);
  switch (style.lineJoin) {
    case 'miter':
      var length = width / Math.sqrt((v0.dotProduct(v1) + 1) / 2);
      var joinVector = v0.add(v1).normalize().scalarProduct(length);
      subPoints.push(pathPoint);
      subPoints.push(pathPoint.add(v0_scale));
      subPoints.push(pathPoint.add(joinVector));
      subPoints.push(pathPoint.add(v1_scale));
      subPoints.push(pathPoint.minus(v0_scale));
      subPoints.push(pathPoint.minus(joinVector));
      subPoints.push(pathPoint.minus(v1_scale));
      mode = "TRIANGLE_FAN";
      break;
    case 'bevel':
      break;
    case 'round':
      subPoints.push(pathPoint);
      var rotateVector;
      for (let angle = 0; angle < 2.1 * Math.PI; angle += Math.PI/16) {
        rotateVector = v0_scale.rotate(angle);
        subPoints.push(pathPoint.add(rotateVector));
      }
      mode = "TRIANGLE_FAN";
      break;
    default:
      console.error('Invalid lineJoin:' + style.lineJoin);
  }
  return {
    points: subPoints,
    mode: mode
  };
}

初始化著色器

initUColorShader負責(zé)建立和初始化著色器望几,主要分為三個步驟绩脆,一是通過UColorShader()獲取單一顏色著色器代碼;二是創(chuàng)建并使用程序橄抹;三是獲取變量位置靴迫。

/**
 * 創(chuàng)建并初始化著色器
 * @param {WebGLRenderingContext} gl 
 */
function initUColorShader(gl) {
  // 獲取著色器代碼
  var shaders = UColorShader();
  // 創(chuàng)建并使用程序
  if (!initShaders(gl, shaders.vshader, shaders.fshader)) {
    console.error('Failed to intialize shaders.');
    return null;
  }
  // 獲取變量位置
  return getLocations();
}

1. 著色器代碼

如前文所述,UColorShader用以生成單一顏色著色器楼誓,代碼如下:

/**
 * UColorShader: 單顏色著色器
 * 單一顏色u_Color矢劲,支持矩陣變換u_MvpMatrix, 頂點坐標a_Position
 */
function UColorShader() {
  var VSHADER_SOURCE = 
    'attribute vec4 a_Position;\n' +
    'uniform mat4 u_MvpMatrix;\n' +
    'void main() {\n' +
    '  gl_Position = u_MvpMatrix * a_Position;\n' +
    '}\n';
  var FSHADER_SOURCE = 
    'precision mediump float;\n' +
    'uniform vec4 u_Color;\n' +
    'void main() {\n' +
    '  gl_FragColor = u_Color;\n' +
    '}\n';
  return {
    vshader: VSHADER_SOURCE,
    fshader: FSHADER_SOURCE
  };
}

2. 創(chuàng)建并使用程序

initShaders這部分是WebGL繪制流程中通用的步驟,不進行過多的解釋慌随,主要有以下7個步驟芬沉。

  1. 創(chuàng)建著色器對象:gl.createShader(type)
  2. 填充著色器源代碼:gl.shaderSource(shader, source)
  3. 編譯著色器:gl.compileShader(shader)
  4. 創(chuàng)建程序?qū)ο螅?code>gl.createProgram()
  5. 為程序?qū)ο蠓峙渲鳎?code>gl.attachShader(program, shader) // 注:頂點著色器、片元著色器需要分別分配
  6. 連接程序?qū)ο螅?code>gl.linkProgram(program) // 注:將頂點著色器與片元著色器連接
  7. 使用程序?qū)ο螅?code>gl.useProgram(program)

3. 獲取變量位置

至此阁猜,我們創(chuàng)建好了一個具有三個屬性變量的著色程序丸逸,之后我們需要為這三個變量賦值,所以需要獲取到這三個變量的存儲位置剃袍。a_Positionu_MvpMatrix黄刚、u_Color的變量聲明不同,獲取存儲位置的方法也相應(yīng)的不同:

function getLocations() {
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
  var u_Color = gl.getUniformLocation(gl.program, 'u_Color');
  return {
    a_Position: a_Position,
    u_MvpMatrix: u_MvpMatrix,
    u_Color: u_Color
  };
}

數(shù)據(jù)緩沖區(qū)

因為需要一次性將全部頂點傳入頂點著色器民效,所以需要initVertexBuffers負責(zé)創(chuàng)建數(shù)據(jù)緩沖區(qū)并寫入數(shù)據(jù)憔维。

/**
 * 創(chuàng)建緩沖區(qū)并傳入數(shù)據(jù)
 * @param {WebGLRenderingContext} gl
 * @param {Float32Array} vertices
 */
function initVertexBuffers(gl, vertices) {
  // 創(chuàng)建緩沖區(qū)
  var vertexBuffer = gl.createBuffer();
  if (!vertexBuffer) {
    console.error('Failed to create the buffer object');
    return false;
  }

  // 綁定緩沖區(qū)對象:指明其用途
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  // 寫入數(shù)據(jù)
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
  return true;
}

變量賦值

u_MvpMatrixu_Color變量可直接調(diào)用對應(yīng)類型的方法進行一次傳值涛救,比如:

gl.uniformMatrix4fv(locations.u_MvpMatrix, false, mvpMatrix.elements);

WebGLRenderingContext.uniformMatrix[234]fv(location, transpose, value)用于給矩陣類型的變量賦值,2业扒、3检吆、4表示矩陣的維度。

a_Position變量賦值需要從緩沖區(qū)中讀取數(shù)據(jù)程储,需要調(diào)用vertexAttribPointer方法將緩沖區(qū)對象分配給變量a_Position蹭沛,并開啟訪問權(quán):

gl.vertexAttribPointer(locations.a_Position, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(location.a_Position);

其中3表示每個頂點的分量數(shù),a_Position是一個vec4變量章鲤,這里讀取三個分量的數(shù)據(jù)賦值給x摊灭、y、z败徊,第4位會自動補1帚呼。gl.FLOAT表示數(shù)據(jù)格式為浮點型。false標明無需將數(shù)據(jù)歸一化皱蹦。最后兩個0表示頂點數(shù)據(jù)間無間隔萝挤,數(shù)據(jù)無偏移。

執(zhí)行繪制任務(wù)

三角剖分步驟中生成了繪制任務(wù)tasks = [{mode, start, cnt}, ...]根欧,每個任務(wù)指定了模式(TRIANGLE_STRIP/TRIANGLE_FAN/TRIANGLES)怜珍、起始點索引值、繪制點數(shù)量凤粗,所以遍歷繪制任務(wù)并調(diào)用drawArrays進行繪制即可:

tasks.forEach(function(task) {
  gl.drawArrays(gl[task.mode], task.start, task.cnt);
});

至此酥泛,繪制線的流程就結(jié)束了。

demo演示

利用上文中構(gòu)造的Shape類嫌拣,最終實現(xiàn)了如下的demo柔袁,繪制了一條S折線,并且可以動態(tài)改變其顏色异逐、寬度捶索、端頭、拐角樣式灰瞻,同時通過鍵盤方向鍵控制Camera腥例,動態(tài)改變視圖投影矩陣。

webgl繪制基本圖形-線

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末酝润,一起剝皮案震驚了整個濱河市燎竖,隨后出現(xiàn)的幾起案子要销,更是在濱河造成了極大的恐慌构回,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異纤掸,居然都是意外死亡脐供,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門借跪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來政己,“玉大人,你說我怎么就攤上這事垦梆∑ゲ” “怎么了仅孩?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵托猩,是天一觀的道長。 經(jīng)常有香客問我辽慕,道長京腥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任溅蛉,我火速辦了婚禮公浪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘船侧。我一直安慰自己欠气,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布镜撩。 她就那樣靜靜地躺著预柒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪袁梗。 梳的紋絲不亂的頭發(fā)上宜鸯,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機與錄音遮怜,去河邊找鬼淋袖。 笑死,一個胖子當(dāng)著我的面吹牛锯梁,可吹牛的內(nèi)容都是我干的即碗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼陌凳,長吁一口氣:“原來是場噩夢啊……” “哼拜姿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起冯遂,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蕊肥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壁却,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡批狱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了展东。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赔硫。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盐肃,靈堂內(nèi)的尸體忽然破棺而出爪膊,到底是詐尸還是另有隱情,我是刑警寧澤砸王,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布推盛,位于F島的核電站,受9級特大地震影響谦铃,放射性物質(zhì)發(fā)生泄漏耘成。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一驹闰、第九天 我趴在偏房一處隱蔽的房頂上張望瘪菌。 院中可真熱鬧,春花似錦嘹朗、人聲如沸师妙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽默穴。三九已至,卻和暖如春惫谤,著一層夾襖步出監(jiān)牢的瞬間壁顶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工溜歪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留若专,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓蝴猪,卻偏偏與公主長得像调衰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子自阱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

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