I am creating a geomip-mapped terrain. So far I have it working fairly well. The terrain tessellation near the camera is very high and gets less so the further out the geometry is. The geometry of the terrain essentially follows the camera and samples a heightmap texture based on the position of the vertices. Because the geometry tessellation is very high, you can at times see each pixel in the texture when its sampled. It creates obvious pixel bumps. I figured I might be able to get around this by smoothing the sampling of the heightmap. However I seem to have a weird problem related to some bilinear sampling code. I am rendering the terrain by displacing each vertex according to a heightmap texture. To get the height of a vertex at a given UV coordinate I can use:
vec2 worldToMapSpace( vec2 worldPosition ) {
return ( worldPosition / worldScale + 0.5 );
}
float getHeight( vec3 worldPosition )
{
#ifdef USE_HEIGHTFIELD
vec2 heightUv = worldToMapSpace(worldPosition.xz);
vec2 tHeightSize = vec2( HEIGHTFIELD_SIZE_WIDTH, HEIGHTFIELD_SIZE_HEIGHT ); //both 512
vec2 texel = vec2( 1.0 / tHeightSize );
//float coarseHeight = texture2DBilinear( heightfield, heightUv, texel, tHeightSize ).r;
float coarseHeight = texture2D( heightfield, vUv ).r;
return altitude * coarseHeight + heightOffset;
#else
return 0.0;
#endif
}
Which produces this (notice how you can see each pixel):
Here is a wireframe:
I wanted to make the terrain sampling smoother. So I figured I could use some bilinear sampling instead of the standard texture2D function. So here is my bilinear sampling function:
vec4 texture2DBilinear( sampler2D textureSampler, vec2 uv, vec2 texelSize, vec2 textureSize )
{
vec4 tl = texture2D(textureSampler, uv);
vec4 tr = texture2D(textureSampler, uv + vec2( texelSize.x, 0.0 ));
vec4 bl = texture2D(textureSampler, uv + vec2( 0.0, texelSize.y ));
vec4 br = texture2D(textureSampler, uv + vec2( texelSize.x, texelSize.y ));
vec2 f = fract( uv.xy * textureSize ); // get the decimal part
vec4 tA = mix( tl, tr, f.x );
vec4 tB = mix( bl, br, f.x );
return mix( tA, tB, f.y );
}
The texelSize is calculated as 1 / heightmap size:
vec2 texel = vec2( 1.0 / tHeightSize );
and textureSize is the width and height of the heightmap. However, when I use this function I get this result:
float coarseHeight = texture2DBilinear( heightfield, heightUv, texel, tHeightSize ).r;
That now seems worse :( Any ideas what I might be doing wrong? Or how I can get a smoother terrain sampling?
EDIT
Here is a vertical screenshot looking down at the terrain. You can see the layers work fine. Notice however that the outer layers that have less triangulation and look smoother while the ones with higher tessellation show each pixel. Im trying to find a way to smooth out the texture sampling.
I was able to find and implement a technique that uses catmulrom interpolation. Code is below.
// catmull works by specifying 4 control points p0, p1, p2, p3 and a weight. The function is used to calculate a point n between p1 and p2 based
// on the weight. The weight is normalized, so if it's a value of 0 then the return value will be p1 and if its 1 it will return p2.
float catmullRom( float p0, float p1, float p2, float p3, float weight ) {
float weight2 = weight * weight;
return 0.5 * (
p0 * weight * ( ( 2.0 - weight ) * weight - 1.0 ) +
p1 * ( weight2 * ( 3.0 * weight - 5.0 ) + 2.0 ) +
p2 * weight * ( ( 4.0 - 3.0 * weight ) * weight + 1.0 ) +
p3 * ( weight - 1.0 ) * weight2 );
}
// Performs a horizontal catmulrom operation at a given V value.
float textureCubicU( sampler2D samp, vec2 uv00, float texel, float offsetV, float frac ) {
return catmullRom(
texture2DLod( samp, uv00 + vec2( -texel, offsetV ), 0.0 ).r,
texture2DLod( samp, uv00 + vec2( 0.0, offsetV ), 0.0 ).r,
texture2DLod( samp, uv00 + vec2( texel, offsetV ), 0.0 ).r,
texture2DLod( samp, uv00 + vec2( texel * 2.0, offsetV ), 0.0 ).r,
frac );
}
// Samples a texture using a bicubic sampling algorithm. This essentially queries neighbouring
// pixels to get an average value.
float textureBicubic( sampler2D samp, vec2 uv00, vec2 texel, vec2 frac ) {
return catmullRom(
textureCubicU( samp, uv00, texel.x, -texel.y, frac.x ),
textureCubicU( samp, uv00, texel.x, 0.0, frac.x ),
textureCubicU( samp, uv00, texel.x, texel.y, frac.x ),
textureCubicU( samp, uv00, texel.x, texel.y * 2.0, frac.x ),
frac.y );
}
// Gets the UV coordinates based on the world X Z position
vec2 worldToMapSpace( vec2 worldPosition ) {
return ( worldPosition / worldScale + 0.5 );
}
// Gets the height at a location p (world space)
float getHeight( vec3 worldPosition )
{
#ifdef USE_HEIGHTFIELD
vec2 heightUv = worldToMapSpace(worldPosition.xz);
vec2 tHeightSize = vec2( HEIGHTFIELD_WIDTH, HEIGHTFIELD_HEIGHT );
// If we increase the smoothness factor, the terrain becomes a lot smoother.
// This is because it has the effect of shrinking the texture size and increaing
// the texel size. Which means when we do sampling the samples are from farther away - making
// it smoother. However this means the terrain looks less like the original heightmap and so
// terrain picking goes a bit off.
float smoothness = 1.1;
tHeightSize /= smoothness;
// The size of each texel
vec2 texel = vec2( 1.0 / tHeightSize );
// Find the top-left texel we need to sample.
vec2 heightUv00 = ( floor( heightUv * tHeightSize ) ) / tHeightSize;
// Determine the fraction across the 4-texel quad we need to compute.
vec2 frac = vec2( heightUv - heightUv00 ) * tHeightSize;
float coarseHeight = textureBicubic( heightfield, heightUv00, texel, frac );
return altitude * coarseHeight + heightOffset;
#else
return 0.0;
#endif
}
Related
I've been following along with the OpenGL 4 Shading Language cookbook and have gotten a teapot rendering with bezier surfaces. The next step I'm attempting is to draw a wireframe over the surfaces using a geometry shader. The directions can be found here on pages 228-230. Following the code that is given, I've gotten the wireframe to display, however, I also have multiple fragments that flicker different shades of my material color.
An image of this can be seen
I have narrowed down the possible issues and have discovered that for some reason, when I perform my triangle height calculations, I am getting variable side lengths for my calculations, as if I hard code the values in the edge distance for each vertex of the triangle within the geometry shader, the teapot no longer flickers, but neither does a wireframe display. (variables ha, hb, hc in the geo shader below)
I was wondering if anyone has run into this issue before or are aware of a workaround.
Below are some sections of my code:
Geometry Shader:
/*
* Geometry Shader
*
* CSCI 499, Computer Graphics, Colorado School of Mines
*/
#version 410 core
layout( triangles ) in;
layout( triangle_strip, max_vertices = 3 ) out;
out vec3 GNormal;
out vec3 GPosition;
out vec3 ghalfwayVec;
out vec3 GLight;
noperspective out vec3 GEdgeDistance;
in vec4 TENormal[];
in vec4 TEPosition[];
in vec3 halfwayVec[];
in vec3 TELight[];
uniform mat4 ViewportMatrix;
void main() {
// Transform each vertex into viewport space
vec3 p0 = vec3(ViewportMatrix * (gl_in[0].gl_Position / gl_in[0].gl_Position.w));
vec3 p1 = vec3(ViewportMatrix * (gl_in[1].gl_Position / gl_in[1].gl_Position.w));
vec3 p2 = vec3(ViewportMatrix * (gl_in[2].gl_Position / gl_in[2].gl_Position.w));
// Find the altitudes (ha, hb and hc)
float a = length(p1 - p2);
float b = length(p2 - p0);
float c = length(p1 - p0);
float alpha = acos( (b*b + c*c - a*a) / (2.0*b*c) );
float beta = acos( (a*a + c*c - b*b) / (2.0*a*c) );
float ha = abs( c * sin( beta ) );
float hb = abs( c * sin( alpha ) );
float hc = abs( b * sin( alpha ) );
// Send the triangle along with the edge distances
GEdgeDistance = vec3( ha, 0, 0 );
GNormal = vec3(TENormal[0]);
GPosition = vec3(TEPosition[0]);
gl_Position = gl_in[0].gl_Position;
EmitVertex();
GEdgeDistance = vec3( 0, hb, 0 );
GNormal = vec3(TENormal[1]);
GPosition = vec3(TEPosition[1]);
gl_Position = gl_in[1].gl_Position;
EmitVertex();
GEdgeDistance = vec3( 0, 0, hc );
GNormal = vec3(TENormal[2]);
GPosition = vec3(TEPosition[2]);
gl_Position = gl_in[2].gl_Position;
EmitVertex();
EndPrimitive();
ghalfwayVec = halfwayVec[0];
GLight = TELight[0];
}
Fragment Shader:
/*
* Fragment Shader
*
* CSCI 441, Computer Graphics, Colorado School of Mines
*/
#version 410 core
in vec3 ghalfwayVec;
in vec3 GLight;
in vec3 GNormal;
in vec3 GPosition;
noperspective in vec3 GEdgeDistance;
layout( location = 0 ) out vec4 FragColor;
uniform vec3 mDiff, mAmb, mSpec;
uniform float shininess;
uniform light {
vec3 lAmb, lDiff, lSpec, lPos;
};
// The mesh line settings
uniform struct LineInfo {
float Width;
vec4 Color;
} Line;
vec3 phongModel( vec3 pos, vec3 norm ) {
vec3 lightVec2 = normalize(GLight);
vec3 normalVec2 = -normalize(GNormal);
vec3 halfwayVec2 = normalize(ghalfwayVec);
float sDotN = max( dot(lightVec2, normalVec2), 0.0 );
vec4 diffuse = vec4(lDiff * mDiff * sDotN, 1);
vec4 specular = vec4(0.0);
if( sDotN > 0.0 ) {
specular = vec4(lSpec * mSpec * pow( max( 0.0, dot( halfwayVec2, normalVec2 ) ), shininess ),1);
}
vec4 ambient = vec4(lAmb * mAmb, 1);
vec3 fragColorOut = vec3(diffuse + specular + ambient);
// vec4 fragColorOut = vec4(0.0,0.0,0.0,0.0);
return fragColorOut;
}
void main() {
// /*****************************************/
// /******* Final Color Calculations ********/
// /*****************************************/
// The shaded surface color.
vec4 color=vec4(phongModel(GPosition, GNormal), 1.0);
// Find the smallest distance
float d = min( GEdgeDistance.x, GEdgeDistance.y );
d = min( d, GEdgeDistance.z );
// Determine the mix factor with the line color
float mixVal = smoothstep( Line.Width - 1, Line.Width + 1, d );
// float mixVal = 1;
// Mix the surface color with the line color
FragColor = vec4(mix( Line.Color, color, mixVal ));
FragColor.a = 1;
}
I ended up stumbling across the solution to my issue. In the geometry shader, I was passing the halfway vector and the light vector after ending the primitive, as such, the values of these vectors was never being correctly sent to the fragment shader. Since no data was given to the fragment shader, garbage values were used and the Phong shading model used random values to compute the fragment color. Moving the two lines after EndPrimative() to the top of the main function in the geometry shader resolved the issue.
I am displacing vertices to form a 3D planet, using a random noise function:
float hash( float n ) { return fract(sin(n)*753.5453123); }
float snoise( in vec3 x )
{
vec3 p = floor(x);
vec3 f = fract(x);
f = f*f*(3.0-2.0*f);
float n = p.x + p.y*157.0 + 113.0*p.z;
return mix(mix(mix( hash(n+ 0.0), hash(n+ 1.0),f.x),
mix( hash(n+157.0), hash(n+158.0),f.x),f.y),
mix(mix( hash(n+113.0), hash(n+114.0),f.x),
mix( hash(n+270.0), hash(n+271.0),f.x),f.y),f.z);
}
As the points are calculated on the GPU I have no way of calculating smooth normals ( apart from flat normals using a geometry shader). I have found various methods of doing this i.e using the neighbour method, however this leaves lots of artefacts on the terrain while performing the lightning calculations.
I am currently calculating the normals using this function with varying different theta values however the lighting has loads of patched areas of bright light :
vec3 calcNormal(vec3 pos)
{
float theta = Theta;
vec3 vecTangent = normalize(cross(pos, vec3(1.0, 0.0, 0.0))
+ cross(pos, vec3(0.0, 1.0, 0.0)));
vec3 vecBitangent = normalize(cross(vecTangent, pos));
vec3 ptTangentSample = getPos(normalize(pos + theta * normalize(vecTangent)));
vec3 ptBitangentSample = getPos(normalize(pos + theta * normalize(vecBitangent)));
return normalize(cross(ptTangentSample - pos, ptBitangentSample - pos));
}
Let's say that I want to downsample from 4x4 to 2x2 texels texture, do some fancy stuff, and upsample it again from 2x2 to 4x4. How do I calculate the correct neighbor texels offsets? I can't use bilinear filtering or nearest filtering. I need to pick 4 samples for each fragment execution and pick the maximum one before downsampling. The same holds for the upsampling pass, i.e., I need to pick 4 samples for each fragment execution.
Have I calculated the neighbor offsets correctly(I'm using a fullscreen quad)?
//Downsample: 1.0 / 2.0, Upsample: 1.0 / 4.0.
vec2 texelSize = vec2(1.0 / textureWidth, 1.0 / textureHeight);
const vec2 DOWNSAMPLE_OFFSETS[4] = vec2[]
(
vec2(-0.5, -0.5) * texelSize,
vec2(-0.5, 0.5) * texelSize,
vec2(0.5, -0.5) * texelSize,
vec2(0.5, 0.5) * texelSize
);
const vec2 UPSAMPLE_OFFSETS[4] = vec2[]
(
vec2(-1.0, -1.0) * texelSize,
vec2(-1.0, 1.0) * texelSize,
vec2(1.0, -1.0) * texelSize,
vec2(1.0, 1.0) * texelSize
);
//Fragment shader.
#version 400 core
uniform sampler2D mainTexture;
in vec2 texCoord;
out vec4 fragColor;
void main(void)
{
#if defined(DOWNSAMPLE)
vec2 uv0 = texCoord + DOWNSAMPLE_OFFSETS[0];
vec2 uv1 = texCoord + DOWNSAMPLE_OFFSETS[1];
vec2 uv2 = texCoord + DOWNSAMPLE_OFFSETS[2];
vec2 uv3 = texCoord + DOWNSAMPLE_OFFSETS[3];
#else
vec2 uv0 = texCoord + UPSAMPLE_OFFSETS[0];
vec2 uv1 = texCoord + UPSAMPLE_OFFSETS[1];
vec2 uv2 = texCoord + UPSAMPLE_OFFSETS[2];
vec2 uv3 = texCoord + UPSAMPLE_OFFSETS[3];
#endif
float val0 = texture(mainTexture, uv0).r;
float val1 = texture(mainTexture, uv1).r;
float val2 = texture(mainTexture, uv2).r;
float val3 = texture(mainTexture, uv3).r;
//Do some stuff...
fragColor = ...;
}
The offsets look correct, assuming texelSize is in both cases the texel size of the render target. That is, twice as big for the downsampling pass than the upsampling pass. In the case of upsampling, you are not hitting the source texel centers exactly, but come close enough that nearest neighbor filtering snaps them to the intended result.
A more efficient option is to use textureGather instruction, specified in the ARB_texture_gather extension. When used to sample a texture, it returns the same four texels, that would be used for filtering. It only returns a single component of each texel to produce a vec4, but given that you only care about the red component, it's an ideal solution if the extension is available. The code would then be the same for both downsampling and upsampling:
#define GATHER_RED_COMPONENT 0
vec4 vals = textureGather(mainTexture, texcoord, GATHER_RED_COMPONENT);
// Output the maximum value
fragColor = max(max(vals.x, vals.y), max(vals.z, vals.w));
I was under the assumption that normal mapping should eliminate the visibility of triangles on a mesh, as lighting will be calculated based on unique normals per fragment instead of per vertex. As you can see in the image below, the normal map is definitely working but triangles are still visible. Is this an error?
I compute tangents as follows :
vec3 vert1( vertices[a+1] - vertices[a] );
vec3 vert2( vertices[a+2] - vertices[a] );
vec2 uv1( uvs[a+1] - uvs[a] );
vec2 uv2( uvs[a+2] - uvs[a] );
float r = (uv1.x * uv2.y) - (uv1.y * uv2.x);
vec3 tangent(vert1 * uv2.y - vert2 * uv1.y)*r;
Vertex Shader :
mat3 TBN_MATRIX;
TBN_MATRIX[0] = (MODEL_MATRIX * vec4( tangent,0 )).xyz;
TBN_MATRIX[2] = (MODEL_MATRIX * vec4( normal,0 )).xyz;
TBN_MATRIX[1] = cross( TBN_MATRIX[2], TBN_MATRIX[0] );
Fragment Shader :
fragment_normal = normalize( TBN_MATRIX * vec3(( 2 * texture( normal_map, uv_coordinates ).rgb ) - 1.0 ) );
My first thought is that a cross product is somehow not enough for the bitangent?
I'd like to know if there is a possibility to draw a rectangle in a fragment shader without using any if. I tried this :
void main(void)
{
vec2 resolution = vec2( 300.0, 300.0 );
vec2 center = resolution / 2.0;
vec2 position = ( gl_FragCoord.xy - center ) / resolution;
if ( ( abs(position.x) < 0.5 ) && ( abs(position.y) < 0.5 ) )
gl_FragColor = vec4( 0.0, 0.0, 1.0, 1.0 );
}
And when i test if the pixel is in the rectangle i must perform an if test.
I think there's a solution not to have an if, if you can help me ?
I'm not sure what you are trying to do but here's one idea?
precision mediump float;
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
void main( void ) {
vec2 position = ( gl_FragCoord.xy / resolution.xy ) + mouse / 4.0;
// x1 y1 x2 y2
vec4 rect = vec4(0.2, 0.3, 0.4, 0.5);
vec2 hv = step(rect.xy, position) * step(position, rect.zw);
float onOff = hv.x * hv.y;
gl_FragColor = mix(vec4(0,0,0,0), vec4(1,0,0,0), onOff);
}
Here's a working version