2011-01-23 19 views
15

En un motor 3D en el que estoy trabajando logré dibujar un cubo en 3D. El único método para llenar los lados es usar un color sólido o degradado en lo que a mí respecta. Para hacer las cosas más emocionantes, me encantaría implementar mapas de texturas usando un mapa de bits simple.Manipulación de imágenes y mapeo de texturas utilizando HTML5 Canvas?

El punto es que apenas puedo encontrar artículos o ejemplos de código sobre el tema de la manipulación de imágenes en JavaScript. Además, el soporte de imágenes en el lienzo HTML5 parece estar restringido al recorte.

¿Cómo podría hacer para estirar un mapa de bits para que un mapa de bits rectangular pueda llenar una cara de cubo no regular? En 2D, una cara proyectada del cubo cuadrado es, debido a la perspectiva, no de forma cuadrada, así que tendré que estirarla para que quepa en cualquier cuadrilátero.

Espero que esta imagen aclare mi punto. La cara izquierda ahora está llena con un degradado blanco/negro. ¿Cómo podría llenarlo con un mapa de bits, después de que se haya asignado un mapa de textura?

Cube

¿Alguien tiene algún consejo sobre perspectiva del mapeo de textura (o la manipulación de imágenes en absoluto) utilizando JavaScript y HTML5 Canvas?

Edit: ¡Lo he conseguido, gracias a 6502!

Sin embargo, es un poco intensivo de CPU, por lo que me encantaría escuchar cualquier idea de optimización.

Result using 6502's technique-Texture image used

+0

Si está fabricando un verdadero motor 3D, esta podría no ser la mejor manera de realizar texturas. Las coordenadas de textura/colores/normales, etc. deben almacenarse por vértice para permitir más flexibilidad. –

+1

Es un proyecto hobby simple, no se convertirá en mucho más que este cubo en realidad. – pimvdb

+0

@pimvdb ¿hay alguna posibilidad de compartir un código? –

Respuesta

37

creo que nunca obtener un resultado preciso ... pasé algún tiempo investigando cómo hacer gráficos en 3D usando el contexto 2d lienzo y lo encontré viable para hacer el mapeo de texturas sombreado Gouraud calculando gradientes y matrices 2D apropiadas:

  • polígonos sólidos son, por supuesto, fácil
  • llenado Gouraud es posible sólo en un componente (es decir, no se puede tener un triángulo donde cada vértice es un RGB arbitraria lleno de interpolación bilineal, pero y ou puede hacer que el llenado usando, por ejemplo tres tonos arbitrarios de un solo color)
  • mapeado de texturas lineal se puede hacer usando el recorte y la imagen de dibujo

I sería implementar perspectiva correcta asignación de texturas utilizando subdivisión de malla (como en PS1).

Sin embargo, encontré muchos problemas ... por ejemplo, el dibujo de la imagen con una transformación matricial (necesaria para el mapeo de texturas) es bastante inexacto en Chrome e IMO; es imposible obtener un resultado preciso; en general, no hay forma de desactivar el antialiasing cuando se dibuja en un lienzo, lo que significa que obtendrá líneas transparentes visibles al subdividir en triángulos. También encontré el renderizado multipaso funcionando realmente mal en Chrome (probablemente debido a la forma en que se implementa el renderizado hw-accellerated).

En general, este tipo de renderizado es sin duda un estrés para los navegadores web y aparentemente estos casos de uso (matrices extrañas, por ejemplo) no se prueban muy bien. Incluso pude hacer que Firefox fallara tanto que quitó todo el sistema X de mi Ubuntu.

Puede ver los resultados de mis esfuerzos here o como un video here ...IMO seguramente está impresionando que esto se puede hacer en un navegador sin utilizar extensiones 3D, pero no creo que los problemas actuales se solucionen en el futuro.

De todos modos, la idea básica utilizada para dibujar una imagen para que las 4 esquinas terminen en una posición de píxeles específicos es dibujar dos triángulos, cada uno de los cuales utilizará la interpolación bilineal.

En el siguiente código que se supone que tiene un objeto de imagen texture y 4 esquinas de cada uno de los cuales es un objeto con campos x,y,u,v donde x,y son coordenadas de píxeles en el lienzo de destino y u,v son coordenadas de píxeles en texture:

function textureMap(ctx, texture, pts) { 
    var tris = [[0, 1, 2], [2, 3, 0]]; // Split in two triangles 
    for (var t=0; t<2; t++) { 
     var pp = tris[t]; 
     var x0 = pts[pp[0]].x, x1 = pts[pp[1]].x, x2 = pts[pp[2]].x; 
     var y0 = pts[pp[0]].y, y1 = pts[pp[1]].y, y2 = pts[pp[2]].y; 
     var u0 = pts[pp[0]].u, u1 = pts[pp[1]].u, u2 = pts[pp[2]].u; 
     var v0 = pts[pp[0]].v, v1 = pts[pp[1]].v, v2 = pts[pp[2]].v; 

     // Set clipping area so that only pixels inside the triangle will 
     // be affected by the image drawing operation 
     ctx.save(); ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); 
     ctx.lineTo(x2, y2); ctx.closePath(); ctx.clip(); 

     // Compute matrix transform 
     var delta = u0*v1 + v0*u2 + u1*v2 - v1*u2 - v0*u1 - u0*v2; 
     var delta_a = x0*v1 + v0*x2 + x1*v2 - v1*x2 - v0*x1 - x0*v2; 
     var delta_b = u0*x1 + x0*u2 + u1*x2 - x1*u2 - x0*u1 - u0*x2; 
     var delta_c = u0*v1*x2 + v0*x1*u2 + x0*u1*v2 - x0*v1*u2 
         - v0*u1*x2 - u0*x1*v2; 
     var delta_d = y0*v1 + v0*y2 + y1*v2 - v1*y2 - v0*y1 - y0*v2; 
     var delta_e = u0*y1 + y0*u2 + u1*y2 - y1*u2 - y0*u1 - u0*y2; 
     var delta_f = u0*v1*y2 + v0*y1*u2 + y0*u1*v2 - y0*v1*u2 
         - v0*u1*y2 - u0*y1*v2; 

     // Draw the transformed image 
     ctx.transform(delta_a/delta, delta_d/delta, 
         delta_b/delta, delta_e/delta, 
         delta_c/delta, delta_f/delta); 
     ctx.drawImage(texture, 0, 0); 
     ctx.restore(); 
    } 
} 

Esas feas fórmulas extrañas para todas esas variables "delta" se usan para resolver dos sistemas lineales de tres ecuaciones en tres incógnitas usando el método Cramer's y el esquema Sarrus para determinantes 3x3.

Más específicamente Buscamos a los valores de a, b, ... f por lo que las siguientes ecuaciones se satisfacen

a*u0 + b*v0 + c = x0 
a*u1 + b*v1 + c = x1 
a*u2 + b*v2 + c = x2 

d*u0 + e*v0 + f = y0 
d*u1 + e*v1 + f = y1 
d*u2 + e*v2 + f = y2 

delta es el determinante de la matriz

u0 v0 1 
u1 v1 1 
u2 v2 1 

y por ejemplo delta_a es el determinante de la misma matriz cuando reemplaza la primera columna con x0, x1, x2. Con estos puedes calcular a = delta_a/delta.

+0

Guau, tu aplicación de toro es impresionante ... No me importa si no es exacta en base a píxeles. Gracias por la gran respuesta. – pimvdb

+2

Ok ... Agregaré la parte del mapeo de textura a la respuesta (la fuente de torus.html es difícil de leer porque "tuve" que reducirla a 4K). – 6502

+0

¡Solo miré la fuente y fue realmente inescrutable, muy bien hecho! – pimvdb