TeaPot 用webgl画茶壶(3) 环境纹理和skybox

  1 <html>
  2     <head>
  3         <title>TeaPolt</title>
  4     </head>
  5     
  6     <body onload="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>

 

posted @ 2017-07-20 17:12  昨日凉花  阅读(636)  评论(0)    收藏  举报