1 <html> 2 <head> 3 <title>TeaPolt</title> 4 </head> 5 6 <body οnlοad="main()"> 7 <canvas id="viewPort" width="600" height="600"> 8 This browser do not support webgl. 9 </canvas> 10 <script src="./examples/lib/cuon-matrix.js"></script> 11 <script src="./TeaPotData.js"></script> 12 <script src="./cuon-utils.js"></script> 13 <script> 14 /* 15 用webgl实现环境映射和skybox 16 我翻看了很多书籍,网页,大多喜欢讲理论,理论很简单,很少有代码的。要我复诉一遍理论,我却讲不好。 17 http://ogldev.atspace.co.uk/www/tutorial25/tutorial25.html 18 http://antongerdelan.net/opengl/cubemaps.html 19 这两个讲的就很清楚了。 20 21 跟据教程实现了代码,但是也遇到了问题,还没解决 22 23 我一点总结 24 1.环境纹理其实使用纹理着色茶壶,所以有一个纹理就可以了,根据反射的向量来做纹理坐标,不需要画一个cube 25 2.skybox要画一个cube,这个cube用纹理着色,我站在cube center处,看到啥画啥。我看的方向跟我看茶壶的一致,但是不能直接用茶壶的viewMatrix,因为这个里面有移动。而这个cube是跟着我移动的,所以相对的要取消移动变换。 26 3.那个教程里的cube图是往里面折叠的(图是贴在外面的,虽然在里面也可以看到),所以位置跟webgl用的坐标是一致的。 27 */ 28 function main() 29 { 30 //alert("bb"); 31 //get webgl context 32 var viewPort = document.getElementById("viewPort"); 33 var gl = viewPort.getContext("webgl") || viewPort.getContext("experimental-webgl"); 34 35 var ENV_VERTEX_SHADER =//画茶壶 36 "attribute vec4 a_Position;\n" + 37 "attribute vec3 a_VNomal;\n" + 38 "varying vec4 v_Position;\n" + 39 "varying vec3 v_VNomal;\n" + 40 "uniform mat4 u_ModelMatrix;\n" + 41 "uniform mat4 u_ViewMatrix;\n" + 42 "uniform mat4 u_ProjMatrix;\n" + 43 "void main()\n" + 44 "{\n" + 45 " gl_Position = u_ProjMatrix*u_ViewMatrix*u_ModelMatrix*a_Position;\n" + 46 " v_Position = a_Position;\n" + 47 " v_VNomal = a_VNomal;\n" + 48 "}\n"; //光线向量l不是线性插值的,必须在FragmentShader里算,所以每一个Fragment要带它所对应的vertex在空间里的位置(位置是可以插值的)。用着个位置和光源位置来算l。 49 50 var ENV_FRAGMENT_SHADER =//画茶壶 51 "#ifdef GL_ES\n" + 52 "precision mediump float;\n" + 53 "#endif\n" + 54 "uniform vec3 u_EyePosition;\n" + 55 "uniform vec3 u_pointLightPosition;\n" + 56 "uniform samplerCube u_EnvTexMap;\n" + 57 "uniform mat4 u_VNmodelMatrix;\n" + 58 "varying vec4 v_Position;\n" + 59 "varying vec3 v_VNomal;\n" + 60 "void main()\n" + 61 "{\n" + 62 " vec4 pointLight = vec4(1.0, 1.0, 1.0, 1.0);\n" + 63 " vec4 envlight = vec4(0.1, 0.1, 0.1, 1.0);\n" + 64 " float p = 200.0;\n" + 65 " vec3 l = normalize(vec3(u_pointLightPosition.x - v_Position.x, u_pointLightPosition.y - v_Position.y, u_pointLightPosition.z - v_Position.z));\n" + 66 " vec3 e = normalize(vec3(u_EyePosition.x - v_Position.x, u_EyePosition.y - v_Position.y, u_EyePosition.z - v_Position.z));\n" + 67 " vec3 n = (u_VNmodelMatrix*vec4(v_VNomal, 1.0)).xyz;\n" + 68 " n = normalize(n);\n" + 69 " float nl = dot(n, l);\n" + 70 " if(nl<0.0) nl=0.0;\n" + 71 " vec3 h = normalize(e+l);\n" + 72 " float hn = dot(h, n);\n" + 73 " vec4 phongColor = envlight+pointLight*nl*0.5+pointLight*pow(hn,p)*0.5;\n" + 74 " gl_FragColor = textureCube(u_EnvTexMap, -1.0*reflect(e, n))+phongColor;\n" +//要注意,在实现环境映射时,-1.0* 是因为我的向量方向取得是跟 Fundamentals of Computer Graphics 一书里是一致的,这根webgl的选择正好相反。就是,e我是等于u_EyePosition-v_Position,而webgl认为是v_Position-u_EyePosition 75 " gl_FragColor.w = 1.0;\n" + 76 "}\n"; 77 78 var SKY_VERTEX_SHADER =//画skybox 79 "attribute vec4 a_Position;\n" + 80 "varying vec3 v_SkyCoord;\n" + 81 "uniform mat4 u_ModelMatrix;\n" + 82 "uniform mat4 u_ViewMatrix;\n" + 83 "uniform mat4 u_ProjMatrix;\n" + 84 "void main()\n" + 85 "{\n" + 86 " vec4 p = u_ProjMatrix*u_ViewMatrix*u_ModelMatrix*a_Position;\n" + 87 " gl_Position = p.xyww;\n" +//gl_Position.z被赋值为gl_Position.w的值,webgl在同质时标准坐标的z(深度值)都变成1.0,在深度测试时它总是输掉,所以不会挡住茶壶 88 //http://antongerdelan.net/opengl/cubemaps.html 却说,这是一个坏方法,因为这用到了深度值的极限,1.0。我的理解是,实践里,我们可能遇到,0.9999999,你的那个显卡硬件设备里,这个值可能跟1.0的bit值是一样的,那这时候覆盖不覆盖,不稳定。 89 //作者提出的方法是,每次画场景时总是第一个画skybox,这时候,disable(gl.DEPTH_TEST),这个意思是,不使用深度buffer, 不往它里面写任何值。 90 //然后enable(gl.DEPTH_TEST),画茶壶 91 " v_SkyCoord = a_Position.xyz;\n" + 92 "}\n"; 93 94 var SKY_FRAGMENT_SHADER =//画skybox 95 "#ifdef GL_ES\n" + 96 "precision mediump float;\n" + 97 "#endif\n" + 98 "uniform samplerCube u_SkyTexMap;\n" + 99 "varying vec3 v_SkyCoord;\n" + 100 "void main()\n" + 101 "{\n" + 102 " gl_FragColor = +textureCube(u_SkyTexMap, v_SkyCoord);\n" +//这里v_SkyCoord*一个非零数,画出的图片一样,我认为textureCube函数会对v_SkyCoord标准化。 103 "}\n"; 104 105 if(!initShaders(gl, ENV_VERTEX_SHADER, ENV_FRAGMENT_SHADER)) 106 return; 107 var programEnv = gl.program;//这个program用来画环境映射 108 109 if(!initShaders(gl, SKY_VERTEX_SHADER, SKY_FRAGMENT_SHADER)) 110 return; 111 var programSky = gl.program;//用来画skybox 112 113 gl.enable(gl.DEPTH_TEST);//开启深度测试,WebGL Programming Guide一书竟然没提及 114 gl.depthFunc(gl.LEQUAL);//指定深度测试的方法,mdn depthFunc有所有的选择,这个是小于等于已有的值就覆盖,这是最常见的用法,还有等于的,大于的等等。 115 gl.clearColor(0.0, 0.0, 0.0, 1.0); 116 gl.clear(gl.COLOR_BUFFER_BIT || gl.DEPTH_BUFFER_BIT); 117 118 119 /* 120 void gl.depthFunc(func); 121 Parameters 122 123 func 124 A GLenum specifying the depth comparison function, which sets the conditions under which the pixel will be drawn. The default value is gl.LESS. Possible values are 125 gl.NEVER (never pass) 126 gl.LESS (pass if the incoming value is less than the depth buffer value) 127 gl.EQUAL (pass if the incoming value equals the the depth buffer value) 128 gl.LEQUAL (pass if the incoming value is less than or equal to the depth buffer value) 129 gl.GREATER (pass if the incoming value is greater than the depth buffer value) 130 gl.NOTEQUAL (pass if the incoming value is not equal to the depth buffer value) 131 gl.GEQUAL (pass if the incoming value is greater than or equal to the depth buffer value) 132 gl.ALWAYS (always pass) 133 */ 134 135 var urls, targets, imgs; 136 var modelMatrix, viewMatrix, projMatrix, VNmodelMatrix; 137 var eyePosition; 138 eyePosition = new Float32Array([30, 10, 30]); 139 var pointLightPosition = new Float32Array([0.0, 50.0, 50.0]); 140 141 modelMatrix = new Matrix4();//模型矩阵 142 viewMatrix = new Matrix4();//视觉矩阵 143 projMatrix = new Matrix4();//投影矩阵 144 VNmodelMatrix = new Matrix4();//modelMatrix的逆,然后转置。茶壶根据modelMatrix转变时,VNmodelMatrix转变茶壶vertex normal 145 viewMatrix.setLookAt(eyePosition[0], eyePosition[1], eyePosition[2], 0.0, 0.0, 0.0, 0, 1, 0); 146 projMatrix.setPerspective(30,viewPort.width/viewPort.height,1,100);//30是视角 147 modelMatrix.setRotate(180, 0, 1, 0); 148 VNmodelMatrix.setInverseOf(modelMatrix); 149 VNmodelMatrix.transpose();//这MVP用来画茶壶 150 151 resource();//这个函数加载所有资源 152 function resource() 153 { 154 urls = [ 155 './teaPotEnvMap/posx.jpg',//Env 156 './teaPotEnvMap/negx.jpg', 157 './teaPotEnvMap/posy.jpg', 158 './teaPotEnvMap/negy.jpg', 159 './teaPotEnvMap/posz.jpg', 160 './teaPotEnvMap/negz.jpg', 161 './teaPotEnvMap/posx.jpg',//Sky 162 './teaPotEnvMap/negx.jpg', 163 './teaPotEnvMap/posy.jpg', 164 './teaPotEnvMap/negy.jpg', 165 './teaPotEnvMap/posz.jpg', 166 './teaPotEnvMap/negz.jpg', 167 ]; 168 targets = [ 169 gl.TEXTURE_CUBE_MAP_POSITIVE_X, 170 gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 171 gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 172 gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 173 gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 174 gl.TEXTURE_CUBE_MAP_NEGATIVE_Z 175 ]; 176 177 var nimgs=0; 178 imgs = new Array(12); 179 for(var i=0;i<12;i++) 180 {//一个img new出来后,定义onload,只是给img对象定义一个函数属性,这时不执行。 181 //定义src,浏览器根据它加载一张图片,加载后执行onload。 182 imgs[i] = new Image(); 183 imgs[i].onload = function() 184 { 185 nimgs++; 186 if(nimgs==12)//图片全部加载好,两个纹理,虽然这里是一样的 187 { 188 envMapping();//画茶壶 189 skyboxMapping();//画skybox 190 } 191 } 192 imgs[i].src = urls[i]; 193 } 194 } 195 196 197 function envMapping() 198 { 199 gl.useProgram(programEnv); 200 201 var a_Position, u_ModelMatrix, u_ViewMatrix, u_ProjMatrix, a_VNomal, u_EyePosition, u_pointLightPosition, u_EnvTexMap, u_VNmodelMatrix; 202 a_Position = gl.getAttribLocation(programEnv, "a_Position"); 203 a_VNomal = gl.getAttribLocation(programEnv, "a_VNomal"); 204 u_ModelMatrix = gl.getUniformLocation(programEnv, "u_ModelMatrix"); 205 u_ViewMatrix = gl.getUniformLocation(programEnv, "u_ViewMatrix"); 206 u_ProjMatrix = gl.getUniformLocation(programEnv, "u_ProjMatrix"); 207 u_EyePosition = gl.getUniformLocation(programEnv, "u_EyePosition"); 208 u_pointLightPosition = gl.getUniformLocation(programEnv, "u_pointLightPosition"); 209 u_EnvTexMap = gl.getUniformLocation(programEnv, "u_EnvTexMap"); 210 u_VNmodelMatrix = gl.getUniformLocation(programEnv, "u_VNmodelMatrix"); 211 if(a_Position < 0 || a_VNomal < 0 || !u_ModelMatrix || !u_ViewMatrix || !u_ProjMatrix || !u_EyePosition || !u_pointLightPosition || !u_EnvTexMap || !u_VNmodelMatrix) 212 { 213 alert("Failed to get store location from progrom"); 214 return; 215 } 216 217 {//在GPU创建缓冲存茶壶的vertex position 218 var teaPotvPropertiesData = gl.createBuffer(); 219 gl.bindBuffer(gl.ARRAY_BUFFER, teaPotvPropertiesData); //alert("bb"+teaPotData); 220 gl.bufferData(gl.ARRAY_BUFFER, teaPotData.vertexPositions, gl.STATIC_DRAW); 221 var VFSIZE = teaPotData.vertexPositions.BYTES_PER_ELEMENTS; 222 gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, VFSIZE * 3, VFSIZE * 0 ); 223 gl.enableVertexAttribArray(a_Position); 224 225 var teaPotvnPropertiesData = gl.createBuffer(); 226 gl.bindBuffer(gl.ARRAY_BUFFER, teaPotvnPropertiesData); 227 gl.bufferData(gl.ARRAY_BUFFER, teaPotData.vertexNormals, gl.STATIC_DRAW); 228 var VNFSIZE = teaPotData.vertexNormals.BYTES_PER_ELEMENT; 229 gl.vertexAttribPointer(a_VNomal, 3, gl.FLOAT, false, VNFSIZE * 3, VNFSIZE * 0); 230 gl.enableVertexAttribArray(a_VNomal); 231 232 //The following code snippet creates a vertex buffer and binds the indices to it 233 teaPotPropertiesIndex = gl.createBuffer(); 234 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, teaPotPropertiesIndex); 235 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, teaPotData.indices, gl.STATIC_DRAW); 236 var IINDEX = teaPotData.indices.length; 237 var IFSIZE = teaPotData.indices.BYTES_PER_ELEMENT;//new Uint16Array(indices) 238 239 240 gl.uniform3fv(u_EyePosition, eyePosition); 241 gl.uniform3fv(u_pointLightPosition, pointLightPosition);} 242 243 gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements); 244 gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements); 245 gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix.elements); 246 gl.uniformMatrix4fv(u_VNmodelMatrix, false, VNmodelMatrix.elements); 247 var texture = gl.createTexture();//cub map也是放在纹理缓冲里 248 gl.activeTexture(gl.TEXTURE0);//选择第一单元,第一号纹理单元变成被选则状态 249 gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);//绑定纹理类型,同时绑定状态为被选择的纹理单元,纹理缓冲跟纹理单元关联是因为,shader里的纹理变量的值是纹理单元的标号 250 gl.generateMipmap(gl.TEXTURE_CUBE_MAP); 251 for(var j=0;j<6;j++) 252 { 253 gl.texImage2D(targets[j], 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, imgs[j]);//为六个面绑定二维的图片 254 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 255 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 256 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 257 } 258 gl.uniform1i(u_EnvTexMap, 0);//告诉u_EnvTexMap这个变量,在取纹理值时到0号纹理单元去取。纹理缓冲比如货船,纹理单元比如港口。这个函数告诉纹理变量去那个港口下货,至于下什么由bindTexture决定 259 //alert("IINDEX is "+IINDEX+" IFSIZE is "+IFSIZE); 260 gl.drawElements(gl.TRIANGLES, IINDEX, gl.UNSIGNED_SHORT, IFSIZE * 0); 261 } 262 263 function skyboxMapping() 264 {gl.useProgram(programSky); 265 var a_Position = gl.getAttribLocation(programSky, "a_Position"); 266 var u_ModelMatrix = gl.getUniformLocation(programSky, "u_ModelMatrix"); 267 var u_ViewMatrix = gl.getUniformLocation(programSky, "u_ViewMatrix"); 268 var u_ProjMatrix = gl.getUniformLocation(programSky, "u_ProjMatrix"); 269 var u_SkyTexMap = gl.getUniformLocation(programSky, "u_SkyTexMap"); 270 if(a_Position<0 || !u_ModelMatrix || !u_ViewMatrix || !u_ProjMatrix || !u_SkyTexMap) 271 { 272 alert("Failed to get store location from progrom"); 273 return; 274 } 275 modelMatrix.setIdentity();//e总是在center 276 //viewMatrix.setLookAt(0.0, 0.0, 0.0, 1.0, 1.0, -1.0, 0, 1, 0); 277 //撤销setLookAt最后一步,因为e总在skybox的center 278 viewMatrix.translate(eyePosition[0], eyePosition[1], eyePosition[2]); 279 projMatrix.setPerspective(120,viewPort.width/viewPort.height,1,100); 280 gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements); 281 gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements); 282 gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix.elements); 283 //理论上正方形的边长是不影响skybox。因为只要确定了视角,你看到的范围就那么大。 284 //(1,1,1,1)是边长为二的cube的v0,(10, 10, 10, 1)是边长为二十的cube的v0。它们在屏幕空间上是同一个点,而在作为纹理坐标(1,1,1),(10, 10, 10)又是一样的。 285 //实践里,边长二十的cube对大概30到150,160的视角的确是这样,但是对于170,179就会出现奇怪的图片,而边长比较小,比如二,当视角大于90时,正面的纹理正确显式其它不显示。 286 //我是真不知道为什么,希望有人知道能告知。 287 // Create a cube 288 // v6----- v5 289 // /| /| 290 // v1------v0| 291 // | | | | 292 // | |v7---|-|v4 293 // |/ |/ 294 // v2------v3 295 /*var vertexSkybox = new Float32Array([ 296 // Vertex coordinates and color 297 1.0, 1.0, 1.0, // v0 298 -1.0, 1.0, 1.0, // v1 299 -1.0, -1.0, 1.0, // v2 300 1.0, -1.0, 1.0, // v3 301 1.0, -1.0, -1.0, // v4 302 1.0, 1.0, -1.0, // v5 303 -1.0, 1.0, -1.0, // v6 304 -1.0, -1.0, -1.0, // v7 305 ]);*/ 306 var vertexSkybox = new Float32Array([ 307 // Vertex coordinates and color 308 10.0, 10.0, 10.0, // v0 309 -10.0, 10.0, 10.0, // v1 310 -10.0, -10.0, 10.0, // v2 311 10.0, -10.0, 10.0, // v3 312 10.0, -10.0, -10.0, // v4 313 10.0, 10.0, -10.0, // v5 314 -10.0, 10.0, -10.0, // v6 315 -10.0, -10.0, -10.0, // v7 316 ]); 317 /*var vertexSkybox = new Float32Array([ 318 // Vertex coordinates and color 319 50.0, 50.0, 50.0, // v0 320 -50.0, 50.0, 50.0, // v1 321 -50.0, -50.0, 50.0, // v2 322 50.0, -50.0, 50.0, // v3 323 50.0, -50.0, -50.0, // v4 324 50.0, 50.0, -50.0, // v5 325 -50.0, 50.0, -50.0, // v6 326 -50.0, -50.0, -50.0, // v7 327 ]);*/ 328 329 // Indices of the vertices 330 var skyboxIndex = new Uint16Array([ 331 0, 1, 2, 0, 2, 3, // front 332 0, 3, 4, 0, 4, 5, // right 333 0, 5, 6, 0, 6, 1, // up 334 1, 6, 7, 1, 7, 2, // left 335 7, 4, 3, 7, 3, 2, // down 336 4, 7, 6, 4, 6, 5 // back 337 ]); 338 var vertexSkyBuffer = gl.createBuffer(); 339 if(!vertexSkyBuffer) 340 { 341 alert("Failed to create the buffer object vertexSkyBuffer"); 342 return; 343 } 344 gl.bindBuffer(gl.ARRAY_BUFFER, vertexSkyBuffer); 345 gl.bufferData(gl.ARRAY_BUFFER, vertexSkybox, gl.STATIC_DRAW); 346 var skybox_FSIZE = vertexSkybox.BYTES_PER_ELEMENT; 347 gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, skybox_FSIZE * 3, skybox_FSIZE * 0); 348 gl.enableVertexAttribArray(a_Position);var indexSkyboxBuffer = gl.createBuffer(); 349 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexSkyboxBuffer); 350 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, skyboxIndex, gl.STATIC_DRAW); 351 skybox_IINDEX = skyboxIndex.length; 352 skybox_IFSIZE = skyboxIndex.BYTES_PER_ELEMENT; 353 354 var texture = gl.createTexture(); 355 gl.activeTexture(gl.TEXTURE1); 356 gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture); 357 //gl.generateMipmap(gl.TEXTURE_CUBE_MAP); 不需要,因为cube离开center距离不变 358 for(var j=0;j<6;j++) 359 {//alert(imgs[j]); 360 gl.texImage2D(targets[j], 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, imgs[j+6]); 361 //gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); 362 // Set the texture parameters 363 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 364 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.GL_TEXTURE_MAG_FILTER, gl.LINEAR); 365 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 366 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 367 } 368 gl.uniform1i(u_SkyTexMap, 1); 369 //skybox 370 //gl.clearColor(0.0, 0.0, 0.0, 1.0); 371 //gl.clear(gl.COLOR_BUFFER_BIT || gl.DEPTH_BUFFER_BIT); 372 //alert("IINDEX is "+IINDEX+" IFSIZE is "+IFSIZE); 373 gl.drawElements(gl.TRIANGLES, skybox_IINDEX, gl.UNSIGNED_SHORT, skybox_IFSIZE * 0); 374 } 375 } 376 377 </script> 378 379 </body> 380 </html>