OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 3 * |
| 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are |
| 6 * met: |
| 7 * |
| 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above |
| 11 * copyright notice, this list of conditions and the following disclaimer |
| 12 * in the documentation and/or other materials provided with the |
| 13 * distribution. |
| 14 * * Neither the name of Google Inc. nor the names of its |
| 15 * contributors may be used to endorse or promote products derived from |
| 16 * this software without specific prior written permission. |
| 17 * |
| 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 */ |
| 30 |
| 31 var gl = null; |
| 32 var g_width = 0; |
| 33 var g_height = 0; |
| 34 var g_bumpTexture = null; |
| 35 var g_envTexture = null; |
| 36 var g_programObject = null; |
| 37 var g_vbo = null; |
| 38 var g_elementVbo = null; |
| 39 var g_normalsOffset = 0; |
| 40 var g_tangentsOffset = 0; |
| 41 var g_binormalsOffset = 0; |
| 42 var g_texCoordsOffset = 0; |
| 43 var g_numElements = 0; |
| 44 |
| 45 // Uniform variables |
| 46 var g_worldLoc = 0; |
| 47 var g_worldInverseTransposeLoc = 0; |
| 48 var g_worldViewProjLoc = 0; |
| 49 var g_viewInverseLoc = 0; |
| 50 var g_normalSamplerLoc = 0; |
| 51 var g_envSamplerLoc = 0; |
| 52 |
| 53 var g_pendingTextureLoads = 0; |
| 54 |
| 55 // The "model" matrix is the "world" matrix in Standard Annotations |
| 56 // and Semantics |
| 57 var model = new Matrix4x4(); |
| 58 var view = new Matrix4x4(); |
| 59 var projection = new Matrix4x4(); |
| 60 |
| 61 var controller = null; |
| 62 |
| 63 function main() { |
| 64 var c = document.getElementById("c"); |
| 65 gl = getWebGLContext(c); |
| 66 if (!gl) |
| 67 return; |
| 68 g_width = c.width; |
| 69 g_height = c.height; |
| 70 controller = new CameraController(c); |
| 71 // Try the following (and uncomment the "pointer-events: none;" in |
| 72 // the index.html) to try the more precise hit detection |
| 73 // controller = new CameraController(document.getElementById("body"), c, gl
); |
| 74 controller.onchange = function(xRot, yRot) { |
| 75 draw(); |
| 76 }; |
| 77 init(); |
| 78 draw(); |
| 79 } |
| 80 |
| 81 function output(str) { |
| 82 document.body.appendChild(document.createTextNode(str)); |
| 83 document.body.appendChild(document.createElement("br")); |
| 84 } |
| 85 |
| 86 function checkGLError() { |
| 87 var error = gl.getError(); |
| 88 if (error != gl.NO_ERROR) { |
| 89 var str = "GL Error: " + error; |
| 90 output(str); |
| 91 throw str; |
| 92 } |
| 93 } |
| 94 |
| 95 function init() { |
| 96 gl.enable(gl.DEPTH_TEST); |
| 97 // Can use this to make the background opaque |
| 98 // gl.clearColor(0.3, 0.2, 0.2, 1.); |
| 99 gl.clearColor(0.0, 0.0, 0.0, 0.0); |
| 100 gl.viewport(0, 0, g_width, g_height); |
| 101 initTeapot(); |
| 102 initShaders(); |
| 103 g_bumpTexture = loadTexture("bump.jpg"); |
| 104 g_envTexture = loadCubeMap("skybox", "jpg"); |
| 105 } |
| 106 |
| 107 function initTeapot() { |
| 108 g_vbo = gl.createBuffer(); |
| 109 gl.bindBuffer(gl.ARRAY_BUFFER, g_vbo); |
| 110 gl.bufferData(gl.ARRAY_BUFFER, |
| 111 teapotPositions.byteLength + |
| 112 teapotNormals.byteLength + |
| 113 teapotTangents.byteLength + |
| 114 teapotBinormals.byteLength + |
| 115 teapotTexCoords.byteLength, |
| 116 gl.STATIC_DRAW); |
| 117 g_normalsOffset = teapotPositions.byteLength; |
| 118 g_tangentsOffset = g_normalsOffset + teapotNormals.byteLength; |
| 119 g_binormalsOffset = g_tangentsOffset + teapotTangents.byteLength; |
| 120 g_texCoordsOffset = g_binormalsOffset + teapotBinormals.byteLength; |
| 121 gl.bufferSubData(gl.ARRAY_BUFFER, 0, teapotPositions); |
| 122 gl.bufferSubData(gl.ARRAY_BUFFER, g_normalsOffset, teapotNormals); |
| 123 gl.bufferSubData(gl.ARRAY_BUFFER, g_tangentsOffset, teapotTangents); |
| 124 gl.bufferSubData(gl.ARRAY_BUFFER, g_binormalsOffset, teapotBinormals); |
| 125 gl.bufferSubData(gl.ARRAY_BUFFER, g_texCoordsOffset, teapotTexCoords); |
| 126 |
| 127 g_elementVbo = gl.createBuffer(); |
| 128 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, g_elementVbo); |
| 129 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, teapotIndices, gl.STATIC_DRAW); |
| 130 g_numElements = teapotIndices.length; |
| 131 } |
| 132 |
| 133 var bumpReflectVertexSource = [ |
| 134 "attribute vec3 g_Position;", |
| 135 "attribute vec3 g_TexCoord0;", |
| 136 "attribute vec3 g_Tangent;", |
| 137 "attribute vec3 g_Binormal;", |
| 138 "attribute vec3 g_Normal;", |
| 139 "", |
| 140 "uniform mat4 world;", |
| 141 "uniform mat4 worldInverseTranspose;", |
| 142 "uniform mat4 worldViewProj;", |
| 143 "uniform mat4 viewInverse;", |
| 144 "", |
| 145 "varying vec2 texCoord;", |
| 146 "varying vec3 worldEyeVec;", |
| 147 "varying vec3 worldNormal;", |
| 148 "varying vec3 worldTangent;", |
| 149 "varying vec3 worldBinorm;", |
| 150 "", |
| 151 "void main() {", |
| 152 " gl_Position = worldViewProj * vec4(g_Position.xyz, 1.);", |
| 153 " texCoord.xy = g_TexCoord0.xy;", |
| 154 " worldNormal = (worldInverseTranspose * vec4(g_Normal, 1.)).xyz;", |
| 155 " worldTangent = (worldInverseTranspose * vec4(g_Tangent, 1.)).xyz;", |
| 156 " worldBinorm = (worldInverseTranspose * vec4(g_Binormal, 1.)).xyz;", |
| 157 " vec3 worldPos = (world * vec4(g_Position, 1.)).xyz;", |
| 158 " worldEyeVec = normalize(worldPos - viewInverse[3].xyz);", |
| 159 "}" |
| 160 ].join("\n"); |
| 161 |
| 162 var bumpReflectFragmentSource = [ |
| 163 "#ifdef GL_ES\n", |
| 164 "precision highp float;\n", |
| 165 "#endif\n", |
| 166 "const float bumpHeight = 0.2;", |
| 167 "", |
| 168 "uniform sampler2D normalSampler;", |
| 169 "uniform samplerCube envSampler;", |
| 170 "", |
| 171 "varying vec2 texCoord;", |
| 172 "varying vec3 worldEyeVec;", |
| 173 "varying vec3 worldNormal;", |
| 174 "varying vec3 worldTangent;", |
| 175 "varying vec3 worldBinorm;", |
| 176 "", |
| 177 "void main() {", |
| 178 " vec2 bump = (texture2D(normalSampler, texCoord.xy).xy * 2.0 - 1.0) * bump
Height;", |
| 179 " vec3 normal = normalize(worldNormal);", |
| 180 " vec3 tangent = normalize(worldTangent);", |
| 181 " vec3 binormal = normalize(worldBinorm);", |
| 182 " vec3 nb = normal + bump.x * tangent + bump.y * binormal;", |
| 183 " nb = normalize(nb);", |
| 184 " vec3 worldEye = normalize(worldEyeVec);", |
| 185 " vec3 lookup = reflect(worldEye, nb);", |
| 186 " vec4 color = textureCube(envSampler, lookup);", |
| 187 " gl_FragColor = color;", |
| 188 "}" |
| 189 ].join("\n"); |
| 190 |
| 191 function loadShader(type, shaderSrc) { |
| 192 var shader = gl.createShader(type); |
| 193 if (shader == null) { |
| 194 return null; |
| 195 } |
| 196 // Load the shader source |
| 197 gl.shaderSource(shader, shaderSrc); |
| 198 // Compile the shader |
| 199 gl.compileShader(shader); |
| 200 // Check the compile status |
| 201 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { |
| 202 var infoLog = gl.getShaderInfoLog(shader); |
| 203 output("Error compiling shader:\n" + infoLog); |
| 204 gl.deleteShader(shader); |
| 205 return null; |
| 206 } |
| 207 return shader; |
| 208 } |
| 209 |
| 210 function initShaders() { |
| 211 var vertexShader = loadShader(gl.VERTEX_SHADER, bumpReflectVertexSource); |
| 212 var fragmentShader = loadShader(gl.FRAGMENT_SHADER, bumpReflectFragmentSourc
e); |
| 213 // Create the program object |
| 214 var programObject = gl.createProgram(); |
| 215 if (programObject == null) { |
| 216 output("Creating program failed"); |
| 217 return; |
| 218 } |
| 219 gl.attachShader(programObject, vertexShader); |
| 220 gl.attachShader(programObject, fragmentShader); |
| 221 // Bind attributes |
| 222 gl.bindAttribLocation(programObject, 0, "g_Position"); |
| 223 gl.bindAttribLocation(programObject, 1, "g_TexCoord0"); |
| 224 gl.bindAttribLocation(programObject, 2, "g_Tangent"); |
| 225 gl.bindAttribLocation(programObject, 3, "g_Binormal"); |
| 226 gl.bindAttribLocation(programObject, 4, "g_Normal"); |
| 227 // Link the program |
| 228 gl.linkProgram(programObject); |
| 229 // Check the link status |
| 230 var linked = gl.getProgramParameter(programObject, gl.LINK_STATUS); |
| 231 if (!linked) { |
| 232 var infoLog = gl.getProgramInfoLog(programObject); |
| 233 output("Error linking program:\n" + infoLog); |
| 234 gl.deleteProgram(programObject); |
| 235 return; |
| 236 } |
| 237 g_programObject = programObject; |
| 238 // Look up uniform locations |
| 239 g_worldLoc = gl.getUniformLocation(g_programObject, "world"); |
| 240 g_worldInverseTransposeLoc = gl.getUniformLocation(g_programObject, "worldIn
verseTranspose"); |
| 241 g_worldViewProjLoc = gl.getUniformLocation(g_programObject, "worldViewProj")
; |
| 242 g_viewInverseLoc = gl.getUniformLocation(g_programObject, "viewInverse"); |
| 243 g_normalSamplerLoc = gl.getUniformLocation(g_programObject, "normalSampler")
; |
| 244 g_envSamplerLoc = gl.getUniformLocation(g_programObject, "envSampler"); |
| 245 checkGLError(); |
| 246 } |
| 247 |
| 248 function draw() { |
| 249 // Note: the viewport is automatically set up to cover the entire Canvas. |
| 250 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); |
| 251 checkGLError(); |
| 252 |
| 253 // For now, don't render if we have incomplete textures, just to |
| 254 // avoid accidentally incurring OpenGL errors -- although we should |
| 255 // be fully able to load textures in in the background |
| 256 if (g_pendingTextureLoads > 0) { |
| 257 gl.flush(); |
| 258 return; |
| 259 } |
| 260 |
| 261 // Set up the model, view and projection matrices |
| 262 projection.loadIdentity(); |
| 263 projection.perspective(45, g_width / g_height, 10, 500); |
| 264 view.loadIdentity(); |
| 265 view.translate(0, -10, -100.0); |
| 266 |
| 267 // Add in camera controller's rotation |
| 268 model.loadIdentity(); |
| 269 model.rotate(controller.xRot, 1, 0, 0); |
| 270 model.rotate(controller.yRot, 0, 1, 0); |
| 271 |
| 272 // Correct for initial placement and orientation of model |
| 273 model.translate(0, -10, 0); |
| 274 model.rotate(90, 1, 0, 0); |
| 275 |
| 276 gl.useProgram(g_programObject); |
| 277 |
| 278 // Compute necessary matrices |
| 279 var mvp = new Matrix4x4(); |
| 280 mvp.multiply(model); |
| 281 mvp.multiply(view); |
| 282 mvp.multiply(projection); |
| 283 var worldInverseTranspose = model.inverse(); |
| 284 worldInverseTranspose.transpose(); |
| 285 var viewInverse = view.inverse(); |
| 286 |
| 287 // Set up uniforms |
| 288 gl.uniformMatrix4fv(g_worldLoc, gl.FALSE, new Float32Array(model.elements)); |
| 289 gl.uniformMatrix4fv(g_worldInverseTransposeLoc, gl.FALSE, new Float32Array(w
orldInverseTranspose.elements)); |
| 290 gl.uniformMatrix4fv(g_worldViewProjLoc, gl.FALSE, new Float32Array(mvp.eleme
nts)); |
| 291 gl.uniformMatrix4fv(g_viewInverseLoc, gl.FALSE, new Float32Array(viewInverse
.elements)); |
| 292 gl.activeTexture(gl.TEXTURE0); |
| 293 gl.bindTexture(gl.TEXTURE_2D, g_bumpTexture); |
| 294 gl.uniform1i(g_normalSamplerLoc, 0); |
| 295 gl.activeTexture(gl.TEXTURE1); |
| 296 gl.bindTexture(gl.TEXTURE_CUBE_MAP, g_envTexture); |
| 297 gl.uniform1i(g_envSamplerLoc, 1); |
| 298 checkGLError(); |
| 299 |
| 300 // Bind and set up vertex streams |
| 301 gl.bindBuffer(gl.ARRAY_BUFFER, g_vbo); |
| 302 gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0); |
| 303 gl.enableVertexAttribArray(0); |
| 304 gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, g_texCoordsOffset); |
| 305 gl.enableVertexAttribArray(1); |
| 306 gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 0, g_tangentsOffset); |
| 307 gl.enableVertexAttribArray(2); |
| 308 gl.vertexAttribPointer(3, 3, gl.FLOAT, false, 0, g_binormalsOffset); |
| 309 gl.enableVertexAttribArray(3); |
| 310 gl.vertexAttribPointer(4, 3, gl.FLOAT, false, 0, g_normalsOffset); |
| 311 gl.enableVertexAttribArray(4); |
| 312 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, g_elementVbo); |
| 313 checkGLError(); |
| 314 gl.drawElements(gl.TRIANGLES, g_numElements, gl.UNSIGNED_SHORT, 0); |
| 315 gl.flush(); |
| 316 } |
| 317 |
| 318 function loadTexture(src) { |
| 319 var texture = gl.createTexture(); |
| 320 gl.bindTexture(gl.TEXTURE_2D, texture); |
| 321 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); |
| 322 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); |
| 323 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); |
| 324 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); |
| 325 ++g_pendingTextureLoads; |
| 326 var image = new Image(); |
| 327 image.onload = function() { |
| 328 --g_pendingTextureLoads; |
| 329 gl.bindTexture(gl.TEXTURE_2D, texture); |
| 330 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); |
| 331 gl.texImage2D( |
| 332 gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); |
| 333 checkGLError(); |
| 334 draw(); |
| 335 waitForFinish(); |
| 336 }; |
| 337 image.src = src; |
| 338 return texture; |
| 339 } |
| 340 |
| 341 function loadCubeMap(base, suffix) { |
| 342 var texture = gl.createTexture(); |
| 343 gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture); |
| 344 checkGLError(); |
| 345 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); |
| 346 checkGLError(); |
| 347 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); |
| 348 checkGLError(); |
| 349 // FIXME: TEXTURE_WRAP_R doesn't exist in OpenGL ES?! |
| 350 // gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDG
E); |
| 351 // checkGLError(); |
| 352 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); |
| 353 checkGLError(); |
| 354 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); |
| 355 checkGLError(); |
| 356 var faces = [["posx", gl.TEXTURE_CUBE_MAP_POSITIVE_X], |
| 357 ["negx", gl.TEXTURE_CUBE_MAP_NEGATIVE_X], |
| 358 ["posy", gl.TEXTURE_CUBE_MAP_POSITIVE_Y], |
| 359 ["negy", gl.TEXTURE_CUBE_MAP_NEGATIVE_Y], |
| 360 ["posz", gl.TEXTURE_CUBE_MAP_POSITIVE_Z], |
| 361 ["negz", gl.TEXTURE_CUBE_MAP_NEGATIVE_Z]]; |
| 362 for (var i = 0; i < faces.length; i++) { |
| 363 var url = base + "-" + faces[i][0] + "." + suffix; |
| 364 var face = faces[i][1]; |
| 365 ++g_pendingTextureLoads; |
| 366 var image = new Image(); |
| 367 // Javascript has function, not block, scope. |
| 368 // See "JavaScript: The Good Parts", Chapter 4, "Functions", |
| 369 // section "Scope". |
| 370 image.onload = function(texture, face, image, url) { |
| 371 return function() { |
| 372 --g_pendingTextureLoads; |
| 373 gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture); |
| 374 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); |
| 375 gl.texImage2D( |
| 376 face, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); |
| 377 checkGLError(); |
| 378 draw(); |
| 379 waitForFinish(); |
| 380 } |
| 381 }(texture, face, image, url); |
| 382 image.src = url; |
| 383 } |
| 384 return texture; |
| 385 } |
| 386 |
| 387 function waitForFinish() { |
| 388 if (g_pendingTextureLoads == 0) { |
| 389 domAutomationController.setAutomationId(1); |
| 390 domAutomationController.send("ok"); |
| 391 } |
| 392 } |
OLD | NEW |