No dijo exactamente cómo estaba generando las posiciones. Por lo tanto, supongo que está utilizando el ruido Perlin para generar valores de altura en un mapa de altura. Por lo tanto, para cualquier posición X, Y en el jeroglífico, utiliza una función de ruido 2D para generar el valor Z.
Por lo tanto, vamos a suponer que su posición se calcula de la siguiente manera:
vec3 CalcPosition(in vec2 loc) {
float height = MyNoiseFunc2D(loc);
return vec3(loc, height);
}
Esto genera una posición 3D. Pero en qué espacio es esta posición? Esa es la pregunta.
La mayoría de las funciones de ruido esperan que loc
sean dos valores en un determinado rango de coma flotante. Qué tan buena es su función de ruido determinará en qué rango puede pasar los valores. Ahora, si las posiciones 2D de su espacio modelo no están garantizadas dentro del rango de la función de ruido, entonces necesita transformarlas a ese rango, hacer los cálculos, y luego, transfórmalo como espacio modelo.
Al hacerlo, ahora tiene una posición 3D. La transformación para los valores X e Y es simple (el inverso de la transformación al espacio de la función de ruido), pero ¿qué ocurre con la Z? Aquí, debes aplicar algún tipo de escala a la altura. La función de ruido devolverá un número en el rango [0, 1), por lo que necesita escalar este rango al mismo espacio modelo al que van sus valores X e Y. Esto se hace típicamente seleccionando una altura máxima y escalando la posición de manera apropiada. Por lo tanto, nuestra posición Calc revisada se ve algo como esto:
vec3 CalcPosition(in vec2 modelLoc, const in mat3 modelToNoise, const in mat4 noiseToModel)
{
vec2 loc = modelToNoise * vec3(modelLoc, 1.0);
float height = MyNoiseFunc2D(loc);
vec4 modelPos = noiseToModel * vec4(loc, height, 1.0);
return modelPos.xyz;
}
Las dos matrices se transforman en el espacio de la función de ruido, y luego se transforman de vuelta. Su código real podría usar estructuras menos complicadas, dependiendo de su caso de uso, pero una transformación afín completa es simple de describir.
OK, ahora que hemos establecido eso, lo que debe tener en cuenta es esto: nada tiene sentido a menos que sepa en qué espacio está. Su normal, sus posiciones, nada importa hasta que establezca el espacio que es en.
Esta función devuelve las posiciones en el espacio modelo. Necesitamos calcular normales en espacio modelo. Para hacer eso, necesitamos 3 posiciones: la posición actual del vértice y dos posiciones que están ligeramente desplazadas de la posición actual. Las posiciones que obtenemos deben estar en el espacio modelo, o nuestra normalidad no será.
Por lo tanto, tenemos que tener la siguiente función:
void CalcDeltas(in vec2 modelLoc, const in mat3 modelToNoise, const in mat4 noiseToModel, out vec3 modelXOffset, out vec3 modelYOffset)
{
vec2 loc = modelToNoise * vec3(modelLoc, 1.0);
vec2 xOffsetLoc = loc + vec2(delta, 0.0);
vec2 yOffsetLoc = loc + vec2(0.0, delta);
float xOffsetHeight = MyNoiseFunc2D(xOffsetLoc);
float yOffsetHeight = MyNoiseFunc2D(yOffsetLoc);
modelXOffset = (noiseToModel * vec4(xOffsetLoc, xOffsetHeight, 1.0)).xyz;
modelYOffset = (noiseToModel * vec4(yOffsetLoc, yOffsetHeight, 1.0)).xyz;
}
Obviamente, puede combinar estas dos funciones en una sola.
El valor delta
es un pequeño desplazamiento en el espacio de la entrada de la textura del ruido. El tamaño de este desplazamiento depende de su función de ruido; necesita ser lo suficientemente grande como para devolver una altura que sea significativamente diferente de la que devuelve la posición actual real. Pero tiene que ser small suficiente para que no esté tirando de partes aleatorias de la distribución de ruido.
Debe conocer su función de ruido.
Ahora que tiene las tres posiciones (la posición actual, el X-offset, y la ordenada en el offset) en el espacio modelo, se puede calcular el vértice normal en el espacio modelo:
vec3 modelXGrad = modelXOffset - modelPosition;
vec3 modelYGrad = modelYOffset - modelPosition;
vec3 modelNormal = normalize(cross(modelXGrad, modelYGrad));
Desde aquí haz lo de siempre Pero nunca olvide hacer un seguimiento de los espacios de sus diversos vectores.
Ah, y una cosa más: esto se debe hacer en el vértice shader. No hay ninguna razón para hacer esto en un sombreador de geometría, ya que ninguno de los cálculos afecta a otros vértices. Deje que el paralelismo de la GPU funcione para usted.
Pensé en un enfoque como este, lo intenté y fallé. Después de leer su respuesta, probablemente no tenga todo en el espacio correcto y esté causando errores. Estoy trabajando con vértices, geometría y sombreadores de fragmentos, así que me doy cuenta de que tengo que tener mucho cuidado con el espacio en el que guardo las cosas. Volveré a intentarlo mañana con un enfoque claro al usar este método y ver qué sucede. – Nitrex88
Lo tengo trabajando con este enfoque. Sin embargo, para que funcione, necesitaba crear 4 vectores de compensación y usar los gradientes entre los desplazamientos (no el degradado del desplazamiento con la posición del modelo). Y para obtener la iluminación en la dirección correcta, tuve que cambiar de orden de tomar el producto cruzado del modelo XGrad y el modelo YGrad (solo menciono para otros que se encuentran con esta solución). Gracias por explicarlo tan bien con el espacio de coordenadas ... que ayudó. Además, quiero mencionar que estaba haciendo esto en el sombreador de geometría porque estoy teselando dinámicamente el terreno en el sombreador de geometría – Nitrex88