2019年4月29日 星期一

用canvas2d繪製font texture的注意事項

用canvas2d繪製font texture的注意事項

前言

  用canvas2d繪製font可以輕鬆地找到範例,但繪製時有些需要注意的事,在此把過程做個紀錄。

內容

  由於WebGL本身不提供字型,所以需要產生font texture,產生的流程如下圖
產生font texture的流程

繪製字型是用canvas2d繪製後輸出到image,再來將imgae的資料輸出到WebGL的texture,之後WebGL再利用該texture來繪製font,整個流程不是很簡潔,但是我目前找到的方法中最好的。

  基本的繪製範例如下
let canvas=document.getElementById('myCanvas');
let ctx=canvas.getContext('2d');
canvas.width=300;
canvas.height=300;
let fontSize=36;
let str='|_jx/|';
ctx.fillStyle='#ff0000';
ctx.font=fontSize+'px sans-serif';
ctx.textBaseline='top';
for(let i=0,curOffset=0;i<str.length;i++){
  let width=ctx.measureText(str[i]).width;
  ctx.fillStyle='#ff0000ff';
  ctx.fillText(str[i],curOffset,0);

  curOffset+=width;
}

繪製的結果如下
範例繪製的結果

繪製的結果看起來沒問題,但事實上有問題,請看"_"與"j"連接的部分會連在一起,這對font texture而言可不是好事,如果WebGL在繪製"_"時會看到與"j"的部分,再想想如果將"_"改成"A",在繪製"A"時下方會多出"j"的一部分,這樣是無法讓人接受的。為什麼會發生這個問題呢?看看範例是如何知道該字的寬,"ctx.measureText()"就這個method,這是唯一可以得到字寬的方法,字高呢?font size有多高該字就多高,像"_"與"A"的高明顯不同,但只能把字型高的空間整個拿去畫。

   前面提到透過"ctx.measureText()"來取得字寬,但"j"這個字很特別,它的左側會會超出繪製的字寬,如下圖
"j"的字寬很特別

第一個"j"的左側會被截掉,是因為超出canvas的左側,第二個"j"就可以看到該字的左側會超出邊框,這就是問題了。這問題怎麼解決呢?目前我想到的是針對"j"來做特例處理,讓"j"的左側會多出一些空間來包住"j",具體的範例如下
let canvas=document.getElementById('myCanvas');
let ctx=canvas.getContext('2d');
canvas.width=300;
canvas.height=300;
let fontSize=36;
let str='jj|_jx/|';
let modWidth=10;
ctx.fillStyle='#ff0000';
ctx.font=fontSize+'px sans-serif';
ctx.textBaseline='top';
for(let i=0,curOffset=0;i<str.length;i++){
  let width=ctx.measureText(str[i]).width;
  if(str[i] === 'j')
    modWidth=5/72 *fontSize;
  else
    modWidth=0;
  ctx.fillStyle='#ff0000ff';
  ctx.fillText(str[i],curOffset+modWidth,0);
  ctx.strokeRect(curOffset+modWidth,0,width,fontSize);

  curOffset+=width+modWidth;
}

這個範例會對"j"這個字的左側增加"5/72"的寬度,這個寬度是我實驗的寬度,可以依據需要自己改,並且會考慮新增的寬度繪製新的邊框(邊框的寬度還是原本的寬度,但字跟字不會連在一起了),繪製的結果如下
修正"j"的寬度的繪製結果

參考資料

Drawing text

2019年4月22日 星期一

改進Vue.js的treeview

改進Vue.js的treeview

前言

  最近要為遊戲引擎製作物件的treeview,就想到之前的使用vue.js建構treeview,但沒想到實際使用後發現不太符合需求,之前的範例是把某個Node作root後將其繪製,但實際的需求是root的Node不只一個,所以要做一個改進,在此做個紀錄。

內容

  先來看改進後的html部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id='app'>
  <root_treenodeview :rootnode_list='nodelist'></root_treenodeview>
</div>
</body>
</html>

再來是javascript的部分
Vue.component('treenodeview',{
  props : ['node'],
  template : [
    '<ul>\n',
    '  <li>\n',
    '    <div @click="onClick">\n',
    '      {{node.name}}\n',
    '      <span v-show="isFolder">[{{isExpand?"-":"+"}}]</span>\n',
    '    </div>\n',
    '  </li>\n',
    '  <treenodeview v-show="isExpand" v-for="childNode in node.child" :node="childNode"></treenodeview>\n',
    '</ul>\n'
  ].join(''),
  data : function(){
    return {
      isExpand : false
    };
  },
  computed:{
    isFolder:function(){
      return this.node.child &&
              this.node.child.length;
    }
  },
  methods:{
    onClick:function(){
      if(this.isFolder){
        this.isExpand=!this.isExpand;
      }
    }
  }
});
Vue.component('root_treenodeview',{
  props : ['rootnode_list'],
  template:[
    '<div>\n',
    '  <treenodeview v-for="rootNode in rootnode_list" :node="rootNode"></treenodeview>\n',
    '</div>\n',
  ].join(''),
});
//
let node1={
  name:'node1',
  child:[
    {
      name : 'subNode1',
      child : []
    },
    {
      name : 'subNode1',
      child : []
    }
  ]
}
let node2={
  name:'node2',
  child:[
    {
      name : 'subNode1',
      child : []
    }
  ]
}
let app = new Vue({
  el : '#app',
  data:function(){
    return {
      nodelist:[node1,node2],
    }
  }
});

這次的程式碼風格跟上次有些不一樣,像之前的template是用html的方式來取值,這次是直接用字串陣列來輸入, 兩者的效果是一樣的,請根據喜好使用。在"treenodeview"的部分跟上次是一樣的,這次多了個"root_treenodeview",作用是將多個root顯示來符合本次的需求,程式碼的部分比"treenodeview"簡單許多,就只是依據"rootnode_list"的內容來產生對應的"treenodeview"。

  這次製作時卡了一陣子,一方面因為太久沒用Vue.js,所以還複習了一下,還有一個我以前沒發現的問題,那就是Vue.js的變數命名對"大寫"不友善,像是"props"的命名規則可以接受"_"與"-"但不接受大寫,而在Component的命名規則可以接受"_",但大寫與"-"都不被接受,在"data"的命名規則可以接受"_"與大寫,但不接受"-"。綜合3種狀況來看,"_"在Vue.js的接受度是最高的,3種狀況都能用,在Vue.js總是使用"_"來隔開字詞,這樣就可以不用管會不會違反命名規則了。

相關文章

使用vue.js建構treeview

2019年4月15日 星期一

關於Canvas的大小

關於Canvas的大小

前言

  Canvas的大小可以在MDN的Canvas 基本用途裡找到說明,但說明有點簡短,所以我就研究了一下,在此做個紀錄。

內容

  Canvas有兩種大小?是的,一個大小是繪製空間(Draw space)的大小另一個是顯示空間(Present space)的大小。繪製空間所代表的是畫布的空間,如CanvasRenderingContext2D.fillRect()、CanvasRenderingContext2D.fillText()與CanvasRenderingContext2D.drawImage()都是使用這個空間定址,顯示空間所表的是最後呈現在螢幕的大小,這兩個空間的關係是瀏覽器會將繪製空間縮放到顯示空間呈現在螢幕上,如下圖
繪製空間與顯示空間

圖中可以看到繪製空間的大小是用"canvas.width"與"canvas.height"來表達,具體的控制如下
let canvas=document.getElementById('myCanvas');
let ctx=canvas.getContext('2d');
ctx.canvas.width=300;
ctx.canvas.height=300;

這樣就可以控制繪製空間的大小。圖中的顯示空間的大小用"canvas.style.width"與"canvas.style.height"來表達,具體的控制如下
let canvas=document.getElementById('myCanvas');
let ctx=canvas.getContext('2d');
ctx.canvas.style.width='300px';
ctx.canvas.style.height='300px';

控制的方法其實就是控制CSS的屬性。如果要取得顯示空間的大小可以透過CSS取得,但畢竟該變數是字串不能直接變成"Number",所以要取得顯示空間大小可以透過"canvas.clientWidth"與"canvas.clientHeight"來取得,取得的範例如下
let canvas=document.getElementById('myCanvas');
let ctx=canvas.getContext('2d');
ctx.canvas.style.width='300px';
ctx.canvas.style.height='300px';
console.log('present space width:'+ctx.canvas.clientWidth);
console.log('present space height:'+ctx.canvas.clientHeight);

要注意的是這兩個數值只能"get"不能"set"。

參考資料

Canvas 基本用途

2019年4月8日 星期一

關於Function declaration和Function expression的差異

關於Function declaration和Function expression的差異

前言

  最近在youtube看到Top 10 JavaScript Interview Questions ( Part 2 ),才發現原來這有差別,在此做個紀錄。

內容

  Function expression的行為和我認知的行為一致,範例如下
funExpression();//ReferenceError: funExpression is not defined
let funExpression=function(){
  console.log('I am a function expression');
}
funExpression();//I am a function expression

再來看看Function declaration,範例如下
funDeclaration();//I am a function declaration
function funDeclaration(){
  console.log('I am a function declaration');
}
funDeclaration();//I am a function declaration

可以看到第一次喚起"funDeclaration"時遽然可以正常喚起!這表示Javascript其實會先把整段程式碼先解讀過才執行,而不是一行一行執行,如果是一行一行執行,在第一次喚起"funDeclaration"應該要和Function expression一樣認定該變數不存在。

  如果上網查詢Javascript會被歸類在"直譯",查了一下直譯器wiki,裡面解釋直譯為"把高階程式語言一行一行直接轉譯執行",而編譯是"編譯器已一次將所有原程式碼翻譯成另一種語言",而從Function declaration的行為來看,是將片段程式碼解讀後執行,這樣也算是"直譯"!?看來我必須對"直譯"的觀念該更新了。

參考資料

2019年4月1日 星期一

矩陣的排列順序(2)

矩陣的排列順序(2)

前言

  在前一篇矩陣的排列順序(1)中,提到了矩陣的排列順序,不過後來發現不是那麼正確,所以在寫一篇來補充。

內容

  在前一篇矩陣的排列順序(1)中有一張比較圖如下
矩陣排列順序比較
在上一篇得到的結論是用"右邊"的排列,但近日實驗了Direct3D、OpenGL、OpenGLES與WebGL後,當使用"右邊"的排列傳入後,乘法的順序如下
 WVPMatrix = WorldMatrix * ViewMatrix * ProjectionMatrix

這個順序會與關於Transform的矩陣乘法的順序一致,個人比較喜歡這個順序!

  在上一篇說過矩陣在ConstantBuffer與UBO的預設順序的規則,但事實上可以在Shader上來改變順序,在HLSL的改法如下
cbuffer cbTest
{
  float4x4 mat;//default is row_major 
  row_major float4x4 mat_row;
  column_major float4x4 mat_col;
};

在GLSL3以後改法如下
//Set all ubo default  
layout(column_major) uniform;

//
uniform uboTest
{
  mat4 mat;//default is column_major 
  layout(row_major) mat_row;
  layout(column_major) mat_col;
} 

如果在沒有UBO時透過glUniformMatrix4fv()的參數可以決定順序。

  雖說順序可以透過Shader來更改,但查了一下WebGL1.0時的uniformMatrix4fv()並不支援Transpose!但卻有該參數的欄位,這規格的設計非常奇怪,如果需要可以Transpose的話必須自己寫。

  最後來說說結論,從繪圖API送矩陣的順序會影響在寫Shader時的乘法順序,如果還要考慮到是否要Transpose的話,右邊的排序不需要做任何轉換,乘法順序與我在數學上的書籍看到的是一樣的,所以我個人較推薦右邊的順序。

參考資料

Interface Block (GLSL)

相關文章

矩陣的排列順序(1)
關於Transform的矩陣乘法