I have to implement simple ray tracing algorithm, but I can not figure out How can I get Normal Vector of the sphere If I have the origin and radius of the sphere and the direction of the ray.
thanks
Wiki aboute sphere but i can not figure it out of this
I have find the solution: here is the code in C#:
Double alpha = Math.Asin(sphere.Radius / (Math.Sqrt(Math.Pow(sphere.Origin.X-ray.Origin.X,2)+Math.Pow(sphere.Origin.Y-ray.Origin.Y,2)+Math.Pow(sphere.Origin.Z-ray.Origin.Z,2))));
Double beta = Math.Acos((ray.Direction * (sphere.Origin - ray.Origin)) / (ray.Direction.Length * (sphere.Origin - ray.Origin).Length));
ray.HitParam = VypA(sphere.Origin - ray.Origin, beta, sphere.Radius) / ray.Direction.Length;
Vector4 g = ray.Origin + ray.Direction * ray.HitParam;
ray.HitNormal = (g - sphere.Origin).Normalized;
////the VypA function
public static Double VypA(Vector4 b, Double beta, Double radius)
{
return b.Length * (Math.Cos(beta) - Math.Sqrt(((radius * radius) / (b.Length2) - (Math.Sin(beta) * (Math.Sin(beta))))));
}
Related
I am following this course to learn computer graphics and write my first ray tracer.
I already have some visible results, but they seem to be too large.
The overall algorithm the course outlines is this:
Image Raytrace (Camera cam, Scene scene, int width, int height)
{
Image image = new Image (width, height) ;
for (int i = 0 ; i < height ; i++)
for (int j = 0 ; j < width ; j++) {
Ray ray = RayThruPixel (cam, i, j) ;
Intersection hit = Intersect (ray, scene) ;
image[i][j] = FindColor (hit) ;
}
return image ;
}
I perform all calculations in camera space (where the camera is at (0, 0, 0)). Thus RayThruPixel returns me a ray in camera coordinates, Intersect returns an intersection point also in camera coordinates, and the image pixel array is a direct mapping from the intersectionr results.
The below image is the rendering of a sphere at (0, 0, -40000) world coordinates and radius 0.15, and camera at (0, 0, 2) world coordinates looking towards (0, 0, 0) world coordinates. I would normally expect the sphere to be a lot smaller given its small radius and far away Z coordinate.
The same thing happens with rendering triangles too. In the below image I have 2 triangles that form a square, but it's way too zoomed in. The triangles have coordinates between -1 and 1, and the camera is looking from world coordinates (0, 0, 4).
This is what the square is expected to look like:
Here is the code snippet I use to determine the collision with the sphere. I'm not sure if I should divide the radius by the z coordinate here - without it, the circle is even larger:
Sphere* sphere = dynamic_cast<Sphere*>(object);
float t;
vec3 p0 = ray->origin;
vec3 p1 = ray->direction;
float a = glm::dot(p1, p1);
vec3 center2 = vec3(modelview * object->transform * glm::vec4(sphere->center, 1.0f)); // camera coords
float b = 2 * glm::dot(p1, (p0 - center2));
float radius = sphere->radius / center2.z;
float c = glm::dot((p0 - center2), (p0 - center2)) - radius * radius;
float D = b * b - 4 * a * c;
if (D > 0) {
// two roots
float sqrtD = glm::sqrt(D);
float root1 = (-b + sqrtD) / (2 * a);
float root2 = (-b - sqrtD) / (2 * a);
if (root1 > 0 && root2 > 0) {
t = glm::min(root1, root2);
found = true;
}
else if (root2 < 0 && root1 >= 0) {
t = root1;
found = true;
}
else {
// should not happen, implies sthat both roots are negative
}
}
else if (D == 0) {
// one root
float root = -b / (2 * a);
t = root;
found = true;
}
else if (D < 0) {
// no roots
// continue;
}
if (found) {
hitVector = p0 + p1 * t;
hitNormal = glm::normalize(result->hitVector - center2);
}
Here I generate the ray going through the relevant pixel:
Ray* RayThruPixel(Camera* camera, int x, int y) {
const vec3 a = eye - center;
const vec3 b = up;
const vec3 w = glm::normalize(a);
const vec3 u = glm::normalize(glm::cross(b, w));
const vec3 v = glm::cross(w, u);
const float aspect = ((float)width) / height;
float fovyrad = glm::radians(camera->fovy);
const float fovx = 2 * atan(tan(fovyrad * 0.5) * aspect);
const float alpha = tan(fovx * 0.5) * (x - (width * 0.5)) / (width * 0.5);
const float beta = tan(fovyrad * 0.5) * ((height * 0.5) - y) / (height * 0.5);
return new Ray(/* origin= */ vec3(modelview * vec4(eye, 1.0f)), /* direction= */ glm::normalize(vec3( modelview * glm::normalize(vec4(alpha * u + beta * v - w, 1.0f)))));
}
And intersection with a triangle:
Triangle* triangle = dynamic_cast<Triangle*>(object);
// vertices in camera coords
vec3 vertex1 = vec3(modelview * object->transform * vec4(*vertices[triangle->index1], 1.0f));
vec3 vertex2 = vec3(modelview * object->transform * vec4(*vertices[triangle->index2], 1.0f));
vec3 vertex3 = vec3(modelview * object->transform * vec4(*vertices[triangle->index3], 1.0f));
vec3 N = glm::normalize(glm::cross(vertex2 - vertex1, vertex3 - vertex1));
float D = -glm::dot(N, vertex1);
float m = glm::dot(N, ray->direction);
if (m == 0) {
// no intersection because ray parallel to plane
}
else {
float t = -(glm::dot(N, ray->origin) + D) / m;
if (t < 0) {
// no intersection because ray goes away from triange plane
}
vec3 Phit = ray->origin + t * ray->direction;
vec3 edge1 = vertex2 - vertex1;
vec3 edge2 = vertex3 - vertex2;
vec3 edge3 = vertex1 - vertex3;
vec3 c1 = Phit - vertex1;
vec3 c2 = Phit - vertex2;
vec3 c3 = Phit - vertex3;
if (glm::dot(N, glm::cross(edge1, c1)) > 0
&& glm::dot(N, glm::cross(edge2, c2)) > 0
&& glm::dot(N, glm::cross(edge3, c3)) > 0) {
found = true;
hitVector = Phit;
hitNormal = N;
}
}
Given that the output image is a circle, and that the same problem happens with triangles as well, my guess is the problem isn't from the intersection logic itself, but rather something wrong with the coordinate spaces or transformations. Could calculating everything in camera space be causing this?
I eventually figured it out by myself. I first noticed the problem was here:
return new Ray(/* origin= */ vec3(modelview * vec4(eye, 1.0f)),
/* direction= */ glm::normalize(vec3( modelview *
glm::normalize(vec4(alpha * u + beta * v - w, 1.0f)))));
When I removed the direction vector transformation (leaving it at just glm::normalize(alpha * u + beta * v - w)) I noticed the problem disappeared - the square was rendered correctly. I was prepared to accept it as an answer, although I wasn't completely sure why.
Then I noticed that after doing transformations on the object, the camera wasn't positioned properly, which makes sense - we're not pointing the rays in the correct direction.
I realized that my entire approach of doing the calculations in camera space was wrong. If I still wanted to use this approach, the rays would have to be transformed, but in a different way that would involve some complex math I wasn't ready to deal with.
I instead changed my approach to do transformations and intersections in world space and only use camera space at the lighting stage. We have to use camera space at some point, since we want to actually look in the direction of the object we are rendering.
I am working on implementing Alchemy AO and reading through their paper and they mention to sample each point by: considering a
Disk of radius r and center C that is parallel to the image plane,
select a screen-space point Q uniformly at random on it's project, and
then read a depth or position buffer to find the camera-space scene
point P = (xp, yp, z(Q)) on that ray.
I am wondering how you would go about selecting a screen-space point in the manor? I have made an attempt below but since my result appears quite incorrect, I think it's the wrong approach.
vec3 Position = depthToPosition(uvCoords);
int turns = 16;
float screen_radius = (sampleRadius * 100.0 / Position.z); //ball around the point
const float disk = (2.0 * PI) / turns;
ivec2 px = ivec2(gl_FragCoord.xy);
float phi = (30u * px.x ^ px.y + 10u * px.x * px.y); //per pixel hash for randdom rotation angle at a pixel
for (int i = 0; i < samples; ++i)
{
float theta = disk * float(i+1) + phi;
vec2 samplepoint = vec2(cos(theta), sin(theta));
}
I'm building my own path tracer by self-learning from online resources. But I find that my implementation has an issue with emissive objects in the scene, especially in a dark environment (no skybox).
For example, in the following environment:
The box in the middle is the only light source in the environment, with emission value of (3.0,3.0,3.0), and all other objects emission value of (0.0,0.0,0.0). I was expecting the light to scatter smoothly on the walls, but it looks like they are biased towards one direction.
My cosine sampling function is (modified from lwjgl3-demos):
float3 SampleHemisphere3(float3 norm, float alpha = 0.0)
{
float3 randomVec = rand3();
float r = saturate(pow(randomVec.x, 1.0 / (1.0 + alpha)));
float angle = randomVec.y * PI_TWO;
float sr = saturate(sqrt(1.0 - r * r));
float3 ph = float3(sr * cos(angle), sr * sin(angle), r);
float3 tangent = normalize(randomVec * 2.0 - 1.0);
float3 bitangent = cross(norm, tangent);
tangent = cross(norm, bitangent);
return mul(ph, float3x3(tangent, bitangent, norm));
}
This is how I compute the shading and next ray info:
float3 Shade(inout Ray ray, HitInfo hit)
{
ray.origin = hit.pos + hit.norm * 1e-5;
ray.dir = normalize(SampleHemisphere3(hit.norm, 0.0));
ray.energy *= 2.0 * hit.colors.albedo * saturate(dot(hit.norm, ray.dir));
return hit.colors.emission;
}
And the recursion happens here:
// generate ray from camera
Ray ray = CreateCameraRay(camera, PixelCenter);
// trace ray
float3 color = 0.0;
for (int i = 0; i < _TraceDepth; i++)
{
// get nearest ray hit
HitInfo hit = Trace(ray);
// accumulate color
color += ray.energy * Shade(ray, hit);
// if ray has no energy, stop tracing
if(!any(ray.energy))
break;
}
// write to frame target
_FrameTarget[id.xy] = float4(color, 1.0);
I learned the last two functions from GPU Path Tracing in Unity.
Here is another example of the similar error:
I feel that the problem is caused by the cosine weighted hemisphere sampling, but I have no idea how to fix it.
What should I do to get distributed light effect from emissive objects on the diffuse surfaces? Do I have to specify light sources and shapes and sample from them directly instead of emissive objects?
Edit:
It is indeed the cosine weighted sampling that is causing the problem.
Instead of:
float3 tangent = normalize(randomVec * 2.0 - 1.0);
I should have another vector of independent random values:
float3 tangent = normalize(rand3() * 2.0 - 1.0);
Now it is shows
Still not perfect, because it is clearly a cross shape. (Probably caused by sparsity of floating values)
How can I further improve this?
Edit 2:
After some more debugging and experiments, I figure out the "solution", but I don't understand the reason behind it.
The random value generator is from this Shadertoy project, because I see that GLSL-PathTracer is also using it.
Here is part of it:
void rng_initialize(float2 p, int frame)
{
//white noise seed
RandomSeed = uint4(p, frame, p.x + p.y);
}
void pcg4d(inout uint4 v)
{
v = v * 1664525u + 1013904223u;
v.x += v.y * v.w;
v.y += v.z * v.x;
v.z += v.x * v.y;
v.w += v.y * v.z;
v = v ^ (v >> 16u);
v.x += v.y * v.w;
v.y += v.z * v.x;
v.z += v.x * v.y;
v.w += v.y * v.z;
}
float3 rand3()
{
pcg4d(RandomSeed);
return float3(RandomSeed.xyz) / float(0xffffffffu);
}
float4 rand4()
{
pcg4d(RandomSeed);
return float4(RandomSeed) / float(0xffffffffu);
}
At initialization, I pass float2(id.xy) from SV_DispatchThreadID and current frame counter to rng_initialize.
And here is my new cosine weighted hemisphere sampling function:
float3 SampleHemisphere3(float3 norm, float alpha = 0.0)
{
float4 rand = rand4();
float r = pow(rand.w, 1.0 / (1.0 + alpha));
float angle = rand.y * PI_TWO;
float sr = sqrt(1.0 - r * r);
float3 ph = float3(sr * cos(angle), sr * sin(angle), r);
float3 tangent = normalize(rand.zyx + rand3() - 1.0);
float3 bitangent = cross(norm, tangent);
tangent = cross(norm, bitangent);
return mul(ph, float3x3(tangent, bitangent, norm));
}
And the results are: (which looks much better)
My discoveries from the experiments are:
r in the sampling function has to be dependent on w component of random values.
angle can be any in x, y, z.
tangent has to be dependent on current xyz values and a new vector of random xyz values. Order doesn't matter so I use zyx here. Missing either current xyz or new xyz will result in a cross shape on the wall.
I'm not sure if this is a correct solution, but as far as my eyes can tell, it solves the problem.
I am struggling with the following problem:
I am given n points and a radius and I have to place them on a circle as symmetrical as possible.
Currently, I used something like this:
float theta = 360.0f / n;
int i = 0;
for (Word w : e.getValue()) {
double newX = Math.sin(theta * i) * RADIUS + I_OFFSET_X;
double newY = Math.cos(theta * i) * RADIUS + I_OFFSET_Y;
mxCell v2 = (mxCell) graph.insertVertex(parent, null, w.getValue(), newX, newY, OW_WIDTH, OW_HEIGHT,"shape=ellipse");
graph.insertEdge(parent, null, "", v1, v2);
i++;
}
where n is my number of points.
This works fine for a large enough n, but for n=3 for example, I get something like:
I would actually like to have something like:
(bad drawing skills are bad..)
So basically, something as symmetric as possible would be awesome.
Any hints on how to solve this?
Thanks <3
Thanks to Jongware, the answer was quite obvious. Because I'm dealing with Java, all the sin/cos parameters should be in radians.
Fix:
double newX = Math.sin(Math.toRadians(theta * i)) * RADIUS + I_OFFSET_X;
double newY = Math.cos(Math.toRadians(theta * i)) * RADIUS + I_OFFSET_Y;
Works like a charm
I hope there will be an easy answer, as often times, something stripped out of Compact Framework has a way of being performed in a seemingly roundabout manner, but works just as well as the full framework (or can be made more efficient).
Simply put, I wish to be able to do a function similar to System.Drawing.Graphics.DrawArc(...) in Compact Framework 2.0.
It is for a UserControl's OnPaint override, where an arc is being drawn inside an ellipse I already filled.
Essentially (close pseudo code, please ignore imperfections in parameters):
FillEllipse(ellipseFillBrush, largeEllipseRegion);
DrawArc(arcPen, innerEllipseRegion, startAngle, endAngle); //not available in CF
I am only drawing arcs in 90 degree spaces, so the bottom right corner of the ellipse's arc, or the top left. If the answer for ANY angle is really roundabout, difficult, or inefficient, while there's an easy solution for just doing just a corner of an ellipse, I'm fine with the latter, though the former would help anyone else who has a similar question.
I use this code, then use FillPolygon or DrawPolygon with the output points:
private Point[] CreateArc(float StartAngle, float SweepAngle, int PointsInArc, int Radius, int xOffset, int yOffset, int LineWidth)
{
if(PointsInArc < 0)
PointsInArc = 0;
if(PointsInArc > 360)
PointsInArc = 360;
Point[] points = new Point[PointsInArc * 2];
int xo;
int yo;
int xi;
int yi;
float degs;
double rads;
for(int p = 0 ; p < PointsInArc ; p++)
{
degs = StartAngle + ((SweepAngle / PointsInArc) * p);
rads = (degs * (Math.PI / 180));
xo = (int)(Radius * Math.Sin(rads));
yo = (int)(Radius * Math.Cos(rads));
xi = (int)((Radius - LineWidth) * Math.Sin(rads));
yi = (int)((Radius - LineWidth) * Math.Cos(rads));
xo += (Radius + xOffset);
yo = Radius - yo + yOffset;
xi += (Radius + xOffset);
yi = Radius - yi + yOffset;
points[p] = new Point(xo, yo);
points[(PointsInArc * 2) - (p + 1)] = new Point(xi, yi);
}
return points;
}
I had this exactly this problem and me and my team solved that creating a extension method for compact framework graphics class;
I hope I could help someone, cuz I spent a lot of work to get this nice solution
Mauricio de Sousa Coelho
Embedded Software Engineer
public static class GraphicsExtension
{
// Implements the native Graphics.DrawArc as an extension
public static void DrawArc(this Graphics g, Pen pen, float x, float y, float width, float height, float startAngle, float sweepAngle)
{
//Configures the number of degrees for each line in the arc
int degreesForNewLine = 5;
//Calculates the number of points in the arc based on the degrees for new line configuration
int pointsInArc = Convert.ToInt32(Math.Ceiling(sweepAngle / degreesForNewLine)) + 1;
//Minimum points for an arc is 3
pointsInArc = pointsInArc < 3 ? 3 : pointsInArc;
float centerX = (x + width) / 2;
float centerY = (y + height) / 2;
Point previousPoint = GetEllipsePoint(x, y, width, height, startAngle);
//Floating point precision error occurs here
double angleStep = sweepAngle / pointsInArc;
Point nextPoint;
for (int i = 1; i < pointsInArc; i++)
{
//Increments angle and gets the ellipsis associated to the incremented angle
nextPoint = GetEllipsePoint(x, y, width, height, (float)(startAngle + angleStep * i));
//Connects the two points with a straight line
g.DrawLine(pen, previousPoint.X, previousPoint.Y, nextPoint.X, nextPoint.Y);
previousPoint = nextPoint;
}
//Garantees connection with the last point so that acumulated errors cannot
//cause discontinuities on the drawing
nextPoint = GetEllipsePoint(x, y, width, height, startAngle + sweepAngle);
g.DrawLine(pen, previousPoint.X, previousPoint.Y, nextPoint.X, nextPoint.Y);
}
// Retrieves a point of an ellipse with equation:
private static Point GetEllipsePoint(float x, float y, float width, float height, float angle)
{
return new Point(Convert.ToInt32(((Math.Cos(ToRadians(angle)) * width + 2 * x + width) / 2)), Convert.ToInt32(((Math.Sin(ToRadians(angle)) * height + 2 * y + height) / 2)));
}
// Converts an angle in degrees to the same angle in radians.
private static float ToRadians(float angleInDegrees)
{
return (float)(angleInDegrees * Math.PI / 180);
}
}
Following up from #ctacke's response, which created an arc-shaped polygon for a circle (height == width), I edited it further and created a function for creating a Point array for a curved line, as opposed to a polygon, and for any ellipse.
Note: StartAngle here is NOON position, 90 degrees is the 3 o'clock position, so StartAngle=0 and SweepAngle=90 makes an arc from noon to 3 o'clock position.
The original DrawArc method has the 3 o'clock as 0 degrees, and 90 degrees is the 6 o'clock position. Just a note in replacing DrawArc with CreateArc followed by DrawLines with the resulting Point[] array.
I'd play with this further to change that, but why break something that's working?
private Point[] CreateArc(float StartAngle, float SweepAngle, int PointsInArc, int ellipseWidth, int ellipseHeight, int xOffset, int yOffset)
{
if (PointsInArc < 0)
PointsInArc = 0;
if (PointsInArc > 360)
PointsInArc = 360;
Point[] points = new Point[PointsInArc];
int xo;
int yo;
float degs;
double rads;
//could have WidthRadius and HeightRadius be parameters, but easier
// for maintenance to have the diameters sent in instead, matching closer
// to DrawEllipse and similar methods
double radiusW = (double)ellipseWidth / 2.0;
double radiusH = (double)ellipseHeight / 2.0;
for (int p = 0; p < PointsInArc; p++)
{
degs = StartAngle + ((SweepAngle / PointsInArc) * p);
rads = (degs * (Math.PI / 180));
xo = (int)Math.Round(radiusW * Math.Sin(rads), 0);
yo = (int)Math.Round(radiusH * Math.Cos(rads), 0);
xo += (int)Math.Round(radiusW, 0) + xOffset;
yo = (int)Math.Round(radiusH, 0) - yo + yOffset;
points[p] = new Point(xo, yo);
}
return points;
}