Me preguntaba si es posible realizar una colisión pixel-perfect utilizando el stencil buffer.
El procedimiento sería así:
Teniendo que verificar la colisión pixel-perfect del Sprite A y el Sprite B:
1. Limpiamos el stencil buffer con un Clear.
2. Fijamos el render state relacionado al stencil a:
m_pD3DDevice->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
m_pD3DDevice->SetRenderState(D3DRS_ALPHAREF, iuColorKey); // uiColorKey color transparente de los sprites.
m_pD3DDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_NOTEQUAL); // Acepta pixels distintos al valor de referencia (uiColorKey)
m_pD3DDevice->SetRenderState(D3DRS_STENCILENABLE, TRUE);
m_pD3DDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
m_pD3DDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCR);
De esta manera todo lo que dibujemos debería incrementar el valor del stencil (en todos los pixels a excepción de los rechazados por el aphatest).
3. Dibujamos el Sprite A y el Sprite B.
4. Y ahora viene la parte que no sé:
¿Como haría para leer el Stencil buffer de manera eficiente para verificar si los pixels de los sprites se han superpuesto?
Me imagino que habría que obtener el puntero al buffer del stencil (por medio de un GetDepthStencilSurface) , luego realizarle un Lock (¿se puede trabar el stencil?) y luego recorrerlo en busca del valor deseado (un doble incremento), finalmente destrabarlo y retornar con el resultado de la verificación de colisión.
Claro que todo esto es válido si el alphatest se hace antes del stenciltest, pero creo que esto es así.
No encontré una implementación de este tipo en la red, imagino que los juegos 2d no están muy de moda y el uso del stencils suponen una placa de video que lo soporte, por lo que usar esta técnica en juego 2d sería requerir cierto hardware normalmente es esperable en un juego 3d.
En caso de que se pueda hacer, se me antoja bastante costoso. En cualquier caso, según la documentación, el único formato de depth-stencil que es bloqueable es el D3DFMT_D16_LOCKABLE.
Creo que mejor solución que esa sería utilizar regiones, o sea polígonos para definir el contorno de los sprites (evidentemente tanto cóncavos como convexos) y entonces crearse un método para detectar la colisión entre dichas regiones.
Precisamente GDI+; y el GDI antiguo supongo que también, tiene una clase Region con un método Intersect que hace eso. De todas formas, tal vez prefieras crearte tus propias funciones.
Saludos.
Yo tb estoy dandole vueltas a lo de la colisión por pixel y no encuentro tutos sobre esto para direct3d. Lo de las regiones no me convence. Primero porque el usuario tendría que andar definiéndolas, y para una animación larga de más de 50 o 60 sprites esto puede ser mortal. Y segundo porque se pierde precisión, busco una solución por pixel perfecta, independientemente del tiempo que esta tarde, aunque claro, cuanto menor sea mejor.
DeadLock, ¿probaste al final lo del stencil? ¿Funcionaba bien?
Mi tarjeta no soporta D3DFMT_D16_LOCKABLE :(
Unas preguntas, ¿se puede lockear directamente el framebuffer? ¿Se podría hacer así la colisión por pixel?
¿Alguien conoce algún método de colisión por pixel alternativo?
Otra idea que se me ocurre, pero que no sé si es factible. Se haría hacer primero la colisión por rectángulo (eso por supuesto) y luego hacer un render to texture SOLO del area donde colisiones los sprites. Un render to texture para cada sprite de esa pequeña area y lockear esas zonas y hacer ahí la comparación. No se me ocurre nada mejor.
Todo el problema viene a partir de que quiero tener en cuenta la rotación y escalado de estos sprites... por lo tanto no hay mas remedio que dibujarlos previamente. ¿No?
Soy todo dudas.
No le veo a Direct3D capaz de poder realizar eficientemente colision por pixel, esa fue una opcion ke hace ya mucho tiempo rechaze para la dx_lib32 por el coste de rendimiento ke esto supondria, asi ke opte simplemente por colision de rectangulos, pero eso no kita para ke se siga probando suerte, ke a lo mejor si existe alguna forma de hacerlo sin ke el coste de rendimiento se tan execesivo.
Salu2...
Voy a probar suerte, lo veo factible. Si se pueden hacer cosas como bumpmapping + soft shadows + historias fijo que se puede hacer algo tan sencillo como esto.
Tengo un vago recuerdo de haber leído algo sobre el tema en tiempos pre-3D pero no le presté mucha atención en su día. A ver si con lo que me acuerdo y algo de imaginación:
Para cada scanline de un sprite, se guarda un array de coordenadas X donde empieza/termina el sprite. No basta con dos coordenadas porque hay que tener en cuenta que el sprite puede ser cóncavo (la letra C por ejemplo) y/o puede tener "huecos" interiores (un donut). Eso sí, siempre habrá un número par. Al final, el array correspondiente al scanline de coordenada Y se interpreta así:
* los tramos definidos por (array[0], Y)-(array[1]), (array[2], Y)-(array[3]), (array[4], Y)-(array[5]), etc. están ocupados por el sprite.
* los tramos definidos por (array[1], Y)-(array[2]), (array[3], Y)-(array[4]), (array[5], Y)-(array[6]), etc. son huecos.
Entonces, para comprobar colisión entre dos sprites, primero se hace por rectángulos y se determina el subrectángulo que se solapa. Finalmente se emparejan los scanlines de un sprite y otro que estén a la misma altura y, para cada pareja, se recorren los tramos de ambos. Si un tramo ocupado de un sprite se solapa con otro tramo ocupado del otro sprite, entonces hay colisión.
Nota 1: Casi todo esto acabo de sacármelo de la manga, o sea que puede que esté incompleto o incluso que sea incorrecto.
Nota 2: Loover, me temo que esto no funcionaría para sprites que pueden estar rotados. Igual se puede adaptar pero se complicaría bastante.
Un saludo y suerte.
No hay ningun problema con que esten rotados o escalados, bastara con hacer un render to texture como ya he dicho para esa zona rectangular de corte para cada sprite (aún sobrando trozos si los sprites están rotados) y comparar para ver si estan solapados. A ver si tengo tiempo y lo hago que ahora con los exámenes ando liado.
¿Pero para qué os complicais la vida con colisiones 2D?
Siempre se han hecho con máscaras de bits y ha funcionado a la perfección. Utilizar el hardware de hoy en día para resolver un problema de esta índole me parece querer matar moscas a cañonazos.
¿Máscaras de bits? ¿Te refieres a los mapas de durezas? o a esto:
http://www.ifm.liu.se/~ulfek/projects/2d_C..._Detection.htmlY no es que lo quiera solucionar por hardware por llevar la contraria, es que si estoy usando direct3d/opengl a la fuerza va a tener que ser por hardware vamos.
PD: Por cierto para la colisión por areas (en el caso de sprites rectángulos rotados que vienen a ser una vez proyectados polígonos de 4 lados) he encontrado este sencillo algoritmo que viene muy bien para detectar colisiones:
http://graphics.cs.ucdavis.edu/~okreylos/T...tInPolygon.html (bueno en realidad lo que hace es verificar si un punto está dentro de un polígono...)
Mirar esta web y bajaros el primer pdf:
http://www.cs.unc.edu/~geom/PIVOT/Habla sobre colisiones 2d usando hardware, que en algunos casos (como en el mio de intentar una colisión por pixel con sprites rotados y escalados montados sobre polígonos) es totalmente necesario utilizar uno de esos métodos. Entre los que está el de el stencil que se le había ocurrido a deadlock y el mio del render to texture. Tb habla sobre otros como haciendo blending en esa zona y mirando el color resultante o usando alpha test... super interesante vamos.
Hadd, no puedo usar bitmasks porque estos se precalculan para sprites que no van a ser rotados ni escalados...
Seguiré leyendo sobre el tema... :D
Bloquear un render target o el stencil para mirar si ha habido colisión es una barbaridad, que además hace ya tiempo se comentó que MS iba a prohibir bloquear el backbuffer, aunque todavía no lo han hecho.
Se me acaba de ocurrir una técnica, a ver que te parece:
Lo que sí permite el hard es hacer Querys de ocultamiento, es decir, saber si un objeto se ha ocultado o no. Bien, pues con esta premisa lo que hacemos es dibujar los sprites, de forma que el primero que dibujes tenga una Z=0.1 y el siguiente tenga una Z=0.15, por ejemplo. Siempre teniendo en cuenta que la ZFUNC es LESSEQUAL. Es decir, nos aseguramos que al pintar el 2º sprite siempre se dibuje detrás del primero, porque si no no vale.
Entonces pueden pasar 2 cosas al pintar el 2º sprite:
1: Que no colisiones, por tanto, la query te devolverá el nº de pixeles que ha pintado=100% del sprite
2: Que sí colisione algo, y la query te dará un nº inferior.
Sigues pintando, aumentado la Z, y vas haciendo estas comprobaciones.
¿y ahora te preguntaras? Vale, pero ..¿cual es el nº de píxels cuando el sprite está rotado? ¿ y se sólo se ve una porción del sprite porque está apareciendo por un lado de la pantalla ?
Bien, lo que tienes que hacer es, el render dos veces, pero la primera vez todos tienen que tener la misma Z. De esta forma el query te devolvera el 100% del sprite que pinta. Ese es el valor que tienes que utilizar para comparar en el 2º caso.
Pues prefiero mi método (creo)... a no ser que los querys los soporten todas las tarjetas... ¿es así? No me has leído bien arriba, yo no voy a usar el stencil, ni siquiera tengo stencil :D
Repito que voy a usar dos render to texture de SOLO la zona rectangular de colisión y compararlos hasta que llegue a una colisión (alpha de alguno de los dos no sea 0).
Así no dependo de querys de ocultamiento, de stencils ni de nada. Solo de un render to texture que pueden hacerlo el 90% de las tarjetas o más. Porque la idea es esa, hacerlo cuánto más compatible hacia abajo, mejor. Que a fin de cuentas es una api 2d...
Aunque dame más información acerca de los querys de ocultación y lo implementaré tb después, y si va mucho más rápido lo pondré como opción para el que soporte eso.
Un saludo!
[EDITADO]
PD: Para lo de los querys tendría q activar el zbuffer, ¿no?
PPD: Otra cosa que no me mola es lo del render 2 veces si está rotado... pues sería 1 vez para obtener el valor... y luego 2 veces una para el sprite A y otra para el B. ¿No? O serían 4. Lo malo es que los sprites estarían casi siempre rotando... y un render adicional ralentizaría mucho el proceso. De la otra forma que había pensado son siempre dos renders a dos texturas ya creadas con anterioridad y luego lo malo es que hay que lockearlas...
Anda parece que los querys de oclusión no implican hardware adicional :D (ole)
Bueno, más o menos...
Citar
This parameter can be set to NULL to see if a query is supported. If the query is not supported, the method returns...
Sino está en las CAPs supongo que significará que casi todas las tarjetas lo soportan, ¿o no?
Lo malo es lo del tercer render si está rotado :(
¿Hadd el método que te he comentado sobre los dos render to texture de la zona de colisión y posterior comparación no te convence? Creo que sería más rápida que un render adicional...
PD: ¿No ibais a arrelgar lo de que no se puede editar pasado un tiempo? (grrr)
Imagínate que tienes dos sprites enormes que ocupan casi toda la pantalla. ¿vas a lockear y comprobar TODA la pantalla? Eso sí que tardaría una barbaridad.
Activar el ZBuffer no es costoso hoy en día. Y lo que dices de los dos renders, podrías mejorarlo si no pintaras en el buffer de color, aunque no sé si en este caso el Query te devolverá los píxeles pintados.
Pero creo que sería muuuucho mejor hacerlo por software con máscaras de dureza, eso sí que es 100% comptaible,
Pero con lo de las máscaras de dureza volvemos a lo de que los sprites no podrían rotarse/escalarse... pq sino habría que ir creando la máscara a cada frame :( Ahí esta el problema.
En cuanto a los sprites grandes tienes razón. Aunque piensa que con tu método deberían dibujarse 3 veces y con el mio solo dos + la comparación. La pregunta es... ¿que es más rapido? ¿lockear y comparar? o ¿dibujar 1 vez más?
Tendré que hacer pruebas.
Bueno asumimos que tu rutina de rotación no es exactamente igual que la que utiliza el hardware y algunos pixels no se dibujan en la misma posición. Por tanto, sólo nos queda o lo del lock o de las querys
Es obvio que en una tarjeta aceleradora es más rápido pintar que bloquear y leer. Porque al leer los datos pasan de la tarjeta a la CPU a través del bus AGP y además impide que la aceleradora pueda seguir haciendo cosas. Desde mi punto de vista, poco óptimo.
Lo de las dos pasadas para la aceleradora no tiene que ser muy costoso, porque piensa que es una aceleradora y está optimizada para ello. De todas formas, podrías utilizar varias técnicas que te permitirían ahorrar:
Lo que "fastidia" a las aceleradoras más es el acceso a memoria. Las texturas están en memoria y cada vez que tiene que pintar un pixel tiene que ir a buscarla en la memoria. Para la primera pasada tu solo necesitas que te devuelva el nº de pixels, así que:
-Desactiva los filtros, con esto ganarás mucho
-Como necesitas el Alpha para controlar las transparencias, crea una textura aparte donde sólo esté el alpha y utiliza un formato comprimido. Ahora no se me ocurre que formato puedes usar, pero piensa que te basta con 1 bit!. De esta forma, los accesos a memoria se reducen drásticamente. Esta opción supongo que no te gustará, porque gastas memoria de vídeo dos veces, pero realmente ganarás bastante.
-Si el Query te devuelve la información por Z en lugar de por color, desactiva la escritura en el buffer de color.
Además de esta forma sabrás el grado de "colision", es decir, el nº de pixels que colisionan y esto te podría servir para tu juego. También sabrías el nº de pixels que ocupa el sprite que vas a dibujar, y eso también te podría servir para dibujar ordenadamente por el que mayor nº de pixels ocupa.
Ale, pues me has convencido :)
Si tengo alguna duda durante el proceso la pondré por aquí, muchas gracias.
Mira el post que he hecho en programación 3D!