una tamaños tamaño son sombreador significado shading shaders que pixeles peso imagenes imagen cuantos convertir como calcular math 3d directx shader hlsl

math - son - tamaños de imagenes en pixeles



¿Cómo calculo la profundidad del sombreado de píxeles para representar un círculo dibujado en un punto sprite como una esfera que se intersectará con otros objetos? (3)

Ayer se me ocurrió una solución que funciona bien y produce el resultado deseado de una esfera dibujada en el sprite, con un valor de profundidad correcto que se interseca con otros objetos y esferas en la escena. Puede ser menos eficiente de lo que debe ser (por ejemplo, calcula y proyecta dos vértices por sprite) y probablemente no sea completamente correcto matemáticamente (toma atajos), pero produce buenos resultados visuales.

La técnica

Para escribir la profundidad de la ''esfera'', debe calcular el radio de la esfera en coordenadas de profundidad, es decir, qué tan gruesa es la mitad de la esfera. Esta cantidad se puede escalar a medida que escribe cada píxel en la esfera a qué distancia del centro de la esfera se encuentra.

Para calcular el radio en coordenadas de profundidad:

  • Sombreador de vértice: en la escena no proyectada, las coordenadas proyectan un rayo desde el ojo a través del centro de la esfera (es decir, el vértice que representa el punto sprite) y agregan el radio de la esfera. Esto te da un punto que se encuentra en la superficie de la esfera. Proyecte el vértice del sprite y el nuevo vértice de la superficie de su esfera y calcule la profundidad (z / w) para cada uno. Lo diferente es el valor de profundidad que necesitas.

  • Pixel Shader: para dibujar un círculo, ya calcula una distancia normalizada desde el centro del sprite, utilizando el clip para no dibujar píxeles fuera del círculo. Ya que está normalizado (0-1), multiplíquelo por la profundidad de la esfera (que es el valor de profundidad del radio, es decir, el píxel en el centro de la esfera) y añádalo a la profundidad del propio sprite plano. Esto da una profundidad más gruesa en el centro de la esfera a 0 y el borde, siguiendo la superficie de la esfera. (Dependiendo de la precisión con la que lo necesite, use un coseno para obtener un grosor curvo. Encontré que los resultados lineales dieron un aspecto perfectamente fino).

Código

Este no es un código completo ya que mis efectos son para mi compañía, pero el código aquí se reescribe desde mi archivo de efectos reales omitiendo cosas innecesarias / propietarias, y debería ser lo suficientemente completo como para demostrar la técnica.

Sombreador de vértices

void SphereVS(float4 vPos // Input vertex, float fPointRadius, // Radius of circle / sphere in world coords out float fDXScale, // Result of DirectX algorithm to scale the sprite size out float fDepth, // Flat sprite depth out float4 oPos : POSITION0, // Projected sprite position out float fDiameter : PSIZE, // Sprite size in pixels (DX point sprites are sized in px) out float fSphereRadiusDepth : TEXCOORDn // Radius of the sphere in depth coords { ... // Normal projection oPos = mul(vPos, g_mWorldViewProj); // DX depth (of the flat billboarded point sprite) fDepth = oPos.z / oPos.w; // Also scale the sprite size - DX specifies a point sprite''s size in pixels. // One (old) algorithm is in http://msdn.microsoft.com/en-us/library/windows/desktop/bb147281(v=vs.85).aspx fDXScale = ...; fDiameter = fDXScale * fPointRadius; // Finally, the key: what''s the depth coord to use for the thickness of the sphere? fSphereRadiusDepth = CalculateSphereDepth(vPos, fPointRadius, fDepth, fDXScale); ... }

Todas las cosas estándar, pero lo incluyo para mostrar cómo se utiliza.

El método clave y la respuesta a la pregunta es:

float CalculateSphereDepth(float4 vPos, float fPointRadius, float fSphereCenterDepth, float fDXScale) { // Calculate sphere depth. Do this by calculating a point on the // far side of the sphere, ie cast a ray from the eye, through the // point sprite vertex (the sphere center) and extend it by the radius // of the sphere // The difference in depths between the sphere center and the sphere // edge is then used to write out sphere ''depth'' on the sprite. float4 vRayDir = vPos - g_vecEyePos; float fLength = length(vRayDir); vRayDir = normalize(vRayDir); fLength = fLength + vPointRadius; // Distance from eye through sphere center to edge of sphere float4 oSphereEdgePos = g_vecEyePos + (fLength * vRayDir); // Point on the edge of the sphere oSphereEdgePos.w = 1.0; oSphereEdgePos = mul(oSphereEdgePos, g_mWorldViewProj); // Project it // DX depth calculation of the projected sphere-edge point const float fSphereEdgeDepth = oSphereEdgePos.z / oSphereEdgePos.w; float fSphereRadiusDepth = fSphereCenterDepth - fSphereEdgeDepth; // Difference between center and edge of sphere fSphereRadiusDepth *= fDXScale; // Account for sphere scaling return fSphereRadiusDepth; }

Sombreador de píxeles

void SpherePS( ... float fSpriteDepth : TEXCOORD0, float fSphereRadiusDepth : TEXCOORD1, out float4 oFragment : COLOR0, out float fSphereDepth : DEPTH0 ) { float fCircleDist = ...; // See example code in the question // 0-1 value from the center of the sprite, use clip to form the sprite into a circle clip(fCircleDist); fSphereDepth = fSpriteDepth + (fCircleDist * fSphereRadiusDepth); // And calculate a pixel color oFragment = ...; // Add lighting etc here }

Este código omite la iluminación, etc. Para calcular qué tan lejos está el píxel del centro del elemento sprite (para obtener fCircleDist ), consulte el código de ejemplo en la pregunta (calcula '' float dist = ... '') que ya dibujó un círculo.

El resultado final es ...

Resultado

Voila, puntos sprites dibujando esferas.

Notas

  • El algoritmo de escalado para los sprites también puede requerir que la profundidad sea escalada. No estoy seguro de que la línea sea correcta.
  • No es completamente matemáticamente correcto (toma atajos) pero como puede ver, el resultado es visualmente correcto
  • Al usar millones de sprites, todavía obtengo una buena velocidad de representación (<10 ms por cuadro para 3 millones de sprites, en un dispositivo Direct3D emulado VMWare Fusion)

Estoy escribiendo un sombreado para representar esferas en puntos sprites, dibujando círculos sombreados, y necesito escribir un componente de profundidad y color para que las esferas cercanas entre sí se intersecten correctamente.

Estoy usando un código similar al escrito por Johna Holwerda :

void PS_ShowDepth(VS_OUTPUT input, out float4 color: COLOR0,out float depth : DEPTH) { float dist = length (input.uv - float2 (0.5f, 0.5f)); //get the distance form the center of the point-sprite float alpha = saturate(sign (0.5f - dist)); sphereDepth = cos (dist * 3.14159) * sphereThickness * particleSize; //calculate how thick the sphere should be; sphereThickness is a variable. depth = saturate (sphereDepth + input.color.w); //input.color.w represents the depth value of the pixel on the point-sprite color = float4 (depth.xxx ,alpha ); //or anything else you might need in future passes }

El video en ese enlace da una buena idea del efecto que busco: las esferas dibujadas en sprites de punto se intersecan correctamente. He añadido imágenes a continuación para ilustrar también.

Puedo calcular bien la profundidad del punto sprite. Sin embargo, no estoy seguro de mostrar calcular el grosor de la esfera en un píxel para agregarlo a la profundidad del sprite, para dar un valor de profundidad final. (El código anterior usa una variable en lugar de calcularla).

He estado trabajando en esto una y otra vez durante varias semanas, pero no lo he descubierto. Estoy seguro de que es simple, pero es algo que mi cerebro no ha manipulado.

Los tamaños de sprite de puntos de Direct3D 9 se calculan en píxeles, y mis sprites tienen varios tamaños, tanto por caída debido a la distancia (implementé el mismo algoritmo que la antigua tubería de función fija utilizada para cálculos de tamaño de punto en mi vértice shader) y también debido a qué el sprite representa.

¿Cómo puedo pasar de los datos que tengo en un sombreador de píxeles (ubicación del sprite, profundidad del sprite, radio original del espacio mundial, radio en píxeles en la pantalla, distancia normalizada del píxel en cuestión desde el centro del sprite) a un valor de profundidad? Una solución parcial simplemente del tamaño del sprite al grosor de la esfera en coordenadas de profundidad estaría bien, que se puede escalar por la distancia normalizada desde el centro para obtener el grosor de la esfera en un píxel.

Estoy utilizando Direct3D 9 y HLSL con sombreador modelo 3 como límite superior de SM.

En fotos

Para demostrar la técnica y el punto en el que tengo problemas:

Comienza con dos sprites de puntos, y en el sombreador de píxeles, dibuja un círculo en cada uno, usando un clip para eliminar fragmentos fuera del límite del círculo:

Uno se renderizará por encima del otro, ya que después de todo son superficies planas.

Ahora, haz que el shader sea más avanzado y dibuja el círculo como si fuera una esfera, con iluminación. Tenga en cuenta que a pesar de que los sprites planos se ven en 3D, aún se dibujan uno frente al otro ya que es una ilusión: todavía son planos.

(Lo anterior es fácil; es el último paso con el que tengo problemas y pregunto cómo lograrlo).

Ahora, en lugar de que el sombreador de píxeles escriba solo los valores de color, también debe escribir la profundidad:

void SpherePS (...any parameters... out float4 oBackBuffer : COLOR0, out float oDepth : DEPTH0 <- now also writing depth ) {

Tenga en cuenta que ahora las esferas se intersecan cuando la distancia entre ellas es menor que sus radios:

¿Cómo calculo el valor de profundidad correcto para lograr este paso final?

Editar / Notas

Varias personas han comentado que una esfera real se distorsionará debido a la perspectiva, que puede ser especialmente visible en los bordes de la pantalla, por lo que debería usar una técnica diferente. Primero, gracias por señalarlo, no es necesariamente obvio y es bueno para los futuros lectores. En segundo lugar, mi objetivo no es generar una esfera con perspectiva correcta, sino hacer que millones de puntos de datos sean rápidos, y visualmente creo que un objeto parecido a una esfera se ve mejor que un sprite plano y también muestra mejor la posición espacial. La distorsión leve o la falta de distorsión no importa. Si ve el video de demostración , puede ver cómo es una herramienta visual útil. No quiero generar mallas de esferas reales debido a la gran cantidad de triángulos en comparación con un sprite de punto simple generado por hardware. Realmente quiero usar la técnica de sprites puntuales, y simplemente quiero extender la técnica de demostración existente para calcular el valor de profundidad correcto, que en la demostración se pasó como una variable sin fuente para la forma en que se derivó.


El primer gran error es que una esfera 3D real no se proyectará a un círculo bajo una proyección 3D en perspectiva.

Esto no es muy intuitivo, pero mire algunas imágenes, especialmente con un gran campo de visión y esferas descentradas.

En segundo lugar, recomendaría no usar sprites de puntos al principio, ya que podría hacer las cosas más difíciles de lo necesario, especialmente considerando el primer punto. Solo dibuja un patio generoso alrededor de tu esfera y ve desde allí.

En tu sombreador debes tener la posición del espacio de la pantalla como entrada. A partir de eso, la vista se transforma y su matriz de proyección puede llegar a una línea en el espacio ocular. Debe cruzar esta línea con la esfera en el espacio ocular (trazado de rayos), obtener el punto de intersección del espacio ocular y transformarlo de nuevo en espacio de pantalla. Luego sale 1 / w como profundidad. No estoy haciendo las cuentas por ti aquí porque estoy un poco borracha y perezosa y no creo que eso sea lo que realmente quieres hacer de todos modos. Sin embargo, es un gran ejercicio para el álgebra lineal, así que quizás deberías intentarlo. :)

El efecto que probablemente estás tratando de hacer se llama Sprites de profundidad y generalmente se usa solo con una proyección ortográfica y con la profundidad de un sprite almacenado en una textura. Simplemente almacene la profundidad junto con su color, por ejemplo, en el canal alfa y solo muestre eye.z + (storagedepth-.5) * depthofsprite.


Esfera no se proyectará en un círculo en el caso general. Aquí está la solución.

Esta técnica se llama spherical billboards . Se puede encontrar una descripción detallada en este documento: Carteles esféricos y su aplicación para generar explosiones.

Dibuje sprites de puntos como cuadrículas y luego muestrea una textura de profundidad para encontrar la distancia entre el valor Z por píxel y su coordenada Z actual. La distancia entre el valor Z muestreado y la Z actual afecta la opacidad del píxel para que se vea como una esfera mientras se interseca la geometría subyacente. Los autores del artículo sugieren el siguiente código para calcular la opacidad:

float Opacity(float3 P, float3 Q, float r, float2 scr) { float alpha = 0; float d = length(P.xy - Q.xy); if(d < r) { float w = sqrt(r*r - d*d); float F = P.z - w; float B = P.z + w; float Zs = tex2D(Depth, scr); float ds = min(Zs, B) - max(f, F); alpha = 1 - exp(-tau * (1-d/r) * ds); } return alpha; }

Esto evitará las intersecciones bruscas de sus carteles con la geometría de la escena.

En el caso de que sea difícil controlar la tubería de sprites de puntos (solo puedo decir sobre OpenGL y no DirectX) es mejor usar la publicidad acelerada por GPU: usted suministra 4 vértices 3D iguales que coinciden con el centro de la partícula. Luego los mueve a las esquinas de la cartelera apropiadas en un sombreado de vértice, es decir:

if ( idx == 0 ) ParticlePos += (-X - Y); if ( idx == 1 ) ParticlePos += (+X - Y); if ( idx == 2 ) ParticlePos += (+X + Y); if ( idx == 3 ) ParticlePos += (-X + Y);

Esto está más orientado a la línea de GPU moderna y, por lo general, funcionará con cualquier proyección en perspectiva no degenerada.