Проблема с швом при сопоставлении текстуры с сферой в OpenGL
Я пытаюсь создать геометрию для представления Земли в OpenGL. У меня есть более или менее сфера (ближе к эллиптическому геоиду, который есть на Земле). Я рисую текстуру поверхности Земли (это, вероятно, проекция меркатора или что-то подобное). Ультрафиолетовые координаты соответствуют геометрии широты и долготы. У меня есть два вопроса, которые я не могу решить. Я использую OpenSceneGraph, но я думаю, что это общий вопрос программирования OpenGL/3D.
-
Там текстурный шов, который очень ясен. Я уверен, что это происходит, потому что я не знаю, как сопоставить UV-координаты XYZ, где происходит шов. Я только сопоставляю UV-координаты до последней вершины, прежде чем обертывать вокруг... Вам нужно будет сопоставить две разные UV-координаты с одной и той же вершиной XYZ, чтобы устранить шов. Существует ли обычно используемый трюк, чтобы обойти это, или я просто делаю это неправильно?
-
Там сумасшедшие искажения на полюсах. Я предполагаю это, потому что я сопоставляю одну точку UV на полюсах (для Земли я использую [0,5,1] для Северного полюса и [0,5,0] для Южного полюса). Что еще вы могли бы сделать? Я могу жить с этим... но его чрезвычайно заметно в сетках с более низким разрешением.
Я добавил изображение, чтобы показать, о чем я говорю.
![I suck at rendering Earth]()
Ответы
Ответ 1
Общий способ обработки - использовать кубическую карту, а не 2D-текстуру.
Однако, если вы настаиваете на использовании 2D-текстуры, вам нужно создать разрыв в вашей топологии сетки. Причина, по которой вы получаете эту продольную линию, состоит в том, что у вас есть одна вершина с координатой текстуры примерно 0,9 или около того, а ее соседняя вершина имеет координату текстуры 0.0. То, что вы действительно хотите, это то, что 0,9 соседствует с текстурной координатой 1.0.
Выполнение этого означает репликацию позиции по одной линии сферы. Таким образом, у вас есть такая же позиция, которая используется дважды в ваших данных. Один прикреплен к координате текстуры 1.0 и соседствует с координатой текстуры 0,9. Другая имеет координату текстуры 0.0 и соседствует с вершиной с 0,1.
Топологически вам нужно взять продольный срез вниз по вашей сфере.
Ответ 2
Ваша ссылка действительно помогла мне, furqan, спасибо.
Почему ты не мог понять это? Точка, где я споткнулся, была, что я не знал, что вы можете превысить интервал [0,1] при вычислении координат текстуры. Это значительно облегчает переход от одной стороны текстуры к другой с помощью OpenGL, выполняющего всю интерполяцию, и без необходимости вычислять точное положение, где текстура действительно заканчивается.
Ответ 3
Понадобилось много времени, чтобы понять эту крайне неприятную проблему. Я программирую с С# в Unity, и я не хотел дублировать любые вершины. (Может быть, будущие проблемы с моей концепцией). Поэтому я пошел с идеей шейдера, и все получилось очень хорошо. Хотя я уверен, что код может использовать некоторую интенсивную оптимизацию, мне пришлось выяснить, как перенести его на CG, но он работает. Это на случай, если кто-то еще столкнется с этим сообщением, как и я, ища решение той же проблемы.
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
}
}
}
Ответ 4
Как сказал Никол Болас, некоторые треугольники имеют УФ-координаты, идущие от ~ 0,9 обратно до 0, поэтому интерполяция разрушает текстуру вокруг шва. В моем коде я создал эту функцию для дублирования вершин вокруг шва. Это создаст резкую линию, разделяющую эти вершины. Если у вашей текстуры есть только вода вокруг шва (Тихоокеанский океан?), Вы можете не заметить эту линию. Надеюсь, что это поможет.
/**
* 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;
}
}
Ответ 5
Вы также можете пойти грязным способом: интерполировать позиции X, Y между шейдером вершин и шейдером фрагмента и пересчитать правильную координату текстуры в шейдере фрагмента. Это может быть несколько медленнее, но это не связано с повторяющимися вершинами, и это проще, я думаю.
Например:
вершинный шейдер:
#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;
}
фрагментарный шейдер:
#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);
}
Ответ 6
Один подход подобен принятому ответу. В коде, генерирующем массив атрибутов вершин, вы получите такой код:
// 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.;
}
}
Если у вас есть треугольники, которые не выровнены по меридиану, вам также понадобится glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
. Вам также нужно использовать glDrawArrays
, поскольку вершины с одинаковой позицией будут иметь разные текстурные координаты.
Я думаю, что лучший способ - устранить корень всего зла, который является интерполяцией текстурных коордов в этом случае. Поскольку вы знаете в основном все о своей сфере/эллипсоиде, вы можете рассчитать текстурные коорды, нормали и т.д. В шейдере фрагмента на основе позиции. Это означает, что ваш код процессора, генерирующий вершинные атрибуты, будет намного проще, и вы можете снова использовать индексированный чертеж. И я не думаю, что этот подход грязный. Он чист.