I'm generating terrain in Opengl geometry shader and am having trouble calculating normals for lighting. I'm generating the terrain dynamically each frame with a perlin noise function implemented in the geometry shader. Because of this, I need an efficient way to calculate normals per-vertex based on the noise function (no texture or anything). I am able to take cross product of 2 side to get face normals, but they are generated dynamically with the geometry so I cannot then go back and smooth the face normals for vertex normals. How can I get vertex normals on the fly just using the noise function that generates the height of my terrain in the y plane (therefore height being between 1 and -1). I believe I have to sample the noise function 4 times for each vertex, but I tried something like the following and it didn't work...
vec3 xP1 = vertex + vec3(1.0, 0.0, 0.0);
vec3 xN1 = vertex + vec3(-1.0, 0.0, 0.0);
vec3 zP1 = vertex + vec3(0.0, 0.0, 1.0);
vec3 zN1 = vertex + vec3(0.0, 0.0, -1.0);
float sx = snoise(xP1) - snoise(xN1);
float sz = snoise(zP1) - snoise(zN1);
vec3 n = vec3(-sx, 1.0, sz);
normalize(n);
return n;
The above actually generated lighting that moved around like perlin noise! So any advice for how I can get the per-vertex normals correctly?
The normal is the vector perpendicular to the tangent (also known as slope). The slope of a function is its derivative; for n dimensions its n partial derivatives. So you sample the noise around a center point P and at P ± (δx, 0) and P ± (0, δy), with δx, δy choosen to be as small as possible, but large enough for numerical stability. This yields you the tangents in each direction. Then you take the cross product of them, normalize the result and got the normal at P.
You didn't say exactly how you were actually generating the positions. So I'm going to assume that you're using the Perlin noise to generate height values in a height map. So, for any position X, Y in the hieghtmap, you use a 2D noise function to generate the Z value.
So, let's assume that your position is computed as follows:
vec3 CalcPosition(in vec2 loc) {
float height = MyNoiseFunc2D(loc);
return vec3(loc, height);
}
This generates a 3D position. But in what space is this position in? That's the question.
Most noise functions expect loc to be two values on some particular floating-point range. How good your noise function is will determine what range you can pass values in. Now, if your model space 2D positions are not guaranteed to be within the noise function's range, then you need to transform them to that range, do the computations, and then transform it back to model space.
In so doing, you now have a 3D position. The transform for the X and Y values is simple (the reverse of the transform to the noise function's space), but what of the Z? Here, you have to apply some kind of scale to the height. The noise function will return a number on the range [0, 1), so you need to scale this range to the same model space that your X and Y values are going to. This is typically done by picking a maximum height and scaling the position appropriately. Therefore, our revised calc position looks something like this:
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;
}
The two matrices transform to the noise function's space, and then transform back. Your actual code could use less complicated structures, depending on your use case, but a full affine transformation is simple to describe.
OK, now that we have established that, what you need to keep in mind is this: nothing makes sense unless you know what space it is in. Your normal, your positions, nothing matters until you establish what space it is in.
This function returns positions in model space. We need to calculate normals in model space. To do that, we need 3 positions: the current position of the vertex, and two positions that are slightly offset from the current position. The positions we get must be in model space, or our normal will not be.
Therefore, we need to have the following function:
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;
}
Obviously, you can merge these two functions into one.
The delta value is a small offset in the space of the noise texture's input. The size of this offset depends on your noise function; it needs to be big enough to return a height that is significantly different from the one returned by the actual current position. But it needs to be small enough that you aren't pulling from random parts of the noise distribution.
You should get to know your noise function.
Now that you have the three positions (the current position, the x-offset, and the y-offset) in model space, you can compute the vertex normal in model space:
vec3 modelXGrad = modelXOffset - modelPosition;
vec3 modelYGrad = modelYOffset - modelPosition;
vec3 modelNormal = normalize(cross(modelXGrad, modelYGrad));
From here, do the usual things. But never forget to keep track of the spaces of your various vectors.
Oh, and one more thing: this should be done in the vertex shader. There's no reason to do this in a geometry shader, since none of the computations affect other vertices. Let the GPU's parallelism work for you.
Related
I'm trying to implement Normal Mapping, using a simple cube that i created. I followed this tutorial https://learnopengl.com/Advanced-Lighting/Normal-Mapping but i can't really get how normal mapping should be done when drawing 3d objects, since the tutorial is using a 2d object.
In particular, my cube seems almost correctly lighted but there's something i think it's not working how it should be. I'm using a geometry shader that will output green vector normals and red vector tangents, to help me out. Here i post three screenshot of my work.
Directly lighted
Side lighted
Here i actually tried calculating my normals and tangents in a different way. (quite wrong)
In the first image i calculate my cube normals and tangents one face at a time. This seems to work for the face, but if i rotate my cube i think the lighting on the adiacent face is wrong. As you can see in the second image, it's not totally absent.
In the third image, i tried summing all normals and tangents per vertex, as i think it should be done, but the result seems quite wrong, since there is too little lighting.
In the end, my question is how i should calculate normals and tangents.
Should i consider per face calculations or sum vectors per vertex across all relative faces, or else?
EDIT --
I'm passing normal and tangent to the vertex shader and setting up my TBN matrix. But as you can see in the first image, drawing face by face my cube, it seems that those faces adjacent to the one i'm looking directly (that is well lighted) are not correctly lighted and i don't know why. I thought that i wasn't correctly calculating my 'per face' normal and tangent. I thought that calculating some normal and tangent that takes count of the object in general, could be the right way.
If it's right to calculate normal and tangent as visible in the second image (green normal, red tangent) to set up the TBN matrix, why does the right face seems not well lighted?
EDIT 2 --
Vertex shader:
void main(){
texture_coordinates = textcoord;
fragment_position = vec3(model * vec4(position,1.0));
mat3 normalMatrix = transpose(inverse(mat3(model)));
vec3 T = normalize(normalMatrix * tangent);
vec3 N = normalize(normalMatrix * normal);
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);
mat3 TBN = transpose(mat3(T,B,N));
view_position = TBN * viewPos; // camera position
light_position = TBN * lightPos; // light position
fragment_position = TBN * fragment_position;
gl_Position = projection * view * model * vec4(position,1.0);
}
In the VS i set up my TBN matrix and i transform all light, fragment and view vectors to tangent space; doing so i won't have to do any other calculation in the fragment shader.
Fragment shader:
void main() {
vec3 Normal = texture(TextSamplerNormals,texture_coordinates).rgb; // extract normal
Normal = normalize(Normal * 2.0 - 1.0); // correct range
material_color = texture2D(TextSampler,texture_coordinates.st); // diffuse map
vec3 I_amb = AmbientLight.color * AmbientLight.intensity;
vec3 lightDir = normalize(light_position - fragment_position);
vec3 I_dif = vec3(0,0,0);
float DiffusiveFactor = max(dot(lightDir,Normal),0.0);
vec3 I_spe = vec3(0,0,0);
float SpecularFactor = 0.0;
if (DiffusiveFactor>0.0) {
I_dif = DiffusiveLight.color * DiffusiveLight.intensity * DiffusiveFactor;
vec3 vertex_to_eye = normalize(view_position - fragment_position);
vec3 light_reflect = reflect(-lightDir,Normal);
light_reflect = normalize(light_reflect);
SpecularFactor = pow(max(dot(vertex_to_eye,light_reflect),0.0),SpecularLight.power);
if (SpecularFactor>0.0) {
I_spe = DiffusiveLight.color * SpecularLight.intensity * SpecularFactor;
}
}
color = vec4(material_color.rgb * (I_amb + I_dif + I_spe),material_color.a);
}
Handling discontinuity vs continuity
You are thinking about this the wrong way.
Depending on the use case your normal map may be continous or discontinous. For example in your cube, imagine if each face had a different surface type, then the normals would be different depending on which face you are currently in.
Which normal you use is determined by the texture itself and not by any blending in the fragment.
The actual algorithm is
Load rgb values of normal
Convert to -1 to 1 range
Rotate by the model matrix
Use new value in shading calculations
If you want continous normals, then you need to make sure that the charts in the texture space that you use obey that the limits of the texture coordinates agree.
Mathematically that means that if U and V are regions of R^2 that map to the normal field N of your Shape then if the function of the mapping is f it should be that:
If lim S(x_1, x_2) = lim S(y_1, y_2) where {x1,x2} \subset U and {y_1, y_2} \subset V then lim f(x_1, x_2) = lim f(y_1, y_2).
In plain English, if the cooridnates in your chart map to positions that are close in the shape, then the normals they map to should also be close in the normal space.
TL;DR do not belnd in the fragment. This is something that should be done by the normal map itself when its baked, not'by you when rendering.
Handling the tangent space
You have 2 options. Option n1, you pass the tangent T and the normal N to the shader. In which case the binormal B is T X N and the basis {T, N, B} gives you the true space where normals need to be expressed.
Assume that in tangent space, x is side, y is forward z is up. Your transformed normal becomes (xB, yT, zN).
If you do not pass the tangent, you must first create a random vector that is orthogonal to the normal, then use this as the tangent.
(Note N is the model normal, where (x,y,z) is the normal map normal)
I'm working on the following shader that
translates (on y)
rotates
repeats (tiles)
a texture:
uniform sampler2D texture;
uniform vec2 resolution;
varying vec4 vertColor;
varying vec4 vertTexCoord;
uniform float rotation;
uniform float yTranslation;
void main() {
vec2 repeat = vec2(2, 2);
vec2 coord = vertTexCoord.st;
coord.y += yTranslation;
float sin_factor = sin(rotation);
float cos_factor = cos(rotation);
coord += vec2(0.5);
coord = coord * mat2(cos_factor, sin_factor, -sin_factor, cos_factor) * 0.3;
coord -= vec2(0.5);
coord = vec2(mod(coord.x * repeat.x, 1.0f), mod(coord.y * repeat.y, 1.0f));
gl_FragColor = texture2D(texture, coord) * vertColor;
}
Current behavior
Desired behavior
I want the texture to always rotate around the center, no matter how far it has been translated.
Simply swapping the order of the steps results in weird behavior. What am I missing?
Your problem statement in the question is really wrong: Your addtional comment (to a now deleted answer):
I have a boat that always stays in the center of the screen, the water texture (controlled by this shader) under it moves to make it look like the boat is moving. The movement of the water texture is controlled by rotation (for steering) and yTranslation (for how far the boat has moved forwards/backwards)
makes it clear that you're asking for a different thing, and the approach described in the question is simply not going to solve your problem.
When your boat moves and rotates, it will basically travel on a curve (and you want the inverse of that curve to travel through texture space). But your 2dof parameters rotation and yTranslation are not capable of describing the curve. Your problem needs at least another parameter xTranslation - so in the end, you need a 2D vector describing the position of your boat + an angle describing the rotation. And you need to properly accumulate this data at each simulation step:
update the rotation accordingly
calculate the 2D vector your ship is heading tom as defined by the current rotation
scale it according to the velocity of the movement
accumulate it onto the position vector.
Then, your shader simply has to
1. translate the texcoords by position (or -position, whatever you store)
2. Rotate around the pivot point (which is constant and only depends on how you layed out your texture space)
coord = vec2(mod(coord.x * repeat.x, 1.0f), mod(coord.y * repeat.y, 1.0f));
that's a waste of GPU ALU cycles, the TMUs will already do the mod for you with the GL_REPEAT wrap modes.
However, what you now have here is rotation, scaling and translation: so just use a single matrix for the whole texcoord transformation - the accumulation of the 2D position that I talked about earlier can nicely by done with the matrix representations. It will also remove the sin and cos from your shader, which is another huge waste right now.
Does anyone know why 'depth' (vertShader) differs from 'gl_FragCoord.z' (rendered from opengl)? Especially with decreasing z the difference becomes higher. Is it possible that 'depth' is at higher z values more precise?
.vsh
out float depth;
void main (void) {
vec4 pos = mvpMatrix * vertex;
depth = ((pos.z / pos.w) + 1.0) * 0.5;
gl_Position = pos;
}
.fsh
in float depth;
void main(void) {
gl_FragDepth = depth;// or gl_FragCoord.z;
}
There are a couple of issues with your approach, with the main points are:
gl_FragCoord.z is hyperbolically distorted window space z value. However, the hyperoblical z/w value for each vertex is just linearily interpolated in screen space for each framgent. But when you use a varying out float depth = (pos.z / pos.w), the GL will do a perspective-corrected interpolation which is non-linear. You could fix this by using flat out float depth.
(pos.z/pos.w) doesn't even make sense. Think about it: if the point lies in a plane where the camera is, you'll get pos.w=0, and no valid result. gl_FragCoord.z does not have this issue because the clipping is done before the divide, and it will do the divide for a new vertex which lies on the near plane, and which you'll never going to see (there's no vertex shader invocation for that).
The issue is also present when points lie behind the camera, they will end up mirrored in front of the camera. If you have a primitive where vertices lie on both sides of the camera, you will get complete bullshit as your interpolated depth value, no matter which interpolation method you chose.
I've gotten shadows working properly for my Directional Lights, but I'm a little stumped when it comes to Point Lights. My idea is to use a cube map to render the depth from all six sides surrounding the light. So far, that's all working and good. I have verified this step by rendering each face of my cube to a 2D image, and it appears to be correct.
Now I'm trying to get the shadows to show up in the world. To do so, I am using GLSL's samplerCubeShadow data type. With it, I do:
vec3 lightToFrag = light.position - fragPos
float lenLightToFrag = length(lightToFrag)
vec3 normLightToFrag = normalize(lightToFrag)
float shadow = texture(depthTexture, vec4(normLightToFrag, lightToFrag))
I've tried a number of configurations, and this always renders my scene in black. Any ideas? My fragPos is just the model matrix times the vertex position. Should I be applying the light's model-view matrix to it? Or, similarly, should I be applying the world's model-view matrix to the light? Any feedback is really appreciated!
Assuming you are storing depth values in cubemap;
AFAIK cubemap is an AABB in world space, so you need to do calculations in world space. In your case light.position and fragPos must be in world space, or provide alternative variables/members if you use these names in view space in somewhere else e.g. per-fragment light calculations
Also you need to convert lightToFrag to depth value before pass to texture.
This answer shows how to convert lightToFrag to depth value: Omnidirectional shadow mapping with depth cubemap
Here my implementation (I removed #ifdef SHAD_CUBE because others use same name):
uniform samplerCubeShadow uShadMap;
uniform vec2 uFarNear;
float depthValue(const in vec3 v) {
vec3 absv = abs(v);
float z = max(absv.x, max(absv.y, absv.z));
return uFarNear.x + uFarNear.y / z;
}
float shadowCoef() {
vec3 L;
float d;
L = vPosWS - light.position_ws;
d = depthValue(L);
return texture(uShadMap, vec4(L, d));
}
This may require uniform model matrix if you only have ModelViewProjection (MVP)
Here how to calculate uNearFar at client side:
float n, f, nfsub, nf[2];
n = sm->near;
f = sm->far;
nfsub = f - n;
nf[0] = (f + n) / nfsub * 0.5f + 0.5f;
nf[1] =-(f * n) / nfsub;
glUniform2f(gkUniformLoc(prog, "uFarNear"), nf[0], nf[1]);
this is just optimization but you don't have to use this and follow the link which I mentioned before.
You may need bias value, related answer uses bias but I'm not sure how to apply it to cubemap correctly. I'm not sure d -+ 0.0001 is correct way or not.
If you want to store world distances in cubemap then this tutorial seems god one: https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows
I use deferred rendering and I store a fragment position in the camera view space. When I perform a shadow calculation I need to transform a camera view space to the shadow map space. I build a shadow matrix this way:
shadowMatrix = shadowBiasMatrix * lightProjectionMatrix * lightViewMatrix * inverseCameraViewMatrix;
shadowBiasMatrix shifts values from [-1,1] to [0,1] range.
lightProjectionMatrix that's orthographic projection matrix for a directional light. lightViewMatrix looks at the frustum center and contains a light direction.
inverseCameraViewMatrix transforms a fragment position from a camera view space to the world space.
I wonder if it is correct to multiply the inverse camera view matrix with the other matrices ? Maybe I should use the inverse camera view matrix separately ?
First scenario:
vec4 shadowCoord = shadowMatrix * vec4(cameraViewSpacePosition, 1.0);
Second scenario, a inverse camera view matrix is use separately:
vec4 worldSpacePosition = inverseCameraViewSpaceMatrix * vec4(cameraViewSpacePosition, 1.0);
vec4 shadowCoord = shadowMatrix * worldSpacePosition;
Precomputing the shadow matrix in the described way is the correct approach and should work as expected.
Because of the associativity of matrix multiplication the results of the three scenarios should be identical (ignoring floating point precision) and are thus interchangeable.
But because these calculations are done in the fragment shader, it is best to premultiply the matrixes in the main program to do as few operations as possible per fragment.
I'm also writing a deferred renderer currently and calculate my matrices in the same way without any problems.
// precomputed: lightspace_mat = light_projection * light_view * inverse_cam_view
// calculate position in clip-space of the lightsource
vec4 lightspace_pos = lightspace_mat * vec4(viewspace_pos, 1.0);
// perspective divide
lightspace_pos/=lightspace_pos.w;
// move range from [-1.0, 1.0] to [0.0, 1.0]
lightspace_pos = lightspace_pos * vec4(0.5) + vec4(0.5);
// sample shadowmap
float shadowmap_depth = texture(shadowmap, lightspace_pos.xy).r;
float fragment_depth = lightspace_pos.z;
I also found this tutorial using a simillar approach, that could be helpfull: http://www.codinglabs.net/tutorial_opengl_deferred_rendering_shadow_mapping.aspx
float readShadowMap(vec3 eyeDir)
{
mat4 cameraViewToWorldMatrix = inverse(worldToCameraViewMatrix);
mat4 cameraViewToProjectedLightSpace = lightViewToProjectionMatrix * worldToLightViewMatrix * cameraViewToWorldMatrix;
vec4 projectedEyeDir = cameraViewToProjectedLightSpace * vec4(eyeDir,1);
projectedEyeDir = projectedEyeDir/projectedEyeDir.w;
vec2 textureCoordinates = projectedEyeDir.xy * vec2(0.5,0.5) + vec2(0.5,0.5);
const float bias = 0.0001;
float depthValue = texture2D( tShadowMap, textureCoordinates ) - bias;
return projectedEyeDir.z * 0.5 + 0.5 < depthValue;
}
The eyeDir that comes in input is in View Space. To find the pixel in
the shadow map we need to take that point and covert it into the
light's clip space, which means going from Camera View Space into
World Space, then into Light View Space, than into Light Projection
Space/Clip space. All these transformations are done using matrices;
if you are not familiar with space changes you may want to read my
article about spaces and transformations.
Once we are in the right space we calculate the texture coordinates
and we are finally ready to read from the shadow map. Bias is a small
offset that we apply to the values in the map to avoid that because of
rounding errors a point ends up shading itself! So we shift all the
map back a bit so that all the values in the map are slightly smaller
than they should.