Fórmula GLSL Light (atenuación, color e intensidad)

17

Estoy implementando luces puntuales en mi motor Voxel, y realmente estoy luchando por conseguir un buen flujo de luz, desde el 100% cerca de la fuente de luz hasta el 0% en el radio de la luz.

Tengo 5 argumentos para la función:

  1. Color claro (Vec3)
  2. Intensidad de la luz (distancia desde la luz hasta la distancia donde la caída es del 100%)
  3. Distancia de la luz al fragmento
  4. El ángulo del fragmento normal a la luz.
  5. La posición de la luz

¿Alguien puede empujarme en la dirección correcta para crear una función para el cálculo del color del fragmento?

Imagen de uno de mis experimentos:

Prueba de iluminación por fragmento de motor Voxel

Editar (código actual solicitado por Byte) Tenga en cuenta que este es solo un código de experimento de mi parte. Obtuve el float att de un sitio web, y funciona, pero lejos de ser perfecto. :

void main()
{
// Light color
vec3 torchColor = vec3(1.0f, 1.0f, 1.0f);

float lightAdd = 0.0f;
for (int i=0; i<5; i++) {
    vec3 pos = lights[i];
    if (pos.x == 0.0f) continue;

    float dist = distance(vertex_pos, pos);
    if (dist < 9) {
        float att=1.0/(1.0+0.1*dist+0.01*dist*dist);
        vec3 surf2light = normalize(pos - vertex_pos);
        vec3 norm = normalize(normal);
        float dcont=max(0.0,dot(norm,surf2light));
        lightAdd += att*(dcont+0.4);
    }
}

vec3 textureColor = texture2D(texture, texture_coordinate).rgb;
vec3 torch_output = lightAdd * torchColor;

vec3 final_color = ((0.1+torch_output) * textureColor);

gl_FragColor = vec4(final_color, 1.0f); 
}
Basaa
fuente
66
Todavía estás diciendo cosas como "luchando por conseguir una buena apariencia , luces naturales " y "funciona, pero lejos de ser perfecto ". Debe incluir un lenguaje específico y exacto. No sabemos qué aspecto tiene para usted, qué aspecto tiene para usted la luz natural o qué es perfecto.
MichaelHouse
2
¿Has intentado eliminar if (dist < 9)? Alternativamente, puede calcular attcon una función que devuelve 1 cuando la distancia es 0 y 0 cuando la distancia es 9. Por ejemplomix(1.0, 0.0, dist / 9.0)
msell

Respuestas:

39

La función de atenuación que tienes,

att = 1.0 / (1.0 + 0.1*dist + 0.01*dist*dist)

es bastante común en gráficos de computadora o, más generalmente, 1.0 / (1.0 + a*dist + b*dist*dist))para algunos parámetros ajustables ay b. Para entender cómo funciona esta curva, es útil jugar con los parámetros de forma interactiva . Esta curva es agradable porque se aproxima a la ley del cuadrado inverso físicamente correcto a grandes distancias, pero no se dispara hasta el infinito a distancias cortas. De hecho, a = 0es un modelo bastante bueno de una luz de área esférica.

Sin embargo, una desventaja es que la luz nunca llega a cero a ninguna distancia finita. Para fines prácticos en tiempo real CG generalmente necesitamos cortar las luces a una distancia finita, como lo está haciendo con la if (dist < 9)cláusula. Sin embargo, el radio de 9 es demasiado corto: con sus ajustes en la función de atenuación, la luz no se acerca a cero hasta que dist es alrededor de 100.

Puede calcular el radio de la luz a partir del bparámetro en la función de atenuación (ya que el término cuadrático domina a grandes distancias). Digamos que desea apagar la luz cuando la atenuación alcanza algún valor minLight, como 0.01. Luego establecer

radius = sqrt(1.0 / (b * minLight))

Eso da un radio de 100 para b = 0.01y minLight = 0.01. Alternativamente, puede establecer el radio y calcular bpara que coincida:

b = 1.0 / (radius*radius * minLight)

Por radius = 9y minLight = 0.01, eso da b = 1.23. Puede configurarlo de cualquier manera, pero la clave es hacer que el radio y la función de atenuación coincidan para que no apague la luz hasta que la función de atenuación ya sea muy baja, por lo que no verá un borde afilado.


Dicho todo esto, hay funciones de atenuación alternativas que puede usar. Otro bastante común es:

att = clamp(1.0 - dist/radius, 0.0, 1.0); att *= att

o el un poco más elegante:

att = clamp(1.0 - dist*dist/(radius*radius), 0.0, 1.0); att *= att

Juega con los parámetros para esos también. Estas curvas tienen la ventaja de ir exactamente a cero en el radio dado, sin dejar de parecerse a la ley del cuadrado inverso natural.

Nathan Reed
fuente
¡Excelente! Sin embargo, yo usaría maxdurante clampsólo razones de rendimiento.
Mike Weir
44
@MikeWeir La fijación a [0, 1] es gratuita en muchas GPU, en realidad. Es una operación tan común que lo tienen como un modificador de instrucciones.
Nathan Reed