æ¬ææ¢è®¨å¦ä½å¨ WebGL 项ç®ä¸è·åæ°æ®ï¼å¹¶å°å ¶æå½±å°éå½ç空é´ä»¥å¨å±å¹ä¸æ¾ç¤ºãå®åå®äºä½ å ·å¤ç¨äºå¹³ç§»ï¼ç¼©æ¾åæè½¬çåºæ¬ç©éµæ°å¦ç¥è¯ãå®è§£éäºç»æ 3D åºæ¯æ¶é常使ç¨çä¸ä¸ªæ ¸å¿ç©éµï¼æ¨¡åï¼è§å¾åæå½±ç©éµã
夿³¨ï¼ æ¬æè¿å¯ä½ä¸º MDN å
容å¥ä»¶ æä¾ãå®è¿ä½¿ç¨ MDN
å
¨å±å¯¹è±¡ä¸å¯ç¨ç å®ç¨å½æ° éåã
WebGL 空é´ä¸çç¹åå¤è¾¹å½¢ç个ä½è½¬åç±åºæ¬ç转æ¢ç©éµï¼ä¾å¦å¹³ç§»ï¼ç¼©æ¾åæè½¬ï¼å¤çãå¯ä»¥å°è¿äºç©éµç»åå¨ä¸èµ·å¹¶ä»¥ç¹æ®æ¹å¼åç»ï¼ä»¥ä½¿å ¶ç¨äºæ¸²æå¤æç 3D åºæ¯ãè¿äºç»åæçç©éµæç»å°åå§æ°æ®ç±»åç§»å¨å°ä¸ä¸ªç§°ä¸ºè£åªç©ºé´çç¹æ®åæ 空é´ä¸ãè¿æ¯ä¸ä¸ªä¸å¿ç¹ä½äº (0, 0, 0)ï¼è§è½èå´å¨ (-1, -1, -1) å° (1, 1, 1) ä¹é´ï¼2 个åä½å®½çç«æ¹ä½ã该åªè£ç©ºé´è¢«å缩å°ä¸ä¸ªäºç»´ç©ºé´å¹¶æ æ ¼å为å¾åã
ä¸é¢è®¨è®ºç第ä¸ä¸ªç©éµæ¯æ¨¡åç©éµï¼å®å®ä¹äºå¦ä½è·ååå§æ¨¡åæ°æ®å¹¶å°å ¶å¨ 3D ä¸ç空é´ä¸ç§»å¨ãæå½±ç©éµç¨äºå°ä¸ç空é´åæ 转æ¢ä¸ºåªè£ç©ºé´åæ ã常ç¨çæå½±ç©éµï¼éè§ç©éµï¼ç¨äºæ¨¡æå å½ 3D èæä¸çä¸è§çè çæ¿èº«çå ¸åç¸æºçææãè§å¾ç©éµè´è´£ç§»å¨åºæ¯ä¸ç对象以模æç¸æºä½ç½®çååï¼æ¹åè§å¯è å½åè½å¤çå°çå 容ã
以ä¸çå 个é¨åæä¾äºå¯¹æ¨¡åï¼è§å¾åæå½±ç©éµèåçææ³åå®ç°çæ·±å ¥çè§£ãè¿äºç©éµæ¯å¨å±å¹ä¸ç§»å¨æ°æ®çæ ¸å¿ï¼æ¯èè¿åä¸ªæ¡æ¶åå¼æçæ¦å¿µã
è£åªç©ºé´å¨ WebGL ç¨åºä¸ï¼æ°æ®é常ä¸ä¼ å°å ·æèªå·±çåæ ç³»ç»ç GPU ä¸ï¼ç¶åé¡¶ç¹çè²å¨å°è¿äºç¹è½¬æ¢å°ä¸ä¸ªç§°ä¸ºè£åªç©ºé´çç¹æ®åæ ç³»ä¸ãå»¶å±å°è£åªç©ºé´ä¹å¤ç任使°æ®é½ä¼è¢«åªè£å¹¶ä¸ä¸ä¼è¢«æ¸²æã妿ä¸ä¸ªä¸è§å½¢è¶ åºäºè¯¥ç©ºé´çè¾¹çï¼åå°å ¶è£åææ°çä¸è§å½¢ï¼å¹¶ä¸ä» ä¿çæ°ä¸è§å½¢å¨è£åªç©ºé´ä¸çé¨åã
ä¸é¢çå¾åè£åªç©ºé´çå¯è§åï¼ææç¹é½å¿ 须被å å«å¨å ¶ä¸ã宿¯ä¸ä¸ªè§å¨ (-1, -1, -1)ï¼å¯¹è§å¨ (1, 1, 1)ï¼ä¸å¿ç¹å¨ (0, 0, 0) çæ¯è¾¹ 2 个åä½çç«æ¹ä½ãè£åªç©ºé´ä½¿ç¨çè¿ä¸ªä¸¤ä¸ªç«æ¹ç±³åæ 系称为å½ä¸å设å¤åæ ï¼NDCï¼ãå¨ç ç©¶åä½¿ç¨ WebGL ä»£ç æ¶ï¼ä½ å¯è½æ¶ä¸æ¶çä¼ä½¿ç¨è¿ä¸ªæ¯è¯ã
卿¬èä¸ï¼æä»¬å°ç´æ¥å°æ°æ®æ¾å ¥è£åªç©ºé´åæ ç³»ä¸ãé常使ç¨ä½äºä»»æåæ ç³»ä¸çæ¨¡åæ°æ®ï¼ç¶å使ç¨ç©éµè¿è¡è½¬æ¢ï¼å°æ¨¡ååæ è½¬æ¢ä¸ºè£åªç©ºé´ç³»ä¸çåæ ãè¿ä¸ªä¾åï¼éè¿ç®åå°ä½¿ç¨ä» (-1ï¼-1ï¼-1) å° (1,1,1) çæ¨¡ååæ å¼æ¥è¯´æåªè¾ç©ºé´ç工使¹å¼æ¯æç®åçãä¸é¢ç代ç å°å建 2 个ä¸è§å½¢ï¼è¿äºä¸è§å½¢å°å¨å±å¹ä¸ç»å¶ä¸ä¸ªæ£æ¹å½¢ãæ£æ¹å½¢ä¸ç Z 深度确å®å½åæ£æ¹å½¢å ±äº«åä¸ä¸ªç©ºé´æ¶å¨é¡¶é¨ç»å¶çå 容ï¼è¾å°ç Z å¼å°åç°å¨è¾å¤§ç Z å¼ä¹ä¸ã
WebGLBox ä¾åæ¬ç¤ºä¾å°å建ä¸ä¸ªèªå®ä¹ WebGL 对象ï¼è¯¥å¯¹è±¡å°å¨å±å¹ä¸ç»å¶ä¸ä¸ª 2D æ¡ã
夿³¨ï¼ æ¯ä¸ä¸ª WebGL 示ä¾ä»£ç 卿¤ github repo ä¸å¯æ¾å°ï¼å¹¶æç« èç»ç»ãæ¤å¤ï¼æ¯ä¸ªç« èåºé¨é½æä¸ä¸ª JSFiddle 龿¥ã
WebGLBox Constructoræé 彿°çèµ·æ¥åè¿æ ·ï¼
function WebGLBox() {
// 设置 canvas å WebGL ä¸ä¸æ
this.canvas = document.getElementById("canvas");
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
this.gl = MDN.createContext(canvas);
var gl = this.gl;
// 设置ä¸ä¸ª WebGL ç¨åºï¼ä»»ä½ MDN 对象ç¸å
³çé¨å卿¬æä¹å¤å®ä¹
this.webglProgram = MDN.createWebGLProgramFromIds(
gl,
"vertex-shader",
"fragment-shader",
);
gl.useProgram(this.webglProgram);
// ä¿å attribute å uniform ä½ç½®
this.positionLocation = gl.getAttribLocation(this.webglProgram, "position");
this.colorLocation = gl.getUniformLocation(this.webglProgram, "color");
// åè¯ WebGL å¨ç»å¶æ¶æµè¯æ·±åº¦ï¼æä»¥å¦æä¸ä¸ªæ£æ¹å½¢å颿å¦ä¸ä¸ªæ£æ¹å½¢
// å¦ä¸ä¸ªæ£æ¹å½¢ä¸ä¼è¢«ç»å¶
gl.enable(gl.DEPTH_TEST);
}
WebGLBox ç»å¶
ç°å¨ï¼æä»¬å°å建ä¸ä¸ªå¨å±å¹ä¸ç»å¶æ¡çæ¹æ³ã
WebGLBox.prototype.draw = function (settings) {
// å建ä¸ä¸ attribute æ°æ®; è¿äºæ¯æç»ç»å¶å°å±å¹ä¸çä¸è§å½¢
// æä¸¤ä¸ªå½¢æä¸ä¸ªæ£æ¹å½¢
var data = new Float32Array([
//Triangle 1
settings.left,
settings.bottom,
settings.depth,
settings.right,
settings.bottom,
settings.depth,
settings.left,
settings.top,
settings.depth,
//Triangle 2
settings.left,
settings.top,
settings.depth,
settings.right,
settings.bottom,
settings.depth,
settings.right,
settings.top,
settings.depth,
]);
// ä½¿ç¨ WebGL å°å
¶ç»å¶å°å±å¹ä¸
// æ§è½è¦ç¹ï¼ä¸ºæ¯ä¸ªç»å¶å建æ°çç¼å²å¨å¾æ
¢
// è¿ä¸ªæ¹æ³ä»
ç¨äºè¯´æ
var gl = this.gl;
// å建ä¸ä¸ªç¼å²åºå¹¶ç»å®æ°æ®
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
// 设置æå attribute æ°æ®çæéï¼ä¸è§å½¢ï¼
gl.enableVertexAttribArray(this.positionLocation);
gl.vertexAttribPointer(this.positionLocation, 3, gl.FLOAT, false, 0, 0);
// 设置å°å¨ææä¸è§å½¢ä¹é´å
±äº«ç color uniform
gl.uniform4fv(this.colorLocation, settings.color);
// å¨å±å¹ä¸ç»å¶è¯¥ä¸è§å½¢
gl.drawArrays(gl.TRIANGLES, 0, 6);
};
çè²å¨æ¯ç¨ GLSL ç¼åç代ç çæ®µï¼å®æ¥æ¶æä»¬çç¹æ°æ®å¹¶æç»å°å®ä»¬æ¸²æå°å±å¹ä¸ãä¸ºäºæ¹ä¾¿èµ·è§ï¼è¿äºçè²å¨åå¨å¨ <script>
å
ç´ ä¹ä¸ï¼è¯¥å
ç´ éè¿èªå®ä¹å½æ° MDN.createWebGLProgramFromIds()
å¼å
¥ç¨åºä¸ãè¿ä¸ªæ¹æ³æ¯ä¸ºè¿äºæç¨ç¼åç å®ç¨å½æ° éåçä¸é¨åï¼æ¤å¤ä¸åèµè¿°ãæ¤å½æ°ç¨äºå¤çè·åä¸äº GLSL æºä»£ç å¹¶å°å
¶ç¼è¯ä¸º WebGL ç¨åºçåºç¡æä½ãè¯¥å½æ°å
·æä¸ä¸ªåæ° - ç¨äºæ¸²æç¨åºçä¸ä¸æï¼å
å«é¡¶ç¹çè²å¨ç <script>
å
ç´ ç ID åå
å«ç段çè²å¨ç <script>
å
ç´ ç IDãé¡¶ç¹çè²å¨æ¾ç½®é¡¶ç¹ï¼ç段çè²å¨ä¸ºæ¯ä¸ªåç´ çè²ã
é¦å çä¸ä¸å°å¨å±å¹ä¸ç§»å¨é¡¶ç¹çé¡¶ç¹çè²å¨ï¼
// ä¸ä¸ªé¡¶ç¹ä½ç½®
attribute vec3 position;
void main() {
// gl_Position æ¯é¡¶ç¹çè²å¨å¯¹å
¶ä¿®æ¹åå¨è£åªç©ºé´çæç»ä½ç½®
gl_Position = vec4(position, 1.0);
}
æ¥ä¸æ¥ï¼è¦å®é å°æ°æ®æ æ ¼å为åç´ ï¼ç段çè²å¨å°å¨æ¯ä¸ªåç´ çåºç¡ä¸è®¡ç®è¯ä¼°ä¸åï¼è®¾ç½®ä¸ä¸ªåä¸é¢è²ãGPU 为éè¦æ¸²æçæ¯ä¸ªåç´ è°ç¨çè²å¨æ¹æ³ãçè²å¨ç工使¯è¿åè¦ç¨äºè¯¥åç´ çé¢è²ã
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
æäºè¿äºè®¾ç½®ï¼æ¯æ¶å使ç¨è£åªç©ºé´åæ ç´æ¥ç»å¶å°å±å¹äºã
var box = new WebGLBox();
é¦å å¨ä¸é´ç»ä¸ä¸ªçº¢è²æ¡ã
box.draw({
top: 0.5, // x
bottom: -0.5, // x
left: -0.5, // y
right: 0.5, // y
depth: 0, // z
color: [1, 0.4, 0.4, 1], // red
});
æ¥ä¸æ¥ï¼å¨ä¸é¢ççº¢è²æ¡çåé¢ç»å¶ä¸ä¸ªç»¿è²æ¡ã
box.draw({
top: 0.9, // x
bottom: 0, // x
left: -0.9, // y
right: 0.9, // y
depth: 0.5, // z
color: [0.4, 1, 0.4, 1], // green
});
æåï¼ä¸ºäºæ¼ç¤ºè£åªå®é ä¸åçäºï¼è¿ä¸ªæ¡æ²¡æè¢«ç»å¶ï¼å 为å®å®å ¨å¨è£åªç©ºé´ä¹å¤ï¼æ·±åº¦è¶ åº -1.0 å° 1.0 çèå´ã
box.draw({
top: 1, // x
bottom: -1, // x
left: -1, // y
right: 1, // y
depth: -1.5, // z
color: [0.4, 0.4, 1, 1], // blue
});
ç»æ
ç»ä¹
å¨è¿ä¸ç¹ä¸ä¸ä¸ªæç¨çç»ä¹ æ¯éè¿æ´æ¹ä»£ç æ¥ä½¿æ¡å¨è£åªç©ºé´ä¸ç§»å¨ï¼æåç¹æ¯å¦ä½å¨è£åªç©ºé´ä¸è¢«åªååç§»å¨çãå°è¯ç»ä¸å¼ æèæ¯çæ¹å½¢ç¬è¸ã
齿¬¡åæ ä¹åçè£åªç©ºé´é¡¶ç¹çè²å¨ä¸»è¦å å«ä»¥ä¸ä»£ç ï¼
gl_Position = vec4(position, 1.0);
ä½ç½®åéæ¯å¨ draw()
æ¹æ³ä¸å®ä¹çï¼å¹¶ä½ä¸º attribute ä¼ éç»çè²å¨ãè¿æ¯ä¸ä¸ªä¸ç»´ç¹ï¼ä½æç»éè¿ç®¡çº¿ä¼ éç gl_Position
åéå®é
䏿¯åç»´ç - æ¯ (x,y,z,w)
è䏿¯ (x,y,z)
ã z
å颿²¡æåæ¯äºï¼å æ¤ä¹ æ¯ä¸å°ç¬¬åç»´æ 记为 w
ãå¨ä¸é¢ç示ä¾ä¸ï¼ w
åæ è®¾ç½®ä¸º 1.0ã
æ¾èæè§çé®é¢æ¯ï¼â为ä»ä¹è¦å¢å 维度ï¼âãäºå®è¯æï¼è¿ç§å¢å å
许使ç¨è®¸å¤ä¸éçææ¯æ¥å¤ç 3D æ°æ®ãè¿ä¸ªå¢å ç维度å°éè§çæ¦å¿µå¼å
¥åæ ç³»ä¸ãå°å
¶æ¾ç½®å¨éå½çä½ç½®åï¼æä»¬å¯ä»¥å° 3D åæ æ å°å° 2D 空é´ä¸ï¼ä»èå
许两æ¡å¹³è¡çº¿å½å®ä»¬å»¶ä¼¸å°è¿æ¹æ¶ç¸äº¤ã w
çå¼è¢«ç¨ä½è¯¥åæ çå
¶ä»å鿾餿°ï¼å æ¤ x
, y
å z
ççå®å¼è¢«è®¡ç®ä¸º x/w
, y/w
å z/w
ï¼ç¶å w
ä¹ w/w
, åæ 1ï¼ã
ä¸ç»´ç¹å®ä¹å¨å ¸åçç¬å¡å°åæ ç³»ä¸ãå¢å ç第åç»´å°è¿ä¸ç¹åä¸ºé½æ¬¡åæ ãå®ä»ç¶ä»£è¡¨ 3D 空é´ä¸çä¸ä¸ªç¹ï¼å¹¶ä¸å¯ä»¥éè¿ä¸å¯¹ç®åç彿°è½»æ¾å°æ¼ç¤ºå¦ä½æé è¿ç§ç±»åçåæ ã
function cartesianToHomogeneous(point) {
var x = point[0];
var y = point[1];
var z = point[2];
return [x, y, z, 1];
}
function homogeneousToCartesian(point) {
var x = point[0];
var y = point[1];
var z = point[2];
var w = point[3];
return [x / w, y / w, z / w];
}
æ£å¦å颿å°çåä¸é¢å±ç¤ºç彿°ï¼w åéå°å x, y å z ç¸é¤ãå½ w åé为éé¶å®æ°æ¶ï¼é½æ¬¡åæ å¾å®¹æè½¬æ¢åç¬å¡å°ç©ºé´ä¸ãç°å¨ï¼å¦æ w åé为é¶ä¼åçä»ä¹ï¼å¨ JavaScript ä¸ï¼è¿åå¼å¦ä¸ï¼
homogeneousToCartesian([10, 4, 5, 0]);
计ç®ç»æä¸ºï¼ [Infinity, Infinity, Infinity]
.
è¯¥é½æ¬¡åæ 表示æ 穷大çæä¸ªç¹ãè¿æ¯ä¸ç§æ¹ä¾¿çæ¹å¼è¡¨ç¤ºä»åç¹åç¹å®æ¹ååå°çå°çº¿ãé¤äºå°çº¿ï¼è¿å¯ä»¥å°å ¶è§ä¸ºæ¹åç¢éç表示ã妿尿¤é½æ¬¡åæ å带æå¹³ç§»çç©éµç¸ä¹ï¼å该平移å°è¢«ææå°æ¶å»äºã
å½è®¡ç®æºä¸çæ°åéå¸¸å¤§ï¼æé常å°ï¼æ¶ï¼å®ä»¬ç精确度å°è¶æ¥è¶ä½ï¼å ä¸ºä» ç¨è¿ä¹å¤çâ1âåâ0âæ¥è¡¨ç¤ºå®ä»¬ã对è¾å¤§çæ°åæ§è¡çæä½è¶å¤ï¼ç»æä¸å°±ä¼ç§¯ç´¯è¶æ¥è¶å¤çé误ãå½é¤ä»¥ w æ¶ï¼è¿å¯ä»¥éè¿ä¸¤ä¸ªå¯è½æ´å°ï¼æ´ä¸æåºéçæ°åè¿è¡è¿ç®æ¥ææå°æé«éå¸¸å¤§çæ°åç精度ã
使ç¨é½æ¬¡åæ çæç»å¥½å¤æ¯ï¼å®ä»¬é常éåä¸ 4x4 ç©éµç¸ä¹ãä¸ä¸ªé¡¶ç¹å¿ é¡»è³å°ä¸ç©éµçä¸ä¸ªç»´æ°ï¼è¡/åï¼å¹é ï¼æè½ä¸å ¶ç¸ä¹ã4x4 ç©éµå¯ç¨äºç¼ç åç§è½¬æ¢ãå®é ä¸ï¼å ¸åçéè§ç©éµä½¿ç¨ w åé餿³æ¥å®ç°å ¶åæ¢ã
å®é ä¸ï¼å¨å°é½æ¬¡åæ 转æ¢åç¬å¡å°åæ ä¹åï¼éè¿é¤ä»¥ wï¼ï¼ä¼åçä»è£åªç©ºé´ä¸è£åªç¹åå¤è¾¹å½¢çæ åµã该æç»ç©ºé´ç§°ä¸ºå½ä¸å设å¤åæ æ NDCã
为äºå¼å§ä½¿ç¨è¿ä¸ªææ³ï¼å¯ä»¥ä¿®æ¹åé¢ç示ä¾ï¼ä»¥å
è®¸ä½¿ç¨ w
åéã
// éæ°å®ä¹ä¸è§å½¢ä»¥ä½¿ç¨ W åé
var data = new Float32Array([
//Triangle 1
settings.left,
settings.bottom,
settings.depth,
settings.w,
settings.right,
settings.bottom,
settings.depth,
settings.w,
settings.left,
settings.top,
settings.depth,
settings.w,
//Triangle 2
settings.left,
settings.top,
settings.depth,
settings.w,
settings.right,
settings.bottom,
settings.depth,
settings.w,
settings.right,
settings.top,
settings.depth,
settings.w,
]);
ç¶åï¼é¡¶ç¹çè²å¨ä½¿ç¨ä¼ å ¥ç 4 ç»´ç¹ã
attribute vec4 position;
void main() {
gl_Position = position;
}
é¦å ï¼æä»¬å¨ä¸é´ç»å¶ä¸ä¸ªçº¢è²æ¡ï¼ä½å° W 设置为 0.7ãä½åæ é¤ä»¥ 0.7 æ¶ï¼å®ä»¬å ¨é¨ä¼è¢«æ¾å¤§ã
box.draw({
top: 0.5, // x
bottom: -0.5, // x
left: -0.5, // y
right: 0.5, // y
w: 0.7, // w - æ¾å¤§è¿ä¸ªçå
depth: 0, // z
color: [1, 0.4, 0.4, 1], // red
});
ç°å¨ï¼æä»¬å¨ä¸é¢ç»å¶ä¸ä¸ªç»¿è²æ¡ï¼ä½æ¯éè¿å° w åé设置为 1.1 æ¥ç¼©å°å®ã
box.draw({
top: 0.9, // x
bottom: 0, // x
left: -0.9, // y
right: 0.9, // y
w: 1.1, // w - 缩å°è¿ä¸ªçå
depth: 0.5, // z
color: [0.4, 1, 0.4, 1], // green
});
æåä¸ä¸ªæ¡æªè¢«ç»å¶ï¼å 为å®å¨è£åªç©ºé´ä¹å¤ãæ·±åº¦è¶ åº -1.0 å° 1.0 èå´ã
box.draw({
top: 1, // x
bottom: -1, // x
left: -1, // y
right: 1, // y
w: 1.5, // w - æè¿ä¸ªçå带åèå´å
depth: -1.5, // z
color: [0.4, 0.4, 1, 1], // blue
});
ç»æ
ç»ä¹
å°ç¹ç´æ¥æ¾å ¥è£åªç©ºé´çç¨éæéãå¨ç°å®ä¸ççåºç¨ç¨åºä¸ï¼ä½ æ¥æçæºåæ ä¸å ¨é¨å¨è£åªç©ºé´ä¸ãå æ¤å¤§å¤æ°æ¶åï¼ä½ éè¦å°æ¨¡åæ°æ®åå ¶ä»åæ 转æ¢å°è£åªç©ºé´ä¸ãç®åçç«æ¹ä½å°±æ¯ä¸ä¸ªå¦ä½æ§è¡æ¤æä½çç®å示ä¾ãç«æ¹ä½æ°æ®ç±é¡¶ç¹ä½ç½®ï¼ç«æ¹ä½è¡¨é¢é¢è²åææå个å¤è¾¹å½¢çé¡¶ç¹ä½ç½®ç顺åºç»æï¼ä»¥ 3 个顶ç¹ä¸ºä¸ç»ï¼ä»¥ææç«æ¹ä½è¡¨é¢çä¸è§å½¢ï¼ãè¿äºä½ç½®åé¢è²åå¨å¨ GL ç¼å²åºä¸ï¼ä½ä¸ºå±æ§åå°çè²å¨ï¼ç¶ååå«è¿è¡æä½ã
æåï¼è®¡ç®å¹¶è®¾ç½®å个模åç©éµã该ç©éµè¡¨ç¤ºè¦å¨ç»ææ¨¡åçæ¯ä¸ªç¹ä¸æ§è¡ç转æ¢ï¼ä»¥å°å ¶ç§»å°æ£ç¡®ç空é´ï¼å¹¶å¨æ¨¡åä¸çæ¯ä¸ªç¹ä¸æ§è¡ä»»ä½å ¶ä»æéç转æ¢ãè¿ä¸ä» éç¨äºæ¯ä¸ä¸ªé¡¶ç¹ï¼èä¸è¿éç¨äºæ¨¡åæ¯ä¸ªè¡¨é¢çæ¯ä¸ªç¹ã
å¨è¿ç§æ åµä¸ï¼å¯¹äºå¨ç»çæ¯ä¸å¸§ï¼ä¸ç³»å缩æ¾ï¼æè½¬å平移ç©éµä¼å°æ°æ®ç§»å¨å°è£åªç©ºé´ä¸æéçä½ç½®ãè¿ä¸ªç«æ¹ä½æ¯è£åªç©ºé´ (-1, -1, -1) å° (1, 1, 1) ç大å°ï¼å æ¤éè¦ç¼©å°ä»¥ä¸å¡«æ»¡æ´ä¸ªè£åªç©ºé´ã该ç©éµäºå å·²ç»å¨ JavaScript ä¸è¿è¡äºä¹æ³è¿ç®ï¼ç´æ¥åå°çè²å¨ã
以ä¸ä»£ç 示ä¾å¨ CubeDemo
对象ä¸å®ä¹äºä¸ä¸ªå建模åç©éµçæ¹æ³ãå®ä½¿ç¨äºèªå®ä¹å½æ°æ¥å建åä¹ä»¥ MDN WebGL å
±äº«ä»£ç ä¸å®ä¹çç©éµãæ°ç彿°å¦ä¸ï¼
CubeDemo.prototype.computeModelMatrix = function (now) {
// ç¼©å° 50%
var scale = MDN.scaleMatrix(0.5, 0.5, 0.5);
// 轻微æè½¬
var rotateX = MDN.rotateXMatrix(now * 0.0003);
// æ ¹æ®æ¶é´æè½¬
var rotateY = MDN.rotateYMatrix(now * 0.0005);
// ç¨å¾®åä¸ç§»å¨
var position = MDN.translateMatrix(0, -0.1, 0);
// ç¸ä¹ï¼ç¡®å®ä»¥ç¸åç顺åºè¯»åå®ä»¬
this.transforms.model = MDN.multiplyArrayOfMatrices([
position, // step 4
rotateY, // step 3
rotateX, // step 2
scale, // step 1
]);
};
为äºå¨çè²å¨ä¸ä½¿ç¨å®ï¼å¿
é¡»å°å
¶è®¾ç½®å¨ uniforms çä½ç½®ãuniforms çä½ç½®ä¿åå¨ locations
对象ä¸ï¼å¦ä¸æç¤ºï¼
this.locations.model = gl.getUniformLocation(webglProgram, "model");
æåï¼å° uniforms 设置å¨é£ä¸ªä½ç½®ï¼è¿å°±æç©éµäº¤ç»äº GPUã
gl.uniformMatrix4fv(
this.locations.model,
false,
new Float32Array(this.transforms.model),
);
å¨çè²å¨ä¸ï¼æ¯ä¸ªä½ç½®é¡¶ç¹é¦å 被转æ¢ä¸ºé½æ¬¡åæ ï¼vec4 对象ï¼ï¼ç¶å䏿¨¡åç©éµç¸ä¹ã
gl_Position = model * vec4(position, 1.0);
夿³¨ï¼ å¨ JavaScript ä¸ï¼ç©éµä¹æ³éè¦èªå®ä¹å½æ°ï¼èå¨çè²å¨ä¸ï¼å®ä½¿ç¨äºå ç½®å¨è¯è¨ä¸çç®åç * è¿ç®ã
ç»ææ¤æ¶ï¼åæ¢ç¹ç w å¼ä»ä¸º 1.0ãç«æ¹ä½ä»ç¶æ²¡æä»ä¹è§åº¦ãä¸ä¸èå°è¿è¡æ¤è®¾ç½®å¹¶ä¿®æ¹ w å¼ä»¥æä¾ä¸äºéè§ææã
ç»ä¹rotateZ
ãä¸ä¸ªå¼å§äºè§£ç«æ¹ä½æ¨¡åéè§çç®åæ¹æ³æ¯è·å Z åæ å¹¶å°å
¶å¤å¶å° w åæ ãé常ï¼å°ç¬å¡å°ç¹è½¬æ¢ä¸ºé½æ¬¡åæ æ¶ï¼å®å为 (x,y,z,1)
ï¼ä½æä»¬å°å
¶è®¾ç½®ä¸º (x,y,z,z)
ãå®é
ä¸ï¼æä»¬å¸æç¡®ä¿è§å¾ä¸çç¹ç z å¼å¤§äº 0ï¼å æ¤æä»¬å°å
¶å¼æ¹ä¸º ((1.0 + z) * scaleFactor)
对å
¶è¿è¡è½»å¾®çä¿®æ¹ãè¿å°éè¦ä¸ä¸ªé常ä½äºè£åªç©ºé´ï¼-1 å° 1ï¼ä¸çç¹ï¼å¹¶å°å
¶ç§»å°æ´åï¼0 å° 1ï¼ç空é´ä¸ï¼å
·ä½åå³äºæ¯ä¾å å设置为ä»ä¹ãæ¯ä¾å åå°æç» w 弿´æ¹ä¸ºæ»ä½ä¸æ´é«ææ´ä½ã
çè²å¨ä»£ç å¦ä¸ï¼
// é¦å
转æ¢ç¹
vec4 transformedPosition = model * vec4(position, 1.0);
// éè§æå¤å¤§çå½±åï¼
float scaleFactor = 0.5;
// éè¿éç¨ä»äº -1 å° 1 ä¹é´ç z 弿¥è®¾ç½® w
// ç¶åè¿è¡ç¼©æ¾ä¸º 0 å°æä¸ªæ°ï¼å¨è¿ç§æ
åµä¸ä¸º 0 å° 1
float w = (1.0 + transformedPosition.z) * scaleFactor;
// 使ç¨èªå®ä¹ w åéä¿åæ°ç gl_Position
gl_Position = vec4(transformedPosition.xyz, w);
ç»æ
çå°é£ä¸ªæ·±èè²çå°ä¸è§å½¢åï¼é£æ¯æ·»å å°å¯¹è±¡ä¸çå¦ä¸ä¸ªé¢ï¼å 为形ç¶çæè½¬å¯¼è´äºè¯¥è§å»¶ä¼¸å°è£åªç©ºé´ä¹å¤ï¼ä»è导è´è¯¥è§è¢«è£åªæãæå ³å¦ä½ä½¿ç¨æ´å¤æçç©éµæ¥å¸®å©æ§å¶å鲿¢è£åªçä»ç»ï¼è¯·åç §ä¸é¢ç Perspective matrixã
ç»ä¹å¦æè¿å¬èµ·æ¥æç¹æ½è±¡ï¼è¯·æå¼é¡¶ç¹çè²å¨ï¼ç¶åä½¿ç¨æ¯ä¾å åï¼è§å¯å ¶å¦ä½å°é¡¶ç¹å表é¢è¿ä¸æ¥æ¶ç¼©ãå®å ¨æ´æ¹ w åéçå¼ï¼ä»¥è¡¨ç¤ºçå®ç©ºé´ã
å¨ä¸ä¸èä¸ï¼æä»¬å°æ§è¡æ Z å¼å¤å¶å° w ææ§½å¹¶å°å ¶è½¬æ¢ä¸ºç©éµçæ¥éª¤ã
ç®åæå½±å¡«å w åéçæå䏿¥å®é ä¸å¯ä»¥ç¨ä¸ä¸ªç®åçç©éµå®æãä» identity ç©éµå¼å§ï¼
var identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
MDN.multiplyPoint(identity, [2, 3, 4, 1]);
//> [2, 3, 4, 1]
ç¶åå°æåä¸åç 1 åä¸ç§»å¨ä¸ä¸ªç©ºæ ¼ã
var copyZ = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0];
MDN.multiplyPoint(copyZ, [2, 3, 4, 1]);
//> [2, 3, 4, 4]
使¯ï¼å¨æåä¸ä¸ªç¤ºä¾ä¸ï¼æä»¬æ§è¡äº (z + 1) * scaleFactor
:
var scaleFactor = 0.5;
var simpleProjection = [
1,
0,
0,
0,
0,
1,
0,
0,
0,
0,
1,
scaleFactor,
0,
0,
0,
scaleFactor,
];
MDN.multiplyPoint(simpleProjection, [2, 3, 4, 1]);
//> [2, 3, 4, 2.5]
è¿ä¸æ¥å±å¼æä»¬å¯ä»¥çå°å®æ¯å¦ä½å·¥ä½çï¼
var x = 2 * 1 + 3 * 0 + 4 * 0 + 1 * 0;
var y = 2 * 0 + 3 * 1 + 4 * 0 + 1 * 0;
var z = 2 * 0 + 3 * 0 + 4 * 1 + 1 * 0;
var w = 2 * 0 + 3 * 0 + 4 * scaleFactor + 1 * scaleFactor;
æåä¸è¡å¯ä»¥ç®å为ï¼
w = 4 * scaleFactor + 1 * scaleFactor;
ç¶åå° scaleFactor æååºæ¥ï¼æä»¬å¾å°ï¼
w = (4 + 1) * scaleFactor;
è¿ä¸æä»¬å¨åé¢ç¤ºä¾ä¸ä½¿ç¨ç (z + 1) * scaleFactor
å®å
¨ç¸åã
å¨ box demo ä¸ï¼æ·»å äºä¸ä¸ªé¢å¤ç .computeSimpleProjectionMatrix()
æ¹æ³ãå¨ .draw()
æ¹æ³ä¸è°ç¨ï¼å¹¶å°æ¯ä¾å åä¼ éç»å®ãç»æåºè¯¥ä¸ä¸ä¸ä¸ªç¤ºä¾ç¸åï¼
CubeDemo.prototype.computeSimpleProjectionMatrix = function (scaleFactor) {
this.transforms.projection = [
1,
0,
0,
0,
0,
1,
0,
0,
0,
0,
1,
scaleFactor,
0,
0,
0,
scaleFactor,
];
};
å°½ç®¡ç»æç¸åï¼ä½éè¦çæ¥éª¤è¿æ¯å¨é¡¶ç¹çè²å¨ä¸ãä¸å ¶ç´æ¥ä¿®æ¹é¡¶ç¹ï¼ä¸å¦å°å ¶ä¹ä»¥ä¸ä¸ªéå ç projection matrixï¼è¯¥ç©éµå° 3D ç¹æå½±å° 2D ç»å¾è¡¨é¢ä¸ï¼
// ç¡®ä¿ä»¥ç¸åç顺åºè¯»å转æ¢ç©éµ
gl_Position = projection * model * vec4(position, 1.0);
ç»æ éè§ç©éµ
è³æ¤ï¼æä»¬éæ¥æå»ºäºèªå·±ç 3D 渲æè®¾ç½®ã使¯ï¼æä»¬å½åæå»ºç代ç åå¨ä¸äºé®é¢ãé¦å ï¼æ¯å½æä»¬è°æ´çªå£å¤§å°æ¶ï¼å®å°±ä¼å¾æãå¦å¤æ¯æä»¬çç®åæå½±æ æ³å¤çåºæ¯æ°æ®ç大èå´å¼ã大夿°åºæ¯å¨è£åªç©ºé´ä¸ä¸èµ·ä½ç¨ãå®ä¹ä¸åºæ¯ç¸å ³çè·ç¦»æ¯å¾æå¸®å©çï¼è¿æ ·å¨è½¬æ¢æ°åæ¶ä¸ä¼æå¤±ç²¾åº¦ãæåï¼å¯¹åªäºç¹æ¾å¨è£åªç©ºé´çå é¨åå¤é¨è¿è¡ç²¾åº¦æ§å¶é常æå¸®å©ãå¨åé¢çä¾åä¸ï¼ç«æ¹ä½çè§å¶å°ä¼è¢«è£åªã
éè§ç©éµæ¯ä¸ç§å¯ä»¥æ»¡è¶³è¿äºè¦æ±çæå½±ç©éµãä¹å¼å§æ¶åæ°å¦æ´å¤çå 容ï¼è¿äºç¤ºä¾ä¸å°ä¸åå åè§£éãç®èè¨ä¹ï¼å®ç»åäºé¤ä»¥ wï¼ä¸åé¢çä¾åç¸åï¼ååºäº ç¸ä¼¼ä¸è§å½¢ ç¸ä¼¼ä¸è§å½¢çä¸äºå·§å¦æä½ãå¦æä½ æ³é 读æå ³å ¶èåæ°å¦ç宿´è¯´æï¼è¯·æ¥ç以ä¸ä¸äºé¾æ¥ï¼
å ³äºä¸é¢ä½¿ç¨çéè§ç©éµï¼éè¦æ³¨æçä¸ä»¶éè¦çäºæ¯å®ä¼ç¿»è½¬ z è½´ãå¨è£åªç©ºé´ä¸ï¼z+ è¿ç¦»è§å¯è ï¼èä½¿ç¨æ¤ç©éµï¼å®æåè§å¯è ã
翻转 z è½´çåå æ¯ï¼è£åªç©ºé´åæ ç³»æ¯å·¦æåæ ç³»ï¼z è½´æåè¿ç¦»è§å¯è å¹¶æå ¥å±å¹çä½ç½®ï¼ï¼èæ°å¦ï¼ç©çå¦å 3D 建模ä¸çæ¯ä¾ä¸ OpenGL ä¸è§å¾/ç¼çåæ ç³»ä¸æ ·ï¼æ¯ä½¿ç¨å³æåæ ç³»ï¼z è½´æåå±å¹ï¼æåè§å¯è ï¼ãæå ³ç Wikipedia æç« çæ´å¤ä¿¡æ¯ï¼ç´è§åæ ç³», 峿æ³åã
让æä»¬çä¸ä¸ perspectiveMatrix()
彿°ï¼è¯¥å½æ°è®¡ç®äºéè§ç©éµã
MDN.perspectiveMatrix = function (
fieldOfViewInRadians,
aspectRatio,
near,
far,
) {
var f = 1.0 / Math.tan(fieldOfViewInRadians / 2);
var rangeInv = 1 / (near - far);
return [
f / aspectRatio,
0,
0,
0,
0,
f,
0,
0,
0,
0,
(near + far) * rangeInv,
-1,
0,
0,
near * far * rangeInv * 2,
0,
];
};
æ¤å½æ°çåä¸ªåæ°æ¯ï¼
fieldOfviewInRadians
ä¸ä¸ªä»¥å¼§åº¦è¡¨ç¤ºçè§åº¦ï¼æç¤ºè§çè ä¸å±å¯ä»¥çå¤å°åºæ¯ãæ°åè¶å¤§ï¼æåæºå¯è§çè¶å¤ãè¾¹ç¼çå ä½å½¢ç¶åå¾è¶æ¥è¶å¤±çï¼çåäºå¹¿è§éãå½è§éæ´å¤§æ¶ï¼ç©ä½é常ä¼åå°ãå½è§éè¾å°æ¶ï¼æåæºå¨åºæ¯ä¸ççå°çä¸è¥¿ä¼è¶æ¥è¶å°ãç©ä½å éè§èåå½¢çç¨åº¦è¦å°å¾å¤ï¼å¹¶ä¸ç©ä½ä¼¼ä¹æ´é è¿ç¸æºã
aspectRatio
åºæ¯ç宽髿¯ï¼çäºå ¶å®½åº¦é¤ä»¥å ¶é«åº¦ã卿¬ç¤ºä¾ä¸ï¼å°±æ¯çªå£ç宽度é¤ä»¥çªå£çé«åº¦ãæ¤åæ°çå¼å ¥æç»è§£å³äºå½ç»å¸è°æ´å¤§å°åå½¢ç¶æ¶æ¨¡åçåå½¢é®é¢ã
nearClippingPlaneDistance
ä¸ä¸ªæ£æ°ï¼è¡¨ç¤ºå°å±å¹çè·ç¦»æ¯åç´äºå°æ¿çå¹³é¢çè·ç¦»ï¼è¯¥è·ç¦»æ¯å°ææå 容é½è£åªçè·ç¦»æ´è¿ãå®å¨è£åªç©ºé´ä¸æ å°ä¸º -1ï¼å¹¶ä¸ä¸åºè®¾ç½®ä¸º 0ã
farClippingPlaneDistance
ä¸ä¸ªæ£æ°ï¼è¡¨ç¤ºä¸å¹³é¢ä¹é´çè·ç¦»ï¼è¶
åºè¯¥è·ç¦»å°è£åªå ä½ä½ãå®å¨è£åªç©ºé´ä¸æ å°ä¸º 1.该å¼åºä¿æåççè·ç¦»ä»¥æ¥è¿å ä½å¾å½¢çè·ç¦»ï¼ä»¥å
卿¸²ææ¶åºç°ç²¾åº¦è¯¯å·®ã 卿æ°çæ¬ççå demo ä¸ï¼ computeSimpleProjectionMatrix()
彿°å·²æ¿æ¢ä¸º computePerspectiveMatrix()
彿°ã
CubeDemo.prototype.computePerspectiveMatrix = function () {
var fieldOfViewInRadians = Math.PI * 0.5;
var aspectRatio = window.innerWidth / window.innerHeight;
var nearClippingPlaneDistance = 1;
var farClippingPlaneDistance = 50;
this.transforms.projection = MDN.perspectiveMatrix(
fieldOfViewInRadians,
aspectRatio,
nearClippingPlaneDistance,
farClippingPlaneDistance,
);
};
çè²å¨ä»£ç ä¸åé¢ç示ä¾ç¸åï¼
gl_Position = projection * model * vec4(position, 1.0);
æ¤å¤ï¼æªæ¾ç¤ºï¼ï¼æ´æ¹äºæ¨¡åçä½ç½®å缩æ¾ç©éµï¼ä»¥ä½¿å ¶è±ç¦»è£åªç©ºé´å¹¶è¿å ¥æ´å¤§çåæ ç³»ã
ç»æ ç»ä¹MDN.orthographicMatrix()
æ¿æ¢ CubeDemo.prototype.computePerspectiveMatrix()
ä¸ç MDN.perspectiveMatrix()
彿°ã尽管æäºå¾å½¢åºæä¾çèæç¸æºå¯ä»¥å¨ææåºæ¯æ¶å¯ä»¥å®ä½åæåï¼ä½ OpenGLï¼ä»¥åæ©å±ç WebGLï¼å´æ²¡æãè¿æ¯è§å¾ç©éµçç¨å¤ãå®çä½ç¨æ¯å¹³ç§»ï¼æè½¬å缩æ¾åºæ¯ä¸çç©ä½ï¼ä»¥ä½¿æ ¹æ®è§å¯è çä½ç½®åæ¹åå°å®ä»¬æ¾ç½®å°æ£ç¡®çä½ç½®ã
模æç¸æºè¿å©ç¨äºç±å æ¯å¦çä¹ç¸å¯¹è®ºçåºæ¬ç论ä¹ä¸ï¼åèç³»åç¸å¯¹è¿å¨çåç说ï¼ä»è§å¯è çè§åº¦æ¥çï¼ä½ å¯ä»¥éè¿å°ç¸åçåååºç¨äºåºæ¯ä¸çç©ä½æ¥æ¨¡ææ¹åè§å¯è çä½ç½®åæ¹åãæ è®ºåªç§æ¹å¼ï¼ç»æä¼¼ä¹å¯¹äºè§å¯è æ¯ä¸æ ·çã
å设ä¸ä¸ªä½äºæ¡åä¸ççååä¸ä¸ªæ¾å¨ä¸ç±³å¤çæ¡åä¸çç¸æºï¼å®æåçåï¼çåçæ£é¢æåç¸æºãç¶åèèå°ç¸æºä»çåä¸ç§»å¼ï¼ç´å° 2 ç±³è¿ï¼éè¿å¨ç¸æºç Z å¼å¢å 1 ç±³ï¼ï¼ç¶åå°å ¶åå·¦æ»å¨ 10 åç±³ãçåä¸ç¸æºçè·ç¦»ç¼©å°äºä¸å®éï¼å¹¶åå³ç¨å¾®æ»å¨ï¼ä»èå¨ç¸æºä¸çèµ·æ¥è¾å°ï¼å·¦ä¾§çä¸å°é¨å乿´é²å¨ç¸æºåã
ç°å¨ï¼è®©æä»¬éç½®åºæ¯ï¼å°çåæ¾åå®çèµ·å§ç¹ï¼ä½¿ç¸æºè·ç¦»çå 2 ç±³ï¼å¹¶æ£å¯¹ççåãä½è¿ä¸æ¬¡ï¼ç¸æºè¢«éå®å¨æ¡å䏿 æ³ç§»å¨ææè½¬ãè¿å°±æ¯å¨ WebGL ä¸è¿ä½çæ ·åãé£ï¼æä»¬å¦ä½æ¨¡æå¨ç©ºé´ä¸ç§»å¨çç¸æºï¼
æä»¬æ²¡æåååå左移å¨ç¸æºï¼èæ¯å¯¹çååºç¨äºéåæ¢ï¼æä»¬å°çåååç§»å¨ 1 ç±³ï¼ç¶ååå³ç§»å¨ 10 åç±³ãä»ä¸¤ä¸ªç©ä½çè§åº¦æ¥çï¼ç»ææ¯ä¸æ ·çã
æå䏿¥æ¯å建è§å¾ç©éµï¼è¯¥ç©éµå°è½¬æ¢åºæ¯ä¸ç对象ï¼ä»¥ä¾¿å¯¹å®ä»¬è¿è¡å®ä½ä»¥æ¨¡æç¸æºå½åä½ç½®ä¸æ¹åãç®åç代ç å¯ä»¥å¨ä¸ç空é´ä¸ç§»å¨ç«æ¹ä½å¹¶æå½±ææå 容以è·å¾éè§å¾ï¼ä½æä»¬ä»ç¶æ æ³ç§»å¨ç¸æºã
æ³è±¡ä¸ä¸ä½¿ç¨ç©çæåæºææçµå½±ãä½ å¯ä»¥èªç±å°å°ç¸æºæ¾å°ä»»ä½ä½ æ³æ¾ç½®çä½ç½®ï¼å¹¶å¯¹åä»»ä½ä½ éæ©çæ¹åã为äºå¨ 3D å¾å½¢ä¸å¯¹æ¤è¿è¡ä»¿çï¼æä»¬ä½¿ç¨è§å¾ç©éµæ¥æ¨¡æç©çç¸æºçä½ç½®åæè½¬ã
ä¸ç´æ¥è½¬æ¢æ¨¡åé¡¶ç¹ç模åç©éµä¸åï¼è§å¾ç©éµä¼ç§»å¨ä¸ä¸ªæ½è±¡çç¸æºãå®é ä¸ï¼é¡¶ç¹çè²å¨ä»ç¶ç§»å¨çæ¯æ¨¡åï¼èâç¸æºâä¿æå¨åä½ã为äºä½¿æ¤è®¡ç®æ£ç¡®ï¼å¿ 须使ç¨åæ¢ç©éµçéãéç©éµå®è´¨ä¸æ¯é转äºåæ¢ï¼å æ¤ï¼å¦ææä»¬ååç§»å¨ç¸æºï¼åéç©éµä¼å¯¼è´åºæ¯ä¸çç©ä½ååç§»å¨ã
以ä¸ç computeViewMatrix()
彿°éè¿åå
ååå¤ï¼åå·¦ååå³ç§»å¨çè§å¾ç©éµæ¥æ¿æ´»è§å¾ç©éµã
CubeDemo.prototype.computeViewMatrix = function (now) {
var moveInAndOut = 20 * Math.sin(now * 0.002);
var moveLeftAndRight = 15 * Math.sin(now * 0.0017);
// å个æ¹åç§»å¨ç¸æº
var position = MDN.translateMatrix(moveLeftAndRight, 0, 50 + moveInAndOut);
// ç¸ä¹ï¼ç¡®ä¿ä»¥ç¸åç顺åºè¯»åå®ä»¬
var matrix = MDN.multiplyArrayOfMatrices([
// ç»ä¹ ï¼æè½¬ç¸æºçè§è§
position,
]);
// ç¿»è½¬ç¸æºçè¿å¨æä½ï¼å 为æä»¬å®é
䏿¯
// ç§»å¨åºæ¯ä¸çå ä½å¾å½¢ï¼è䏿¯ç¸æºæ¬èº«
this.transforms.view = MDN.invertMatrix(matrix);
};
çè²å¨ç°å¨ä½¿ç¨ä¸ä¸ªç©éµã
gl_Position = projection * view * model * vec4(position, 1.0);
æ¤æ¥éª¤åï¼GPU 管线å°è£åªè¶ åºèå´çé¡¶ç¹ï¼å¹¶å°æ¨¡ååä¸åéå°ç段çè²å¨ä»¥è¿è¡æ æ ¼åã
ç»æ ç¸å ³åæ ç³»æ¤æ¶ï¼å顾并æ è®°æä»¬ä½¿ç¨çåç§åæ ç³»æ¯å¾æç¨çãé¦å ï¼å¨æ¨¡å空é´ä¸å®ä¹äºç«æ¹ä½çé¡¶ç¹ãå¨åºæ¯ä¸ç§»å¨æ¨¡åãè¿äºé¡¶ç¹éè¦éè¿åºç¨æ¨¡åç©éµè½¬æ¢å°ä¸ç空é´ã
模åç©ºé´ â æ¨¡åç©éµ â ä¸ç空é´
ç¸æºå°æªæ§è¡ä»»ä½æä½ï¼éè¦å次移å¨è¿äºç¹ãç®åå®ä»¬å¨ä¸ç空é´ä¸ï¼ä½éè¦å°å®ä»¬ç§»å¨å°è§å¾ç©ºé´ï¼ä½¿ç¨è§å¾ç©éµï¼ä»¥è¡¨ç¤ºç¸æºçä½ç½®ã
ä¸çç©ºé´ â è§å¾ç©éµ â è§å¾ç©ºé´
æåï¼éè¦æ·»å æå½±ï¼å¨æä»¬ç示ä¾ä¸æ¯éè§ç©éµï¼ï¼ä»¥ä¾¿å°ä¸çåæ æ å°å°è£åªç©ºé´ã
è§å¾ç©ºé´ â æå½±ç©éµ â è£åªç©ºé´
ç»ä¹RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4