I need to calculate projection of point on specific line segment in shader (OpenGL ES 2).
Here is how I test the algorithm:
I draw simple triangle with points A(0, 0.5), B(1, -0.5), C(-1, -0.5).
I calculate projection of every point on line segment AC.
I draw points with a projection in the middle of a line segment AC in blue. And the remaining points in green.
I expect to get a green triangle with a blue line perpendicular to the side AC. But blue line is not perpendicular to AC.
I check projection formula in code with drawing on canvas and got expected result.
What's my mistake?
Result of shader:
Vertex shader:
uniform mat4 matrix;
attribute vec4 position;
varying vec4 vPosition;
void main()
{
vPosition = matrix * position;
gl_Position = matrix * position;
}
Fragment shader:
precision mediump float;
varying vec4 vPosition;
void main()
{
vec2 P = vPosition.xy;
vec2 A = vec2(0.0, 0.5);
vec2 B = vec2(-1.0, -0.5);
vec2 AP = P - A;
vec2 AB = B - A;
vec2 projection = A + dot(AP, AB) / dot(AB, AB) * AB;
if(projection.x > -0.51 && projection.x < -0.49 && projection.y > -0.01 && projection.y < 0.01) {
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
} else {
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
}
You didn't consider the rectangular aspect of the of the window. When the normalized device coordinates in the range [-1, 1] are mapped to the viewport rectangle (see glViewport) then the triangle gets stretched. This causes that angles of 90 degree are not maintained.
Add a uniform variable to the fragment shader which contains the width and height of the viewport:
uniform vec2 u_resolution;
Calculate the aspect ratio:
float aspect = u_resolution.x / u_resolution.y;
Of course you can initialize the variable float aspect, by a constant value, too.
e.g. float aspect = 16.0/9.0;
Correct the coordinates of the points A, B and P according to the aspect ratio:
vec2 P = vPosition.xy;
vec2 A = vec2(0.0, 0.5);
vec2 B = vec2(-1.0, -0.5);
A.x *= aspect;
B.x *= aspect;
P.x *= aspect;
And consider the aspect ration when evaluating the result projection:
vec2 projection = A + dot(AP, AB) / dot(AB, AB) * AB;
projection.x /= aspect;
The final fragment shader may look like this:
precision mediump float;
varying vec4 vPosition;
uniform vec2 u_resolution;
void main()
{
float aspect = u_resolution.x / u_resolution.y;
vec2 as = vec2(aspect, 1.0);
vec2 P = as * vPosition.xy;
vec2 A = as * vec2(0.0, 0.5);
vec2 B = as * vec2(-1.0, -0.5);
vec2 AP = P - A;
vec2 AB = B - A;
vec2 projection = A + dot(AP, AB) / dot(AB, AB) * AB / as;
if(projection.x > -0.51 && projection.x < -0.49 && projection.y > -0.01 && projection.y < 0.01) {
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
} else {
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
}
Related
Based on the accepted answer for the question Is there a way to draw a circle with the fragment shader at the position of a point from the vertex shader?, I am having the following results when I try to draw 3 circles based on the points (0.0, -0.75, 0.0), (0.0, 0.0, 0.0) and (0.0, 0.75, 0.0). Both window and viewport are 418x418.
The question is: Why only the center point is rendered properly, while the bottom circle is stretched and the top circle is shrinked, both on the Y-axis?
Vertex Shader
precision highp float;
attribute vec4 vPosition;
varying vec2 pointPos;
uniform vec2 windowSize; // = (window-width, window-height)
void main()
{
gl_Position = vPosition;
gl_PointSize = 900.0;
vec2 ndcPos = gl_Position.xy / gl_Position.w;
pointPos = windowSize * (ndcPos*0.5 + 0.5);
}
Fragment Shader
precision highp float;
varying vec2 pointPos;
uniform vec4 fColor;
const float threshold = 0.3;
const float aRadius = 10.0;
void main()
{
float dist = distance(pointPos, gl_FragCoord.xy);
if (dist > aRadius)
discard;
float d = dist / aRadius;
vec3 color = mix(fColor.rgb, vec3(0.0), step(1.0-threshold, d));
gl_FragColor = vec4(color, 1.0);
}
I am struggling to understand why the top and bottom circles are not being rendered properly, but I could not figure it out yet.
In the following example I would like to manually create points (x, y, angle) from SFML then fill a circle around each point. The angle will be used later, for now I use it for debugging.
SFML draws 2 points
Vertex shader convert points to -1..1 range
Geometry shader creates squares at each point position and pass the center to fragment shader
Fragment shader would pain a circle within each square.
From my understanding, in the geometry shader I emit center which is the center coordinates of each primitive. From computing the distance from this center I would be able to paint a circle in each primitive from the fragment shader.
In the picture below I notice the center is only set once and I don't understand why.
SFML App
#include <iostream>
#include <SFML/Graphics.hpp>
#include <vector>
#include <GL/glew.h>
#include <random>
#define WIDTH 800
int main() {
sf::RenderWindow window(sf::VideoMode(WIDTH, WIDTH), "Test");
sf::Shader shader;
shader.loadFromFile("shader.vert", "shader.geom", "shader.frag");
sf::Transform matrix = sf::Transform::Identity;
matrix.scale(1.0 / WIDTH, 1.0 / WIDTH);
sf::Glsl::Mat4 projectionViewMatrix = matrix;
shader.setUniform("projectionViewMatrix", projectionViewMatrix);
std::vector<GLfloat> vertices;
vertices.push_back(400.0); vertices.push_back(400.0); vertices.push_back(0.0);
vertices.push_back(400.0); vertices.push_back(-400.0); vertices.push_back(0.25);
vertices.push_back(-400.0); vertices.push_back(-400.0); vertices.push_back(0.5);
vertices.push_back(-400.0); vertices.push_back(400.0); vertices.push_back(0.75);
while (window.isOpen()) {
sf::Event currEvent;
while (window.pollEvent(currEvent)) {
switch (currEvent.type) {
case(sf::Event::Closed):
window.close(); break;
}
}
window.clear(sf::Color::Black);
glVertexPointer(3, GL_FLOAT, 0, vertices.data());
glEnableClientState(GL_VERTEX_ARRAY);
glDrawArrays(GL_POINTS, 0, vertices.size() / 3);
glDisableClientState(GL_VERTEX_ARRAY);
sf::Shader::bind(&shader);
window.display();
}
}
Vertex Shader
#version 150
in vec3 position;
out vec3 pass_colour;
out float angle;
uniform mat4 projectionViewMatrix;
void main(void) {
gl_Position = projectionViewMatrix * vec4(position.xy, 0.0 ,1.0);
angle = position.z;
pass_colour = vec3(1.0);
}
Geometry shader
#version 150
layout (points) in;
layout (triangle_strip, max_vertices = 6) out;
in vec3 pass_colour[];
in float angle[];
out vec3 finalColour;
out vec4 centerPosition;
uniform mat4 projectionViewMatrix;
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void createVertex(vec3 offset, vec3 colour, float z = 0.0) {
vec4 actualOffset = vec4(offset, z);
vec4 worldPosition = gl_in[0].gl_Position + actualOffset;
gl_Position = worldPosition;
finalColour = colour;
vec4 pointPosition = gl_in[0].gl_Position;
centerPosition = pointPosition;
EmitVertex();
}
void main(void) {
float corner = 0.3;
vec3 colour = hsv2rgb(vec3(angle[0], 1.0, 1.0));
createVertex(vec3(-corner, -corner, 0.0), colour, 0.0);
createVertex(vec3(corner, -corner, 0.0), colour, 0.0);
createVertex(vec3(-corner, corner, 0.0), colour, 0.0);
createVertex(vec3(corner, corner, 0.0), colour, 0.0);
createVertex(vec3(corner, -corner, 0.0), colour, 0.0);
createVertex(vec3(-corner, corner, 0.0), colour, 0.0);
EndPrimitive();
}
Fragment shader
#version 150
in vec3 finalColour;
in vec4 centerPosition;
out vec4 out_Colour;
void main(void){
vec2 resolution = vec2(800.0/2.0, 800.0/2.0);
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec2 uvc = (centerPosition.xy + vec2(1.0)) / 2.0;
float dist = length(uv - uvc);
out_Colour = vec4(finalColour * dist, 0.8);
}
I still don't explain everything, but it works with this fragment:
#version 150
in vec4 finalColour;
in vec4 centerPosition;
out vec4 out_Colour;
void main(void){
vec2 resolution = vec2(800.0/2.0, 800.0/2.0);
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec2 p = vec2(1.0, 1.0) + centerPosition.xy;
vec2 uvc = p;
float dist = length(uv - uvc);
float col = 1.0 - smoothstep(0.0, 0.1, dist);
out_Colour = vec4(finalColour.rgb * col, 1.0);
}
I'm trying to implement Oren-Nayar lighting in the fragment shader as shown here.
However, I'm getting some strange lighting effects on the terrain as shown below.
I am currently sending the shader the 'view direction' uniform as the camera's 'front' vector. I am not sure if this is correct, as moving the camera around changes the artifacts.
Multiplying the 'front' vector by the MVP matrix gives a better result, but the artifacts are still very noticable when viewing the terrain from some angles. It is particularly noticable in dark areas and around the edges of the screen.
What could be causing this effect?
Artifact example
How the scene should look
Vertex Shader
#version 450
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
out VS_OUT {
vec3 normal;
} vert_out;
void main() {
vert_out.normal = normal;
gl_Position = vec4(position, 1.0);
}
Tesselation Control Shader
#version 450
layout(vertices = 3) out;
in VS_OUT {
vec3 normal;
} tesc_in[];
out TESC_OUT {
vec3 normal;
} tesc_out[];
void main() {
if(gl_InvocationID == 0) {
gl_TessLevelInner[0] = 1.0;
gl_TessLevelInner[1] = 1.0;
gl_TessLevelOuter[0] = 1.0;
gl_TessLevelOuter[1] = 1.0;
gl_TessLevelOuter[2] = 1.0;
gl_TessLevelOuter[3] = 1.0;
}
tesc_out[gl_InvocationID].normal = tesc_in[gl_InvocationID].normal;
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
}
Tesselation Evaluation Shader
#version 450
layout(triangles, equal_spacing) in;
in TESC_OUT {
vec3 normal;
} tesc_in[];
out TESE_OUT {
vec3 normal;
float height;
vec4 shadow_position;
} tesc_out;
uniform mat4 model_view;
uniform mat4 model_view_perspective;
uniform mat3 normal_matrix;
uniform mat4 depth_matrix;
vec3 lerp(vec3 v0, vec3 v1, vec3 v2) {
return (
(vec3(gl_TessCoord.x) * v0) +
(vec3(gl_TessCoord.y) * v1) +
(vec3(gl_TessCoord.z) * v2)
);
}
vec4 lerp(vec4 v0, vec4 v1, vec4 v2) {
return (
(vec4(gl_TessCoord.x) * v0) +
(vec4(gl_TessCoord.y) * v1) +
(vec4(gl_TessCoord.z) * v2)
);
}
void main() {
gl_Position = lerp(
gl_in[0].gl_Position,
gl_in[1].gl_Position,
gl_in[2].gl_Position
);
tesc_out.normal = normal_matrix * lerp(
tesc_in[0].normal,
tesc_in[1].normal,
tesc_in[2].normal
);
tesc_out.height = gl_Position.y;
tesc_out.shadow_position = depth_matrix * gl_Position;
gl_Position = model_view_perspective * gl_Position;
}
Fragment Shader
#version 450
in TESE_OUT {
vec3 normal;
float height;
vec4 shadow_position;
} frag_in;
out vec4 colour;
uniform vec3 view_direction;
uniform vec3 light_position;
#define PI 3.141592653589793
void main() {
const vec3 ambient = vec3(0.1, 0.1, 0.1);
const float roughness = 0.8;
const vec4 water = vec4(0.0, 0.0, 0.8, 1.0);
const vec4 sand = vec4(0.93, 0.87, 0.51, 1.0);
const vec4 grass = vec4(0.0, 0.8, 0.0, 1.0);
const vec4 ground = vec4(0.49, 0.27, 0.08, 1.0);
const vec4 snow = vec4(0.9, 0.9, 0.9, 1.0);
if(frag_in.height == 0.0) {
colour = water;
} else if(frag_in.height < 0.2) {
colour = sand;
} else if(frag_in.height < 0.575) {
colour = grass;
} else if(frag_in.height < 0.8) {
colour = ground;
} else {
colour = snow;
}
vec3 normal = normalize(frag_in.normal);
vec3 view_dir = normalize(view_direction);
vec3 light_dir = normalize(light_position);
float NdotL = dot(normal, light_dir);
float NdotV = dot(normal, view_dir);
float angleVN = acos(NdotV);
float angleLN = acos(NdotL);
float alpha = max(angleVN, angleLN);
float beta = min(angleVN, angleLN);
float gamma = dot(view_dir - normal * dot(view_dir, normal), light_dir - normal * dot(light_dir, normal));
float roughnessSquared = roughness * roughness;
float roughnessSquared9 = (roughnessSquared / (roughnessSquared + 0.09));
// calculate C1, C2 and C3
float C1 = 1.0 - 0.5 * (roughnessSquared / (roughnessSquared + 0.33));
float C2 = 0.45 * roughnessSquared9;
if(gamma >= 0.0) {
C2 *= sin(alpha);
} else {
C2 *= (sin(alpha) - pow((2.0 * beta) / PI, 3.0));
}
float powValue = (4.0 * alpha * beta) / (PI * PI);
float C3 = 0.125 * roughnessSquared9 * powValue * powValue;
// now calculate both main parts of the formula
float A = gamma * C2 * tan(beta);
float B = (1.0 - abs(gamma)) * C3 * tan((alpha + beta) / 2.0);
// put it all together
float L1 = max(0.0, NdotL) * (C1 + A + B);
// also calculate interreflection
float twoBetaPi = 2.0 * beta / PI;
float L2 = 0.17 * max(0.0, NdotL) * (roughnessSquared / (roughnessSquared + 0.13)) * (1.0 - gamma * twoBetaPi * twoBetaPi);
colour = vec4(colour.xyz * (L1 + L2), 1.0);
}
First I've plugged your fragment shader into my renderer with my view/normal/light vectors and it works perfectly. So the problem has to be in the way you calculate those vectors.
Next, you say that you set view_dir to your camera's front vector. I assume that you meant "camera's front vector in the world space" which would be incorrect. Since you calculate the dot products with vectors in the camera space, the view_dir must be in the camera space too. That is vec3(0,0,1) would be an easy way to check that. If it works -- we found your problem.
However, using (0,0,1) for the view direction is not strictly correct when you do perspective projection, because the direction from the fragment to the camera then depends on the location of the fragment on the screen. The correct formula then would be view_dir = normalize(-pos) where pos is the fragment's position in camera space (that is with model-view matrix applied without the projection). Further, this quantity now depends only on the fragment location on the screen, so you can calculate it as:
view_dir = normalize(vec3(-(gl_FragCoord.xy - frame_size/2) / (frame_width/2), flen))
flen is the focal length of your camera, which you can calculate as flen = cot(fovx/2).
I know this is a long dead thread, but I've been having the same problem (for several years), and finally found the solution...
It can be partially solved by fixing the orientation of the surface normals to match the polygon winding direction, but you can also get rid of the artifacts in the shader, by changing the following two lines...
float angleVN = acos(cos_nv);
float angleLN = acos(cos_nl);
to this...
float angleVN = acos(clamp(cos_nv, -1.0, 1.0));
float angleLN = acos(clamp(cos_nl, -1.0, 1.0));
Tada!
So I am currently working on trying to create a spotlight in my vertex shader, currently I can produce directional and/or point light by using the Phong lighting model.
Im finding it hard to calculate the correct angles for the spotlight, basically just want a spotlight that comes from 0,0,0 in eye space and looks down the Z co-ord.
I am trying to just make everything (for now) in the cone to be bright white and everything outside it dark
#version 130
uniform mat4 model_view_matrix;
uniform mat4 projection_matrix;
uniform mat3 normal_matrix;
uniform int light_mode;
uniform vec4 light_pos;
uniform vec3 light_ambient;
uniform vec3 light_diffuse;
uniform vec3 light_specular;
uniform vec3 mtl_ambient;
uniform vec3 mtl_diffuse;
uniform vec3 mtl_specular;
uniform float mtl_shininess;
// Spotlight test
const float spotCutOff = 100.00f;
in vec3 position;
in vec3 normal;
in vec2 texCoord;
out vec2 st;
out vec4 litColour;
vec3 phongLight(in vec4 position, in vec3 norm)
{
// s is the direction from the light to the vertex
vec3 s;
if (light_pos.w == 0.0) {
s = normalize(light_pos.xyz);
}
else {
s = normalize(vec3(light_pos - position));
}
// v is the direction from the eye to the vertex
vec3 v = normalize(-position.xyz);
// r is the direction of light reflected from the vertex
vec3 r = reflect(-s, norm);
vec3 ambient = light_ambient * mtl_ambient;
// The diffuse component
float sDotN = max(dot(s,norm), 0.0);
vec3 diffuse = light_diffuse * mtl_diffuse * sDotN;
// Specular component
vec3 spec = vec3(0.0);
if (sDotN > 0.0)
spec = light_specular * mtl_specular * pow(max(dot(r,v), 0.0), mtl_shininess);
return ambient + diffuse + spec;
}
vec3 spotLight(in vec4 position, in vec3 norm)
{
vec3 ambient = vec3(0.2, 0.2, 0.2);
vec3 lightDir = normalize(vec3(light_pos - position));
vec3 spotDir = vec3(0.0, 0.0, -1.0);
float angle = degrees(acos(dot(spotDir, lightDir)));
//angle = max (angle, 0);
if ((angle) < spotCutOff) {
return vec3(1.0, 1.0, 1.0);
}
float dist = sqrt(positon.x * position.x + position.y + position.y + position.z * position.z);
if (dist < 1) {
return vec3(1.0,1.0,0.0);
}
return vec3(0.2, 0.2, 0.2);
}
void main(void)
{
// Convert normal and position to eye coords
vec3 eyeNorm = normalize(normal_matrix * normal);
vec4 eyePos = model_view_matrix * vec4(position, 1.0);
// No lighting effect
if (light_mode == 0)
{
litColour = vec4(1.0, 1.0, 1.0, 1.0);
}
// Directional overhead light
else if (light_mode == 1)
{
litColour = vec4(phongLight(eyePos, eyeNorm), 1.0);
}
// Point light
else if (light_mode == 2)
{
litColour = vec4(phongLight(eyePos, eyeNorm), 1.0);
}
else if (light_mode == 3)
{
litColour = vec4(spotLight(eyePos, eyeNorm), 1.0);
}
//litColour = vec4(normal*1000, 1.0);
gl_Position = projection_matrix * eyePos;
st = texCoord;
}
Your spotlight is defined by a position (ps) and a direction (ds). So for every vertex at position vp you can compute d=vp-ps, normalize that to dn=normalize(d), and then dot(dn,ds) will give you the angle in the spotlight. Just scale it or compare it to a cut off to get a scalar!
Alternatively, and in the long term better, is to think of a spotlight as a camera. Do the same as you do for your camera: A model and view matrix! Transform every vertex into that space, and project it from x,y,z,w to x,y,z. z is the distance which is always useful for lighting and x,y you can use to look up in a texture that has a round shape (or any other).
One thing to mind with both techniques is back projection: Make sure you check that the light only points forward! Check the sign of z or the dot product!
I found a good example of environment mapping equirectangular. Here's the code:
VERTEX SHADER
varying vec3 Normal;
varying vec3 EyeDir;
varying float LightIntensity;
uniform vec3 LightPos;
void main(void){
gl_Position = ftransform();
Normal = normalize(gl_NormalMatrix * gl_Normal);
vec4 pos = gl_ModelViewMatrix * gl_Vertex;
EyeDir = pos.xyz;
LightIntensity = max(dot(normalize(LightPos - EyeDir), Normal), 0.0);
}
FRAGMENT SHADER
const vec3 Xunitvec = vec3 (1.0, 0.0, 0.0);
const vec3 Yunitvec = vec3 (0.0, 1.0, 0.0);
uniform vec3 BaseColor;
uniform float MixRatio;
uniform sampler2D EnvMap;
varying vec3 Normal;
varying vec3 EyeDir;
varying float LightIntensity;
void main (void){
// Compute reflection vector
vec3 reflectDir = reflect(EyeDir, Normal);
// Compute altitude and azimuth angles
vec2 index;
index.y = dot(normalize(reflectDir), Yunitvec);
reflectDir.y = 0.0;
index.x = dot(normalize(reflectDir), Xunitvec) * 0.5;
// Translate index values into proper range
if (reflectDir.z >= 0.0)
index = (index + 1.0) * 0.5;
else
{
index.t = (index.t + 1.0) * 0.5;
index.s = (-index.s) * 0.5 + 1.0;
}
// if reflectDir.z >= 0.0, s will go from 0.25 to 0.75
// if reflectDir.z < 0.0, s will go from 0.75 to 1.25, and
// that's OK, because we've set the texture to wrap.
// Do a lookup into the environment map.
vec3 envColor = vec3 (texture2D(EnvMap, index));
// Add lighting to base color and mix
vec3 base = LightIntensity * BaseColor;
envColor = mix(envColor, base, MixRatio);
gl_FragColor = vec4 (envColor, 1.0);
}
My problem is in the vertex shader.
LightIntensity = max(dot(normalize(LightPos - EyeDir), Normal), 0.0);
I'm subtracting the eye direction to the direction of light. But if I have more than one light source ... What I should do the calculation?
I use version 1.2 of GLSL.
Light is additive, so you just need to sum up the contributions of each light. If you have a fixed number of them, you can do that in a single pass through the shader—you just define a uniform for each light (position to start with, though you’ll probably want intensity/color as well) and calculate the final intensity like this:
LightIntensity = max(dot(normalize(Light1Pos - EyeDir), Normal), 0.0) + max(dot(normalize(Light2Pos - EyeDir), Normal), 0.0) + max(dot(normalize(Light3Pos - EyeDir), Normal), 0.0);