c++ - tutorial - Problema de costura al mapear una textura a una esfera en OpenGL
texturas multiples opengl (6)
Intento crear geometría para representar la Tierra en OpenGL. Tengo lo que es más o menos una esfera (más cerca del geoide elíptico que la Tierra). Mapeo una textura de la superficie de la Tierra (probablemente sea una proyección Mercator o algo similar). Las coordenadas UV de la textura corresponden a la latitud y longitud de la geometría. Tengo dos problemas que no puedo resolver. Estoy usando OpenSceneGraph, pero creo que esta es una pregunta general de programación de OpenGL / 3D.
Hay una costura de textura que es muy evidente. Estoy seguro de que esto ocurre porque no sé cómo mapear las coordenadas UV a XYZ donde ocurre la costura. Solo asocie los coords UV hasta el último vértice antes de envolverlos ... Necesitaría asignar dos coordenadas UV diferentes al mismo vértice XYZ para eliminar la costura. ¿Existe un truco comúnmente utilizado para evitar esto, o lo estoy haciendo mal?
Hay una loca distorsión swirly en los polos. Estoy adivinando esto porque mapeo un único punto UV en los polos (para la Tierra, uso [0.5,1] para el Polo Norte, y [0.5,0] para el Polo Sur). ¿Qué más harías? Puedo vivir con esto ... pero es muy notable en mallas de baja resolución.
Adjunté una imagen para mostrar de lo que estoy hablando.
La forma general en que se maneja es usando un mapa de cubo , no una textura 2D.
Sin embargo, si insiste en usar una textura 2D, debe crear un corte en la topología de su malla. La razón por la que obtienes esa línea longitudinal es porque tienes un vértice con una coordenada de textura de algo así como 0.9, y su vértice vecino tiene una coordenada de textura de 0.0. Lo que realmente quieres es que el 0.9 uno colinda con una coordenada de 1.0 textura.
Hacer esto significa replicar la posición en una línea de la esfera. Entonces tiene la misma posición utilizada dos veces en sus datos. Uno está adjunto a una coordenada de textura de 1.0 y vecina a una coordenada de textura de 0.9. El otro tiene una coordenada de textura de 0.0, y vecinos un vértice con 0.1.
Topológicamente, debe tomar una porción longitudinal en su esfera.
Su enlace realmente me ayudó, furqan, gracias.
¿Por qué no pudiste resolverlo? Un punto donde tropecé fue que no sabía que se puede superar el intervalo [0,1] al calcular las coordenadas de la textura. Eso hace que sea mucho más fácil saltar de un lado de la textura al otro con OpenGL haciendo toda la interpolación y sin tener que calcular la posición exacta donde la textura realmente termina.
Como dijo Nicol Bolas, algunos triángulos tienen coordenadas UV que van desde ~ 0.9 a 0, por lo que la interpolación ensucia la textura alrededor de la costura. En mi código, he creado esta función para duplicar los vértices alrededor de la costura. Esto creará una línea nítida que divide esos vértices. Si tu textura solo tiene agua alrededor de la veta (el océano Pacífico?), Es posible que no notes esta línea. Espero eso ayude.
/**
* After spherical projection, some triangles have vertices with
* UV coordinates that are far away (0 to 1), because the Azimuth
* at 2*pi = 0. Interpolating between 0 to 1 creates artifacts
* around that seam (the whole texture is thinly repeated at
* the triangles around the seam).
* This function duplicates vertices around the seam to avoid
* these artifacts.
*/
void PlatonicSolid::SubdivideAzimuthSeam() {
if (m_texCoord == NULL) {
ApplySphericalProjection();
}
// to take note of the trianges in the seam
int facesSeam[m_numFaces];
// check all triangles, looking for triangles with vertices
// separated ~2π. First count.
int nSeam = 0;
for (int i=0;i < m_numFaces; ++i) {
// check the 3 vertices of the triangle
int a = m_faces[3*i];
int b = m_faces[3*i+1];
int c = m_faces[3*i+2];
// just check the seam in the azimuth
float ua = m_texCoord[2*a];
float ub = m_texCoord[2*b];
float uc = m_texCoord[2*c];
if (fabsf(ua-ub)>0.5f || fabsf(ua-uc)>0.5f || fabsf(ub-uc)>0.5f) {
//test::printValue("Face: ", i, "/n");
facesSeam[nSeam] = i;
++nSeam;
}
}
if (nSeam==0) {
// no changes
return;
}
// reserve more memory
int nVertex = m_numVertices;
m_numVertices += nSeam;
m_vertices = (float*)realloc((void*)m_vertices, 3*m_numVertices*sizeof(float));
m_texCoord = (float*)realloc((void*)m_texCoord, 2*m_numVertices*sizeof(float));
// now duplicate vertices in the seam
// (the number of triangles/faces is the same)
for (int i=0; i < nSeam; ++i, ++nVertex) {
int t = facesSeam[i]; // triangle index
// check the 3 vertices of the triangle
int a = m_faces[3*t];
int b = m_faces[3*t+1];
int c = m_faces[3*t+2];
// just check the seam in the azimuth
float u_ab = fabsf(m_texCoord[2*a] - m_texCoord[2*b]);
float u_ac = fabsf(m_texCoord[2*a] - m_texCoord[2*c]);
float u_bc = fabsf(m_texCoord[2*b] - m_texCoord[2*c]);
// select the vertex further away from the other 2
int f = 2;
if (u_ab >= 0.5f && u_ac >= 0.5f) {
c = a;
f = 0;
} else if (u_ab >= 0.5f && u_bc >= 0.5f) {
c = b;
f = 1;
}
m_vertices[3*nVertex] = m_vertices[3*c]; // x
m_vertices[3*nVertex+1] = m_vertices[3*c+1]; // y
m_vertices[3*nVertex+2] = m_vertices[3*c+2]; // z
// repeat u from texcoord
m_texCoord[2*nVertex] = 1.0f - m_texCoord[2*c];
m_texCoord[2*nVertex+1] = m_texCoord[2*c+1];
// change this face so all the vertices have close UV
m_faces[3*t+f] = nVertex;
}
}
También puede ir de una manera sucia: interpolar las posiciones X, Y entre el sombreador de vértices y el sombreador de fragmentos y recalcular la coordenada de textura correcta en el sombreador de fragmentos. Esto puede ser algo más lento, pero no implica duplicar vértices y es más simple, creo.
Por ejemplo:
sombreador de vértices:
#version 150 core
uniform mat4 projM;
uniform mat4 viewM;
uniform mat4 modelM;
in vec4 in_Position;
in vec2 in_TextureCoord;
out vec2 pass_TextureCoord;
out vec2 pass_xy_position;
void main(void) {
gl_Position = projM * viewM * modelM * in_Position;
pass_xy_position = in_Position.xy; // 2d spinning interpolates good!
pass_TextureCoord = in_TextureCoord;
}
fragment shader:
#version 150 core
uniform sampler2D texture1;
in vec2 pass_xy_position;
in vec2 pass_TextureCoord;
out vec4 out_Color;
#define PI 3.141592653589793238462643383279
void main(void) {
vec2 tc = pass_TextureCoord;
tc.x = (PI + atan(pass_xy_position.y, pass_xy_position.x)) / (2 * PI); // calculate angle and map it to 0..1
out_Color = texture(texture1, tc);
}
Tomó mucho tiempo resolver este problema extremadamente molesto. Estoy programando con C # en Unity y no quería duplicar ningún vértice. (Podría causar problemas en el futuro con mi concepto) Así que fui con la idea del sombreador y funciona bastante bien. Aunque estoy seguro de que el código podría usar una optimización de alto rendimiento, tuve que averiguar cómo transferirlo a CG a partir de esto, pero funciona. Esto es en caso de que alguien más se encuentre con esta publicación, como lo hice yo, buscando una solución para el mismo problema.
Shader "Custom/isoshader" {
Properties {
decal ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Pass {
Fog { Mode Off }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#define PI 3.141592653589793238462643383279
sampler2D decal;
struct appdata {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 tex : TEXCOORD0;
float3 pass_xy_position : TEXCOORD1;
};
v2f vert(appdata v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.pass_xy_position = v.vertex.xyz;
o.tex = v.texcoord;
return o;
}
float4 frag(v2f i) : COLOR {
float3 tc = i.tex;
tc.x = (PI + atan2(i.pass_xy_position.x, i.pass_xy_position.z)) / (2 * PI);
float4 color = tex2D(decal, tc);
return color;
}
ENDCG
}
}
}
Un enfoque es como en la respuesta aceptada. En el código que genera la matriz de atributos de vértices, tendrá un código como este:
// FOR EVERY TRIANGLE
const float threshold = 0.7;
if(tcoords_1.s > threshold || tcoords_2.s > threshold || tcoords_3.s > threshold)
{
if(tcoords_1.s < 1. - threshold)
{
tcoords_1.s += 1.;
}
if(tcoords_2.s < 1. - threshold)
{
tcoords_2.s += 1.;
}
if(tcoords_3.s < 1. - threshold)
{
tcoords_3.s += 1.;
}
}
Si tiene triángulos que no están alineados con meridianos, también querrá glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
. También necesita usar glDrawArrays
ya que los vértices con la misma posición tendrán diferentes coords de textura.
Creo que el mejor camino a seguir es eliminar la raíz de todo mal, que es la interpolación de coords de textura en este caso. Como sabes básicamente todo sobre tu esfera / elipsoide, puedes calcular coords de texturas, normales, etc. en el sombreador de fragmentos en función de la posición. Esto significa que su código de CPU que genera atributos de vértice será mucho más simple y podrá usar el dibujo indexado nuevamente. Y no creo que este enfoque sea sucio. Está limpio.