I found a few strange HLSL bugs - or Pix is telling nonsense:
I have 2 orthogonal Vectors: A = { 0.0f, -1.0f, 0.0f } and B { 0.0f, 0.0f, 1.0f }
If I use the HLSL dot function, the output is (-0.0f) which makes sense BUT now the acos of that output is -0.0000675917 (that's what Pix says - and what the shader outputs) which is not what I had expected;
Even if I compute the dotproduct myself (A.x*B.x + A.y * B.y + etc.) the result is still 0.0f but the acos of my result isn't zero.
I do need the result of acos to be as precisely as possible because i want to color my vertices according to the angle between the triangle normal and a given vector.
float4 PS_MyPS(VS_OUTPUT input) : COLOR
{
float Light = saturate(dot(input.Normal, g_LightDir)) + saturate(dot(-input.Normal, g_LightDir)); // compute the lighting
if (dot(input.Vector, CameraDirection) < 0) // if the angle between the normal and the camera direction is greater than 90 degrees
{
input.Vector = -input.Vector; // use a mirrored normal
}
float angle = acos(0.0f) - acos(dot(input.Vector, Vector));
float4 Color;
if (angle > Angles.x) // Set the color according to the Angle
{
Color = Color1;
}
else if (angle > Angles.y)
{
Color = Color2;
}
else if (angle >= -abs(Angles.y))
{
Color = Color3;
}
else if (angle >= Angles.z)
{
Color = Color4;
}
else
{
Color = Color5;
}
return Light * Color;
}
It works fine for angles above 0.01 degrees, but gives wrong results for smaller values.
The other bugs I found are: The "length"-function in hlsl returns 1 for the vector (0, -0, -0, 0) in Pix and the HLSL function "any" on that vector returns true as well. This would mean that -0.0f != 0.0f.
Has anyone else encountered these and maybe has a workaround for my problem?
I tested it on an Intel HD Graphics 4600 and a Nvidia card with the same results.
One of the primary reasons why acos may return bad results is because always remember that acos takes values between -1.0 and 1.0.
Hence if the value exceeds even slightly(1.00001 instead of 1.0) , it may return incorrect result.
I deal with this problem by forced capping i.e putting in a check for
if(something>1.0)
something = 1.0;
else if(something<-1.0)
something = -1.0;
Related
I am following "Ray Tracing in One Weekend" to build a ray tracer on my own. Everything is OK until I met Dilectric Material.
The refraction performs well (I am not sure, we can see it in image 3 and 4), but when I add the total internal reflection, the sphere gets a black edge, the images are listed as below:
img1 - black edge for total internal reflection
img2 - black edge for total internal reflection
img3 - dilectric without total internal reflection
img4 - dilectric without total internal reflection
My analysis
I debuged my program and found that total internal reflection happens at the edge of the sphere, and the ray bounces infinitely inside the sphere until it exceeds boundce limits, so it returns (0.f, 0.f, 0.f) for the result color.
I don't think the infinite bounce of inner relection is right, but i have compared my code with the one in the book, and could not find any problem.
The scatter method is here:
bool Dilectric::scatter(const Ray& input, const HitRecord& rec, glm::vec3& attenuation, Ray& scatterRay) const
{
glm::vec3 dir;
if (rec.frontFace)
{
// ray is from air to inside surface, only refraction happens
float ratio = 1.f / m_refractIndex;
dir = GfxLib::refract(glm::normalize(input.direction()), rec.n, ratio);
}
else
{
// ray is from inside surface to air, need to think of total internal reflection
float ratio = m_refractIndex;
float cosTheta = std::fmin(glm::dot(-input.direction(), rec.n), 1.f);
float sinTheta = std::sqrt(1.f - cosTheta * cosTheta);
bool internalReflection = (ratio * sinTheta) > 1.f;
if (internalReflection)
{
dir = GfxLib::reflect(glm::normalize(input.direction()), rec.n);
}
else
{
dir = GfxLib::refract(glm::normalize(input.direction()), rec.n, ratio);
}
}
scatterRay.setOrigin(rec.pt);
scatterRay.setDirection(dir);
// m_albedo is set to vec3(1.f)
attenuation = m_albedo;
return true;
}
outer method, rayColor is here:
glm::vec3 RrtTest::rayColor(const Ray& ray, const HittableList& objList, int reflectDepth)
{
if (reflectDepth <= 0) return glm::vec3(0.f);
HitRecord rec;
// use 0.001 instead of 0.f to fix shadow acne
if (objList.hit(ray, 0.001f, FLT_MAX, rec) && rec.hitInd >= 0)
{
Ray scatterRay;
glm::vec3 attenu{ 1.f };
std::shared_ptr<Matl> mat = objList.at(rec.hitInd)->getMatl();
if (!mat->scatter(ray, rec, attenu, scatterRay))
return glm::vec3(0.f);
glm::vec3 retColor = rayColor(scatterRay, objList, --reflectDepth);
return attenu * retColor;
}
else
{
glm::vec3 startColor{ 1.f }, endColor{ 0.5f, 0.7f, 1.f };
float t = (ray.direction().y + 1.f) * 0.5f;
return GfxLib::blend(startColor, endColor, t);
}
}
reflect method is here:
glm::vec3 GfxLib::reflect(const glm::vec3& directionIn, const glm::vec3& n)
{
float b = glm::dot(directionIn, n);
glm::vec3 refDir = directionIn - 2 * b * n;
return glm::normalize(refDir);
}
However, I am not sure my analysis is right or not, can any one lend me a hand and give me a solution for it? The main logic of the rendering is here(including the total demo).
i will really appreciate your advice!
NOTE: I've edited my code. See below the divider.
I'm implementing refraction in my (fairly basic) ray tracer, written in C++. I've been following (1) and (2).
I get the result below. Why is the center of the sphere black?
The center sphere has a transmission coefficient of 0.9 and a reflective coefficient of 0.1. It's index of refraction is 1.5 and it's placed 1.5 units away from the camera. The other two spheres just use diffuse lighting, with no reflective/refraction component. I placed these two different coloured spheres behind and in front of the transparent sphere to ensure that I don't see a reflection instead of a transmission.
I've made the background colour (the colour achieved when a ray from the camera does not intersect with any object) a colour other than black, so the center of the sphere is not just the background colour.
I have not implemented the Fresnel effect yet.
My trace function looks like this (verbatim copy, with some parts omitted for brevity):
bool isInside(Vec3f rayDirection, Vec3f intersectionNormal) {
return dot(rayDirection, intersectionNormal) > 0;
}
Vec3f trace(Vec3f origin, Vec3f ray, int depth) {
// (1) Find object intersection
std::shared_ptr<SceneObject> intersectionObject = ...;
// (2) Compute diffuse and ambient color contribution
Vec3f color = ...;
bool isTotalInternalReflection = false;
if (intersectionObject->mTransmission > 0 && depth < MAX_DEPTH) {
Vec3f transmissionDirection = refractionDir(
ray,
normal,
1.5f,
isTotalInternalReflection
);
if (!isTotalInternalReflection) {
float bias = 1e-4 * (isInside(ray, normal) ? -1 : 1);
Vec3f transmissionColor = trace(
add(intersection, multiply(normal, bias)),
transmissionDirection,
depth + 1
);
color = add(
color,
multiply(transmissionColor, intersectionObject->mTransmission)
);
}
}
if (intersectionObject->mSpecular > 0 && depth < MAX_DEPTH) {
Vec3f reflectionDirection = computeReflectionDirection(ray, normal);
Vec3f reflectionColor = trace(
add(intersection, multiply(normal, 1e-5)),
reflectionDirection,
depth + 1
);
float intensity = intersectionObject->mSpecular;
if (isTotalInternalReflection) {
intensity += intersectionObject->mTransmission;
}
color = add(
color,
multiply(reflectionColor, intensity)
);
}
return truncate(color, 1);
}
If the object is transparent then it computes the direction of the transmission ray and recursively traces it, unless the refraction causes total internal reflection. In that case, the transmission component is added to the reflection component and thus the color will be 100% of the traced reflection color.
I add a little bias to the intersection point in the direction of the normal (inverted if inside) when recursively tracing the transmission ray. If I don't do that, then I get this result:
The computation for the direction of the transmission ray is performed in refractionDir. This function assumes that we will not have a transparent object inside another, and that the outside material is air, with a coefficient of 1.
Vec3f refractionDir(Vec3f ray, Vec3f normal, float refractionIndex, bool &isTotalInternalReflection) {
float relativeIndexOfRefraction = 1.0f / refractionIndex;
float cosi = -dot(ray, normal);
if (isInside(ray, normal)) {
// We should be reflecting across a normal inside the object, so
// re-orient the normal to be inside.
normal = multiply(normal, -1);
relativeIndexOfRefraction = refractionIndex;
cosi *= -1;
}
assert(cosi > 0);
float base = (
1 - (relativeIndexOfRefraction * relativeIndexOfRefraction) *
(1 - cosi * cosi)
);
if (base < 0) {
isTotalInternalReflection = true;
return ray;
}
return add(
multiply(ray, relativeIndexOfRefraction),
multiply(normal, relativeIndexOfRefraction * cosi - sqrtf(base))
);
}
Here's the result when the spheres are further away from the camera:
And closer to the camera:
Edit: I noticed a couple bugs in my code.
When I add bias to the intersection point, it should be in the same direction as the transmission. I was adding it in the wrong direction by adding negative bias when inside the sphere. This doesn't make sense as when the ray is coming from inside the sphere, it will transmit outside the sphere (when TIR is avoided).
Old code:
add(intersection, multiply(normal, bias))
New code:
add(intersection, multiply(transmissionDirection, 1e-4))
Similarly, the normal that refractionDir receives is the surface normal pointing away from the center of the sphere. The normal I want to use when computing the transmission direction is one pointing outside if the transmission ray is going to go outside the object, or inside if the transmission ray is going to go inside the object. Thus, the surface normal pointing out of the sphere should be inverted if we're entering the sphere, thus is the ray is outside.
New code:
Vec3f refractionDir(Vec3f ray, Vec3f normal, float refractionIndex, bool &isTotalInternalReflection) {
float relativeIndexOfRefraction;
float cosi = -dot(ray, normal);
if (isInside(ray, normal)) {
relativeIndexOfRefraction = refractionIndex;
cosi *= -1;
} else {
relativeIndexOfRefraction = 1.0f / refractionIndex;
normal = multiply(normal, -1);
}
assert(cosi > 0);
float base = (
1 - (relativeIndexOfRefraction * relativeIndexOfRefraction) * (1 - cosi * cosi)
);
if (base < 0) {
isTotalInternalReflection = true;
return ray;
}
return add(
multiply(ray, relativeIndexOfRefraction),
multiply(normal, sqrtf(base) - relativeIndexOfRefraction * cosi)
);
}
However, this all still gives me an unexpected result:
I've also added some unit tests. They pass the following:
A ray entering the center of the sphere parallel with the normal will transmit through the sphere without being bent (this tests two refractionDir calls, one outside and one inside).
Refraction at 45 degrees from the normal through a glass slab will bend inside the slab by 15 degrees towards the normal, away from the original ray direction. Its direction when it exits the sphere will be the original ray direction.
Similar test at 75 degrees.
Ensuring that total internal reflection happens when a ray is coming from inside the object and is at 45 degrees or wider.
I'll include one of the unit tests here and you can find the rest at this gist.
TEST_CASE("Refraction at 75 degrees from normal through glass slab") {
Vec3f rayDirection = normalize(Vec3f({ 0, -sinf(5.0f * M_PI / 12.0f), -cosf(5.0f * M_PI / 12.0f) }));
Vec3f normal({ 0, 0, 1 });
bool isTotalInternalReflection;
Vec3f refraction = refractionDir(rayDirection, normal, 1.5f, isTotalInternalReflection);
REQUIRE(refraction[0] == 0);
REQUIRE(refraction[1] == Approx(-sinf(40.0f * M_PI / 180.0f)).margin(0.03f));
REQUIRE(refraction[2] == Approx(-cosf(40.0f * M_PI / 180.0f)).margin(0.03f));
REQUIRE(!isTotalInternalReflection);
refraction = refractionDir(refraction, multiply(normal, -1), 1.5f, isTotalInternalReflection);
REQUIRE(refraction[0] == Approx(rayDirection[0]));
REQUIRE(refraction[1] == Approx(rayDirection[1]));
REQUIRE(refraction[2] == Approx(rayDirection[2]));
REQUIRE(!isTotalInternalReflection);
}
Ok. So, I've been messing around with shadows in my game engine for the last week. I've mostly implemented cascading shadow maps (CSM), but I'm having a bit of a problem with shadowing that I just can't seem to solve.
The only light in this scene is a directional light (sun), pointing {-0.1 -0.25 -0.65}. I calculate 4 sets of frustum bounds for the four splits of my CSMs with this code:
// each projection matrix calculated with same near plane, different far
Frustum make_worldFrustum(const glm::mat4& _invProjView) {
Frustum fr; glm::vec4 temp;
temp = _invProjView * glm::vec4(-1, -1, -1, 1);
fr.xyz = glm::vec3(temp) / temp.w;
temp = _invProjView * glm::vec4(-1, -1, 1, 1);
fr.xyZ = glm::vec3(temp) / temp.w;
...etc 6 more times for ndc cube
return fr;
}
For the light, I get a view matrix like this:
glm::mat4 viewMat = glm::lookAt(cam.pos, cam.pos + lightDir, {0,0,1});
I then create each ortho matrix from the bounds of each frustum:
lightMatVec.clear();
for (auto& frus : cam.frusVec) {
glm::vec3 arr[8] {
glm::vec3(viewMat * glm::vec4(frus.xyz, 1)),
glm::vec3(viewMat * glm::vec4(frus.xyZ, 1)),
etc...
};
glm::vec3 minO = {INFINITY, INFINITY, INFINITY};
glm::vec3 maxO = {-INFINITY, -INFINITY, -INFINITY};
for (auto& vec : arr) {
minO = glm::min(minO, vec);
maxO = glm::max(maxO, vec);
}
glm::mat4 projMat = glm::ortho(minO.x, maxO.x, minO.y, maxO.y, minO.z, maxO.z);
lightMatVec.push_back(projMat * viewMat);
}
I have a 4 layer TEXTURE_2D_ARRAY bound to 4 framebuffers that I draw the scene into with a very simple vertex shader (frag disabled or punchthrough alpha).
I then draw the final scene. The vertex shader outputs four shadow texcoords:
out vec3 slShadcrd[4];
// stuff
for (int i = 0; i < 4; i++) {
vec4 sc = WorldBlock.skylMatArr[i] * vec4(world_pos, 1);
slShadcrd[i] = sc.xyz / sc.w * 0.5f + 0.5f;
}
And a fragment shader, which determines the split to use with:
int csmIndex = 0;
for (uint i = 0u; i < CameraBlock.csmCnt; i++) {
if (-view_pos.z > CameraBlock.csmSplits[i]) index++;
else break;
}
And samples the shadow map array with this function:
float sample_shadow(vec3 _sc, int _csmIndex, sampler2DArrayShadow _tex) {
return texture(_tex, vec4(_sc.xy, _csmIndex, _sc.z)).r;
}
And, this is the scene I get (with each split slightly tinted and the 4 depth layers overlayed):
Great! Looks good.
But, if I turn the camera slightly to the right:
Then shadows start disappearing (and depending on the angle, appearing where they shouldn't be).
I have GL_DEPTH_CLAMP enabled, so that isn't the issue. I'm culling front faces, but turning that off doesn't make a difference to this issue.
What am I missing? I feel like it's an issue with one of my projections, but they all look right to me. Thanks!
EDIT:
All four of the the light's frustums drawn. They are all there, but only z is changing relative to the camera (see comment below):
EDIT:
Probably more useful, this is how the frustums look when I only update them once, when the camera is at (0,0,0) and pointing forwards (0,1,0). Also I drew them with depth testing this time.
IMPORTANT EDIT:
It seems that this issue is directly related to the light's view matrix, currently:
glm::mat4 viewMat = glm::lookAt(cam.pos, cam.pos + lightDir, {0,0,1});
Changing the values for eye and target seems to affect the buggered shadows. But I don't know what I should actually be setting this to? Should be easy for someone with a better understanding than me :D
Solved it! It was indeed an issue with the light's view matrix! All I had to do was replace camPos with the centre point of each frustum! Meaning that each split's light matrix needed a different view matrix. So I just create each view matrix like this...
glm::mat4 viewMat = glm::lookAt(frusCentre, frusCentre+lightDir, {0,0,1});
And get frusCentre simply...
glm::vec3 calc_frusCentre(const Frustum& _frus) {
glm::vec3 min(INFINITY, INFINITY, INFINITY);
glm::vec3 max(-INFINITY, -INFINITY, -INFINITY);
for (auto& vec : {_frus.xyz, _frus.xyZ, _frus.xYz, _frus.xYZ,
_frus.Xyz, _frus.XyZ, _frus.XYz, _frus.XYZ}) {
min = glm::min(min, vec);
max = glm::max(max, vec);
}
return (min + max) / 2.f;
}
And bam! Everything works spectacularly!
EDIT (Last one!):
What I had was not quite right. The view matrix should actually be:
glm::lookAt(frusCentre-lightDir, frusCentre, {0,0,1});
I'm creating some random vectors/directions in a loop as a dome shape like this:
void generateDome(glm::vec3 direction)
{
for(int i=0;i<1000;++i)
{
float xDir = randomByRange(-1.0f, 1.0f);
float yDir = randomByRange(0.0f, 1.0f);
float zDir = randomByRange(-1.0f, 1.0f);
auto vec = glm::vec3(xDir, yDir, zDir);
vec = glm::normalize(vec);
...
//some transformation with direction-vector
}
...
}
This creates vectors as a dome-shape in +y direction (0,1,0):
Now I want to rotate the vec-Vector by a given direction-Vector like (1,0,0).
This should rotate the "dome" to the x-direction like this:
How can I achieve this? (preferably with glm)
A rotation is generally defined using some sort of offset (axis-angle, quaternion, euler angles, etc) from a starting position. What you are looking for would be more accurately described (in my opinion) as a re-orientation. Luckily this isn't too hard to do. What you need is a change-of-basis matrix.
First, lets just define what we're working with in code:
using glm::vec3;
using glm::mat3;
vec3 direction; // points in the direction of the new Y axis
vec3 vec; // This is a randomly generated point that we will
// eventually transform using our base-change matrix
To calculate the matrix, you need to create unit vectors for each of the new axes. From the example above it becomes apparent that you want the vector provided to become the new Y-axis:
vec3 new_y = glm::normalize(direction);
Now, calculating the X and Z axes will be a tad more complicated. We know that they must be orthogonal to each other and to the Y axis calculated above. The most logical way to construct the Z axis is to assume that the rotation is taking place in the plane defined by the old Y axis and the new Y axis. By using the cross-product we can calculate this plane's normal vector, and use that for the Z axis:
vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
Technically the normalization isn't necessary here since both input vectors are already normalized, but for the sake of clarity, I've left it. Also note that there is a special case when the input vector is colinear with the Y-axis, in which case the cross product above is undefined. The easiest way to fix this is to treat it as a special case. Instead of what we have so far, we'd use:
if (direction.x == 0 && direction.z == 0)
{
if (direction.y < 0) // rotate 180 degrees
vec = vec3(-vec.x, -vec.y, vec.z);
// else if direction.y >= 0, leave `vec` as it is.
}
else
{
vec3 new_y = glm::normalize(direction);
vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
// code below will go here.
}
For the X-axis, we can cross our new Y-axis with our new Z-axis. This yields a vector perpendicular to both of the others axes:
vec3 new_x = glm::normalize(glm::cross(new_y, new_z));
Again, the normalization in this case is not really necessary, but if y or z were not already unit vectors, it would be.
Finally, we combine the new axis vectors into a basis-change matrix:
mat3 transform = mat3(new_x, new_y, new_z);
Multiplying a point vector (vec3 vec) by this yields a new point at the same position, but relative to the new basis vectors (axes):
vec = transform * vec;
Do this last step for each of your randomly generated points and you're done! No need to calculate angles of rotation or anything like that.
As a side note, your method of generating random unit vectors will be biased towards directions away from the axes. This is because the probability of a particular direction being chosen is proportional to the distance to the furthest point possible in a given direction. For the axes, this is 1.0. For directions like eg. (1, 1, 1), this distance is sqrt(3). This can be fixed by discarding any vectors which lie outside the unit sphere:
glm::vec3 vec;
do
{
float xDir = randomByRange(-1.0f, 1.0f);
float yDir = randomByRange(0.0f, 1.0f);
float zDir = randomByRange(-1.0f, 1.0f);
vec = glm::vec3(xDir, yDir, zDir);
} while (glm::length(vec) > 1.0f); // you could also use glm::length2 instead, and avoid a costly sqrt().
vec = glm::normalize(vec);
This would ensure that all directions have equal probability, at the cost that if you're extremely unlucky, the points picked may lie outside the unit sphere over and over again, and it may take a long time to generate one that's inside. If that's a problem, it could be modified to limit the iterations: while (++i < 4 && ...) or by increasing the radius at which a point is accepted every iteration. When it is >= sqrt(3), all possible points would be considered valid, so the loop would end. Both of these methods would result in a slight biasing away from the axes, but in almost any real situation, it would not be detectable.
Putting all the code above together, combined with your code, we get:
void generateDome(glm::vec3 direction)
{
// Calculate change-of-basis matrix
glm::mat3 transform;
if (direction.x == 0 && direction.z == 0)
{
if (direction.y < 0) // rotate 180 degrees
transform = glm::mat3(glm::vec3(-1.0f, 0.0f 0.0f),
glm::vec3( 0.0f, -1.0f, 0.0f),
glm::vec3( 0.0f, 0.0f, 1.0f));
// else if direction.y >= 0, leave transform as the identity matrix.
}
else
{
vec3 new_y = glm::normalize(direction);
vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
vec3 new_x = glm::normalize(glm::cross(new_y, new_z));
transform = mat3(new_x, new_y, new_z);
}
// Use the matrix to transform random direction vectors
vec3 point;
for(int i=0;i<1000;++i)
{
int k = 4; // maximum number of direction vectors to guess when looking for one inside the unit sphere.
do
{
point.x = randomByRange(-1.0f, 1.0f);
point.y = randomByRange(0.0f, 1.0f);
point.z = randomByRange(-1.0f, 1.0f);
} while (--k > 0 && glm::length2(point) > 1.0f);
point = glm::normalize(point);
point = transform * point;
// ...
}
// ...
}
You need to create a rotation matrix. Therefore you need a identity Matrix. Create it like this with
glm::mat4 rotationMat(1); // Creates a identity matrix
Now your can rotate the vectorspacec with
rotationMat = glm::rotate(rotationMat, 45.0f, glm::vec3(0.0, 0.0, 1.0));
This will rotate the vectorspace by 45.0 degrees around the z-axis (as shown in your screenshot). Now your almost done. To rotate your vec you can write
vec = glm::vec3(rotationMat * glm::vec4(vec, 1.0));
Note: Because you have a 4x4 matrix you need a vec4 to multiply it with the matrix. Generally it is a good idea always to use vec4 when working with OpenGL because vectors in smaller dimension will be converted to homogeneous vertex coordinates anyway.
EDIT: You can also try to use GTX Extensions (Experimental) by including <glm/gtx/rotate_vector.hpp>
EDIT 2: When you want to rotate the dome "towards" a given direction you can get your totation axis by using the cross-product between the direction and you "up" vector of the dome. Lets say you want to rotate the dome "toward" (1.0, 1.0, 1.0) and the "up" direction is (0.0, 1.0, 0.0) use:
glm::vec3 cross = glm::cross(up, direction);
glm::rotate(rotationMat, 45.0f, cross);
To get your rotation matrix. The cross product returns a vector that is orthogonal to "up" and "direction" and that's the one you want to rotate around. Hope this will help.
I am after smooth texture based outline effect in OpenGL. So far I tried mostly all kinds of edge detection algorithms which result mostly in crude and jagged outlines. Then I read about Distance Field. I found an example which does pretty nice distance field. Here is the GLSL code:
#version 420
layout(binding=0) uniform sampler2D colorMap;
flat in vec4 diffuseOut;
in vec2 uvsOut;
out vec4 outputColor;
const float ALPHA_THRESHOLD = 0.9;
const float NUM_SPOKES = 36.0; // Number of radiating lines to check in.
const float ANGULAR_STEP =360.0 / NUM_SPOKES;
const int ZERO_VALUE =128; // Color channel containing 0 => -128, 128 => 0, 255 => +127
int in_StepSize=15; // Distance to check each time (larger steps will be faster, but less accurate).
int in_MaxDistance=30; // Maximum distance to search out to. Cannot be more than 127!
vec4 distField(){
vec2 pixel_size = 1.0 / vec2(textureSize(colorMap, 0));
vec2 screenTexCoords = gl_FragCoord.xy * pixel_size;
int distance;
if(texture(colorMap, screenTexCoords).a == 0.0)
{
// Texel is transparent, search for nearest opaque.
distance = ZERO_VALUE + 1;
for(int i = in_StepSize; i < in_MaxDistance; i += in_StepSize)
{
if(find_alpha_at_distance(screenTexCoords, float(i) * pixel_size, 1.0))
{
i = in_MaxDistance + 1; // BREAK!
}
else
{
distance = ZERO_VALUE + 1 + i;
}
}
}
else
{
// Texel is opaque, search for nearest transparent.
distance = ZERO_VALUE;
for(int i = in_StepSize; i <= in_MaxDistance; i += in_StepSize)
{
if(find_alpha_at_distance(screenTexCoords, float(i) * pixel_size, 0.0))
{
i = in_MaxDistance + 1; // BREAK!
}
else
{
distance = ZERO_VALUE - i;
}
}
}
return vec4(vec3(float(distance) / 255.0) * diffuseOut.rgb, 1.0 - texture(colorMap, screenTexCoords).a);
}
void main()
{
outputColor= distField();
}
The result of this shader covers the whole screen using the diffuse color for filling the screen area outside the Distance Field outline.Here is how it looks like :
What I need is to leave all the area which has the solid red fill outside the distance field as transparent.
I came to the solution by using Distance Field gray scale 8 bit alpha map.Stefan Gustavson
describes in detail how to do it.Basically one needs to generate the distance field version of the original texture.Then this texture is rendered with the primitive normally in the first pass into an FBO.In the second pass the alpha blending mode should be on.The texture from the first pass in used with the screen quad.At this stage the the fragment shader samples the alpha from that texture.This results in both smooth edges and alpha transparency around the edges.
Here is the result:
Based on the screenshot I'm assuming you're rendering a fullscreen quad? If that's the case Tim just provided the answer, try:
glEnable( GL_BLEND );
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Before you render the quad. Obviously if you're going to render non-transparent stuff too, I advise you to render those first so you won't get depth buffer problems. When you're done drawing the transparent stuff, call:
glDisable( GL_BLEND );
To turn alphablending off again.