2019年10月28日 星期一

將曲線( Curve )連起來

將曲線( Curve )連起來

前言

  續之前的 WebGL的繪製三次方貝茲曲線( Cubic bezier curve ) ,在該篇只是單純的繪製曲線( Curve ),如果需要彎曲變化較豐富的話,就需要增加控制點的數量,查詢資料發現貝茲曲線( ( Bezier curve )不只有二次方與三次方,可以到四次方、五次方...等,每次改變控制點數量就要換一種算式計算,實在不是個好方法,所以打算用多個三次方貝茲曲線( Cubic bezier curve )連起來達到增加控制點的方式來增加彎曲變化,在此把學習的過程做個紀錄。

內容

  雖然貝茲曲線( Bezier curve )不止到三次方,但每次增加控制點就要改變算式相當麻煩,所以採用"連"起來的方式來完成曲線,到底是怎麼連起來的呢?請看下圖
曲線( Curve )的連結

圖中是連結兩個三次方貝茲曲線( Cubic bezier curve),比較要注意的是連結後所需要控制點是7個,並不是8個,第一個曲線( Curve )的結尾會跟第二個曲線( Curve)的開頭共用同一個控制點。接著來看實作範例
HTML 的部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<canvas id="myCanvas1" width=400 height=300></canvas>
<br>
<input id="btnDrawCtrlPoints"type="button" value="DrawCtrlPoints"/>
<br>
<input id="btnDrawCurve"type="button" value="DrawCurve"/>
<input type="range" min="2" max="100" value="30" class="slider" id="sliderLerp">
<label id="labelLerpValue">30</label>
</body>
</html>

Javascript 的部分
let canvas1 = document.getElementById('myCanvas1');
let glCTX1 = canvas1.getContext('webgl');
let vboPrimitiveCon = 0;
let vbo=createDynamicBuffer(glCTX1);
let shaderProg = createShader(glCTX1);
//
function createShader(glContext){
  let vertShader = glContext.createShader(glContext.VERTEX_SHADER);
  glContext.shaderSource(
    vertShader , 
    'attribute vec3 pos;void main(void){gl_Position=vec4(pos, 1.0);}'
  );
  glContext.compileShader(vertShader);
  let fragShader = glContext.createShader(glContext.FRAGMENT_SHADER);
  glContext.shaderSource(
    fragShader, 
    'void main(void){gl_FragColor=vec4(1,1,1,1);}'
  );
  glContext.compileShader(fragShader);
  let prog = glContext.createProgram();
  glContext.attachShader(prog, vertShader);
  glContext.attachShader(prog, fragShader);
  glContext.linkProgram(prog);
  glContext.useProgram(prog);  
  
  return prog;
}
function createDynamicBuffer(glContext){
  let vertexBuf = glContext.createBuffer();
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuf);
  let dataArray=new Float32Array([ 
       0.0, 0.5, 0.0,  
      -0.5,-0.5, 0.0,  
      -0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.0, 0.5, 0.0
    ]);
  glContext.bufferData(
    glContext.ARRAY_BUFFER, 
    3000, 
    glContext.DYNAMIC_DRAW
  );
  //write deafult data...
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
  return vertexBuf;
}
function simpleDraw(glContext){
  glContext.useProgram(shaderProg);
  //
  glContext.viewport(0,0,glContext.canvas.width,glContext.canvas.height);
  glContext.clearColor(0, 0, 1, 1);
  glContext.clear(glContext.COLOR_BUFFER_BIT);
  //
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vbo);
  let posLoc = glContext.getAttribLocation(shaderProg, "pos");
  glContext.vertexAttribPointer(posLoc, 3, glContext.FLOAT, false, 0, 0);
  glContext.enableVertexAttribArray(posLoc);

  glContext.drawArrays(glContext.LINES, 0, vboPrimitiveCon);
}
function generateLineListData(ar){
  let tagAr=[];
  let mod=ar.length%3;
  let elementAmount=(ar.length-mod)/3;
  if(elementAmount>=2 && mod===0){
    tagAr.push(ar[0]);
    tagAr.push(ar[1]);
    tagAr.push(ar[2]);
    //
    for(let i=3;i<(ar.length-3);i+=3){
      tagAr.push(ar[i]);
      tagAr.push(ar[i+1]);
      tagAr.push(ar[i+2]);
      //
      tagAr.push(ar[i]);
      tagAr.push(ar[i+1]);
      tagAr.push(ar[i+2]);
    }
    //
    tagAr.push(ar[ar.length-3]);
    tagAr.push(ar[ar.length-2]);
    tagAr.push(ar[ar.length-1]);
  }
  return new Float32Array(tagAr);
}
function generateBezierCurve(p0,p1,p2,lerp){
  if(lerp < 2)
    return [];
  //
  let tagAr = [];
  for(let i=0;i < lerp;i++){
    let t = i/(lerp-1);
    let invT = 1.0-t;
    let part0Value = invT * invT;
    let part1Value = 2 * t * invT;
    let part2Value = t * t;
    let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ];
    let part1 = [part1Value*p1[0], part1Value*p1[1], part1Value*p1[2] ];
    let part2 = [part2Value*p2[0], part2Value*p2[1], part2Value*p2[2] ];
    tagAr.push(part0[0] + part1[0] + part2[0]);
    tagAr.push(part0[1] + part1[1] + part2[1]);
    tagAr.push(part0[2] + part1[2] + part2[2]);
  }
  return tagAr;
}
function generateCubicBezierCurve(p0,p1,p2,p3,lerp){
  if(lerp < 2)
    return [];
  //
  let tagAr = [];
  for(let i=0;i < lerp;i++){
    let t = i/(lerp-1);
    let invT = 1.0-t;
    let part0Value = invT * invT * invT;
    let part1Value = 3 * t * invT * invT;
    let part2Value = 3 * t * t * invT;
    let part3Value = t * t * t;
    let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ];
    let part1 = [part1Value*p1[0], part1Value*p1[1], part1Value*p1[2] ];
    let part2 = [part2Value*p2[0], part2Value*p2[1], part2Value*p2[2] ];
    let part3 = [part3Value*p3[0], part3Value*p3[1], part3Value*p3[2] ];
    tagAr.push(part0[0] + part1[0] + part2[0] + part3[0]);
    tagAr.push(part0[1] + part1[1] + part2[1] + part3[1]);
    tagAr.push(part0[2] + part1[2] + part2[2] + part3[2]);
  }
  return tagAr;
}
function myRender(){
  simpleDraw(glCTX1);
  //
  window.requestAnimationFrame(myRender);
}
//
let ctrlPointList=[
  [-0.9,0.0,0.0],
  [-0.7,0.9,0.0],
  [-0.5,0.9,0.0],
  [-0.3,0.0,0.0],
  [-0.1,-0.6,0.0],
  [0.1,-0.6,0.0],
  [0.3,0.0,0.0],
  [0.5,0.9,0.0],
  [0.7,0.9,0.0],
  [0.9,0.0,0.0],
];
let tagLerpValue=document.getElementById("sliderLerp").value;
function UpdateCurveData(){
  let tagCurveData=[];
  for(let i=0;i<ctrlPointList.length;i+=3){
    if( (ctrlPointList.length - i) < 4)
      break;
    //
    let data=generateCubicBezierCurve(
      ctrlPointList[i],
      ctrlPointList[i+1],
      ctrlPointList[i+2],
      ctrlPointList[i+3],
      tagLerpValue);
    tagCurveData.push(...data);
  }
  let dataArray=generateLineListData(tagCurveData);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
document.getElementById("btnDrawCtrlPoints").onclick=function(evt){
  let data = [];
  for(let i=0;i<ctrlPointList.length;i++)
    data.push(...ctrlPointList[i]);
  //
  let dataArray=generateLineListData(data);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
document.getElementById("btnDrawCurve").onclick=function(evt){
  UpdateCurveData();
}
document.getElementById("sliderLerp").oninput=function(evt){
  tagLerpValue=this.value;
  UpdateCurveData();
  document.getElementById("labelLerpValue").innerHTML = this.value;
}
window.onload = function(){
  window.requestAnimationFrame(myRender);
  
}

執行結果如下
繪製控制點
繪製曲線( Curve )

這次的範例從 WebGL的繪製三次方貝茲曲線( Cubic bezier curve ) 的範例更改而來,這次只說明不一樣的部分。這次的範例會"連"接三個三次方貝茲曲線( Cubic bezier curve ),所以控制點的數量改成10個,接著在 UpadteCurveData() 中,由於這次要把各個曲線( Curve )"連"起來,所以改成用迴圈的方式來產生。

參考資料

 [ Wiki ]貝茲曲線

 相關網站

  WebGL的繪製三次方貝茲曲線( Cubic bezier curve )

2019年10月21日 星期一

WebGL的繪製三次方貝茲曲線( Cubic bezier curve )

WebGL的繪製三次方貝茲曲線( Cubic bezier curve )

前言

  續之前的 WebGL的繪製曲線( Curve ) ,該篇所繪製的曲線為二次貝茲曲線(Quadratic bezier curve),但常見的貝茲曲線是三次方貝茲曲線( Cubic bezier curve ),這次會製作繪製三次方貝茲曲線( Cubic bezier curve )的範例,在此做個紀錄。

內容

  二次貝茲曲線(Quadratic bezier curve)與三次方貝茲曲線( Cubic bezier curve )的差異在於前者的控制點是3個,後者的控制點是4個。為什麼說三次方貝茲曲線( Cubic bezier curve )比較常見呢?手上的繪圖軟體 Painter.NET 與 Blender 的貝茲曲線工具都是4個控制點,如下圖
Painter.NET 的曲線工具
Blender的曲線工具
接著就來看範例,如下
HTML 的部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<canvas id="myCanvas1" width=400 height=300></canvas>
<br>
<input id="btnDrawCtrlPoints"type="button" value="DrawCtrlPoints"/>
<br>
<input id="btnDrawCurve"type="button" value="DrawCurve"/>
<input type="range" min="2" max="100" value="30" class="slider" id="sliderLerp">
<label id="labelLerpValue">30</label>
</body>
</html>

Javascript 的部分
let canvas1 = document.getElementById('myCanvas1');
let glCTX1 = canvas1.getContext('webgl');
let vboPrimitiveCon = 0;
let vbo=createDynamicBuffer(glCTX1);
let shaderProg = createShader(glCTX1);
//
function createShader(glContext){
  let vertShader = glContext.createShader(glContext.VERTEX_SHADER);
  glContext.shaderSource(
    vertShader , 
    'attribute vec3 pos;void main(void){gl_Position=vec4(pos, 1.0);}'
  );
  glContext.compileShader(vertShader);
  let fragShader = glContext.createShader(glContext.FRAGMENT_SHADER);
  glContext.shaderSource(
    fragShader, 
    'void main(void){gl_FragColor=vec4(1,1,1,1);}'
  );
  glContext.compileShader(fragShader);
  let prog = glContext.createProgram();
  glContext.attachShader(prog, vertShader);
  glContext.attachShader(prog, fragShader);
  glContext.linkProgram(prog);
  glContext.useProgram(prog);  
  
  return prog;
}
function createDynamicBuffer(glContext){
  let vertexBuf = glContext.createBuffer();
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuf);
  let dataArray=new Float32Array([ 
       0.0, 0.5, 0.0,  
      -0.5,-0.5, 0.0,  
      -0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.0, 0.5, 0.0
    ]);
  glContext.bufferData(
    glContext.ARRAY_BUFFER, 
    3000, 
    glContext.DYNAMIC_DRAW
  );
  //write deafult data...
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
  return vertexBuf;
}
function simpleDraw(glContext){
  glContext.useProgram(shaderProg);
  //
  glContext.viewport(0,0,glContext.canvas.width,glContext.canvas.height);
  glContext.clearColor(0, 0, 1, 1);
  glContext.clear(glContext.COLOR_BUFFER_BIT);
  //
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vbo);
  let posLoc = glContext.getAttribLocation(shaderProg, "pos");
  glContext.vertexAttribPointer(posLoc, 3, glContext.FLOAT, false, 0, 0);
  glContext.enableVertexAttribArray(posLoc);

  glContext.drawArrays(glContext.LINES, 0, vboPrimitiveCon);
}
function generateLineListData(ar){
  let tagAr=[];
  let mod=ar.length%3;
  let elementAmount=(ar.length-mod)/3;
  if(elementAmount>=2 && mod===0){
    tagAr.push(ar[0]);
    tagAr.push(ar[1]);
    tagAr.push(ar[2]);
    //
    for(let i=3;i<(ar.length-3);i+=3){
      tagAr.push(ar[i]);
      tagAr.push(ar[i+1]);
      tagAr.push(ar[i+2]);
      //
      tagAr.push(ar[i]);
      tagAr.push(ar[i+1]);
      tagAr.push(ar[i+2]);
    }
    //
    tagAr.push(ar[ar.length-3]);
    tagAr.push(ar[ar.length-2]);
    tagAr.push(ar[ar.length-1]);
  }
  return new Float32Array(tagAr);
}
function generateBezierCurve(p0,p1,p2,lerp){
  if(lerp < 2)
    return [];
  //
  let tagAr = [];
  for(let i=0;i < lerp;i++){
    let t = i/(lerp-1);
    let invT = 1.0-t;
    let part0Value = invT * invT;
    let part1Value = 2 * t * invT;
    let part2Value = t * t;
    let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ];
    let part1 = [part1Value*p1[0], part1Value*p1[1], part1Value*p1[2] ];
    let part2 = [part2Value*p2[0], part2Value*p2[1], part2Value*p2[2] ];
    tagAr.push(part0[0] + part1[0] + part2[0]);
    tagAr.push(part0[1] + part1[1] + part2[1]);
    tagAr.push(part0[2] + part1[2] + part2[2]);
  }
  return tagAr;
}
function generateCubicBezierCurve(p0,p1,p2,p3,lerp){
  if(lerp < 2)
    return [];
  //
  let tagAr = [];
  for(let i=0;i < lerp;i++){
    let t = i/(lerp-1);
    let invT = 1.0-t;
    let part0Value = invT * invT * invT;
    let part1Value = 3 * t * invT * invT;
    let part2Value = 3 * t * t * invT;
    let part3Value = t * t * t;
    let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ];
    let part1 = [part1Value*p1[0], part1Value*p1[1], part1Value*p1[2] ];
    let part2 = [part2Value*p2[0], part2Value*p2[1], part2Value*p2[2] ];
    let part3 = [part3Value*p3[0], part3Value*p3[1], part3Value*p3[2] ];
    tagAr.push(part0[0] + part1[0] + part2[0] + part3[0]);
    tagAr.push(part0[1] + part1[1] + part2[1] + part3[1]);
    tagAr.push(part0[2] + part1[2] + part2[2] + part3[2]);
  }
  return tagAr;
}
function myRender(){
  simpleDraw(glCTX1);
  //
  window.requestAnimationFrame(myRender);
}
//
let ctrlPointList=[
  [-0.9,0.0,0.0],
  [-0.45,0.9,0.0],
  [0.45,0.9,0.0],
  [0.9,0.0,0.0],
];
let tagLerpValue=document.getElementById("sliderLerp").value;
function UpdateCurveData(){
  let data=generateCubicBezierCurve(
    ctrlPointList[0],
    ctrlPointList[1],
    ctrlPointList[2],
    ctrlPointList[3],
    tagLerpValue);
  let dataArray=generateLineListData(data);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
document.getElementById("btnDrawCtrlPoints").onclick=function(evt){
  let data = [];
  for(let i=0;i<ctrlPointList.length;i++)
    data.push(...ctrlPointList[i]);
  //
  let dataArray=generateLineListData(data);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
document.getElementById("btnDrawCurve").onclick=function(evt){
  UpdateCurveData();
}
document.getElementById("sliderLerp").oninput=function(evt){
  tagLerpValue=this.value;
  UpdateCurveData();
  document.getElementById("labelLerpValue").innerHTML = this.value;
}
window.onload = function(){
  window.requestAnimationFrame(myRender);
  
}

執行結果如下
繪製控制點
繪製曲線


這次的範例是由 WebGL的繪製曲線( Curve ) 的範例改來,重複的部分就不再說明。這次多了generateCubicBezierCurve() ,由於控制點多了一個,所以參數多一個 p3 ,曲線的攻勢可以參考 [ Wiki ]貝茲曲線 ,裡面有對應的公式。 ctrlPointList 要多一個控制點,UpdateCurveData()換成用 generateCubicBezierCurve() 來產生資料,最後在 btnDrawCtrlPoints 的事件改了作法,上次直接使用寫3行推入陣列,這次改採使用迴圈的方法。

參考資料

[ Wiki ]貝茲曲線

相關文章

WebGL的繪製曲線( Curve )

2019年10月14日 星期一

WebGL的繪製曲線( Curve )

WebGL的繪製曲線( Curve )

前言

  續之前的 WebGL的繪製線段 ,該篇介紹了 WebGL 的基本繪製線段的方法,這次要建立一個繪製曲線( Curve )的基本繪製環境,在此做個紀錄。

內容

  這次會製作一個繪製曲線的基本環境,範例會繪製一個二次貝茲曲線,二次貝茲曲線的知識可以在 [ Wiki ]貝茲曲線 取得。範例可以在曲線( Curve )與控制點線段做切換,方便比對差異,範例如下
HTML 的部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<canvas id="myCanvas1" width=400 height=300></canvas>
<br>
<input id="btnDrawCtrlPoints"type="button" value="DrawCtrlPoints"/>
<br>
<input id="btnDrawCurve"type="button" value="DrawCurve"/>
<input type="range" min="2" max="100" value="30" class="slider" id="sliderLerp">
<label id="labelLerpValue">30</label>
</body>
</html>

Javascript 的部分
let canvas1 = document.getElementById('myCanvas1');
let glCTX1 = canvas1.getContext('webgl');
let vboPrimitiveCon = 0;
let vbo=createDynamicBuffer(glCTX1);
let shaderProg = createShader(glCTX1);
//
function createShader(glContext){
  let vertShader = glContext.createShader(glContext.VERTEX_SHADER);
  glContext.shaderSource(
    vertShader , 
    'attribute vec3 pos;void main(void){gl_Position=vec4(pos, 1.0);}'
  );
  glContext.compileShader(vertShader);
  let fragShader = glContext.createShader(glContext.FRAGMENT_SHADER);
  glContext.shaderSource(
    fragShader, 
    'void main(void){gl_FragColor=vec4(1,1,1,1);}'
  );
  glContext.compileShader(fragShader);
  let prog = glContext.createProgram();
  glContext.attachShader(prog, vertShader);
  glContext.attachShader(prog, fragShader);
  glContext.linkProgram(prog);
  glContext.useProgram(prog);  
  
  return prog;
}
function createDynamicBuffer(glContext){
  let vertexBuf = glContext.createBuffer();
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuf);
  let dataArray=new Float32Array([ 
       0.0, 0.5, 0.0,  
      -0.5,-0.5, 0.0,  
      -0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.0, 0.5, 0.0
    ]);
  glContext.bufferData(
    glContext.ARRAY_BUFFER, 
    3000, 
    glContext.DYNAMIC_DRAW
  );
  //write deafult data...
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
  return vertexBuf;
}
function simpleDraw(glContext){
  glContext.useProgram(shaderProg);
  //
  glContext.viewport(0,0,glContext.canvas.width,glContext.canvas.height);
  glContext.clearColor(0, 0, 1, 1);
  glContext.clear(glContext.COLOR_BUFFER_BIT);
  //
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vbo);
  let posLoc = glContext.getAttribLocation(shaderProg, "pos");
  glContext.vertexAttribPointer(posLoc, 3, glContext.FLOAT, false, 0, 0);
  glContext.enableVertexAttribArray(posLoc);

  glContext.drawArrays(glContext.LINES, 0, vboPrimitiveCon);
}
function generateLineListData(ar){
  let tagAr=[];
  let mod=ar.length%3;
  let elementAmount=(ar.length-mod)/3;
  if(elementAmount>=2 && mod===0){
    tagAr.push(ar[0]);
    tagAr.push(ar[1]);
    tagAr.push(ar[2]);
    //
    for(let i=3;i<(ar.length-3);i+=3){
      tagAr.push(ar[i]);
      tagAr.push(ar[i+1]);
      tagAr.push(ar[i+2]);
      //
      tagAr.push(ar[i]);
      tagAr.push(ar[i+1]);
      tagAr.push(ar[i+2]);
    }
    //
    tagAr.push(ar[ar.length-3]);
    tagAr.push(ar[ar.length-2]);
    tagAr.push(ar[ar.length-1]);
  }
  return new Float32Array(tagAr);
}
function generateBezierCurve(p0,p1,p2,lerp){
  if(lerp < 2)
    return [];
  //
  let tagAr = [];
  for(let i=0;i < lerp;i++){
    let t = i/(lerp-1);
    let invT = 1.0-t;
    let part0Value = invT * invT;
    let part1Value = 2 * t * invT;
    let part2Value = t * t;
    let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ];
    let part1 = [part1Value*p1[0], part1Value*p1[1], part1Value*p1[2] ];
    let part2 = [part2Value*p2[0], part2Value*p2[1], part2Value*p2[2] ];
    tagAr.push(part0[0] + part1[0] + part2[0]);
    tagAr.push(part0[1] + part1[1] + part2[1]);
    tagAr.push(part0[2] + part1[2] + part2[2]);
  }
  return tagAr;
}
function myRender(){
  simpleDraw(glCTX1);
  //
  window.requestAnimationFrame(myRender);
}
//
let ctrlPointList=[
  [-0.9,0.0,0.0],
  [0.0,0.9,0.0],
  [0.9,0.0,0.0],
];
let tagLerpValue=document.getElementById("sliderLerp").value;
function UpdateCurveData(){
  let data=generateBezierCurve(
    ctrlPointList[0],
    ctrlPointList[1],
    ctrlPointList[2],
    tagLerpValue);
  let dataArray=generateLineListData(data);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
document.getElementById("btnDrawCtrlPoints").onclick=function(evt){
  let data = [];
  data.push(...ctrlPointList[0]);
  data.push(...ctrlPointList[1]);
  data.push(...ctrlPointList[2]);
  let dataArray=generateLineListData(data);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
document.getElementById("btnDrawCurve").onclick=function(evt){
  UpdateCurveData();
}
document.getElementById("sliderLerp").oninput=function(evt){
  tagLerpValue=this.value;
  UpdateCurveData();
  document.getElementById("labelLerpValue").innerHTML = this.value;
}
window.onload = function(){
  window.requestAnimationFrame(myRender);
  
}

範例的的執行結果如下
執行結果

在按下 DrawCtrlPoints 的按鍵後可以看到控制點線段,如下圖
控制點線段

在按下 DarwCurve 的按鍵後可以看到曲線( Curve ),如下圖
曲線( Curve )

 DarwCurve 按鍵有個可以調的數值,這個數值可以控制線段的精細度,各種精細度的比對如下
左上精細度為10,右上的精細度為8,左下的精細度為5,右下的精細度為3,可以看到精細度兌現對的影響程度

這次的範例是從 WebGL的繪製線段 的範例改來,所以只解說改的地方,在createDynamicBuffer() 的地方和上次不一樣,這次是用 size 來絕決定 Vertex buffer 的大小,主要是要給一個不容易爆的值,3000可以塞1000個點,如果不夠可以再改大。generateLineListData() 可以將頂點的資料轉成 WebGL 的 drawArrays() 所需要的頂點排列,其實內容很簡單,除了第一個頂點與最後一個頂點外都要複製一次。generateBezierCurve() 是負責產生二次貝茲曲線,輸入3個頂點與精細度後產生頂點,如果對二次貝茲曲線不熟可以參考
 [ Wiki ]貝茲曲線  。剩下就是因應這次的需求所增加的UI事件,由於期望每次改變精細度的時候能馬上反應,所以刻意留一個 updateCurveData() 可以馬上反應資料到畫面。

  本次的範例只是做為一個實驗場,由於對曲線( Curve )不是很熟,需要實驗場來輔助理解曲線( Curve ),下次會嘗試繪製三次貝茲曲線。

參考資料

[ Wiki ]貝茲曲線

相關文章 

WebGL的繪製線段

2019年10月7日 星期一

WebGL的繪製線段

WebGL的繪製線段

前言

  最近想整合曲線( Curve )到引擎,需要將曲線( Curve )繪製出來,曲線( Curve )要繪製出來就需要靠繪製大量的直線來實現,但想想自己只有在 OpenGL 與 Direct3D 繪製線段, WebGL 的繪製方法也一樣嗎?在此做個紀錄。

內容

  WebGL 繪製線段的方法和 OpenGL 差不多,有一點不太一樣,就是 WebGL 的繪製線段不能控制線段寬度,這裡提供一個繪製線段的範例,方便日後實驗繪製曲線( Curve )
HTML 的部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<canvas id="myCanvas1" width=400 height=300></canvas>
<br>
<input id="btnUpdate"type="button" value="Update"/>
</body>
</html>

Javascript 的部分
let canvas1 = document.getElementById('myCanvas1');
let glCTX1 = canvas1.getContext('webgl');
let vboPrimitiveCon = 0;
let vbo=createDynamicBuffer(glCTX1);
let shaderProg = createShader(glCTX1);
//
function createShader(glContext){
  let vertShader = glContext.createShader(glContext.VERTEX_SHADER);
  glContext.shaderSource(
    vertShader , 
    'attribute vec3 pos;void main(void){gl_Position=vec4(pos, 1.0);}'
  );
  glContext.compileShader(vertShader);
  let fragShader = glContext.createShader(glContext.FRAGMENT_SHADER);
  glContext.shaderSource(
    fragShader, 
    'void main(void){gl_FragColor=vec4(1,1,1,1);}'
  );
  glContext.compileShader(fragShader);
  let prog = glContext.createProgram();
  glContext.attachShader(prog, vertShader);
  glContext.attachShader(prog, fragShader);
  glContext.linkProgram(prog);
  glContext.useProgram(prog);  
  
  return prog;
}
function createDynamicBuffer(glContext){
  let vertexBuf = glContext.createBuffer();
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuf);
  let dataArray=new Float32Array([ 
       0.0, 0.5, 0.0,  
      -0.5,-0.5, 0.0,  
      -0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.0, 0.5, 0.0
    ]);
  glContext.bufferData(
    glContext.ARRAY_BUFFER, 
    dataArray, 
    glContext.DYNAMIC_DRAW
  );
  vboPrimitiveCon = dataArray.length / 3;
  return vertexBuf;
}
function simpleDraw(glContext){
  glContext.useProgram(shaderProg);
  //
  glContext.viewport(0,0,glContext.canvas.width,glContext.canvas.height);
  glContext.clearColor(0, 0, 1, 1);
  glContext.clear(glContext.COLOR_BUFFER_BIT);
  //
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vbo);
  let posLoc = glContext.getAttribLocation(shaderProg, "pos");
  glContext.vertexAttribPointer(posLoc, 3, glContext.FLOAT, false, 0, 0);
  glContext.enableVertexAttribArray(posLoc);

  glContext.drawArrays(glContext.LINES, 0, vboPrimitiveCon);
}

function myRender(){
  simpleDraw(glCTX1);
  //
  window.requestAnimationFrame(myRender);
}
document.getElementById("btnUpdate").onclick=function(evt){
  let dataArray=new Float32Array([ 
       0.0,-0.5, 0.0,  
      -0.5, 0.5, 0.0,  
      -0.5, 0.5, 0.0,
       0.5, 0.5, 0.0,
       0.5, 0.5, 0.0,
       0.0,-0.5, 0.0
  ]);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
window.onload = function(){
  window.requestAnimationFrame(myRender);
  
}


這次的範例從 解決多個 WebGLRenderContext 之間無法共享資源的問題 的範例修改而來,Vertex buffer 的部分採用動態的方式來建造,初始化時會給一個正三角的線段資料,畫面如下
範例執行畫面

範例有個 Update 的按鈕,按下去後會填充倒三角形的資料到 Vertex buffer ,畫面如下
更改Vertex buffer 的線段資料

這次的範例是個開端,之後會再推出繪製曲線( Curve )的範例。

相關文章

解決多個 WebGLRenderContext 之間無法共享資源的問題