I have a volume rendering implementation in shaders which uses the gpu raycasting technique. Basically I have a unit cube at the center of my scene.
I render the vertices of the unit cube in my vertex shader and pass texture coordinates to the fragment shader like this:
in vec3 aPosition;
uniform mat4 uMVPMatrix;
smooth out vec3 vUV;
void main() {
gl_Position = uMVPMatrix * vec4(aPosition.xyz,1);
vUV = aPosition + vec3(0.5);
}
Since the unit cube coordinates goes from -0.5 to 0.5 I clamp the texture coordinates from 0.0 to 1.0 by adding 0.5 to them..
In the fragment shader I got the texture coordinate which is interpolated by the rasterizer:
...
smooth in vec3 vUV; // Position of the data interpolated by the rasterizer
...
void main() {
...
vec3 dataPos = vUV;
...
for (int i = 0; i < MAX_SAMPLES; i++) {
dataPos = dataPos + dirStep;
...
float sample = texture(volume, dataPos).r;
...//Some more operations on the sampled color
float prev_alpha = transferedColor.a * (1.0 - fragColor.a);
fragColor.rgb += prev_alpha * transferedColor.rgb;
fragColor.a += prev_alpha; //final color
if(fragColor.a>0.99)
break;
}
}
My rendering works well.
Now I have implemented a selection algorithm, which is working fine with particles (real vertices in the world coordinates).
My question is how can I make it work with the volumetric dataset? Because only vertices I have is the vertices of the unit cube. Since the data points are interpolated by the rasterizer I don't know the real(world) coordinates of the voxels.
It's fair enough for me to get the center coordinates of the voxels and treat them like particles so I can omit or include the necesseary voxels (I guess vUV coordinates?) in the fragment shader.
First you have to work out your sampled voxel coordinate. (I'm assuming that volume is your 3D texture). To find it you have to de-linearization it from dataPos into the 3 axis components in your 3D texture (w x h x d). So if a sample in MAX_SAMPLES has an index computed like ((z * d) + y) * h + x, then the coordinate can be found by..
z = floor(sample / (w * h))
y = floor((sample - (z * w * h)) / w)
x = sample - (z * w * h) - (y * w)
The floor operation is important to retrieve the integer index.
This is the coordinate of your sample. Now you can multiply it with the inverse of the mvp you used for the 4 vertices, this gives you the position (or the center, maybe you have to add vec3(0.5)) of your sample in world space.
This raises a new question however: see if you can rewrite your selection algorithm so that you don't have to jump through all the computations, and do the selection in screen-space rather than world space.
Related
I have the following fragment shader:
#version 330
layout(location=0) out vec4 frag_colour;
in vec2 texelCoords;
uniform sampler2D uTexture; // the color
uniform sampler2D uTextureHeightmap; // the heightmap
uniform float uSunDistance = -10000000.0; // really far away vertically
uniform float uSunInclination; // height from the heightmap plane
uniform float uSunAzimuth; // clockwise rotation point
uniform float uQuality; // used to determine number of steps and steps size
void main()
{
vec4 c = texture(uTexture,texelCoords);
vec2 textureD = textureSize(uTexture,0);
float d = max(textureD.x,textureD.y); // use the largest dimension to determine stepsize etc
// position the sun in the centre of the screen and convert from spherical to cartesian coordinates
vec3 sunPosition = vec3(textureD.x/2,textureD.y/2,0) + vec3( uSunDistance*sin(uSunInclination)*cos(uSunAzimuth),
uSunDistance*sin(uSunInclination)*sin(uSunAzimuth),
uSunDistance*cos(uSunInclination) );
float height = texture2D(uTextureHeightmap, texelCoords).r; // starting height
vec3 direction = normalize(vec3(texelCoords,height) - sunPosition); // sunlight direction
float sampleDistance = 0;
float samples = d*uQuality;
float stepSize = 1.0 / ((samples/d) * d);
for(int i = 0; i < samples; i++)
{
sampleDistance += stepSize; // increase the sample distance
vec3 newPoint = vec3(texelCoords,height) + direction * sampleDistance; // get the coord for the next sample point
float newHeight = texture2D(uTextureHeightmap,newPoint.xy).r; // get the height of that sample point
// put it in shadow if we hit something that is higher than our starting point AND is heigher than the ray we're casting
if(newHeight > height && newHeight > newPoint.z)
{
c *= 0.5;
break;
}
}
frag_colour = c;
}
The purpose is for it to cast shadows based on a heightmap. Pretty nifty, and the results look good.
However, there's a problem where the shadows appear longer when they are horizontal compared to vertical. If I make the window size different, with a window that is taller than wide, I get the opposite effect. I.e., the shadows are casting longer in the longer dimension.
This tells me that it's to do with the way I'm stepping in the above shader, but I can't tell the problem.
To illustrate, here is the with a uSunAzimuth that results in a horizontally cast shadow:
And here is the exact same code with a uSunAzimuth for a vertical shadow:
It's not very pronounced in these low resolution images, but in larger resolutions the effect gets more exaggerated. Essentially; the shadow when you measure how it casts in all 360 degrees of azimuth clears out an ellipse instead of a circle.
The shadow fragment shader operates on a "snapshot" of the viewport. When your scene is rendered and this "snapshot" is generated, then the vertex positions are transformed by the projection matrix. The projection matrix describes the mapping from 3D points of a scene, to 2D points of the viewport and takes in account the aspect ration of the viewport.
(see Both depth buffer and triangle face orientation are reversed in OpenGL,
and Transform the modelMatrix).
This causes that the high map (uTextureHeightmap) represents a rectangular field of view, dependent on the aspect ratio.
But the texture coordinates, which you use to access the height map describe a quad in the range (0, 0) to (1, 1).
This mismatch must be balanced, by scaling with the aspect ratio.
vec3 direction = ....;
float aspectRatio = textureD.x / textureD.y;
direction.xy *= vec2( 1.0/aspectRatio, 1.0 );
I just needed to adjust the direction slightly.
float aspectCorrection = textureD.x / textureD.y;
...
vec3 direction = normalize(vec3(texelCoords,height) - sunPosition);
direction.y *= aspectCorrection;
I've been working on a deferred renderer to do lighting with, and it works quite well, albeit using a position buffer in my G-buffer. Lighting is done in world space.
I have tried to implement an algorithm to recreate the world space positions from the depth buffer, and the texture coordinates, albeit with no luck.
My vertex shader is nothing particularly special, but this is the part of my fragment shader in which I (attempt to) calculate the world space position:
// Inverse projection matrix
uniform mat4 projMatrixInv;
// Inverse view matrix
uniform mat4 viewMatrixInv;
// texture position from vertex shader
in vec2 TexCoord;
... other uniforms ...
void main() {
// Recalculate the fragment position from the depth buffer
float Depth = texture(gDepth, TexCoord).x;
vec3 FragWorldPos = WorldPosFromDepth(Depth);
... fun lighting code ...
}
// Linearizes a Z buffer value
float CalcLinearZ(float depth) {
const float zFar = 100.0;
const float zNear = 0.1;
// bias it from [0, 1] to [-1, 1]
float linear = zNear / (zFar - depth * (zFar - zNear)) * zFar;
return (linear * 2.0) - 1.0;
}
// this is supposed to get the world position from the depth buffer
vec3 WorldPosFromDepth(float depth) {
float ViewZ = CalcLinearZ(depth);
// Get clip space
vec4 clipSpacePosition = vec4(TexCoord * 2.0 - 1.0, ViewZ, 1);
// Clip space -> View space
vec4 viewSpacePosition = projMatrixInv * clipSpacePosition;
// Perspective division
viewSpacePosition /= viewSpacePosition.w;
// View space -> World space
vec4 worldSpacePosition = viewMatrixInv * viewSpacePosition;
return worldSpacePosition.xyz;
}
I still have my position buffer, and I sample it to compare it against the calculate position later, so everything should be black:
vec3 actualPosition = texture(gPosition, TexCoord).rgb;
vec3 difference = abs(FragWorldPos - actualPosition);
FragColour = vec4(difference, 0.0);
However, what I get is nowhere near the expected result, and of course, lighting doesn't work:
(Try to ignore the blur around the boxes, I was messing around with something else at the time.)
What could cause these issues, and how could I get the position reconstruction from depth working successfully? Thanks.
You are on the right track, but you have not applied the transformations in the correct order.
A quick recap of what you need to accomplish here might help:
Given Texture Coordinates [0,1] and depth [0,1], calculate clip-space position
Do not linearize the depth buffer
Output: w = 1.0 and x,y,z = [-w,w]
Transform from clip-space to view-space (reverse projection)
Use inverse projection matrix
Perform perspective divide
Transform from view-space to world-space (reverse viewing transform)
Use inverse view matrix
The following changes should accomplish that:
// this is supposed to get the world position from the depth buffer
vec3 WorldPosFromDepth(float depth) {
float z = depth * 2.0 - 1.0;
vec4 clipSpacePosition = vec4(TexCoord * 2.0 - 1.0, z, 1.0);
vec4 viewSpacePosition = projMatrixInv * clipSpacePosition;
// Perspective division
viewSpacePosition /= viewSpacePosition.w;
vec4 worldSpacePosition = viewMatrixInv * viewSpacePosition;
return worldSpacePosition.xyz;
}
I would consider changing the name of CalcViewZ (...) though, that is very much misleading. Consider calling it something more appropriate like CalcLinearZ (...).
I followed a paper called "GPU Based Algorithms for Terrain Texturing" and it says the following:
The main algorithm to apply triplanar texturing is fairly simple.
First, we check whether the slope is relatively large in the same way
that we do with slope based texturing. These regions with high slope
will be the only regions aected by the algorithm. We then check what
the larger component of the normal is, out of x and z. If x is the
larger component, we use the geometry z coordinate as the texture
coordinate s, and the geometry y coordinate as the texture coordinate
t. If z is the larger component, we use the geometry x coordinate as
the texture coordinate s, and the geometry y coordinate as the texture
coordinate t.
So I tried to implement it. This is my heightmap:
Note that I added white lines in the borders just for the experiment, so now I have maximum-height walls surrounding my map.
Now following the articles, here's the implementation in the vertex shader:
#version 430
uniform mat4 ProjectionMatrix;
uniform mat4 CameraMatrix;
uniform vec3 scale;
layout(location = 0) in vec3 vertex;
layout(location = 1) in vec3 normal;
out vec3 fsVertex;
out vec3 fsNormal;
out vec2 fsUvs;
void main()
{
fsVertex = vertex;
fsNormal = normalize(normal);
if(fsNormal.y < 0.75) {
if(fsNormal.x > fsNormal.z)
fsUvs = vertex.zy * scale.zy;
else
fsUvs = vertex.xy * scale.xy;
}
else
fsUvs = vertex.xz * scale.xz;
gl_Position = ProjectionMatrix * CameraMatrix * vec4(vertex * scale, 1.0);
}
Here's the fragment shader, if it helps.
This is what I get:
Here's a further look, for proportion.
The top and left walls (of the heightmap) are rendered ok, and the bottom and right walls still suffer from stretching. I also get these weird stretches spots next to the beginning of the walls.
What could be the cause of this?
If you want to check if the normal's x or z coordinate are longer, you should use the abs function:
if(abs(fsNormal.x) > abs(fsNormal.z))
Furthermore, the y > 0.75 seems like a coarse approximation, which is probably good enough in most cases. Actually, the maximum of abs(x), abs(y), abs(z) gives you the correct plane.
Here is a DX11/HLSL implementation i used. GLSL conversion should be easy.
With the exponent value you can tune the blending speed at the borders. i used something like 3.
float3 SampleTriplanarTexture(Texture2D<float4> tex1, Texture2D<float4> tex2, Texture2D<float4> tex3, float3 normal, float3 pos, float exponent)
{
//triplanar projection
float mXY = pow(abs(normal.z), exponent);
float mXZ = pow(abs(normal.y), exponent);
float mYZ = pow(abs(normal.x), exponent);
float total = 1.0f / (mXY + mXZ + mYZ);
mXY *= total;
mXZ *= total;
mYZ *= total;
return tex1.SampleLevel(linearSampler2, pos.xz, 0) * mXZ +
tex2.SampleLevel(linearSampler2, pos.xy, 0) * mXY +
tex3.SampleLevel(linearSampler2, pos.yz, 0) * mYZ;
}
I am writing a 2D game using OpenGL and I have planned a shadow casting algorithm which needs a transformation of a texture from Polar Coordinates to Rectangular Coordinates. The desired effect is the following:
From this:
To this:
I know the formulas for converting coordinates between both Polar and Rectangular systems but I am having problems on writing the shader to achieve the desired effect.
My shader receives a texture as an input and should draw the warped texture to the screen. I planned the following (knowing that the fragment shader acts upon one fragment at a time):
Find the coordinates of the current fragment using gl_FragCoord.xy
Determine r and theta that correspond to the point (x, y).
Transform r and theta into texture_x and texture_y (which will be used to sample the texture)
Transfer the sampled pixel to the current fragment
My final result is the same input texture rotated 90 degrees clock-wise. I think that I'm missing something on step 3. I might be just getting the same x and y of the current fragment, because I'm simply using both the transform and inverse transform formulas.
How should I proceed to get the expected result?
Here is my shader:
#version 120
uniform sampler2D tex;
void main() {
vec2 fragCoords = gl_FragCoord.xy - vec2(128, 128); //shift the coordinates so that 0, 0 is in the center of the screen (the final texture is 256 * 256)
fragCoords /= vec2(256, 256);
float r = sqrt(pow(fragCoords.x, 2) + pow(fragCoords.y, 2));
float theta = atan(fragCoords.y, fragCoords.x);
if (fragCoords.y/fragCoords.x <= 0.5 && fragCoords.y/fragCoords.x >= -0.5) {
r *= 1/(256*sin(theta));
} else {
r *= 1/(0.5*256*cos(theta));
}
vec2 texCoords = vec2(r, theta);
vec4 texFrag = texture2D(tex, texCoords);
gl_FragColor = texFrag * vec4(1.0, 0.0, 0.0, 1.0);
}
In your shader you're first translating into polar coordinates
float r = sqrt(pow(fragCoords.x, 2) + pow(fragCoords.y, 2));
float theta = atan(fragCoords.y, fragCoords.x);
and then you't translating them back into cartesian
float tX = r * sin(theta);
float tY = r * cos(theta);
You want to stay in polar coordinates, so just plug r and theta into the texture coordinates
vec2 texCoords = vec2(r , theta);
vec4 texFrag = texture2D(tex, texCoords);
However by the looks of the images you pasted there's some renormalization step involved, so that (r, theta) will cover a rectangular area. If I'm not entirely mistaken, then r is scaled by the distance it takes a ray from the center-bottom to intersect with the rectangular area. If we assume theta=0 to be straight up, then for the range [-atan(0.5)…atan(0.5)] it's scaled by 1/(height*sin(theta)) and outside that range by 1/(0.5*width*cos(theta))
I am doing a ray-casting in a 3d texture until I hit a correct value. I am doing the ray-casting in a cube and the cube corners are already in world coordinates so I don't have to multiply the vertices with the modelviewmatrix to get the correct position.
Vertex shader
world_coordinate_ = gl_Vertex;
Fragment shader
vec3 direction = (world_coordinate_.xyz - cameraPosition_);
direction = normalize(direction);
for (float k = 0.0; k < steps; k += 1.0) {
....
pos += direction*delta_step;
float thisLum = texture3D(texture3_, pos).r;
if(thisLum > surface_)
...
}
Everything works as expected, what I now want is to sample the correct value to the depth buffer. The value that is now written to the depth buffer is the cube coordinate. But I want the value of pos in the 3d texture to be written.
So lets say the cube is placed 10 away from origin in -z and the size is 10*10*10. My solution that does not work correctly is this:
pos *= 10;
pos.z += 10;
pos.z *= -1;
vec4 depth_vec = gl_ProjectionMatrix * vec4(pos.xyz, 1.0);
float depth = ((depth_vec.z / depth_vec.w) + 1.0) * 0.5;
gl_FragDepth = depth;
The solution was:
vec4 depth_vec = ViewMatrix * gl_ProjectionMatrix * vec4(pos.xyz, 1.0);
float depth = ((depth_vec.z / depth_vec.w) + 1.0) * 0.5;
gl_FragDepth = depth;
One solution you might try is to draw a cube that is directly on top of the cube you're trying to raytrace. Send the cube's position in the same space as you get from your ray-tracing algorithm, and perform the same transforms to compute your "depth_vec", only do it in the vertex shader.
This way, you can see where your problems are coming from. Once you get this part of the transform to work, then you can back-port this transformation sequence into your raytracer. If that doesn't fix everything, then it would only be because your ray-tracing algorithm isn't outputting positions in the space that you think it is in.