You are on page 1of 29

9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal

Hiring? Toptal handpicks top JavaScript engineers to suit your needs.

Start hiring
Login

Top 3%
Why
Clients
Partners
Community
Blog
About Us
Start hiring
Apply as a Developer
Login
Questions?
Contact Us

Search Topics
Hire a developer

Grficos 3D: Un Tutorial de WebGL


View all articles

by Adnan Ademovic - Freelance Software Engineer @ Toptal (translated by Marisela Ordaz)

#3DGraphics #Tutorial #WebGL

0shares

This article was originally written in English

El mundo de los grficos 3D puede ser muy intimidante al principio. Ya sea que slo desee crear un logotipo 3D interactivo o disear un juego
completo, si no conoces los principios de la representacin en 3D, te puedes quedar atrapado en una biblioteca que abstrae un montn de cosas.

Usar una biblioteca puede ser la herramienta adecuada y JavaScript tiene una increble fuente abierta en forma de three.js. Hay algunas desventajas
en el uso de soluciones pre-hechas, aunque:

Pueden tener muchas caractersticas que no planeas utilizar. El tamao de las caractersticas base minificadas tres.js es alrededor de 500kB,
y cualquier caracterstica adicional (cargar archivos de modelo real es una de ellas) hacen que la carga til sea an ms grande. Transferir
esa cantidad de datos slo para mostrar un logotipo giratorio en su sitio web sera un desperdicio.
Una capa extra de abstraccin puede hacer modificaciones que normalmente son fciles, difciles de hacer. Su forma creativa de sombrear un
objeto en la pantalla puede ser sencilla de implementar o requerir decenas de horas de trabajo para incorporar en las abstracciones de la
biblioteca.
https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 1/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
Mientras que la biblioteca se optimiza muy bien en la mayora de los escenarios, se pueden eliminar adornos extras para tu caso de uso
particular. El renderizador puede hacer que ciertos procedimientos se ejecuten millones de veces en la tarjeta grfica. Cada instruccin
eliminada de tal procedimiento significa que una tarjeta grfica ms dbil puede manejar tu contenido sin problemas.

Incluso si decides utilizar una biblioteca de grficos de alto nivel, tener conocimientos bsicos de las cosas ms escondidas te permite utilizarla
con mayor eficacia. Las bibliotecas tambin pueden tener funciones avanzadas, como ShaderMaterial en three.js. El conocer los principios de
representacin grfica te permite utilizar tales caractersticas.

Nuestro objetivo es dar una breve introduccin a todos los conceptos clave detrs de la prestacin de grficos 3D y el uso de WebGL para su
aplicacin. Vers que lo ms comn que se hace es mostrar y mover objetos 3D en un espacio vaco.

El cdigo final est disponible para que experimentes y juegues con l.

Representando Modelos 3D
Lo primero que tendras que entender es cmo se representan los modelos 3D. Un modelo est hecho de una malla de tringulos. Cada tringulo
est representado por tres vrtices para cada una de las esquinas del tringulo. Hay tres propiedades unidas a los vrtices que son las ms comunes.

Posicin del Vrtice


La posicin es la propiedad ms intuitiva de un vrtice. Es la posicin en el espacio 3D, representada por un vector 3D de coordenadas. Si conoces
las coordenadas exactas de tres puntos en el espacio, tendras toda la informacin que necesita para dibujar un tringulo simple entre ellos. Para
que los modelos parezcan realmente buenos cuando se procesan, hay un par de cosas ms que deben proporcionarse al renderizador.

Vrtice Normal

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 2/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal

Considera los dos modelos anteriores. Consisten en las mismas posiciones del vrtice aunque se ven totalmente diferente cuando son
representadas. Cmo es eso posible?

Adems de decirle al renderer donde queremos que se encuentre un vrtice, tambin podemos darle una pista sobre cmo la superficie est
inclinada en esa posicin exacta. La pista est en la forma del vector normal de la superficie en ese punto especfico del modelo, representado con
un vector 3D. La siguiente imagen debe darte un aspecto ms descriptivo de cmo se maneja.

Las superficies de la izquierda y derecha corresponden a la bola izquierda y derecha de la imagen anterior, respectivamente. Las flechas rojas
representan los vectores normales que se especifican para un vrtice, mientras que las flechas azules representan los clculos del procesador de
cmo un vector normal debe buscar todos los puntos entre los vrtices. La imagen muestra una demostracin para el espacio 2D, pero el mismo
principio se aplica en 3D.

El vector normal es un indicio de cmo las luces iluminarn la superficie. Cuanto ms cerca est la direccin de un rayo de luz al vector normal,
ms brillante es el punto. Tener cambios graduales en direccin al vector normal causa gradientes ligeros, mientras que tener cambios abruptos sin
cambios entre estos, da como resultado superficies con iluminacin constante a travs de ellos, al igual que cambios repentinos en la iluminacin.

Coordenadas de Textura
La ltima propiedad significativa son las coordenadas de textura, comnmente referidas como mapeo UV. Tienes un modelo y una textura que
deseas aplicarle. La textura tiene varias reas y stas representan las imgenes que queremos aplicar a diferentes partes del modelo. Debe haber
una manera de marcar qu tringulo debe representarse con qu parte de la textura. Ah es donde entra el mapeo de textura.

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 3/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
Para cada vrtice, marcamos dos coordenadas, U y V. Estas coordenadas representan una posicin en la textura, con U representando el eje
horizontal y V el eje vertical. Los valores no estn en pxeles sino en una posicin porcentual dentro de la imagen. La esquina inferior izquierda de
la imagen se representa con dos ceros, mientras que la parte superior derecha se representa con dos unos.

Un tringulo se pinta tomando las coordenadas UV de cada vrtice en el tringulo y aplicando la imagen que se captura entre esas coordenadas en
la textura.

Puedes ver una demostracin de mapeo UV en la imagen de arriba. El modelo esfrico fue tomado y cortado en partes que son lo suficientemente
pequeas para ser aplanadas en una superficie 2D. Las costuras donde se realizaron los cortes estn marcadas con lneas ms gruesas. Uno de los
parches ha sido resaltado, as que puedes ver bien cmo las cosas coinciden. Tambin puedes ver cmo una costura a travs de la mitad de la
sonrisa coloca partes de la boca en dos parches diferentes.

Los wireframes no forman parte de la textura, pero se superponen sobre la imagen para que puedas ver cmo se correlacionan las cosas.

Cargando un modelo OBJ


Lo creas o no, esto es todo lo que necesitas saber para crear tu propio cargador de modelos simple. El formato de archivo OBJ es lo
suficientemente simple para implementar un analizador en unas pocas lneas de cdigo.

El archivo enlista las posiciones de los vrtices en un formato v <float> <float> <float>, con un cuarto float opcional, que ignoraremos, para
mantener las cosas simples. Los vrtices se representan de forma similar con vn <float> <float> <float>. Finalmente, las coordenadas de textura
se representan con vt <float> <float>, con un tercer float opcional que seguiremos ignorando. En los tres casos, los float representan las
respectivas coordenadas. Estas tres propiedades se acumulan en tres matrices.

Los rostros se representan con grupos de vrtices. Cada vrtice se representa con el ndice de cada una de las propiedades, por lo que los ndices
comienzan en 1. Hay varias maneras de cmo esto est representado, pero nos adheriremos a la v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3`, requiriendo
que las tres propiedades sean proporcionadas y limitando a tres el nmero de vrtices por cada cara. Todas estas limitaciones se estn haciendo
para mantener el cargador tan simple como sea posible, ya que todas las dems opciones requieren algn procesamiento extra trivial antes de que
puedan estar en un formato que a WebGL le guste.

Hemos puesto muchos requisitos para nuestro cargador de archivos. Eso puede sonar limitante, pero las aplicaciones de modelado 3D tienden a
darle la capacidad de establecer esas limitaciones al exportar un modelo como un archivo OBJ.

El siguiente cdigo analiza una cadena que representa un archivo OBJ y crea un modelo en forma de una matriz de rostros.
function Geometry (faces) {
this.faces = faces || []
}

// Parses an OBJ file, passed as a string


Geometry.parseOBJ = function (src) {
var POSITION = /^v\s+([\d\.\+\-eE]+)\s+([\d\.\+\-eE]+)\s+([\d\.\+\-eE]+)/
var NORMAL = /^vn\s+([\d\.\+\-eE]+)\s+([\d\.\+\-eE]+)\s+([\d\.\+\-eE]+)/
var UV = /^vt\s+([\d\.\+\-eE]+)\s+([\d\.\+\-eE]+)/
var FACE = /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/

lines = src.split('\n')
var positions = []
var uvs = []
var normals = []
var faces = []

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 4/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
lines.forEach(function (line) {
// Match each line of the file against various RegEx-es
var result
if ((result = POSITION.exec(line)) != null) {
// Add new vertex position
positions.push(new Vector3(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3])))
} else if ((result = NORMAL.exec(line)) != null) {
// Add new vertex normal
normals.push(new Vector3(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3])))
} else if ((result = UV.exec(line)) != null) {
// Add new texture mapping point
uvs.push(new Vector2(parseFloat(result[1]), 1 - parseFloat(result[2])))
} else if ((result = FACE.exec(line)) != null) {
// Add new face
var vertices = []
// Create three vertices from the passed one-indexed indices
for (var i = 1; i < 10; i += 3) {
var part = result.slice(i, i + 3)
var position = positions[parseInt(part[0]) - 1]
var uv = uvs[parseInt(part[1]) - 1]
var normal = normals[parseInt(part[2]) - 1]
vertices.push(new Vertex(position, normal, uv))
}
faces.push(new Face(vertices))
}
})

return new Geometry(faces)


}

// Loads an OBJ file from the given URL, and returns it as a promise
Geometry.loadOBJ = function (url) {
return new Promise(function (resolve) {
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
resolve(Geometry.parseOBJ(xhr.responseText))
}
}
xhr.open('GET', url, true)
xhr.send(null)
})
}

function Face (vertices) {


this.vertices = vertices || []
}

function Vertex (position, normal, uv) {


this.position = position || new Vector3()
this.normal = normal || new Vector3()
this.uv = uv || new Vector2()
}

function Vector3 (x, y, z) {


this.x = Number(x) || 0
this.y = Number(y) || 0
this.z = Number(z) || 0
}

function Vector2 (x, y) {


this.x = Number(x) || 0
this.y = Number(y) || 0
}

La estructura Geometry contiene los datos exactos necesarios para enviar un modelo a la tarjeta grfica para ser procesados. Antes de hacer eso, es
probable que quieras tener la capacidad de mover el modelo en la pantalla.

Realizacin de Transformaciones Espaciales


Todos los puntos en el modelo que cargamos son relativos a su sistema de coordenadas. Si queremos traducir, rotar y escalar el modelo, lo que
debemos hacer es realizar esa operacin en su sistema de coordenadas. El sistema de coordenadas A, relativo al sistema de coordenadas B, est
definido por la posicin de su centro como vector p_ab y el vector para cada uno de sus ejes x_ab, y_ab y z_ab, representando la direccin de ese
eje. As que si un punto se mueve por 10 en el eje x del sistema de coordenadas A, entonces en el sistema de coordenadas B se mover en la
direccin de x_ab, multiplicado por 10.

Toda esta informacin se almacena en el siguiente formulario matriz:


x_ab.x y_ab.x z_ab.x p_ab.x
x_ab.y y_ab.y z_ab.y p_ab.y

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 5/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
x_ab.z y_ab.z z_ab.z p_ab.z
0 0 0 1

Si queremos transformar el vector 3D q, slo tenemos que multiplicar la matriz de transformacin con el vector:
q.x
q.y
q.z
1

Esto hace que el punto se mueva por q.x a lo largo del nuevo eje x, por q.y a lo largo del nuevo eje y, y por q.z a lo largo del nuevo eje z.
Finalmente hace que el punto se mueva adicionalmente por el vector p, que es la razn por la que usamos un uno como el elemento final de la
multiplicacin.

La gran ventaja de utilizar estas matrices es el hecho de que si tenemos mltiples transformaciones a realizar en el vrtice, podemos fusionarlas en
una transformacin, multiplicando sus matrices antes de transformar el vrtice mismo.

Hay varias transformaciones que se pueden realizar, y echaremos un vistazo a las principales.

Sin Transformacin
Si no ocurre ninguna transformacin, entonces el vector p es un vector cero, el vector x es [1, 0, 0], y es [0, 1, 0] y z es [0, 0, 1]. A partir de
ahora nos referiremos a estos valores como valores por defecto para estos vectores. La aplicacin de estos valores nos da una matriz de identidad:
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

ste es un buen punto de partida para encadenar transformaciones.

Traduccin

Al momento de realizar la traduccin todos los vectores, excepto el vector p, tienen sus valores por defecto. Esto da lugar a la siguiente matriz:

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 6/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
1 0 0 p.x
0 1 0 p.y
0 0 1 p.z
0 0 0 1

Ajuste

Ajustar un modelo significa reducir la cantidad que cada coordenada contribuye a la posicin de un punto. No existe un desplazamiento uniforme
causado por el ajuste, por lo que el vector p mantiene su valor predeterminado. Los vectores de eje predeterminados deben multiplicarse por sus
respectivos factores de escala, lo que da como resultado la siguiente matriz:
s_x 0 0 0
0 s_y 0 0
0 0 s_z 0
0 0 0 1

Aqu s_x, s_y, y s_z representan el ajuste aplicado a cada eje.

Rotacin

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 7/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal

La imagen anterior muestra lo que sucede cuando giramos el marco de coordenadas alrededor del eje Z.

La rotacin no da lugar a un desplazamiento uniforme, por lo que el vector p mantiene su valor por defecto. Ahora las cosas se ponen un poco ms
complicadas. Las rotaciones causan que el movimiento a lo largo de cierto eje en el sistema de coordenadas original se mueva en una direccin
diferente. Por lo tanto, si giramos un sistema de coordenadas 45 grados alrededor del eje Z, movindonos a lo largo del eje x del sistema de
coordenadas original, se produce un movimiento en una direccin diagonal entre el eje x y el eje y en el nuevo sistema de coordenadas.

Para simplificar las cosas, le mostraremos cmo las matrices de transformacin buscan rotaciones alrededor de los ejes principales.
Around X:
1 0 0 0
0 cos(phi) sin(phi) 0
0 -sin(phi) cos(phi) 0
0 0 0 1

Around Y:
cos(phi) 0 sin(phi) 0
0 1 0 0
-sin(phi) 0 cos(phi) 0
0 0 0 1

Around Z:
cos(phi) -sin(phi) 0 0
sin(phi) cos(phi) 0 0
0 0 1 0
0 0 0 1

Implementacin
Todo esto se puede implementar como una clase que almacena 16 nmeros, almacenando matrices en un orden de columna mayor.
function Transformation () {
// Create an identity transformation
this.fields = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
}

// Multiply matrices, to chain transformations


Transformation.prototype.mult = function (t) {
https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 8/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
var output = new Transformation()
for (var row = 0; row < 4; ++row) {
for (var col = 0; col < 4; ++col) {
var sum = 0
for (var k = 0; k < 4; ++k) {
sum += this.fields[k * 4 + row] * t.fields[col * 4 + k]
}
output.fields[col * 4 + row] = sum
}
}
return output
}

// Multiply by translation matrix


Transformation.prototype.translate = function (x, y, z) {
var mat = new Transformation()
mat.fields[12] = Number(x) || 0
mat.fields[13] = Number(y) || 0
mat.fields[14] = Number(z) || 0
return this.mult(mat)
}

// Multiply by scaling matrix


Transformation.prototype.scale = function (x, y, z) {
var mat = new Transformation()
mat.fields[0] = Number(x) || 0
mat.fields[5] = Number(y) || 0
mat.fields[10] = Number(z) || 0
return this.mult(mat)
}

// Multiply by rotation matrix around X axis


Transformation.prototype.rotateX = function (angle) {
angle = Number(angle) || 0
var c = Math.cos(angle)
var s = Math.sin(angle)
var mat = new Transformation()
mat.fields[5] = c
mat.fields[10] = c
mat.fields[9] = -s
mat.fields[6] = s
return this.mult(mat)
}

// Multiply by rotation matrix around Y axis


Transformation.prototype.rotateY = function (angle) {
angle = Number(angle) || 0
var c = Math.cos(angle)
var s = Math.sin(angle)
var mat = new Transformation()
mat.fields[0] = c
mat.fields[10] = c
mat.fields[2] = -s
mat.fields[8] = s
return this.mult(mat)
}

// Multiply by rotation matrix around Z axis


Transformation.prototype.rotateZ = function (angle) {
angle = Number(angle) || 0
var c = Math.cos(angle)
var s = Math.sin(angle)
var mat = new Transformation()
mat.fields[0] = c
mat.fields[5] = c
mat.fields[4] = -s
mat.fields[1] = s
return this.mult(mat)
}

Mira a Travs de una Cmara


Aqu viene la parte clave de la presentacin de objetos en la pantalla: la cmara. Hay dos componentes clave para una cmara; Su posicin, y
cmo proyecta objetos observados en la pantalla.

La posicin de la cmara se maneja con un simple truco. No hay diferencia visual entre mover la cmara un metro adelante y mover el mundo
entero un metro hacia atrs. As que naturalmente, hacemos esto ltimo aplicando la inversa de la matriz como una transformacin.

El segundo componente clave es la forma en que los objetos observados se proyectan sobre el lente. En WebGL, todo lo visible en la pantalla se
encuentra en una caja. La caja se extiende entre -1 y 1 en cada eje. Todo lo visible est dentro de esa caja. Podemos utilizar el mismo enfoque de
matrices de transformacin para crear una matriz de proyeccin.

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 9/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal

Proyeccin Ortogrfica

La proyeccin ms simple es la [proyeccin ortogrfica] (https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-


orthographical-projection-matrix/orthographic-projection-matrix). Se toma una caja en el espacio que indica el ancho, la altura y la profundidad
con la suposicin de que su centro est en la posicin cero. A continuacin, la proyeccin redimensiona la caja para que se ajuste a la caja
previamente descrita dentro de la cual WebGL observa objetos. Dado que queremos cambiar el tamao de cada dimensin a dos, ajustamos cada
eje a 2/size, donde size es la dimensin del eje respectivo. Una pequea advertencia es el hecho de que estamos multiplicando el eje Z con un
negativo. Esto se hace porque queremos voltear la direccin de esa dimensin. La matriz final tiene esta forma:
2/width 0 0 0
0 2/height 0 0
0 0 -2/depth 0
0 0 0 1

Proyeccin Perspectiva

No vamos a pasar por los detalles de cmo se disea esta proyeccin, pero slo usa la frmula final, que es bastante estndar por ahora. Podemos
simplificarlo colocando la proyeccin en la posicin cero en los ejes x e y, haciendo los lmites derecho/izquierdo y superior/inferior iguales a
width/2 y height/2, respectivamente. Los parmetros n y f representan los planos de recorte near y far, que son la distancia ms pequea y ms
grande que un punto puede ser para poder ser capturado por la cmara. Estn representados por los lados paralelos del frustum en la imagen
anterior.

Una proyeccin de perspectiva suele estar representada con un campo de visin (well use the vertical one), relacin de aspecto, y las distancias de
plano cercano y lejano. Esa informacin se puede utilizar para calcular width y height, y entonces la matriz se puede crear a partir de la siguiente

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 10/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
plantilla:

2*n/width 0 0 0
0 2*n/height 0 0
0 0 (f+n)/(n-f) 2*f*n/(n-f)
0 0 -1 0

Para calcular el ancho y la altura, se pueden usar las siguientes frmulas:


height = 2 * near * Math.tan(fov * Math.PI / 360)
width = aspectRatio * height

El FOV (campo de visin) representa el ngulo vertical que la cmara captura con su lente. La relacin de aspecto representa la relacin entre el
ancho y la altura de la imagen y se basa en las dimensiones de la pantalla que estamos mostrando.

Implementacin
Ahora podemos representar una cmara como una clase, la cual almacena la posicin de la cmara y la matriz de proyeccin. Tambin necesitamos
saber cmo calcular las transformaciones inversas. Resolver las inversiones de matriz generales puede ser problemtico, pero existe un enfoque
simplificado para nuestro caso en especial.
function Camera () {
this.position = new Transformation()
this.projection = new Transformation()
}

Camera.prototype.setOrthographic = function (width, height, depth) {


this.projection = new Transformation()
this.projection.fields[0] = 2 / width
this.projection.fields[5] = 2 / height
this.projection.fields[10] = -2 / depth
}

Camera.prototype.setPerspective = function (verticalFov, aspectRatio, near, far) {


var height_div_2n = Math.tan(verticalFov * Math.PI / 360)
var width_div_2n = aspectRatio * height_div_2n
this.projection = new Transformation()
this.projection.fields[0] = 1 / height_div_2n
this.projection.fields[5] = 1 / width_div_2n
this.projection.fields[10] = (far + near) / (near - far)
this.projection.fields[10] = -1
this.projection.fields[14] = 2 * far * near / (near - far)
this.projection.fields[15] = 0
}

Camera.prototype.getInversePosition = function () {
var orig = this.position.fields
var dest = new Transformation()
var x = orig[12]
var y = orig[13]
var z = orig[14]
// Transpose the rotation matrix
for (var i = 0; i < 3; ++i) {
for (var j = 0; j < 3; ++j) {
dest.fields[i * 4 + j] = orig[i + j * 4]
}
}

// Translation by -p will apply R^T, which is equal to R^-1


return dest.translate(-x, -y, -z)
}

Esta es la pieza final que necesitamos antes de que podamos empezar a dibujar en la pantalla.

Dibujar un objeto con el canal de Grficos WebGL


La superficie ms simple que puedes dibujar es un tringulo. De hecho, la mayora de las cosas que dibujas en el espacio 3D consisten en un gran
nmero de tringulos.

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 11/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal

Lo primero que debes entender es cmo se representa la pantalla en WebGL. Es un espacio 3D, que abarca entre -1 y 1 en el eje x, y y z. De forma
predeterminada, este eje z no se utiliza, pero ests interesado en grficos 3D, por lo que debers habilitarlo de inmediato.

Teniendo esto en cuenta, lo que sigue son tres pasos necesarios para dibujar un tringulo sobre esta superficie.

Puedes definir tres vrtices que representaran el tringulo que deseas dibujar. Los datos se serializan y se envan a la GPU (unidad de
procesamiento grfico). Con un modelo entero disponible, puedes hacer eso para todos los tringulos en el modelo. Las posiciones de vrtice que
das estn en el espacio de coordenadas local del modelo que cargaste. Puesto de manera ms simple, las posiciones que proporcionas son las
exactas del archivo y no las que consigues despus de realizar transformaciones de la matriz.

Ahora que le has dado los vrtices a la GPU, le dices a la GPU qu lgica usar cuando colocas los vrtices en la pantalla. Este paso se utilizar
para aplicar nuestras transformaciones de matriz. La GPU es muy buena para multiplicar muchas matrices 4x4, por lo que pondremos esa
caracterstica a buen uso.

En el ltimo paso, la GPU rasterizar ese tringulo. La rasterizacin es el proceso de tomar grficos vectoriales y determinar qu pxeles de la
pantalla necesitan ser pintados, para que el objeto grfico vectorial se muestre. En nuestro caso, la GPU est tratando de determinar qu pxeles se
encuentran dentro de cada tringulo. Para cada pxel, la GPU te preguntar de qu color deseas que est pintado.

Estos son los cuatro elementos necesarios para dibujar lo que quieras y son el ejemplo ms simple de un canal de grficos. Lo que sigue es una
mirada a cada uno de ellos y una implementacin simple.

Like what you're reading?


Get the latest updates first.
Enter your email address...
Get Exclusive Updates
No spam. Just great engineering posts.
Like what you're reading?
Get the latest updates first.
Thank you for subscribing!
Check your inbox to confirm subscription. You'll start receiving posts after you confirm.

122shares

El Framebuffer Predeterminado
El elemento ms importante para una aplicacin WebGL es el contexto WebGL. Puedes acceder a ste con gl = canvas.getContext ('webgl'), o
usar experimental-webgl' como alternativa, en caso de que el navegador utilizado actualmente no soporte todas las funciones de WebGL. El

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 12/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
lienzo al que nos referimos es el elemento DOM del lienzo en el que queremos dibujar. El contexto contiene muchas cosas, entre las cuales est
el framebuffer predeterminado.

Podra describir un framebuffer como cualquier buffer (objeto) en el que puedas dibujar. De forma predeterminada, el framebuffer predeterminado
almacena el color de cada pxel del lienzo al que est vinculado el contexto de WebGL. Como se describe en la seccin anterior, cuando dibujamos
sobre el framebuffer, cada pxel est situado entre -1 y 1 en el eje x y y. Algo que tambin mencionamos es el hecho de que, por defecto, WebGL
no utiliza el eje z. Esa funcionalidad se puede habilitar al ejecutar gl.enable (gl.DEPTH_TEST). Genial, pero qu es una prueba de profundidad?

Habilitar la prueba de profundidad permite que un pxel almacene tanto el color como la profundidad. La profundidad es la coordenada z de ese
pxel. Despus de dibujar un pxel a una cierta profundidad z, para actualizar el color de ese pxel debes dibujar en una posicin z que est ms
cerca de la cmara. De lo contrario, el intento de dibujo ser ignorado. Esto permite la ilusin de 3D ya que dibujar objetos que estn detrs de
otros objetos har que los objetos sean ocluidos por otros objetos delante de ellos.

Cualquier dibujo que realices permanecer en la pantalla hasta que les digas que se despejen. Para ello, tienes que llamar a gl.clear
(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT). Esto borra tanto el color como el buffer de profundidad. Para elegir el color al cual se ajustan los
pxeles borrados, usa gl.clearColor (rojo, verde, azul, alfa).

Vamos a crear un procesador que utiliza un lienzo y lo borra a peticin:


function Renderer (canvas) {
var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
gl.enable(gl.DEPTH_TEST)
this.gl = gl
}

Renderer.prototype.setClearColor = function (red, green, blue) {


gl.clearColor(red / 255, green / 255, blue / 255, 1)
}

Renderer.prototype.getContext = function () {
return this.gl
}

Renderer.prototype.render = function () {
this.gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
}

var renderer = new Renderer(document.getElementById('webgl-canvas'))


renderer.setClearColor(100, 149, 237)

loop()

function loop () {
renderer.render()
requestAnimationFrame(loop)
}

Adjuntar esta secuencia de comandos al siguiente HTML dar un rectngulo azul brillante en la pantalla
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="webgl-canvas" width="800" height="500"></canvas>
<script src="script.js"></script>
</body>
</html>

El llamado requestAnimationFrame hace que el nudo sea llamado nuevamente tan pronto como se haya hecho el renderizado anterior y se haya
terminado todo el manejo del evento.

Objetos de Vrtices Buffer


Lo primero que debes hacer es definir los vrtices que deseas dibujar. Puedes hacer esto describindolos a travs de vectores en el espacio 3D.
Despus de eso, debes mover esos datos a la memoria RAM de la GPU, creando un nuevo Buffer Vertex Buffer (VBO).

Un Objeto Buffer en general es un objeto que almacena una matriz de fragmentos de memoria en la GPU. Al ser un VBO, ste slo denota para lo
que la GPU puede utilizar la memoria. La mayora de las veces, los objetos de buffer que crees sern VBOs.

Puedes llenar el VBO tomando todos los vrtices N que tenemos y creando una matriz de floats con elementos 3N para la posicin de vrtice y
VBOs normales de vrtice, y 2N para las coordenadas de textura VBO. Cada grupo de tres floats, o dos floats para coordenadas UV, representa
coordenadas individuales de un vrtice. Luego pasamos estas matrices a la GPU, y nuestros vrtices estn listos para el resto del canal.

Dado que la data est ahora en la RAM de la GPU, puedes eliminarla de la memoria RAM de uso general. Es decir, a menos que desees
modificarla ms tarde y volver a cargarla. Cada modificacin debe ser seguida por una subida ya que las modificaciones en nuestras matrices JS no
se aplican a VBOs en la memoria RAM real de la GPU.

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 13/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
A continuacin se muestra un ejemplo de cdigo, el cual proporciona toda la funcionalidad descrita. Una nota importante es el hecho de que las
variables almacenadas en la GPU no son recolectadas de la basura. Eso significa que tenemos que eliminarlas manualmente, una vez que no
queremos usarlas ms. Slo te daremos un ejemplo de cmo se hace aqu y no nos centrar ms en ese concepto. La supresin de variables de la
GPU slo es necesaria si piensa dejar de usar cierta geometra en todo el programa.

Tambin aadimos serializacin a nuestra clase Geometry y elementos dentro de ella.


Geometry.prototype.vertexCount = function () {
return this.faces.length * 3
}

Geometry.prototype.positions = function () {
var answer = []
this.faces.forEach(function (face) {
face.vertices.forEach(function (vertex) {
var v = vertex.position
answer.push(v.x, v.y, v.z)
})
})
return answer
}

Geometry.prototype.normals = function () {
var answer = []
this.faces.forEach(function (face) {
face.vertices.forEach(function (vertex) {
var v = vertex.normal
answer.push(v.x, v.y, v.z)
})
})
return answer
}

Geometry.prototype.uvs = function () {
var answer = []
this.faces.forEach(function (face) {
face.vertices.forEach(function (vertex) {
var v = vertex.uv
answer.push(v.x, v.y)
})
})
return answer
}

////////////////////////////////

function VBO (gl, data, count) {


// Creates buffer object in GPU RAM where we can store anything
var bufferObject = gl.createBuffer()
// Tell which buffer object we want to operate on as a VBO
gl.bindBuffer(gl.ARRAY_BUFFER, bufferObject)
// Write the data, and set the flag to optimize
// for rare changes to the data we're writing
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW)
this.gl = gl
this.size = data.length / count
this.count = count
this.data = bufferObject
}

VBO.prototype.destroy = function () {
// Free memory that is occupied by our buffer object
this.gl.deleteBuffer(this.data)
}

El tipo de datos VBO genera el VBO en el contexto WebGL pasado, basado en la matriz pasada como segundo parmetro.

Puedes ver tres llamadas al contexto gl. La llamada createBuffer () crea el buffer. La llamada bindBuffer () indica a la mquina de estado
WebGL que utilice esta memoria especfica como la actual VBO (ARRAY_BUFFER) para todas las operaciones futuras, hasta que se indique lo
contrario. Despus de eso, establecemos el valor de la VBO actual a los datos proporcionados, con bufferData ().

Tambin proporcionamos un mtodo de destruccin que elimina nuestro objeto de memoria intermedia de la GPU RAM, usando deleteBuffer().

Puedes utilizar tres VBOs y una transformacin para describir todas las propiedades de una malla, junto a su posicin.
function Mesh (gl, geometry) {
var vertexCount = geometry.vertexCount()
this.positions = new VBO(gl, geometry.positions(), vertexCount)
this.normals = new VBO(gl, geometry.normals(), vertexCount)
this.uvs = new VBO(gl, geometry.uvs(), vertexCount)
this.vertexCount = vertexCount
this.position = new Transformation()
https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 14/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
this.gl = gl
}

Mesh.prototype.destroy = function () {
this.positions.destroy()
this.normals.destroy()
this.uvs.destroy()
}

Como ejemplo, aqu se ve cmo podemos cargar un modelo, almacenar sus propiedades en la malla (mesh) y luego destruirlo:
Geometry.loadOBJ('/assets/model.obj').then(function (geometry) {
var mesh = new Mesh(gl, geometry)
console.log(mesh)
mesh.destroy()
})

Shaders
Lo que sigue es el proceso de dos pasos previamente descrito, que se trata de mover puntos en las posiciones deseadas y pintar todos los pxeles
individuales. Para ello, escribimos un programa que se ejecuta en la tarjeta grfica muchas veces. Este programa normalmente consta de al menos
dos partes. La primera parte es un Shader de Vrtices, que se ejecuta para cada vrtice, y las salidas donde debemos colocar el vrtice en la
pantalla, entre otras cosas. La segunda parte es el Fragmento de Shader, que se ejecuta para cada pxel que cubre un tringulo en la pantalla, y
produce el color en el cual el pxel debe ser pintado.

Shaders de Vrtices

Digamos que quieres tener un modelo que se mueve de izquierda y derecha en la pantalla. En un enfoque ingenuo, puedes actualizar la posicin de
cada vrtice y volver a enviarlo a la GPU. Ese proceso es costoso y lento. Alternativamente, daras un programa para que la GPU se ejecute para
cada vrtice y hacer todas esas operaciones en paralelo con un procesador que est construido para hacer exactamente ese trabajo. se es el papel
de un shader de vrtices.

Un shader de vrtices es la parte de la canalizacin de procesamiento que procesa vrtices individuales. Una llamada al shader de vrtices recibe
un nico vrtice y genera un nico vrtice despus de aplicar todas las transformaciones posibles al vrtice.

Shaders estn escritos en GLSL. Hay muchos elementos nicos en este lenguaje, pero la mayora de la sintaxis es muy similar a C, por lo que debe
ser comprensible para la mayora de las personas.

Hay tres tipos de variables que entran y salen de un shader de vrtices, y todas ellas sirven a un uso especfico:

atributo Estas son entradas que contienen propiedades especficas de un vrtice. Anteriormente describimos la posicin de un vrtice
como un atributo en forma de un vector de tres elementos. Puedes ver a los atributos como valores que describen un vrtice.
uniforme Estas son entradas que son las mismas para cada vrtice dentro de la misma llamada de renderizado. Digamos que queremos
mover nuestro modelo, definiendo una matriz de transformacin. Puedes usar una variable uniforme para describir eso. Tambin puedes usar
recursos de la GPU, como texturas. Puedes ver estos uniformes como valores que describen un modelo o una parte de un modelo.
variaciones Estas son las salidas que pasamos al fragmento de shader. Puesto que hay potencialmente miles de pxeles para un tringulo
de vrtices, cada pxel recibir un valor interpolado para esta variable, dependiendo de la posicin. As que si un vrtice enva 500 como una
salida, y en otro 100, un pxel que est en el medio de estos recibir 300 como entrada para esa variable. Puedes ver las variaciones como
valores que describen superficies entre vrtices.

Por lo tanto, digamos que deseas crear un shader de vrtices que reciba una posicin normal y coordenadas uv para cada vrtice, y una posicin
vista (posicin de cmara inversa) y una matriz de proyeccin para cada objeto representado. Digamos que tambin quieres pintar pxeles
individuales basados en sus coordenadas uv y sus normales. Te preguntars Cmo se vera ese cdigo?.
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
varying vec3 vNormal;
varying vec2 vUv;

void main() {
vUv = uv;
vNormal = (model * vec4(normal, 0.)).xyz;
gl_Position = projection * view * model * vec4(position, 1.);
}

La mayora de estos elementos deben ser auto-explicativos. Lo ms importante es notar que no hay valores de retorno en la funcin main. Todos los
valores que quisiramos retornar se asignan, ya sea a variables variantes o a variables especiales. Aqu asignamos a gl_Position, que es un vector
de cuatro dimensiones, por lo que la ltima dimensin siempre debe estar en uno. Otra cosa extraa que podras notar es la forma en que
construimos un vec4 fuera del vector de posicin. Puedes construir un vec4 usando cuatro floats, dos vec2s, o cualquier otra combinacin que
resulte en cuatro elementos. Hay un muchos tipos de castings, aparentemente, extraos que tienen sentido una vez que ests familiarizado con las
matrices de transformacin.
https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 15/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
Tambin puedes ver que aqu podemos realizar transformaciones de matriz fcilmente. GLSL est especficamente diseado para este tipo de
trabajo. La posicin de salida se calcula multiplicando la proyeccin, la vista y la matriz del modelo y aplicndola a la posicin. La salida normal
se acaba de transformar en el espacio mundial. Ms adelante explicaremos por qu nos hemos detenido all con las transformaciones normales.

Por ahora, lo mantendremos sencillo y pasaremos a pintar pxeles individuales.

Fragmento de Shaders
Un fragmento shader es el paso despus de la rasterizacin en el canal de grficos. Genera color, profundidad y otros datos para cada pxel del
objeto que se est pintando

Los principios detrs de la implementacin de shaders de fragmentos son muy similares a los shaders de vrtices. Sin embargo, hay tres grandes
diferencias:

No hay ms salidas variables, y las entradas atributos han sido reemplazadas con entradas `variadas. Acabamos de pasar a nuestro canal,
y las cosas que son la salida en el shader vrtice son ahora entradas en el fragmento de shader.
Nuestra nica salida ahora es gl_FragColor, que es un vec4. Los elementos representan rojo, verde, azul y alfa (RGBA), respectivamente,
con variables en el rango de 0 a 1. Debes mantener alfa en 1, a menos que ests haciendo la transparencia. Sin embargo, la transparencia es
un concepto bastante avanzado, por lo que nos atenemos a los objetos opacos.
Al principio del fragmento de shader, es necesario establecer la precisin del flotador, que es importante para las interpolaciones. En casi
todos los casos, slo se adhieren a las lneas del siguiente shader.

Con esto en mente, puedes escribir fcilmente un shader que pinta el canal rojo basado en la posicin U, canal verde basado en la posicin V, y fija
el canal azul al mximo.
#ifdef GL_ES
precision highp float;
#endif

varying vec3 vNormal;


varying vec2 vUv;

void main() {
vec2 clampedUv = clamp(vUv, 0., 1.);
gl_FragColor = vec4(clampedUv, 1., 1.);
}

La funcin clamp slo limita todos los floats en un objeto para estar dentro de los lmites dados. El resto del cdigo debe ser bastante sencillo.

Con todo esto en mente, lo que queda por hacer es implementar esto en WebGL.

Combinacin de Shaders en un Programa

El siguiente paso es combinar los shaders en un programa:


function ShaderProgram (gl, vertSrc, fragSrc) {
var vert = gl.createShader(gl.VERTEX_SHADER)
gl.shaderSource(vert, vertSrc)
gl.compileShader(vert)
if (!gl.getShaderParameter(vert, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(vert))
throw new Error('Failed to compile shader')
}

var frag = gl.createShader(gl.FRAGMENT_SHADER)


gl.shaderSource(frag, fragSrc)
gl.compileShader(frag)
if (!gl.getShaderParameter(frag, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(frag))
throw new Error('Failed to compile shader')
}

var program = gl.createProgram()


gl.attachShader(program, vert)
gl.attachShader(program, frag)
gl.linkProgram(program)
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program))
throw new Error('Failed to link program')
}

this.gl = gl
this.position = gl.getAttribLocation(program, 'position')
this.normal = gl.getAttribLocation(program, 'normal')
this.uv = gl.getAttribLocation(program, 'uv')
this.model = gl.getUniformLocation(program, 'model')
this.view = gl.getUniformLocation(program, 'view')
this.projection = gl.getUniformLocation(program, 'projection')
https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 16/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
this.vert = vert
this.frag = frag
this.program = program
}

// Loads shader files from the given URLs, and returns a program as a promise
ShaderProgram.load = function (gl, vertUrl, fragUrl) {
return Promise.all([loadFile(vertUrl), loadFile(fragUrl)]).then(function (files) {
return new ShaderProgram(gl, files[0], files[1])
})

function loadFile (url) {


return new Promise(function (resolve) {
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
resolve(xhr.responseText)
}
}
xhr.open('GET', url, true)
xhr.send(null)
})
}
}

No hay mucho que decir sobre lo que est sucediendo aqu. A cada shader se le asigna una cadena como fuente y se compila, despus de lo cual
verificamos si hay errores de compilacin. Entonces, creamos un programa enlazando estos dos shaders. Finalmente, almacenamos punteros para
todos los atributos y uniformes relevantes para la posteridad.

Realmente dibujando el modelo


Por ltimo, pero no menos importante, dibuja el modelo.

Primero escoge el programa de shader que deseas usar


ShaderProgram.prototype.use = function () {
this.gl.useProgram(this.program)
}

A continuacin, enva todos los uniformes relacionados con la cmara a la GPU. Estos uniformes cambian slo una vez por cada cambio de
cmara o movimiento.
Transformation.prototype.sendToGpu = function (gl, uniform, transpose) {
gl.uniformMatrix4fv(uniform, transpose || false, new Float32Array(this.fields))
}

Camera.prototype.use = function (shaderProgram) {


this.projection.sendToGpu(shaderProgram.gl, shaderProgram.projection)
this.getInversePosition().sendToGpu(shaderProgram.gl, shaderProgram.view)
}

Por ltimo, se toman las transformaciones y VBOs y se asignan a uniformes y atributos, respectivamente. Dado que esto tiene que hacerse a cada
VBO, puede crear su vinculacin de datos como un mtodo.
VBO.prototype.bindToAttribute = function (attribute) {
var gl = this.gl
// Tell which buffer object we want to operate on as a VBO
gl.bindBuffer(gl.ARRAY_BUFFER, this.data)
// Enable this attribute in the shader
gl.enableVertexAttribArray(attribute)
// Define format of the attribute array. Must match parameters in shader
gl.vertexAttribPointer(attribute, this.size, gl.FLOAT, false, 0, 0)
}

A continuacin, se asigna una matriz de tres floats para el uniforme. Cada tipo de uniforme tiene una firma diferente, por lo que la documentacin
y ms documentacin son tus amigos aqu. Finalmente, dibuja la matriz triangular en la pantalla. Le dice a la llamada de dibujo [drawArrays ()]
(https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawArrays) desde que vrtice puede iniciar, y cuntos vrtices
dibujar. El primer parmetro pasado le dice a WebGL cmo interpretar la matriz de vrtices. El uso de TRINGULOS toma tres por tres vrtices y
dibuja un tringulo para cada triplete. Usar PUNTOS slo dibujara un punto para cada vrtice pasado. Hay muchas ms opciones, pero no hay
necesidad de descubrir todo al mismo tiempo. A continuacin se muestra el cdigo para dibujar un objeto:
Mesh.prototype.draw = function (shaderProgram) {
this.positions.bindToAttribute(shaderProgram.position)
this.normals.bindToAttribute(shaderProgram.normal)
this.uvs.bindToAttribute(shaderProgram.uv)
this.position.sendToGpu(this.gl, shaderProgram.model)
this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertexCount)
}

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 17/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
El renderizador necesita ser extendido un poco, para acomodar todos los elementos adicionales que necesitan ser manejados. Debe ser posible
adjuntar un programa de shader y renderizar una matriz de objetos basada en la posicin actual de la cmara.
Renderer.prototype.setShader = function (shader) {
this.shader = shader
}

Renderer.prototype.render = function (camera, objects) {


this.gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
var shader = this.shader
if (!shader) {
return
}
shader.use()
camera.use(shader)
objects.forEach(function (mesh) {
mesh.draw(shader)
})
}

Podemos combinar todos los elementos que tenemos para finalmente dibujar algo en la pantalla:
var renderer = new Renderer(document.getElementById('webgl-canvas'))
renderer.setClearColor(100, 149, 237)
var gl = renderer.getContext()

var objects = []

Geometry.loadOBJ('/assets/sphere.obj').then(function (data) {
objects.push(new Mesh(gl, data))
})
ShaderProgram.load(gl, '/shaders/basic.vert', '/shaders/basic.frag')
.then(function (shader) {
renderer.setShader(shader)
})

var camera = new Camera()


camera.setOrthographic(16, 10, 10)

loop()

function loop () {
renderer.render(camera, objects)
requestAnimationFrame(loop)
}

Esto parece un poco aleatorio, pero se puede ver los diferentes parches de la esfera, en funcin de dnde estn en el mapa UV. Puedes cambiar el
shader para pintar el objeto marrn. Simplemente establece el color para cada pxel para que sea el RGBA para marrn:
#ifdef GL_ES
precision highp float;
#endif

varying vec3 vNormal;


varying vec2 vUv;

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 18/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
void main() {
vec3 brown = vec3(.54, .27, .07);
gl_FragColor = vec4(brown, 1.);
}

No parece muy convincente. Parece que la escena necesita algunos efectos de sombreado.

Aadiendo luz
Las luces y las sombras son las herramientas que nos permiten percibir la forma de los objetos. Las luces vienen en muchas formas y tamaos:
focos que brillan en un cono, bombillas que difunden luz en todas direcciones, y lo ms interesante, el sol, que est tan lejos que toda la luz que
brilla sobre nosotros irradia, para todos los intentos y en la misma direccin.

La luz solar suena como si fuera la ms sencilla de implementar ya que todo lo que necesita proporcionar es la direccin en la que se propagan
todos los rayos. Para cada pxel que dibujes en la pantalla, comprueba el ngulo bajo el cual la luz golpea el objeto. Aqu es donde entran las
normales de superficie.

Puedes ver todos los rayos de luz que fluyen en la misma direccin y golpear la superficie bajo diferentes ngulos, los cuales se basan en el ngulo
entre el rayo de luz y la normal de superficie. Cuanto ms coinciden, ms fuerte es la luz.

Si realizas un producto de punto entre los vectores normalizados para el rayo de luz y la normal de superficie, obtendrs -1 si el rayo golpea la
superficie perfectamente perpendicular, 0 si el rayo es paralelo a la superficie y 1 si lo ilumina desde el lado opuesto. As que cualquier cosa entre

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 19/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
0 y 1 no debe agregar luz, mientras que los nmeros entre 0 y -1 deben aumentar gradualmente la cantidad de luz que golpea el objeto. Puedes
probar esto agregando una luz fija en el cdigo shader.
#ifdef GL_ES
precision highp float;
#endif

varying vec3 vNormal;


varying vec2 vUv;

void main() {
vec3 brown = vec3(.54, .27, .07);
vec3 sunlightDirection = vec3(-1., -1., -1.);
float lightness = -clamp(dot(normalize(vNormal), normalize(sunlightDirection)), -1., 0.);
gl_FragColor = vec4(brown * lightness, 1.);
}

Pusimos el sol para que brille en la direccin hacia delante-izquierda-abajo. Puedes ver que tan suave es el shader, aunque el modelo es muy
irregular. Tambin se puede notar la oscuridad de la parte inferior izquierda. Podemos aadir un nivel de luz ambiente, lo que har ms brillante el
rea en la sombra.
#ifdef GL_ES
precision highp float;
#endif

varying vec3 vNormal;


varying vec2 vUv;

void main() {
vec3 brown = vec3(.54, .27, .07);
vec3 sunlightDirection = vec3(-1., -1., -1.);
float lightness = -clamp(dot(normalize(vNormal), normalize(sunlightDirection)), -1., 0.);
float ambientLight = 0.3;
lightness = ambientLight + (1. - ambientLight) * lightness;
gl_FragColor = vec4(brown * lightness, 1.);
}

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 20/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal

Puedes lograr este mismo efecto introduciendo una clase de luz que almacena la direccin de la luz y la intensidad de la luz ambiental. A
continuacin, puedes cambiar el fragmento de shader para acomodar esa adicin.

Ahora el shader se convierte en:


#ifdef GL_ES
precision highp float;
#endif

uniform vec3 lightDirection;


uniform float ambientLight;
varying vec3 vNormal;
varying vec2 vUv;

void main() {
vec3 brown = vec3(.54, .27, .07);
float lightness = -clamp(dot(normalize(vNormal), normalize(lightDirection)), -1., 0.);
lightness = ambientLight + (1. - ambientLight) * lightness;
gl_FragColor = vec4(brown * lightness, 1.);
}

Ahora puedes definir la luz:


function Light () {
this.lightDirection = new Vector3(-1, -1, -1)
this.ambientLight = 0.3
}

Light.prototype.use = function (shaderProgram) {


var dir = this.lightDirection
var gl = shaderProgram.gl
gl.uniform3f(shaderProgram.lightDirection, dir.x, dir.y, dir.z)
gl.uniform1f(shaderProgram.ambientLight, this.ambientLight)
}

En la clase del programa shader, agrega los uniformes necesarios:


this.ambientLight = gl.getUniformLocation(program, 'ambientLight')
this.lightDirection = gl.getUniformLocation(program, 'lightDirection')

En el programa, agrega una llamada a la nueva luz en el renderizador:


Renderer.prototype.render = function (camera, light, objects) {
this.gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
var shader = this.shader
if (!shader) {
return
}
shader.use()
light.use(shader)
camera.use(shader)
objects.forEach(function (mesh) {
mesh.draw(shader)
})
}

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 21/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
El nudo cambiar un poco:
var light = new Light()

loop()

function loop () {
renderer.render(camera, light, objects)
requestAnimationFrame(loop)
}

Si has hecho todo bien, entonces la imagen renderizada debe ser la misma que en la ltima imagen.

Un ltimo paso a considerar sera aadir una textura real a nuestro modelo. Vamos a hacer eso ahora.

Aadiendo texturas
HTML5 tiene un gran soporte para cargar imgenes, por lo que no hay necesidad de hacer el anlisis de imagen muy intenso. Las imgenes se
pasan a GLSL como sampler2D dicindole al shader cul de las texturas enlazadas debe muestrear. Hay un nmero limitado de texturas que uno
podra vincular y el lmite se basa en el hardware utilizado. Un sampler2D puede ser consultado para los colores en ciertas posiciones. Aqu es
donde entran las coordenadas UV. Aqu hay un ejemplo en el que reemplazamos el marrn con colores muestreados.
#ifdef GL_ES
precision highp float;
#endif

uniform vec3 lightDirection;


uniform float ambientLight;
uniform sampler2D diffuse;
varying vec3 vNormal;
varying vec2 vUv;

void main() {
float lightness = -clamp(dot(normalize(vNormal), normalize(lightDirection)), -1., 0.);
lightness = ambientLight + (1. - ambientLight) * lightness;
gl_FragColor = vec4(texture2D(diffuse, vUv).rgb * lightness, 1.);
}

El nuevo uniforme tiene que ser agregado al listado en el programa del shader:
this.diffuse = gl.getUniformLocation(program, 'diffuse')

Finalmente, implementaremos la carga de la textura. Como se dijo anteriormente, HTML5 proporciona facilidades para cargar imgenes. Todo lo
que necesitamos hacer es enviar la imagen a la GPU:
function Texture (gl, image) {
var texture = gl.createTexture()
// Set the newly created texture context as active texture
gl.bindTexture(gl.TEXTURE_2D, texture)
// Set texture parameters, and pass the image that the texture is based on
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)
// Set filtering methods
// Very often shaders will query the texture value between pixels,
// and this is instructing how that value shall be calculated
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
this.data = texture
this.gl = gl
}

Texture.prototype.use = function (uniform, binding) {


binding = Number(binding) || 0
var gl = this.gl
// We can bind multiple textures, and here we pick which of the bindings
// we're setting right now
gl.activeTexture(gl['TEXTURE' + binding])
// After picking the binding, we set the texture
gl.bindTexture(gl.TEXTURE_2D, this.data)
// Finally, we pass to the uniform the binding ID we've used
gl.uniform1i(uniform, binding)
// The previous 3 lines are equivalent to:
// texture[i] = this.data
// uniform = i
}

Texture.load = function (gl, url) {


return new Promise(function (resolve) {
var image = new Image()
image.onload = function () {
resolve(new Texture(gl, image))

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 22/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
}
image.src = url
})
}

El proceso no es muy diferente al proceso usado para cargar y enlazar VBOs. La principal diferencia es que ya no estamos vinculados a un atributo,
sino que unimos el ndice de la textura a un uniforme entero. El tipo sampler2D no es ms que un puntero desplazado a una textura.

Ahora todo lo que hay que hacer es ampliar la clase Mesh para manejar texturas tambin:
function Mesh (gl, geometry, texture) { // added texture
var vertexCount = geometry.vertexCount()
this.positions = new VBO(gl, geometry.positions(), vertexCount)
this.normals = new VBO(gl, geometry.normals(), vertexCount)
this.uvs = new VBO(gl, geometry.uvs(), vertexCount)
this.texture = texture // new
this.vertexCount = vertexCount
this.position = new Transformation()
this.gl = gl
}

Mesh.prototype.destroy = function () {
this.positions.destroy()
this.normals.destroy()
this.uvs.destroy()
}

Mesh.prototype.draw = function (shaderProgram) {


this.positions.bindToAttribute(shaderProgram.position)
this.normals.bindToAttribute(shaderProgram.normal)
this.uvs.bindToAttribute(shaderProgram.uv)
this.position.sendToGpu(this.gl, shaderProgram.model)
this.texture.use(shaderProgram.diffuse, 0) // new
this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertexCount)
}

Mesh.load = function (gl, modelUrl, textureUrl) { // new


var geometry = Geometry.loadOBJ(modelUrl)
var texture = Texture.load(gl, textureUrl)
return Promise.all([geometry, texture]).then(function (params) {
return new Mesh(gl, params[0], params[1])
})
}

Y el guin final principal se vera de la siguiente manera:


var renderer = new Renderer(document.getElementById('webgl-canvas'))
renderer.setClearColor(100, 149, 237)
var gl = renderer.getContext()

var objects = []

Mesh.load(gl, '/assets/sphere.obj', '/assets/diffuse.png')


.then(function (mesh) {
objects.push(mesh)
})

ShaderProgram.load(gl, '/shaders/basic.vert', '/shaders/basic.frag')


.then(function (shader) {
renderer.setShader(shader)
})

var camera = new Camera()


camera.setOrthographic(16, 10, 10)
var light = new Light()

loop()

function loop () {
renderer.render(camera, light, objects)
requestAnimationFrame(loop)
}

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 23/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal

Incluso animar puede ser fcil en este punto. Si quisieras que la cmara girara alrededor de nuestro objeto, puedes hacerlo con slo agregando una
lnea de cdigo:

function loop () {
renderer.render(camera, light, objects)
camera.position = camera.position.rotateY(Math.PI / 120)
requestAnimationFrame(loop)
}

Tienes libertad de jugar con shaders. Agregar una lnea de cdigo convertir esta iluminacin realista en algo caricaturesco.

void main() {
float lightness = -clamp(dot(normalize(vNormal), normalize(lightDirection)), -1., 0.);
lightness = lightness > 0.1 ? 1. : 0.; // new
lightness = ambientLight + (1. - ambientLight) * lightness;
gl_FragColor = vec4(texture2D(diffuse, vUv).rgb * lightness, 1.);
}

Es tan simple como decirle a la iluminacin que vaya a sus extremos, basndose en si cruz un umbral establecido.

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 24/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal

A Dnde ir Despus
Hay muchas fuentes de informacin para aprender todos los trucos y complejidades de WebGL. Y la mejor parte es que si no puedes encontrar una
respuesta relacionada con WebGL, puedes buscarla en OpenGL, ya que WebGL se basa en un subconjunto de OpenGL con algunos nombres
cambiados.

En ningn orden en particular, aqu hay algunas fuentes excelentes para obtener informacin ms detallada, tanto para WebGL como para
OpenGL.

Fundamentos de WebGL
WebGL de aprendizaje
Un tutorial muy detallado OpenGL tutorial, te guiar a travs de todos los principios fundamentales descritos aqu, de una manera muy lenta
y detallada.
Y hay muchos, muchos otros sitios dedicados a ensearte los principios de la computacin grfica.
Documentacin de MDN para WebGL
Especificacin de Khronos WebGL 1.0 si ests interesado en comprender los detalles ms tcnicos de cmo debera funcionar la API
WebGL en todos los casos.

Understanding the Basics


What is WebGL?
WebGL is a JavaScript API that adds native support for rendering 3D graphics within compatible web browsers, through an API similar to
OpenGL.

How are 3D models represented in memory?

The most common way to represent 3D models is through an array of vertices, each having a defined position in space, normal of the surface that
the vertex should be a part of, and coordinates on a texture used to paint the model. These vertices are then put in groups of three, to form
triangles.

What is a vertex shader?


A vertex shader is the part of the rendering pipeline that processes individual vertices. A call to the vertex shader receives a single vertex, and
outputs a single vertex after all possible transformations to the vertex are applied. This allows applying movement and deformations to whole
objects.

What is a fragment shader?

A fragment shader is the part of the rendering pipeline that takes a pixel of an object on the screen, together with properties of the object at that
pixel position, and can generates color, depth and other data for it.

How do objects move in a 3D scene?

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 25/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
All vertex positions of an object are relative to its local coordinate system, which is represented with a 4x4 identity matrix. If we move that
coordinate system, by multiplying it with transformation matrices, the object's vertices move with it.

About the author

View full profile


Hire the Author
Adnan Ademovic, Bosnia and Herzegovina
member since May 12, 2015
C++Python
Adnan is an engineer with experience in desktop, embedded, and distributed systems. He has worked extensively in C++ (working at both low and
high levels), Python, and in other languages. He is experienced in developing complex algorithms and intelligent systems, optimization,
networking, and signal processing. He is very passionate about problems faced by robotics and mechatronics in general. [click to continue...]
Hiring? Meet the Top 10 Freelance JavaScript Developers for Hire in September 2017
0 Comments Toptal
1 Login

Sort by Best
Recommend Share

Start the discussion

LOG IN WITH
OR SIGN UP WITH DISQUS ?

Name

Be the first to comment.

Subscribe d Add Disqus to your siteAdd DisqusAdd Privacy

Subscribe
The #1 Blog for Engineers
Get the latest content first.
Enter your email address...
Get Exclusive Updates
No spam. Just great engineering posts.
The #1 Blog for Engineers
Get the latest content first.
Thank you for subscribing!
Check your inbox to confirm subscription. You'll start receiving posts after you confirm.

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 26/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal
122shares

Trending articles

Common Mistakes in Client Communication: How to Not Frustrate Your Client2 days ago Conquer String Search with the

Aho-Corasick Algorithm4 days ago Web Accessibility: Why W3C Standards Are Often Ignored10 days ago Maximum Flow

and the Linear Assignment Problem12 days ago Getting Started with the SRVB Cryptosystem15 days ago Guide to Spring

Boot REST API Error Handling18 days ago Orchestrating a Background Job Workflow in Celery for Python19 days ago
iOS Centralized and Decoupled Networking: AFNetworking Tutorial with a Singleton Classabout 1 month ago
Relevant Technologies

JavaScript
OpenGL

About the author

Adnan Ademovic
C++ Developer
Adnan is an engineer with experience in desktop, embedded, and distributed systems. He has worked extensively in C++ (working at both low and
high levels), Python, and in other languages. He is experienced in developing complex algorithms and intelligent systems, optimization,
networking, and signal processing. He is very passionate about problems faced by robotics and mechatronics in general.
https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 27/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal

Hire the Author


Toptal connects the top 3% of freelance talent all over the world.

Toptal Developers
Android Developers
AngularJS Developers
Back-End Developers
C++ Developers
Data Scientists
DevOps Engineers
Ember.js Developers
Freelance Developers
Front-End Developers
Full Stack Developers
HTML5 Developers
iOS Developers
Java Developers
JavaScript Developers
Machine Learning Engineers
Magento Developers
Mobile App Developers
.NET Developers
Node.js Developers
PHP Developers
Python Developers
React.js Developers
Ruby Developers
Ruby on Rails Developers
Salesforce Developers
Scala Developers
Software Developers
Unity or Unity3D Developers
Web Developers
WordPress Developers

See more freelance developers


Learn how enterprises benefit from Toptal experts.

Join the Toptal community.


Hire a developer
or
Apply as a Developer

Highest In-Demand Talent

iOS Developer
Front-End Developer
UX Designer
UI Designer
Financial Modeling Consultants
Interim CFOs

About

Top 3%
Clients
Freelance Developers
Freelance Designers
Freelance Finance Experts
About Us

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 28/29
9/9/2017 Grficos 3D: Un Tutorial de WebGL | Toptal

Contact

Contact Us
Press Center
Careers
FAQ

Social

Facebook
Twitter
Google+
LinkedIn

Toptal

Hire the top 3% of freelance talent

Copyright 2010 - 2017 Toptal, LLC

Privacy Policy
Website Terms

Home Blog 3D Graphics: A WebGL Tutorial


Hiring? Toptal handpicks top JavaScript engineers to suit your needs.

Start hiring
Login

https://www.toptal.com/javascript/gr%C3%A1ficos-3d-un-tutorial-de-webgl/es 29/29