
Assembler es mi idioma favorito ... pero la vida es muy corta.
Continúo el ciclo de investigación sobre el tema de las sombras adecuadas para algunos panecillos. Después de la publicación,
me enfrié
una y
dos veces para este tema, pero el efecto de una acción incompleta me lleva a volver a los escombros de píxeles y completar la
gestalt .
Conociéndome a mí mismo, estoy seguro de que el juego difícilmente tendrá su encarnación, pero tal vez algunos del público estén interesados en mis logros en este camino espinoso. Y entonces comencemos.
Al final del último ciclo, llegué a comprender que el cálculo de gráficos en una CPU ya era del siglo pasado, pero la obstinación natural insistió: no todas las posibilidades se utilizaron, todavía había opciones para soluciones interesantes.
El trazado de rayos permaneció sin implementarse. Más precisamente, su tipo, donde para cada píxel de la imagen (bloque de píxeles) se arroja un rayo y se determina el nivel de iluminación del punto actual. El algoritmo en sí se describe en un artículo anterior y no tiene sentido volver a él. Para el trazado de rayos inversos, el código se simplificó aún más, toda la trigonometría se eliminó por completo, lo que en el futuro podría dar un resultado aceptable.
Por desgracia, el resultado fue mucho peor de lo esperado, valió la pena desplegar la imagen a pantalla completa, FPS buscó unidades.

Agrupar píxeles en macrobloques para reducir los cálculos y aplicar el suavizado posterior no mejoró mucho el rendimiento. Al efecto, francamente, no le gustó la palabra en absoluto.

El algoritmo era perfectamente paralelo, pero no tenía sentido usar muchas transmisiones, el efecto parecía mucho peor que en el artículo anterior, incluso con una mejor calidad de imagen.
Resultó ser un callejón sin salida. Tenía que admitir que la CPU en el cálculo de los gráficos en mis ojos se ha agotado. El telón
Digresión 1Durante la última década, prácticamente no ha habido progreso en el desarrollo de procesadores de propósito general. Si lo aborda el usuario, el aumento máximo notable del rendimiento no es más del 30% por núcleo. El progreso, por decirlo suavemente, es insignificante. Si omitimos la extensión de la longitud de las instrucciones del vector, y alguna aceleración de los bloques transportadores, esto es un aumento en el número de núcleos de trabajo. El trabajo seguro con subprocesos sigue siendo un placer, y no todas las tareas se pueden paralelizar con éxito. Me gustaría tener un núcleo funcional, aunque sea uno, pero si es así, es 5-10 más rápido, pero ay, oh, como dicen.
Aquí en Habré hay una excelente serie de artículos
"La vida en la era del silicio" oscuro ", que explica algunos de los requisitos previos para el estado actual de las cosas, pero también regresa del cielo a la tierra. En la próxima década, no puede esperar ningún aumento significativo en la informática por núcleo. Pero podemos esperar un mayor desarrollo del número de núcleos de GPU y su aceleración general. Incluso en mi vieja computadora portátil, el rendimiento total estimado de la GPU es 20 veces mayor que un solo hilo de CPU. Incluso si carga efectivamente los 4 núcleos de procesador, es mucho menos de lo que nos gustaría.
Rindo homenaje a los desarrolladores de los gráficos del pasado, que hicieron sus obras maestras sin aceleradores de hardware, maestros reales.
Entonces, tratamos con la GPU. Resultó ser algo inesperado para mí que en la práctica real, pocas personas simplemente dispersan polígonos en forma. Todas las cosas interesantes se crean usando
sombreadores . Después de descartar los motores 3D terminados, traté de estudiar los despojos de la tecnología, ya que está en un nivel profundo. Los mismos procesadores son el mismo ensamblador, solo un conjunto truncado de instrucciones y sus propios detalles de trabajo. Para la prueba, me detuve en
GLSL , una sintaxis tipo C, simplicidad, muchas lecciones de capacitación y ejemplos, incluido el Habr.
Como estaba acostumbrado a escribir en
Pascal , la tarea era cómo conectar OpenGL
al proyecto
Logré encontrar dos formas de conectarme: la biblioteca
GLFW y el archivo de encabezado
dglOpenGL . Lo único en el primero que no pude conectar los sombreadores, pero aparentemente esto es por la curvatura de mis manos.
Digresión 2Muchos amigos me preguntan por qué escribo en Pascal. Obviamente, este es un lenguaje en peligro de extinción, su comunidad está disminuyendo constantemente, casi no hay desarrollo. Los ingenieros de sistemas de bajo nivel prefieren C y Java, Python, Ruby o lo que sea que esté en su apogeo en este momento.
Para mí, Pascal es similar al primer amor. Hace dos décadas, en los días de
Turbo Pascal 5.5 , se hundió en mi alma y ha estado caminando conmigo desde entonces, ya sea Delphi o
Lázaro en los últimos años. Me gusta la previsibilidad del lenguaje, el nivel relativamente bajo (inserción del ensamblador y visualización de las instrucciones del procesador), compatibilidad con C. Lo principal es que el código está ensamblado y ejecutado sin problemas, pero el hecho de que no esté de moda está desactualizado y no hay algunas características, esto no tiene sentido. Se rumorea que hay personas que todavía están escribiendo en
LISP , pero para él en general durante medio siglo.
Entonces, profundicemos en el desarrollo. Para un paso de prueba, no tomaremos modelos realistas precisos de sombreado, sino que intentaremos implementar lo que ya hemos probado antes, pero con el rendimiento de la GPU, por así decirlo, para una comparación clara.
Inicialmente, pensé en obtener una sombra de aproximadamente esta forma, usando triángulos para un objeto.

Para crear el efecto de un círculo suave, necesita muchos polígonos. Pero, ¿qué pasa si usa triángulos al mínimo, usando un sombreador de píxeles para crear un agujero en la forma? La idea se me ocurrió después de leer un
artículo de un maestro respetado, en el que se abrió la oportunidad de crear una esfera con un sombreador.

Si extiende el triángulo más allá de los límites de la pantalla, el resultado es este:

Los bordes de la sombra resultaron ser muy rígidos y también escalonados. Pero hay una manera de obtener un resultado aceptable sin usar
supermuestreo , esto es usar bordes suavizados. Para hacer esto, cambie ligeramente el esquema. Las esquinas de los polígonos en la intersección de la tangente al círculo se volverán transparentes.

El resultado es mejor, pero aún no parece natural.

Agregue un poco de suavizado del círculo para darle suavidad, y también cambie la forma del gradiente de lineal a potencia.

Es un resultado aceptable.
Y al final agregaremos objetos que imitan obstáculos a la forma.

Código de sombreador//
#version 330 core
layout (location = 0) in vec2 aVertexPosition;
void main(void) {
gl_Position = vec4(aVertexPosition.xy, 0, 1.0);
}
//
#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices = 5) out;
uniform mat4 uModelViewMatrix;
uniform float uRadius;
uniform vec2 uHeroPoint;
out float fTransparency;
out vec2 vCenter;
void main(){
vCenter = gl_in[0].gl_Position.xy;
vec2 d = uHeroPoint - vCenter;
float l = length(d);
float i = uRadius / l;
float ii = i*i;
float ij = i * sqrt(1 - ii);
vec2 p1 = vec2(vCenter.x + dx*ii - dy*ij , vCenter.y + dx*ij + dy*ii);
vec2 p2 = vec2(vCenter.x + dx*ii + dy*ij , vCenter.y - dx*ij + dy*ii);
d = uHeroPoint - p1;
vec2 p3 = vec2(p1 - d/length(d)*1000000);
d = uHeroPoint - p2;
vec2 p4 = vec2(p2 - d/length(d)*1000000);
fTransparency = 0;
gl_Position = uModelViewMatrix * vec4(p1, 0, 1);
EmitVertex();
fTransparency = 1;
gl_Position = uModelViewMatrix * vec4(p3, 0, 1);
EmitVertex();
gl_Position = uModelViewMatrix * vec4(vCenter, 0, 1);
EmitVertex();
gl_Position = uModelViewMatrix * vec4(p4, 0, 1);
EmitVertex();
fTransparency = 0;
gl_Position = uModelViewMatrix * vec4(p2, 0, 1);
EmitVertex();
EndPrimitive();
}
//
#version 330 core
precision mediump float;
varying float fTransparency;
varying vec2 vCenter;
uniform float uRadius;
uniform vec2 uScreenHalfSize;
uniform float uShadowTransparency;
uniform float uShadowSmoothness;
out vec4 FragColor;
void main(){
float l = distance(vec2((gl_FragCoord.xy - uScreenHalfSize.xy)/uScreenHalfSize.y), vCenter.xy);
if (l<uRadius) {discard;}
else {FragColor = vec4(0, 0, 0, min(pow(fTransparency, uShadowSmoothness), (l-uRadius)/uRadius*10)*uShadowTransparency);}
}
Espero que sea informativo
Su humilde servidor, atormentador de píxeles, reconstructor.
Adjunto una pequeña
demostración . (EXE Windows)
PD: El título del artículo contiene un
huevo de Pascua , una referencia a la trilogía de
Siala Chronicle . Un excelente trabajo en los estilos de fantasía, sobre las desgracias de los cuernos, de Alexei Pekhov.