Fragments Shaders en Digi3D.NET

Digi3D.NET permite transformar los parámetros radiomátricos (brillo/color/contraste/gama/niveles/tonos de gris/positivo o negativo…) de visualización de las imágenes que muestra en la ventana fotogramétrica mediante los controles del panel Propiedades de la imagen de la ventana fotogramétrica.

Podemos configurar el programa para que esta transformación se realice o por software o por hardware, en la opción del menú Herramientas/Configuración/Estereoscopía/Método para Brillo y Contraste.

Disponemos de dos opciones:

  • Software
  • Fragment Shaders

Si instalamos Digi3D.NET por primera vez en un equipo la opción predeterminada será Fragment Shaders que es la implementación por hardware.

La opción ideal es hacer la transformación por hardware, pero no todas las tarjetas gráficas están preparadas para realizar operaciones por hardware. Las tarjetas gráficas que admiten esta operación son tarjetas que admiten el lenguaje de programación de Shaders.

Si nuestra tarjeta gráfica no admite shaders, si tenemos seleccionada la opción Fragment Shaders comprobaremos que al desplazarnos por la imagen, esta dará tirones, en cuyo caso tendremos que seleccionar la opción Software.

Mi recomendación es que si tu tarjeta gráfica lo admite, selecciones la opción Fragment Shaders, por varios motivos:

  • Velocidad: ya que si la operación se realiza por software, cada vez que Digi3D.NET cargue una tesela de la imagen, tendrá que modificar pixel por pixel su valor en función de la configuración de brillo/contraste/color… seleccionada por el usuario.

    Además, me atrevería a asegurar que el 99% de los equipos cuya tarjeta gráfica no admite shaders son antiguos y lentos, lo que provocará que la carga de la tesela se relentice, provocando que el usuario visualice por uno o varios segundos esta tesela de color negro.

  • Carga del procesador: Si el procesador está ocupado cambiando el color de la imagen, el scroll de la imagen se puede ralentizar.

    Y tal y como expongo en el punto anterior, como el procesador seguro que tiene pocos núcleos (dos, no más) esto afectará al rendimiento de Digi3D.NET.

  • Modificación real de la imagen en memoria: Si nos disponemos a realizar alguna operación con la imagen cargada en memoria, como por ejemplo correlar un modelo, y esta ha sido alterada por software, los resultados variarán en función del los parámetros radiométricos de la imagen.

Digi3D.NET programa los shaders de radiometría mediante el lenguaje de programación GLSL que son las siglas de OpenGL Shading Language, y este lenguaje se programación se caracteriza porque los programas no se pueden almacenar compilados, sino que los compila la tarjeta gráfica (el Driver de OpenGL más bien) en el mismo instante en el que cargamos el programa en la tarjeta gráfica.

Puedes localizar el código fuente del Fragment Shader que gestiona los parámetros radiométricos de visualización de Digi3D.NET en la ruta:

Sistema operativo Versión de Digi3D.NET Ruta
32 bits 32 bits %ProgramFiles%Digi21.netDigi3D2011FragmentShader.glsl
64 bits 64 bits %ProgramFiles%Digi21.netDigi3D2011FragmentShader.glsl
64 bits 32 bits %ProgramFiles (x86)%Digi21.netDigi3D2011FragmentShader.glsl

Este archivo es un archivo de texto que puedes editar con cualquier editor como el bloc de notas. Está programado en el lenguaje CLSL que es muy parecido al lenguaje de programación C.

Lo que más te va a sorprender si no sabes de programación de shaders es que el programa se ejecuta una vez por cada pixel de pantalla, si has leido bien, si nuestro monitor tiene 1920×1080 píxeles, cada vez que movamos la ventana de Digi3D.NET un único píxel, se ejecutará este programa 2073600 veces!!!!! y en tiempo real, seguro que si nuestra tarjeta no es muy lenta podremos llegar a los 140 frames por segundo sin ningún problema.

No es mi intención enseñarte a programar shaders, lo que si que puedo es recomendarte un libro, como es OpenGL Shading Language, pero si te puedo explicar un poquito el contenido del código y cómo modificarlo para hacer una broma o para investigar.

El código del shader es el siguiente:

// Fragment Shader para las transformaciones de color en Digi3D 2011-2012
// Desarrollado por José Ángel Martínez Torres - Dreaming With Objects S.L.
// www.digi21.net/digi3d
//

// Indica realizar una transformación por tabla de color. Si es verdadero no se ajustarán ni brillo, 
// ni contraste ni gamma, sino que se transformará el color de entrada
// en un color de salida.
// Lo activa por ejemplo la casilla de Niveles Automáticos. Si el usuario activa esta casilla, se 
// calcula una tabla de transformación de colores mediante el algoritmo
// indicado en http://en.wikipedia.org/wiki/Histogram_equalization
uniform bool aplicarTablaColor;
uniform vec3 tablaColor[256];

// En la variable brillo se almacena el desplazamiento a realizar en el brillo. Se admiten valores entre [-1,+1]
uniform float brillo;

// En la variable brillo se almacena el desplazamiento de contraste a aplicar. Se admiten valores entre [-1,+1]
uniform float contraste;

// La variable transformarTonoGris controla si se debe transformar la imagen a tonos de gris.
uniform bool transformarTonosGris;

// La variable negativo controla si se debe mostrar la imagen en negativo.
uniform bool negativo;

// El valor escalaColor indica el factor (que será potencia de 2) por el cual se debe escalar el color.
uniform float escalaColor;

// Indica si el usuario ha desplazado el control de Gamma.
// Documentación de Gamma en http://www.ivl.disco.unimib.it/Teaching/AIC-2010-specialistica/proof_paper_JEI.pdf
uniform float gamma;


// Fórmula del brillo contraste de The Gimp tal y como aparece en el artículo de la wikipedia
// http://en.wikipedia.org/wiki/Image_editing#Contrast_change_and_brightening
// Modificada para no calcular la tangente por cada componente
float CalculaValorBrilloContrasteRapido(float value, float brightness, float tangentePiCeroCinco)
{
    if (brightness < 0.0)
        value = value * ( 1.0 + brightness);
    else
        value = value + ((1.0 - value) * brightness);

    value = (value - 0.5) * tangentePiCeroCinco + 0.5;
    return value;
}

void main(void)
{
    vec4 colorTransformado = gl_Color;

    if( aplicarTablaColor ) {
        colorTransformado.r = tablaColor[int(gl_Color.r*255.0)].r;
        colorTransformado.g = tablaColor[int(gl_Color.g*255.0)].g;
        colorTransformado.b = tablaColor[int(gl_Color.b*255.0)].b;
    } else {
        if( brillo != 0.0 || contraste != 0.0) {
            float tangentePiCeroCinco = tan((contraste + 1.0) * 3.14159265358979323/4.0);
            colorTransformado.r = CalculaValorBrilloContrasteRapido(gl_Color.r, brillo, tangentePiCeroCinco);
            colorTransformado.g = CalculaValorBrilloContrasteRapido(gl_Color.g, brillo, tangentePiCeroCinco);
            colorTransformado.b = CalculaValorBrilloContrasteRapido(gl_Color.b, brillo, tangentePiCeroCinco);
        }

        if( 1.0 != escalaColor )
            colorTransformado *= vec4(escalaColor, escalaColor, escalaColor, 1.0);

        if( gamma != 1.0 )
            colorTransformado = pow(colorTransformado, vec4(1.0/gamma, 1.0/gamma, 1.0/gamma, 1.0));
    }

    if( negativo )
        colorTransformado = vec4(1.0, 1.0, 1.0, 0.0) - colorTransformado;

    if( transformarTonosGris ) {
        float iluminacion = colorTransformado.r * 0.299 + colorTransformado.g * 0.587 + colorTransformado.b * 0.114;
        colorTransformado = vec4(iluminacion, iluminacion, iluminacion, 1.0);
    }

    gl_FragColor = colorTransformado;
}

Como puedes comprobar, al principio se declaran una serie de variables (aplicarTablaColor, tablaColor, brillo, …) que son las que se modifican cuando el usuario interactua con los controles, y dentro de la función main tienes el código que realiza las correspondientes transformaciones.

Te recuerdo que este programa se ejecuta una vez por cada pixel a mostrar, y el objetivo de cualquier Fragment Shader es el de asignar la variable global gl_FragColor que es un tipo con cuatro componentes (rojo, verde, azul, alfa). El color original lo tenemos almacenado en la viable global gl_Color.

Vamos a modificar el Fragment Shader para que no se muestren colores ni verde ni azul, únicamente rojo, de esta manera conseguiremos ver el modelo de Bronchales (que es el que utilizo para la captura de pantalla del comienzo de la entrada) de color rojo. Para ello, voy a obtener el valor de iluminación (que como puedes ver en el código fuente original se obtiene multiplicando la componente roja del pixel por 0,299, la componente verde por 0,587 y la componente azul por 0,114, y vamos a indicar que este valor será el color a utilizar en la componente roja del color de salida.

A continuación el código fuente de nuestro Fragment Shader modificado…

void main(void)
{
    float iluminacion = gl_Color.r * 0.299 + gl_Color.g * 0.587 + gl_Color.b * 0.114;
    gl_FragColor = vec4(iluminacion, 0.0, 0.0, 1.0);
}