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 ),下次會嘗試繪製三次貝茲曲線。




沒有留言:
張貼留言