How do I render a convincing skydome?
Asked Answered
I

1

14

I am writing an OpenGL ES 2.0 app which renders a 3D island. I already have code to generate a sky dome around the island. This is a hemisphere comprised of triangles which over lay the island with z point upwards.

The dome has some very basic moving clouds created using a perlin noise texture overlaid over itself and moving different speeds.

But ultimately I need the dome to also render:

  • Sun (which tracks across the sky) Moon (including phase)
  • Stars (at night)
  • Distant land as a static texture
  • Different colours to simulate night, dawn, dusk, day

I need to do this quite efficiently since it will end up on Android although for the moment its running in a test harness. So the sun, moon and stars for example will just be textures although their points may be plotted with reasonable accuracy.

I already have the code to generate the dome, and code to plot a sun against a date and time. So mostly its the shaders I need and what they're supplied with.

Is there any example which demonstrates these things? I've found plenty which do basic cube maps, or limited stuff, but nothing to the level of sophistication I need. Obviously since this is OpenGL ES 2.0 it has to be done in shaders.

The shader for my existing sky dome renders 2 layers of perlin noise to simulate clouds. The texture wraps continuously so I can compute the u offset based on the angle of a dome's vertex into the xy plane (by feeding the x and y into atan) and the v offset using the dome's vertex z.

The vertex shader demonstrates how I do it:

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

attribute vec4 aVertex;

uniform mat4 uPMVMatrix;
uniform float uTime;
uniform float uSkyDomeRadius;

const float PI = 3.1415926535897932384626433832795;

varying vec2 texCoord0, texCoord1;

void main()
{
    vec2 centre = vec2(600., 600.);

    gl_Position = uPMVMatrix * aVertex;

    float slow_time = uTime / 100.;

    vec2 dome_point = aVertex.xy - centre;
    float tex_u = atan(dome_point.x, dome_point.y);// / (.25 * PI);
    float tex_v = aVertex.z / (uSkyDomeRadius * .5);

    texCoord0 = vec2(tex_u / 2.0 + slow_time, tex_v / 2.0);
    texCoord1 = vec2(tex_u + slow_time / 2.0, tex_v);
}

I also use a time to offset u a bit each frame so the clouds move. This works fine except atan goes from -PI to PI and suddenly the texCoord0 and texCoord1 values the fragment shader interpolates are separated by a large distance which hoses the interpolation.

The best way to describe it is that if I have 16 triangles at my base then interpolation from 0/16 to 1/16 works, 1/16 to 2/16 works and so on. But when I get to 15/16 to 0/16 the interpolator goes backwards and the large variation causes the frag shader to repeat the texture over and over in a small space leading to stripes as shown.

Interpolation goes haywire as angle goes into reverse

I cannot see any way to have a seamless 360 degree view I think the only way I'll fix this is if I rotate the entire dome so the seam is always behind the camera but it might still show if the camera points straight up to the top of a dome.

A working example with source would be great especially if it addresses these sorts of issues.

Incept answered 13/12, 2012 at 0:34 Comment(3)
stupid question but can tex_x be > PI or <-PIPeden
tex_x will always be between PI and -PI because atan returns those values. I have 16 triangles at the base of the dome so each triangle base prescribes an arc of 2PI / 16 or PI / 8. So v0 might be 3/16th the way around and v1 4/16th and the so tex_x interpolates properly. The issue comes if v0 were 15/16 and v1 were 0/16 and interpolation is completely screwed. I can't think of any way to fix this properly so I'm speculating that I'll have to rotate the dome so the seam is always behind the camera and offset whatever I render by an equivalent amount.Incept
I updated the question to clarify this and renamed tex_x and tex_y as tex_u and tex_vIncept
M
3

The problem is, that you use the same vertices at an angle of 0° and 360°. So you now have a triangle where the first vertex has a texture coordinate of 15/16, 0 and the second has 0, 0 instead of 1,0. To fix that, you have to open up the sphere so you have a pair of vertices at the same spatial positions, one for 0° and one for 360°. That way the two vertices can have different texture coordinates and there won't be any disturbed textures.

To rotate them you have to use the wrapping modes, make sure, that the texture wrapping is set to repeat. If you haven't changed it, it should be set up correctly, but it can be set with function glTexParameter, the parameters are GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T and the value has to be GL_REPEAT. Then if a texture value is > 1 it won't wrap around but repeat the texture. Now you only need to be able to tell the vertices at the start of the sphere (for vertices at 0°) from those at the end (for vertices at 360°) so you can correct tex_u, you probably need an additional attribute for that.

Midland answered 23/12, 2012 at 17:39 Comment(1)
Thanks I'll give it a go. I'm aware that the issue is the angle effectively wrapping back to zero but I'll see if I can generate the skydome in some way which compensates for this such as how you suggest.Incept

© 2022 - 2024 — McMap. All rights reserved.