tag:blogger.com,1999:blog-25909518305365086642024-02-06T21:08:13.555-08:00Director kk.閒暇時隨手寫程式,各種筆記以及研究心得。Anonymoushttp://www.blogger.com/profile/10986289662729116266noreply@blogger.comBlogger2125tag:blogger.com,1999:blog-2590951830536508664.post-19395800565390194162015-11-12T04:03:00.001-08:002015-11-12T04:52:57.048-08:00GLSL & GLUT with C++入門篇 - 01<link href="https://googledrive.com/host/0B-CmqX-CUPZHTElWRGU4VlE5ZFU" rel="stylesheet" type="text/css"></link><script src="https://googledrive.com/host/0B-CmqX-CUPZHYzZTWjBjbnJlcWs" type="text/javascript"></script><body onload="prettyPrint();"><br />
這系列修改自我曾經發佈在電腦遊戲製作開發設計論壇的文章<a href="http://www.gamelife.idv.tw/viewtopic.php?t=2706">《GLSL&GLUT 從環境設定開始的基礎教學》</a>。<br />
<br />
當初寫那份教學至今已經過了兩年,我也讀了不少新東西,雖然裏頭的內容仍然可以用,但稍微也想更新一下裡面的資訊,就趁著機會順便玩玩blog和寫點HTML。<br />
<br />
我最近才知道google的blogger可以直接寫HTML和javascript,作為一個(自認)喜歡寫程式的人,真是無顏面對我自己(つд⊂)<br />
<br />
那麼一樣做點前情提要:<br />
<blockquote class="tr_bq">1. 這系列教學適用條件:具備C++基礎知識、能夠利用GLUT繪製簡易圖形、熟悉二維三維數學(國高中程度)、少許線性代數觀念。<br />
2. 雖然我使用的IDE是Visual Studio 2012,專案上我會盡量不使用IDE提供的管理,讓各個不同的IDE都能成功編譯。<br />
3. 如果你是跟我一樣用Visual Studio來編譯程式,最後出來的.exe執行檔若要拿到別台電腦上執行,需確認該台電腦有安裝跟你VS一樣版本的「可轉散發套件」。<br />
4. 對於視窗事件管理(滑鼠、鍵盤之類的),建議使用個人習慣的函式庫(Win32API、SDL之類的),在入門篇我都會使用GLUT的Callback函數。</blockquote><a name='more'></a><br />
再來是開始前的準備:<br />
<blockquote class="tr_bq">1. <a href="http://glew.sourceforge.net/">GLEW(GL Easy Extension library)</a>,點前面的超連結下載,這是要寫GLSL必備的函式庫,之後要實作的內容都需要他。<br />
2. <a href="https://rawvideoplayer.googlecode.com/files/glut-3.7.6-bin.zip">GLUT(The OpenGL Utility Toolkit)</a>,點前面的超連結下載,這是入門篇要用來當作視窗控制用的工具,因操作上容易,沒要寫大型程式的話這個比較方便。<br />
※備點:<a href="https://drive.google.com/open?id=0B-CmqX-CUPZHQVZEbnRsX2Z3SVU">GLEW</a>、<a href="https://drive.google.com/file/d/0B-CmqX-CUPZHM2p2TlNGUFNaR2M/view?usp=sharing">GLUT</a><br />
3. 函式庫的安裝不多介紹了,.h檔、.lib檔丟到IDE能抓到的地方(專案資料夾或以VS來說是在C:\Program Files (x86)\Microsoft Visual Studio 版本.0\VC\include還有lib),.dll放到專案資料夾裡(之後要跟著.exe執行檔跑)。</blockquote><br />
那麼環境準備好了,就先簡單介紹一下GLSL,他是OpenGL Shading Language的簡寫,我們要設計被稱作shader(著色器)的東西,簡言之就是去更直接的控制OpenGL對像素上色的語言,算是比較深入的圖學程式設計。<br />
<br />
曾經單用GLUT無法做出的效果,如normal mapping(法線貼圖)就需要利用自製的fragment shader才有辦法實作:<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmwSmxVjMFh2ey4jPBjEsioaNnjNLu7Rzffkbly2vRfbqSJh34Jr3QkmgXvj_vKjjsMt3wFV80TU_GBP4ghtU5La7HNcUjsUDDB7dMRBvxyVtqxppWSOmvEwRXX-ymLdsqrqxV_9ZaIOJJ/s1600/normal+mapping.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="417" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmwSmxVjMFh2ey4jPBjEsioaNnjNLu7Rzffkbly2vRfbqSJh34Jr3QkmgXvj_vKjjsMt3wFV80TU_GBP4ghtU5La7HNcUjsUDDB7dMRBvxyVtqxppWSOmvEwRXX-ymLdsqrqxV_9ZaIOJJ/s640/normal+mapping.jpg" width="640" /></a></div><br />
GLSL在一支程式裡的立場就是,額外設計程式去讓你的顯示卡跑,詳細的vertex shader和fragment shader(DirectX裡叫做pixel shader),往後再慢慢介紹,這裡就先從第一個實作開始。<br />
<br />
首先思考的是,如何在一支程式裡面再運作別的程式?<br />
<br />
其實底層的東西都已經被建置完了,我們只需要寫好程式碼,存成字串丟給OpenGL內的編譯器就行,許多教學都會把shader的程式碼存成別的文件,這邊也不例外。<br />
<br />
流程如下:<br />
<blockquote class="tr_bq">Step1: 把著色器的程式碼寫在兩個檔案(分vertex和fragment)<br />
Step2: 讀取兩個文件把它存進memory(char陣列)<br />
Step3: 經過官方文件內標示的特定流程compile他們<br />
Step4: 然後就可以開始用你剛剛寫的著色器程式來渲染你的面</blockquote><br />
<br />
首先會需要用到的前置有這些<br />
<blockquote class="tr_bq"><pre class=prettyprint style="-moz-tab-size:4; -o-tab-size:4; tab-size:4">#include <string>
#include <iostream>
#include <fstream>
#include <GL\glew.h>	// 依個人路徑include
#include <GL\glut.h>
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "glut32.lib")
</pre></blockquote><br />
<br />
那這就開始正題:<br />
<blockquote class="tr_bq"><h1>Step1</h1><br />
shader分為兩個部分,vertex還有fragment,簡單來說vertex shader負責輸出頂點座標,fragment shader負責輸出像素顏色,詳細之後會慢慢講,這裡先簡單記憶下就行。(或者可到NeHe的網站,裡面有較為詳盡的解說,<a href="http://nehe.gamedev.net/article/glsl_an_introduction/25007/">連結點我</a>)<br />
<br />
這邊寫個最簡單的vertex shader和fragment shader(檔名隨意,跟讀寫檔相同即可)。<br />
※先寫最舊版的glsl,下一章改作4.0,可與本章相比較<br />
<pre class=prettyprint style="-moz-tab-size:4; -o-tab-size:4; tab-size:4">// vertex.vs
void main()
{
	gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
</pre><pre class=prettyprint style="-moz-tab-size:4; -o-tab-size:4; tab-size:4">// fragment.frag
void main()
{
	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
</pre>gl_Position<br />
-> 是一個4維的向量(vector),就是整個環境你設置的每一個vertex的最終型態,他只能在vertex shader中存在。<br />
gl_FragColor<br />
-> 是一個4維的向量,就是你畫面中每一個pixel的最終型態,他只能出現在fragment shader。<br />
<br />
gl_ModelViewProjectionMatrix<br />
-> 是一個4*4的矩陣,代表把vertex轉換成model-view-projection position的矩陣(即立體空間的座標轉變成螢幕上的平面2D座標),他的值來自我們使用的glMatrixMode以及其他轉換矩陣函式。<br />
gl_Vertex<br />
-> 是一個4維的向量,代表每一個vertex,他的值來自我們使用的glVertex系列函式。<br />
<br />
※稱vector是向量可能不太好,畢竟不是全部的使用都會具有方向性,我也不能很好的說明,但我個人會認為把它看作1*4的矩陣可能會比較好理解。 <br />
<br />
這些在上面PO的那篇NeHe介紹GLSL的文章裡都有說到。 <br />
<br />
簡言之,無論OpenGL之後會對gl_Position和gl_FragColor做什麼,在最後算出來的gl_Position就會是你在畫面上面看到的每一個頂點。<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTkZ_qghmJ2jSBXMYMIM1HaveauOoQSy2PQSd0sW1jqZoY-wMTXf1R9LbHj7nQFby3qDWJ8CkxWLj9TPGm_sAD6zovL9PsTXlDl53I9j2IBOOcOVHPP90xjblZNA9JKKduncb-IjjPQ_Nv/s1600/01-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTkZ_qghmJ2jSBXMYMIM1HaveauOoQSy2PQSd0sW1jqZoY-wMTXf1R9LbHj7nQFby3qDWJ8CkxWLj9TPGm_sAD6zovL9PsTXlDl53I9j2IBOOcOVHPP90xjblZNA9JKKduncb-IjjPQ_Nv/s400/01-1.png" /></a></div><br />
而gl_FragColor就會是畫面上面每一個"pixel"的顏色,請注意不是每個頂點,是每個像素的顏色(這個和計算機圖學裡面的Graphic Rendering Pipeline裡的Rasterization有關,我這裡就不贅述了,有興趣可自行google)。 <br />
<br />
綜上所述,要理解那兩個子程式應該足夠簡單了。 <br />
vertex.vs裡面說的就是,我要把我的每個立體空間座標轉換成投影在螢幕上的點。 <br />
fragment.frag則是,把存在面的地方上的每個pixel都塗成紅色的。 <br />
</blockquote><br />
<br />
那麼再來的問題就是怎麼compile這兩個shader程式碼了。<br />
<blockquote class="tr_bq"><h1>Step2</h1><br />
首先我們需要一個能夠讀取文件的Function。<br />
<pre class=prettyprint style="-moz-tab-size:4; -o-tab-size:4; tab-size:4">void loadFile(const char* filename, std::string &string)
{
	std::ifstream fp(filename);
	if(!fp.is_open()){
		std::cout << "Open <" << filename << "> error." << std::endl;
		return;
	}
	char temp[300];
	while(!fp.eof()){
		fp.getline(temp, 300);
		string += temp;
		string += '\n';
	}
	fp.close();
}
</pre><br />
很基礎的C++讀寫檔應該不用多介紹,就是把檔案存進引數的string裡。<br />
</blockquote><br />
然後就要把這段引進的程式碼跟OpenGL的關聯函式做連結了! <br />
<blockquote class="tr_bq"><h1>Step3-1</h1><pre class=prettyprint style="-moz-tab-size:4; -o-tab-size:4; tab-size:4">unsigned int loadShader(std::string &source, GLenum type)
{
	// 告訴OpenGL我們要創的是哪種shader
	unsigned int ShaderID;
	ShaderID = glCreateShader(type);
	// 把std::string結構轉換成const char*
	const char* csource = source.c_str();
	// 把程式碼放進去剛剛創建的shader object中
	glShaderSource(ShaderID, 1, &csource, NULL);
	// 編譯shader
	glCompileShader(ShaderID);
	// 這是編譯過程的訊息, 錯誤什麼的把他丟到error裡面
	char error[1000] = "";
	glGetShaderInfoLog(ShaderID, 1000, NULL, error);
	// 然後輸出出來
	std::cout << "Complie status: \n" << error << std::endl;
	return ShaderID;
}
</pre>glCreateShader(type)<br />
首先我們要告訴OpenGL我們現在要compile的是vertex shader還是fragment shader。<br />
type通常是放GL_VERTEX_SHADER或是GL_FRAGMENT_SHADER。<br />
也有更多其他功能的type,可參照<a href="http://www.opengl.org/sdk/docs/man/xhtml/glCreateShader.xml">官方文件點我</a>。<br />
<br />
glShaderSource(shader_id, how_many_string_array, string_array, string_array_length);<br />
再來我們要把剛剛載入的程式碼送進去OpenGL的shader object裡面。<br />
要注意,一份source code一般就放在一個string,string array是說有很多份source code。<br />
第一個是你的shader的handle或是代號,就類似glut裡的window number那樣的東西。<br />
第二個how_many_string_array,不是說有幾行程式碼,像我們一個shader才用一份程式碼就寫1。<br />
第三個string_array結構是const char**,結構是字串陣列,所以只有一個字串的話就要記得加上&。<br />
第四個也是要用string array才會用上的,一般寫NULL就好。<br />
<br />
glCompileShader(shader_id);<br />
用跟glShaderSource一樣的ID就可以compile那個shader。<br />
<br />
glGetShaderInfoLog<br />
如註解不贅述,可用可不用。<br />
<br />
<br />
<h1>Step3-2</h1><br />
再來要做的是,把vertex shader和fragment shader連結,做成program。<br />
<pre class=prettyprint style="-moz-tab-size:4; -o-tab-size:4; tab-size:4">// 用來儲存shader還有program的id
unsigned int vs, fs, program;
void initShader(const char* vname, const char* fname)
{
	std::string source;
	// 把程式碼讀進source
	loadFile(vname, source);
	// 編譯shader並且把id傳回vs
	vs = loadShader(source, GL_VERTEX_SHADER);
	source = "";
	loadFile(fname, source);
	fs = loadShader(source, GL_FRAGMENT_SHADER);
	// 創建一個program
	program = glCreateProgram();
	// 把vertex shader跟program連結上
	glAttachShader(program, vs);
	// 把fragment shader跟program連結上
	glAttachShader(program, fs);
	// 根據被連結上的shader, link出各種processor
	glLinkProgram(program);
	// 然後使用它
	glUseProgram(program);
}
</pre><br />
glCreateProgram(); <br />
創建一個program,算是乘載著各種shader的東西。 <br />
<br />
glAttachShader(program_id, shader_id); <br />
把你compile好的shader連上program,可以連上複數個shader。 <br />
<br />
glLinkProgram(program_id); <br />
各種shader對應的processor可參照<a href="https://www.opengl.org/sdk/docs/man/html/glLinkProgram.xhtml">官方文件點我</a><br />
<br />
glUseProgram(program_id); <br />
使用這個program。<br />
</blockquote><br />
那這樣子GLSL裡面shader的基礎再基礎的建置介紹大概就這樣完成了,依照Step1寫的fragment shader,在這個程式裡不管畫什麼都會是紅色的。 <br />
<br />
之後若想要設定不同點不同顏色,或是貼材質,製作光影等等各種更高階的運用,就要正式介紹shader的程式設計,這在之後幾篇會陸續介紹。<br />
<br />
網路上很多範例的shader程式碼,雖然都是外文的,但不算太難,附上一個我覺得很不錯的youtube教程(雖然他唸英文很快),他講得都很詳細而且會順便連數學都一起教你XD<br />
<iframe width="600" height="450" src="https://www.youtube.com/embed/pE9ZDNcg3kw" frameborder="0" allowfullscreen></iframe><br />
<br />
<br />
附上本篇<a href="https://drive.google.com/open?id=0B-CmqX-CUPZHNWFtVXkzVWVuVGc">程式範例碼(點我下載)</a>,沒意外的話會輸出Step1第三張圖的那個程式。<br />
<br />
只是要再創建兩個檔案命名為vertex.vs和fragment.frag,內容如Step1,然後放在跟主程式相同目錄就能運行了。 <br />
<pre class=prettyprint style="-moz-tab-size:4; -o-tab-size:4; tab-size:4">/* * * * * * * * * * * * * * * * * * *
* 2015/11/12	Author: Director. kk
* glut: ver 3.7.6
* glew: ver 1.13.0
*
*/
#include <string>
#include <iostream>
#include <fstream>
#include <GL\glew.h>	// 依個人路徑include
#include <GL\glut.h>
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "glut32.lib")
void loadFile(const char* filename, std::string &string)
{
	std::ifstream fp(filename);
	if(!fp.is_open()){
		std::cout << "Open <" << filename << "> error." << std::endl;
		return;
	}
	char temp[300];
	while(!fp.eof()){
		fp.getline(temp, 300);
		string += temp;
		string += '\n';
	}
	fp.close();
}
unsigned int loadShader(std::string &source, GLenum type)
{
	// 告訴OpenGL我們要創的是哪種shader
	unsigned int ShaderID;
	ShaderID = glCreateShader(type);
	// 把std::string結構轉換成const char*
	const char* csource = source.c_str();
	// 把程式碼放進去剛剛創建的shader object中
	glShaderSource(ShaderID, 1, &csource, NULL);
	// 編譯shader
	glCompileShader(ShaderID);
	// 這是編譯過程的訊息, 錯誤什麼的把他丟到error裡面
	char error[1000] = "";
	glGetShaderInfoLog(ShaderID, 1000, NULL, error);
	// 然後輸出出來
	std::cout << "Complie status: \n" << error << std::endl;
	return ShaderID;
}
// 用來儲存shader還有program的id
unsigned int vs, fs, program;
void initShader(const char* vname, const char* fname)
{
	std::string source;
	// 把程式碼讀進source
	loadFile(vname, source);
	// 編譯shader並且把id傳回vs
	vs = loadShader(source, GL_VERTEX_SHADER);
	source = "";
	loadFile(fname, source);
	fs = loadShader(source, GL_FRAGMENT_SHADER);
	// 創建一個program
	program = glCreateProgram();
	// 把vertex shader跟program連結上
	glAttachShader(program, vs);
	// 把fragment shader跟program連結上
	glAttachShader(program, fs);
	// 根據被連結上的shader, link出各種processor
	glLinkProgram(program);
	// 然後使用它
	glUseProgram(program);
}
// 回收資源
void clean()
{
	glDetachShader(program, vs);
	glDetachShader(program, fs);
	glDeleteShader(vs);
	glDeleteShader(fs);
	glDeleteProgram(program);
}
void glRenderScene()
{
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glViewport(0, 0, 400, 400);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0, 400/400.0, 0.1, 1000);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(20, 20, -20, 0, 0, 0, 0, 1, 0);
	glBegin(GL_QUADS);
	glVertex3f(10, 0, -10);
	glVertex3f(-10, 0, -10);
	glVertex3f(-10, 0, 10);
	glVertex3f(10, 0, 10);
	glEnd();
	glutSwapBuffers();
}
int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);
	glutInitWindowSize(400, 400);
	glutInitWindowPosition(0, 0);
	glutCreateWindow("glsl lesson 01");
	glewInit();
	glutDisplayFunc(glRenderScene);
	initShader("vertex.vs", "fragment.frag");
	glutMainLoop();
	clean();
	return 0;
}
</pre>
</body>Anonymoushttp://www.blogger.com/profile/10986289662729116266noreply@blogger.com2tag:blogger.com,1999:blog-2590951830536508664.post-12247199027159667982015-11-06T19:00:00.003-08:002015-11-09T23:35:26.856-08:00程式碼轉HTML版型<html>
<script>
function codeStyle() {
var text = document.getElementById('input').value;
var text = convert(text);
var lines = text.split("<br/>");
var textOut = "<pre style='-moz-tab-size:4; -o-tab-size:4; tab-size:4'><table width='100%' style='border-spacing:0px; table-layout: fixed; word-wrap:break-word;'>";
var i=0;
for(i=0 ; i<lines.length-1 ; i++){
if(i%2 == 0){
textOut += "<tr style='background:#EEEEEE'>";
} else {
textOut += "<tr style='background:#DDDDDD'>";
}
textOut += "<td style='width:40px; text-align:right; font-size:10px; font-style:italic;'>";
textOut += i+1;
textOut += "</td>"
textOut += "<td style='width:10px'></td>";
textOut += "<td style='font-family: Consolas, Courier, sans-serif; font-size:12px;'>";
textOut += lines[i];
textOut += "</td>";
textOut += "</tr>";
}
textOut += "</table></pre>";
return textOut;
}
var textComment = false;
var textCommentEnd = false;
function convert(codeData)
{
// from: http://blog.xuite.net/givemepassxd/blog/40467229-%E5%B0%87%E7%A8%8B%E5%BC%8F%E7%A2%BC%E8%BD%89%E6%88%90%E7%B6%B2%E9%A0%81%E6%A0%BC%E5%BC%8F
var str = codeData;
var lines = str.split("\n");
var result = "";
for(var i = 0; i < lines.length; i++){
var codePart = lines[i];
var commentPart = "";
if((cmPos = lines[i].indexOf("/*")) >= 0){
var cmEndPos = lines[i].indexOf("*/");
if(cmEndPos >= 0){
codePart = lines[i].substring(0, cmPos);
commentPart += "<span style='color:#7F9F7F; font-style:italic;'>" + lines[i].substring(cmPos, cmEndPos+2) + "</span>";
} else {
codePart = lines[i].substring(0, cmPos);
commentPart += "<span style='color:#7F9F7F; font-style:italic;'>" + lines[i].substring(cmPos) + "</span>";
textComment = true;
}
}
else if((cmPos = lines[i].indexOf("*/")) >= 0){
commentPart += "<span style='color:#7F9F7F; font-style:italic;'>" + lines[i].substring(0, cmPos+2) + "</span>";
codePart = lines[i].substring(cmPos+2);
textComment = false;
textCommentEnd = true;
}
else if((cmPos = lines[i].indexOf("//")) >= 0){
codePart = lines[i].substring(0, cmPos);
commentPart = "<span style='color:#7F9F7F; font-style:italic;'>" + lines[i].substring(cmPos) + "</span>";
}
codePart = codePart.replace(/&/ig, "&");
//codePart = codePart.replace(/ /ig, "	");
codePart = codePart.replace(/</ig, "<");
codePart = codePart.replace(/>/ig, ">");
codePart = codePart.replace(/ /ig, " ");
//codePart = codePart.replace(/\(/ig, '(</span>');
//codePart = codePart.replace(/\)/ig, ')</span>');
codePart = codePart.replace(/\r\n/ig, "<br/>");
if(textComment){
codePart = "<span style='color:#7F9F7F; font-style:italic;'>" + codePart + "</span>";
}
if(textCommentEnd){
result += commentPart + codePart + "<br/>";
textCommentEnd = false;
} else {
result += codePart + commentPart + "<br/>";
}
}
return result;
}
function getHTML() {
var result = codeStyle();
document.getElementById('output').value = result ;
}
function showHTML(id) {
document.getElementById(id).innerHTML= document.getElementById('output').value;
}
var stateTextarea = 1;
function changeStateText(state) {
stateTextarea = state;
}
function clearTextarea(id) {
if(stateTextarea == 1){
document.getElementById(id).value = "";
}
}
function initTextarea(id, str){
if(stateTextarea == 1)
document.getElementById(id).value = str;
}
</script>
<body onload="prettyPrint()">
<div>
為了方便以後將程式碼貼在blog<br />
簡單弄了一個這個部落格風格的程式碼HTML轉換器<br />
只先實作一些版型的部分<br />
語法保留字的顏色有空再慢慢弄<br />
應該還堪用(´・ω・`)<br />
<br />
趁當兵前看能寫多少東西上來<br />
基本上主要會是寫OpenGL和DirectX相關的<br />
大學時在做這方面的個人研究相當苦手於沒有中文教學<br />
花費不少精力才找到足夠的資源學習Orz<br />
在此留下過去學習的一些經驗<br />
讓有需要的人可做參考<br />
<br />
</div>
<a name='more'></a><br />
<textarea cols="80" id="input"
onblur="initTextarea('input','待轉換的一般程式碼')"
onfocus="clearTextarea('input')" rows="10"
onchange="changeStateText(2)" style="resize: none;">待轉換的一般程式碼</textarea><br />
<input onclick="getHTML()" type="button" value="轉換" /><br /><br />
<textarea cols="80" id="output" onkeydown="javascript:if(window.event.keyCode==8) return false;" readonly="" rows="10" style="resize: none;">轉換完成的HTML代碼</textarea><br />
<input onclick="showHTML('demo')" type="button" value="預覽" /><br /><br /><br />
<div>
<h1>
Result</h1>
</div>
<div id="demo">
</div>
</body>
</html>Anonymoushttp://www.blogger.com/profile/10986289662729116266noreply@blogger.com0