Lo esencial de WebGL Parte I

WebGL es un renderizador 3D en el navegador basado en OpenGL, que le permite mostrar su contenido 3D directamente en una página HTML5. En este tutorial cubriré todos los elementos esenciales que necesita para comenzar a usar este marco..


Introducción

Hay un par de cosas que debe saber antes de comenzar. WebGL es una API de JavaScript que representa contenido 3D en un lienzo HTML5. Lo hace utilizando dos scripts que se conocen en el "mundo 3D" como Sombreadores. Los dos shaders son:

  • El sombreado de vértice
  • El fragmento shader

Ahora no te pongas demasiado nervioso cuando escuches estos nombres; es solo una forma elegante de decir "calculadora de posición" y "selector de color" respectivamente. El shader de fragmentos es el más fácil de entender; simplemente le dice a WebGL de qué color debe ser un punto determinado en su modelo. El sombreado de vértices es un poco más técnico, pero básicamente convierte los puntos en tus modelos 3D en coordenadas 2D. Debido a que todos los monitores de computadora son superficies planas 2D, y cuando ves objetos 3D en tu pantalla, son simplemente una ilusión de perspectiva..

Si quieres saber exactamente cómo funciona este cálculo, deberías preguntarle a un matemático, ya que usa multiplicaciones matriciales avanzadas de 4 x 4, que son un poco más allá del tutorial de 'Essentials'. Por suerte, no tienes que saber cómo funciona, porque WebGL se encargará de la mayoría. Entonces empecemos.


Paso 1: Configuración de WebGL

WebGL tiene una gran cantidad de pequeños ajustes que debe configurar casi cada vez que dibuja algo en la pantalla. Con el fin de ahorrar tiempo y hacer que su código sea ordenado, voy a crear un objeto JavaScript que contendrá todas las cosas "detrás de la escena" en un archivo separado. Para comenzar, cree un nuevo archivo llamado 'WebGL.js' y coloque el siguiente código dentro de él:

función WebGL (CID, FSID, VSID) var canvas = document.getElementById (CID); if (! canvas.getContext ("webgl") &&! canvas.getContext ("experimental-webgl") alerta ("Su navegador no soporta WebGL"); else this.GL = (canvas.getContext ("webgl"))? canvas.getContext ("webgl"): canvas.getContext ("experimental-webgl"); this.GL.clearColor (1.0, 1.0, 1.0, 1.0); // este es el color this.GL.enable (this.GL.DEPTH_TEST); // Habilitar las pruebas de profundidad this.GL.depthFunc (this.GL.LEQUAL); // Establecer vista de perspectiva this.AspectRatio = canvas.width / canvas.height; // Carga Shaders Aquí

Esta función constructora admite los identificadores del lienzo y los dos objetos de sombreado. Primero, obtenemos el elemento de lienzo y nos aseguramos de que sea compatible con WebGL. Si lo hace, entonces asignamos el contexto de WebGL a una variable local llamada "GL". El color claro es simplemente el color de fondo, y vale la pena señalar que en WebGL, la mayoría de los parámetros van de 0.0 a 1.0, por lo que tendría que dividir sus valores de rgb por 255. En nuestro ejemplo 1.0, 1.0, 1.0, 1.0 significa Un fondo blanco con 100% de visibilidad (sin transparencia). Las siguientes dos líneas le dicen a WebGL que calcule la profundidad y la perspectiva para que un objeto más cercano a usted bloquee los objetos detrás de él. Finalmente, establecemos la relación de aspecto que se calcula al dividir el ancho del lienzo por su altura.

Antes de continuar y cargar los dos sombreadores, vamos a escribirlos. Los voy a escribir en el archivo HTML donde vamos a colocar el elemento del lienzo real. Cree un archivo HTML y coloque los siguientes dos elementos de script justo antes de la etiqueta de cuerpo de cierre:

 

El sombreador de vértices se crea primero, y definimos dos atributos:

  • la posición del vértice, que es la ubicación en las coordenadas x, y, z del vértice actual (punto en su modelo)
  • la coordenada de la textura; la ubicación en la imagen de textura que debe asignarse a este punto

A continuación, creamos variables para las matrices de transformación y perspectiva. Estos se utilizan para convertir el modelo 3D en una imagen 2D. La siguiente línea crea una variable compartida para el fragmento de sombreado, y en la función principal calculamos gl_Position (la posición 2D final). Luego asignamos la 'coordenada de textura actual' a la variable compartida.

En el fragmento de sombreado solo tomamos las coordenadas que definimos en el vértice sombreado y "muestreamos" la textura en esa coordenada. Básicamente, solo estamos obteniendo el color en la textura que corresponde al punto actual en nuestra geometría.

Ahora que hemos escrito los sombreadores, podemos volver a cargarlos en nuestro archivo JS. Entonces reemplace "// Load Shaders Here" con el siguiente código:

var FShader = document.getElementById (FSID); var VShader = document.getElementById (VSID); alerta de (! FShader ||! VShader) ("Error, No se pudieron encontrar los Shaders"); else // Load and Compile Fragment Shader var Code = LoadShader (FShader); FShader = this.GL.createShader (this.GL.FRAGMENT_SHADER); this.GL.shaderSource (FShader, Código); this.GL.compileShader (FShader); // Cargar y compilar código de sombreado de vértice = LoadShader (VShader); VShader = this.GL.createShader (this.GL.VERTEX_SHADER); this.GL.shaderSource (VShader, Código); this.GL.compileShader (VShader); // Crear el programa Shader this.ShaderProgram = this.GL.createProgram (); this.GL.attachShader (this.ShaderProgram, FShader); this.GL.attachShader (this.ShaderProgram, VShader); this.GL.linkProgram (this.ShaderProgram); this.GL.useProgram (this.ShaderProgram); // Enlace del atributo de posición de vértice desde Shader this.VertexPosition = this.GL.getAttribLocation (this.ShaderProgram, "VertexPosition"); this.GL.enableVertexAttribArray (this.VertexPosition); // Enlace del atributo de coordenadas de la textura desde Shader this.VertexTexture = this.GL.getAttribLocation (this.ShaderProgram, "TextureCoord"); this.GL.enableVertexAttribArray (this.VertexTexture); 

Sus texturas deben estar en tamaños de bytes iguales o recibirá un error ... como 2x2, 4x4, 16x16, 32x32 ...

Primero nos aseguramos de que existan los shaders, y luego continuamos cargándolos uno por uno. El proceso básicamente obtiene el código fuente del sombreador, lo compila y lo adjunta al programa central de sombreado. Existe una función, llamada LoadShader, que obtiene el código del sombreado del archivo HTML; Llegaremos a eso en un segundo. Utilizamos el 'programa de sombreado' para vincular los dos sombreadores, y nos da acceso a sus variables. Almacenamos los dos atributos que definimos en los shaders; para que podamos introducir nuestra geometría en ellos más tarde.

Ahora veamos la función LoadShader, deberías poner esto fuera de la función WebGL:

función LoadShader (Script) var Code = ""; var CurrentChild = Script.firstChild; while (CurrentChild) if (CurrentChild.nodeType == CurrentChild.TEXT_NODE) ​​Código + = CurrentChild.textContent; CurrentChild = CurrentChild.nextSibling;  código de retorno; 

Básicamente solo recorre el sombreador y recopila el código fuente.


Paso 2: El Cubo "Simple"

Para dibujar objetos en WebGL necesitarás los siguientes tres arreglos:

  • vértices; Los puntos que conforman tus objetos.
  • triangulos; le dice a WebGL cómo conectar los vértices en las superficies
  • coordenadas de textura; Define cómo se mapean los vértices en la imagen de textura.

Esto se conoce como mapeo UV. Para nuestro ejemplo vamos a crear un cubo básico. Dividiré el cubo en 4 vértices por lado que se conectan en dos triángulos. hagamos una variable que contenga las matrices de un cubo.

var Cube = Vértices: [// X, Y, Z Coordenadas // Front 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, // Atrás 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, // Derecha 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0 , -1.0, 1.0, -1.0, -1.0, // Izquierda -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, // Arriba 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, // Inferior 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0 , -1.0, -1.0, -1.0, -1.0, -1.0], Triángulos: [// También en grupos de tres para definir los tres puntos de cada triángulo // Los números aquí son los números de índice en la matriz de vértices // Frente 0, 1, 2, 1, 2, 3, // Atrás 4, 5, 6, 5, 6, 7, // Derecha 8, 9, 10, 9, 10, 11, // Izquierda 12, 13, 14, 13, 14, 15, // Top 16, 17, 18, 17, 18, 19, // Bottom 20, 21, 22, 21, 22, 23], Textura: [// Esta matriz está en grupos de dos, las coordenadas x e y (aka U, V) en la textura // Los números van de 0.0 a 1.0, un par para cada vértice // Front 1.0, 1.0, 1.0, 0.0 , 0.0, 1.0, 0.0, 0.0, // Back 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, // Right 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // Left 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, // Top 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, // Bottom 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0];

Puede parecer una gran cantidad de datos para un cubo simple, sin embargo, en la segunda parte de este tutorial, haré un script que importará sus modelos 3D para que no tenga que preocuparse por calcular estos.

Quizás también se pregunte por qué hice 24 puntos (4 para cada lado), cuando en realidad solo hay ocho puntos únicos en un cubo. Hice esto porque solo puedes asignar una coordenada de textura por vértice; así que si solo pusiéramos los 8 puntos, entonces todo el cubo tendría que verse igual porque envolvería la textura alrededor de todos los lados que toca el vértice. Pero de esta manera, cada lado tiene sus propios puntos para que podamos poner una parte diferente de la textura en cada lado..

Ahora tenemos esta variable de cubo y estamos listos para comenzar a dibujarla. Volvamos al método WebGL y agreguemos un Dibujar función.


Paso 3: La función de dibujo

El procedimiento para dibujar objetos en WebGL tiene muchos pasos; Entonces, es una buena idea hacer una función para simplificar el proceso. La idea básica es cargar los tres arreglos en los buffers de WebGL. Luego conectamos estos buffers a los atributos que definimos en los shaders junto con las matrices de transformación y perspectiva. A continuación, tenemos que cargar la textura en la memoria, y, finalmente, podemos llamar a la dibujar mando. Entonces empecemos.

El siguiente código va dentro de la función WebGL:

this.Draw = function (Object, Texture) var VertexBuffer = this.GL.createBuffer (); // Crear un nuevo búfer // Enlazarlo como el búfer actual this.GL.bindBuffer (this.GL.ARRAY_BUFFER, VertexBuffer); // Llénelo con los datos this.GL.bufferData (this.GL.ARRAY_BUFFER, nuevo Float32Array (Object.Vertices), this.GL.STATIC_DRAW); // Conectar el atributo Buffer To Shader this.GL.vertexAttribPointer (this.VertexPosition, 3, this.GL.FLOAT, false, 0, 0); // Repetir para las siguientes dos var TextureBuffer = this.GL.createBuffer (); this.GL.bindBuffer (this.GL.ARRAY_BUFFER, TextureBuffer); this.GL.bufferData (this.GL.ARRAY_BUFFER, nuevo Float32Array (Object.Texture), this.GL.STATIC_DRAW); this.GL.vertexAttribPointer (this.VertexTexture, 2, this.GL.FLOAT, false, 0, 0);
 var TriangleBuffer = this.GL.createBuffer (); this.GL.bindBuffer (this.GL.ELEMENT_ARRAY_BUFFER, TriangleBuffer); // Generar la matriz de perspectiva var PerspectiveMatrix = MakePerspective (45, this.AspectRatio, 1, 10000.0); var TransformMatrix = MakeTransform (Object); // Establecer la ranura 0 como la textura activa this.GL.activeTexture (this.GL.TEXTURE0); // Cargar en la textura a la memoria this.GL.bindTexture (this.GL.TEXTURE_2D, Texture); // Actualice The Texture Sampler en el fragmento de sombreado para usar la ranura 0 this.GL.uniform1i (this.GL.getUniformLocation (this.ShaderProgram, "uSampler"), 0); // Establezca las matrices de perspectiva y transformación var pmatrix = this.GL.getUniformLocation (this.ShaderProgram, "PerspectiveMatrix"); this.GL.uniformMatrix4fv (pmatrix, false, new Float32Array (PerspectiveMatrix)); var tmatrix = this.GL.getUniformLocation (this.ShaderProgram, "TransformationMatrix"); this.GL.uniformMatrix4fv (tmatrix, false, new Float32Array (TransformMatrix)); // Dibuja los triángulos this.GL.drawElements (this.GL.TRIANGLES, Object.Trinagles.length, this.GL.UNSIGNED_SHORT, 0); ;

El sombreado de vértices coloca, rota y escala su objeto en función de las matrices de transformación y perspectiva. Vamos a profundizar más en transformaciones en la segunda parte de esta serie..

He añadido dos funciones: HacerPerspectiva () y MakeTransform (). Estos solo generan las matrices 4x4 necesarias para WebGL. los HacerPerspectiva () La función acepta el campo de visión vertical, la relación de aspecto y los puntos más cercanos y más lejanos como argumentos. No se mostrará nada que esté más cerca de 1 unidad y más de 10000 unidades, pero puede editar estos valores para obtener el efecto que está buscando. Ahora echemos un vistazo a estas dos funciones:

función MakePerspective (FOV, AspectRatio, Closest, Farest) var YLimit = Más cercano * Math.tan (FOV * Math.PI / 360); var A = - (Farest + Closest) / (Farest - Closest); var B = -2 * Farest * Closest / (Farest - Closest); var C = (2 * más cercano) / ((YLimit * AspectRatio) * 2); var D = (2 * más cercano) / (YLimit * 2); devuelva [C, 0, 0, 0, 0, D, 0, 0, 0, 0, A, -1, 0, 0, B, 0];  función MakeTransform (Object) return [1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, -6, 1]; 

Ambas matrices afectan el aspecto final de sus objetos, pero la matriz de perspectiva edita su 'mundo 3D' como el campo de visión y los objetos visibles, mientras que la matriz de transformación edita los objetos individuales como su escala y posición de rotación. Una vez hecho esto, estamos casi listos para dibujar, todo lo que queda es una función para convertir una imagen en una textura WebGL..


Paso 4: Cargando Texturas

Cargar una textura es un proceso de dos pasos. Primero tenemos que cargar una imagen como lo harías en una aplicación de JavaScript estándar, y luego tenemos que convertirla en una textura WebGL. Así que empecemos con la segunda parte ya que ya estamos en el archivo JS. Agregue lo siguiente en la parte inferior de la función WebGL justo después del comando Dibujar:

this.LoadTexture = function (Img) // Crear una nueva textura y asignarla como la activa var TempTex = this.GL.createTexture (); this.GL.bindTexture (this.GL.TEXTURE_2D, TempTex); // Flip Positive Y (Opcional) this.GL.pixelStorei (this.GL.UNPACK_FLIP_Y_WEBGL, true); // Cargar en la imagen this.GL.texImage2D (this.GL.TEXTURE_2D, 0, this.GL.RGBA, this.GL.RGBA, this.GL.UNSIGNED_BYTE, Img); // Configurar las propiedades de escalamiento this.GL.texParameteri (this.GL.TEXTURE_2D, this.GL.TEXTURE_MAG_FILTER, this.GL.LINEAR); this.GL.texParameteri (this.GL.TEXTURE_2D, this.GL.TEXTURE_MIN_FILTER, this.GL.LINEAR_MIPMAP_NEAREST); this.GL.generateMipmap (this.GL.TEXTURE_2D); // Desenlazar la textura y devolverla. this.GL.bindTexture (this.GL.TEXTURE_2D, null); vuelve TempTex; ;

Vale la pena señalar que sus texturas deben estar en tamaños de bytes iguales, o recibirá un error; por lo que tienen que ser dimensiones, como 2x2, 4x4, 16x16, 32x32, etc. Agregué la línea para voltear las coordenadas Y simplemente porque las coordenadas Y de mi aplicación 3D estaban atrasadas, pero dependerá de lo que esté usando. Esto se debe a que algunos programas hacen de 0 en el eje Y la esquina superior izquierda y algunas aplicaciones lo hacen en la esquina inferior izquierda. Las propiedades de escala que configuro solo le dicen a WebGL cómo la imagen debería aumentar o disminuir la escala. Puedes jugar con diferentes opciones para obtener diferentes efectos, pero pensé que funcionaban mejor.

Ahora que hemos terminado con el archivo JS, volvamos al archivo HTML e implementemos todo esto.


Paso 5: Envolviéndolo

Como mencioné anteriormente, WebGL se procesa en un elemento de lienzo. Eso es todo lo que necesitamos en la sección de cuerpo. Después de agregar el elemento del lienzo, su página html debería verse como la siguiente:

        Tu navegador no es compatible con el lienzo de HTML5.     

Es una página bastante simple. En el área de la cabeza he vinculado a nuestro archivo JS. Ahora vamos a implementar nuestra función Ready, que se llama cuando se carga la página:

// Esto mantendrá nuestra variable WebGL var GL; // Nuestra textura terminada var Texture; // Esto mantendrá la imagen de texturas var TextureImage; función Ready () GL = new WebGL ("GLCanvas", "FragmentShader", "VertexShader"); TextureImage = nueva imagen (); TextureImage.onload = function () Texture = GL.LoadTexture (TextureImage); GL.Draw (Cubo, Textura); ; TextureImage.src = "Texture.png"; 

Así que creamos un nuevo objeto WebGL y pasamos las ID para el lienzo y los sombreadores. A continuación, cargamos la imagen de textura. Una vez cargado, llamamos al Dibujar() Método con el cubo y la textura. Si siguió adelante, su pantalla debería tener un cubo estático con una textura en ella..

Ahora, aunque dije que cubriremos las transformaciones la próxima vez, no puedo dejarlos con un cuadrado estático; no es lo suficientemente 3D Volvamos atrás y añadamos una pequeña rotación. En el archivo HTML, cambie el onload Funciona para parecerse así:

TextureImage.onload = function () Texture = GL.LoadTexture (TextureImage); setInterval (Update, 33); ;

Esto llamará a una función llamada Actualizar() cada 33 milisegundos, lo que nos dará una velocidad de fotogramas de aproximadamente 30 fps. Aquí está la función de actualización:

función Update () GL.GL.clear (16384 | 256); GL.Draw (GL.Cube, Textura); 

Esta es una función bastante simple; limpia la pantalla y luego dibuja el cubo actualizado. Ahora, vamos al archivo JS para agregar el código de rotación.


Paso 6: Agregando Algunos Giros

No voy a implementar transformaciones completamente, porque las estoy guardando para la próxima vez, pero agreguemos una rotación alrededor del eje Y. Lo primero que debe hacer es agregar una variable de Rotación a nuestro objeto Cubo. Esto mantendrá un registro del ángulo actual y nos permitirá continuar incrementando la rotación. Así que la parte superior de su variable Cubo debería verse así:

var Cube = Rotación: 0, // Las otras tres matrices;

Ahora vamos a actualizar el MakeTransform () Función para incorporar la rotación:

función MakeTransform (Object) var y = Object.Rotation * (Math.PI / 180.0); var A = Math.cos (y); var B = -1 * Math.sin (y); var C = Math.sin (y); var D = Math.cos (y); Object.Rotation + = .3; devuelva [A, 0, B, 0, 0, 1, 0, 0, C, 0, D, 0, 0, 0, -6, 1]; 

Conclusión

¡Y eso es! En el siguiente tutorial, cubriremos la carga de modelos y la realización de transformaciones. Espero que disfrutes este tutorial; no dude en dejar cualquier pregunta o comentario que pueda tener a continuación.