I wrote a simple Sphere Tracer in Processing (Java) and am porting it to WebGL / GLSL. When I wrote it in Processing I had a base class Shape and would extend it for specific shapes such as Box, Plane, Sphere, etc. Each specific shape had members that were relevant to it, for example Sphere instances had a radius, Box instances had a length vector, etc. In addition each had a shape specific distance function.
Unfortunately I cannot use classes like this in GLSL and so I made a single struct that can represent any shape (I refer to it as Object below):
struct Object {
vec3 pos, len, nDir;
float rad;
} objects[4];
Then I wrote a distance function for each kind of shape:
float boxSignedDist(Object inBox, vec3 inPos) {
vec3 boxDelta = abs(inPos-inBox.pos)-inBox.len;
return min(max(boxDelta.x, max(boxDelta.y, boxDelta.z)), 0.0)+length(max(boxDelta, 0.0));
}
float planeSignedDist(Object inPlane, vec3 inPos) {
return dot(inPos-inPlane.pos, inPlane.nDir);
}
float roundBoxUnsignedDist(Object inRoundBox, vec3 inPos) {
return length(max(abs(inPos-inRoundBox.pos)-inRoundBox.len, 0.0))-inRoundBox.rad;
}
float sphereSignedDist(Object inSphere, vec3 inPos) {
return length(inPos-inSphere.pos)-inSphere.rad;
}
Now I have run into a different problem which is wrapping shape specific distance functions with another function such as a rotation, it is not obvious how to do this efficiently in GLSL. I added a member to Object, int type, and then made a few #defines for each shape I support at the moment:
#define BOX_SIGNED 1
#define PLANE_SIGNED 2
#define ROUNDBOX_UNSIGNED 3
#define SPHERE_SIGNED 4
struct Object {
int type;
vec3 pos, len, nDir;
float rad;
} objects[4];
So that now I can write a rotation wrapper to a distance function like this:
float rotateY(Object inObject, vec3 inPos, float inRadians) {
inPos -= inObject.pos;
inObject.pos = vec3(0.0, 0.0, 0.0);
float cRad = cos(inRadians);
float sRad = sin(inRadians);
if (inObject.type == BOX_SIGNED)
return boxSignedDist(inObject, vec3(cRad*inPos.x-sRad*inPos.z, inPos.y, cRad*inPos.z+sRad*inPos.x));
else if (inObject.type == PLANE_SIGNED)
return planeSignedDist(inObject, vec3(cRad*inPos.x-sRad*inPos.z, inPos.y, cRad*inPos.z+sRad*inPos.x));
else if (inObject.type == ROUNDBOX_UNSIGNED)
return roundBoxUnsignedDist(inObject, vec3(cRad*inPos.x-sRad*inPos.z, inPos.y, cRad*inPos.z+sRad*inPos.x));
else if (inObject.type == SPHERE_SIGNED)
return sphereSignedDist(inObject, vec3(cRad*inPos.x-sRad*inPos.z, inPos.y, cRad*inPos.z+sRad*inPos.x));
else
return 0.0;
}
It seems ridiculous that this would be necessary, is there a better way to do it? It would be nice if rotateY could receive a function pointer to just call the appropriate function instead of the all the else if
GLSL is quite a limited language really. The compiler does a great job at optimizing certain things but isn't perfect.
A few things to remember:
Local memory is expensive, both in just declaring and in access
Dynamically indexed arrays are put in local memory.
Arrays and objects are padded to align to 16 byte boundaries. An int[4] array takes the same memory as a vec4[4] array. Your Object should group vec3s with floats.
There's no such thing as a function call. Everything is inlined.
Arguments passed to functions are copied in and copied out. The complier doesn't always optimized out these copies when the functions are inlined. Keep as much global as possible.
Switch statements don't have jump operators, they are expanded to nested if-statements.
Divergence is a tricky thing to optimize out. Your if (type == ... code could be improved by constructing the rotated inPos beforehand, but I can't see a way around the if-statements. Perhaps you could write permutations of functions for each object type (or use macros) and trace the types in batches separately?
You might get some good ideas looking at what people have written for https://www.shadertoy.com/.
Finally, GLSL subroutines have a similar intent as function pointers, but are used on a global scale for all shader executions and won't help here.
Related
If I want to define a Matrix class in C++, for OpenGL rendering for example, the way I like to do it, and which also seems the most convenient, is to first define a Vector class as such :
class vec3 {
double x;
double y;
double z;
[ ... ]
}
class vec4 {
double x;
double y;
double z;
double w;
[ ... ]
}
Now, as far as I understand, the values of x, y, z{, w} are supposed to be contiguous in memory when I create a vec{3|4} (right ???).
If I then create my matrix as such :
class mat3 {
vec3 _m[3];
[ ... ]
}
class mat4 {
vec4 _m[4];
[ ... ]
}
Would the values of x, y, z{, w} of each vector in the matrix class always be next to one another in memory ? If I give the adress of the first element to OpenGL, which reads the next 16 values in memory (for a 4x4 matrix), would it read the 16 values of the matrix in order, or could some other information from elsewhere in the program get in the way ?
Edit (29/Nov./2019) : Fixed a typo
Would the values of x, y, z{, w} of each vector in the matrix class always be next to one another in memory ?
Probably not guaranteed to be so by the language, but probably will be contiguous in practice.
would it read the 16 values of the matrix
The behaviour of indexing over members is undefined in C++. The API probably written in C which may have different rules through.
There is a data structure that you can iterate over and is guaranteed to have 16 adjacent elements: array of 16 doubles.
The robust solution is to start with a double[16]. To the compiler, &vec3::y is just a way to express the offset of y, and you want that to be 1. You can achieve the same by writing
struct vec3 {
double* base;
double& x() { return *base; }
double& y() { return *(base+1); }
double& z() { return *(base+2); }
};
struct mat3 {
double values[9];
vec3 operator[](size_t s) { return vec3{values+3*s}; }
};
It's all inline, so the compiler can still calculate the offsets statically. There won't be a function call at runtime when you use y().
The standard allows arbitrary padding, so technically whether the members are contiguous in memory is implementation-defined. I don't know of any implementation that does something unexpected for your case though, so you should be fine by just adding a static_assert(sizeof(vec3) == 3 * sizeof(double));.
Similarly, accessing a bunch of members as if they were an array (i.e. via pointer arithmetic) in your code is undefined behavior by the standard, as there is no actual array object there. Now, if you give OpenGL this struct which has the values in the right places there should be no problem (because your compiler presumably doesn't touch the OpenGL code and the OpenGL only cares that the bytes are what they should be).
But note that this is only "fine" for the scope of the course I would say. For actual production code, these assumptions are too flimsy and a more robust (but possibly slightly less convenient) approach should be preferred.
I've been trying to make efficient collision detection between objects and terrain. The objects are represented by mobile spheres and the terrain is made of static triangles.
So far, I managed to implement a collision algorithm, but it has some major issues:
1. It's very resource-consuming.
2. Collision only works on one side of the triangles.
3. Multiple, simultaneous collisions give bad results.
Below I've put the algorithm that I have. It's based on an article from realtimecollisiondetection.net, and it's using the GLM math library. I've simplified loops, variable names, unnecessary code and class members:
//Class definitions:
class Sphere
{
public:
float x;
float y;
float z;
float radius;
float sqradius;
float xmov;
float ymov;
float zmov;
};
class TriangularWall
{
public:
float x[3];
float y[3];
float z[3];
glm::vec3 Normal;
};
using namespace glm;
Sphere Obj;
TriangularWall Wall;
//Assume that the 2 objects above are constructed. I didn't include their constructors because they looked ugly.
float rr=Obj.sqradius;
vec3 A=vec3(Wall.x[0], Wall.y[0], Wall.z[0])-vec3(Obj.x, Obj.y, Obj.z);
vec3 B=vec3(Wall.x[1], Wall.y[1], Wall.z[1])-vec3(Obj.x, Obj.y, Obj.z);
vec3 C=vec3(Wall.x[2], Wall.y[2], Wall.z[2])-vec3(Obj.x, Obj.y, Obj.z);
vec3 V=cross(B-A, C-A);
float d=dot(A, V);
float e=dot(V, V);
float di=d;
float ei=e;
vec3 Ai;
vec3 Bi;
vec3 Ci;
vec3 Vi;
if(!(di*di>rr*ei))
{
float aa=dot(A, A);
float ab=dot(A, B);
float ac=dot(A, C);
float bb=dot(B, B);
float bc=dot(B, C);
float cc=dot(C, C);
if(!(aa>rr && ab>aa && ac>aa))
if(!(bb>rr && ab>bb && bc>bb))
if(!(cc>rr && ac>cc && bc>cc))
{
vec3 AB=B-A;
vec3 BC=C-B;
vec3 CA=A-C;
float d1=ab-aa;
float d2=bc-bb;
float d3=ac-cc;
float e1=dot(AB, AB);
float e2=dot(BC, BC);
float e3=dot(CA, CA);
vec3 Q1=A*e1-d1*AB;
vec3 Q2=B*e2-d2*BC;
vec3 Q3=C*e3-d3*CA;
vec3 QC=C*e1-Q1;
vec3 QA=A*e2-Q2;
vec3 QB=B*e3-Q3;
if(!(dot(Q1, Q1)>rr*e1*e1 && dot(Q1, QC)>0)
if(!(dot(Q2, Q2)>rr*e2*e2 && dot(Q2, QA)>0)
if(!(dot(Q3, Q3)>rr*e3*e3 && dot(Q3, QB)>0)
{
vec3 ObjectMov=vec3(Obj.xmov, Obj.ymov, Obj.zmov);
if(dot(ObjectMov, Wall.Normal)<0)
ObjectMov-=dot(ObjectMov, Wall.Normal);
Obj.xmov=ObjectMov [0];
Obj.ymov=ObjectMov [1];
Obj.zmov=ObjectMov [2];
}
}
}
For the third issue, I re-made the above algorithm (simply stopping the object on the corresponding axis if a collision still takes place). However, this loads up the program significantly, and doesn't produce that nice effects.
I'm also aware that I could include a few performance tweaks to the algorithm above, but I don't see anything that could improve performance by a significant margin.
I know that I could also use octrees, however they seemed pretty complicated to me and I'm also worried that they could, in some situations, create a lot of latency.
Therefore, is there any way to improve the algorithm I used in order to fix its main issues? Or should I just try to use another one?
Assuming that what you call "resource consuming" is too high running-time, the number of triangles must be significant. So the first thing to do is to reduce the number of triangles to be tested against.
You mentioned oct-trees. More generally, a hierarchy of bounding volumes is the way to go. (https://en.wikipedia.org/wiki/Bounding_volume_hierarchy.) Speedups will be tremendous for million triangles. In your particular case, I would probably opt for a binary tree of bounding spheres.
Note that before implementing this, you will already obtain a speedup by precomputing the bounding sphere of every triangle (the center of the sphere is the center of the circumscribed circle, i.e. the intersection of the mediator planes and the plane of the triangle). Then non-intersecting triangles are detected by comparing the distance between the centers to the sum of radii.
Regarding the exact intersection test between the sphere and a triangle, I am afraid that there is no free lunch. Using the "inflating/deflating" method, the center of the sphere can be inside either the right prism over the triangle, one of three truncated cylindres around the edges or one of three spheres around the vertices.
Whatever approach you take, you will have to deal with that complexity of the case analysis.
I am trying to use glVertexAttribPointer with a structure of Eigen objects, similar to this:
struct Vertex {
Eigen::Vector3f position;
Eigen::Vector3f normal;
};
The problem is setting the offset of glVertexAttribPointer. Since there is no public access to the m_data member used to store internally the data in Eigen, offset cannot be used.
It seems like there is no nice way to do this. My current approach is something like:
(void*)((char*)vertices[0].Position.data() - (char*)(&vertices[0]))
, where vertices is a std::vector<Vertex>.
This is by no means nice (especially in modern C++). I doubt there can be a nice solution, but what would be a safer way of doing this, or at least how can this operation be isolated as much as possible, so I don't have to write it for every call to glVertexAttribPointer.
The Eigen documentation guarantees that the layout of a Eigen::Vector3f = Eigen::Matrix<float,3,1> is as follows:
struct {
float data[Rows*Cols]; // with (size_t(data)%A(Rows*Cols*sizeof(T)))==0
};
In other words, the float[3] is at offset 0 of the Eigen::Vector3f structure. You are allowed to pass offsets of position and normal as-is (offsetof(Vertex, position) and offsetof(Vertex, normal)) to your glVertexAttrib calls for the offsets, and sizeof(Eigen::Vector3f) for the sizes.
I use oglplus - it's a c++ wrapper for OpenGL.
I have a problem with defining instanced data for my particle renderer - positions work fine but something goes wrong when I want to instance a bunch of ints from the same VBO.
I am going to skip some of the implementation details to not make this problem more complicated. Assume that I bind VAO and VBO before described operations.
I have an array of structs (called "Particle") that I upload like this:
glBufferData(GL_ARRAY_BUFFER, sizeof(Particle) * numInstances, newData, GL_DYNAMIC_DRAW);
Definition of the struct:
struct Particle
{
float3 position;
//some more attributes, 9 floats in total
//(...)
int fluidID;
};
I use a helper function to define the OpenGL attributes like this:
void addInstancedAttrib(const InstancedAttribDescriptor& attribDesc, GLSLProgram& program, int offset=0)
{
//binding and some implementation details
//(...)
oglplus::VertexArrayAttrib attrib(program, attribDesc.getName().c_str());
attrib.Pointer(attribDesc.getPerVertVals(), attribDesc.getType(), false, sizeof(Particle), (void*)offset);
attrib.Divisor(1);
attrib.Enable();
}
I add attributes for positions and fluidids like this:
InstancedAttribDescriptor posDesc(3, "InstanceTranslation", oglplus::DataType::Float);
this->instancedData.addInstancedAttrib(posDesc, this->program);
InstancedAttribDescriptor fluidDesc(1, "FluidID", oglplus::DataType::Int);
this->instancedData.addInstancedAttrib(fluidDesc, this->program, (int)offsetof(Particle,fluidID));
Vertex shader code:
uniform vec3 FluidColors[2];
in vec3 InstanceTranslation;
in vec3 VertexPosition;
in vec3 n;
in int FluidID;
out float lightIntensity;
out vec3 sphereColor;
void main()
{
//some typical MVP transformations
//(...)
sphereColor = FluidColors[FluidID];
gl_Position = projection * vertexPosEye;
}
This code as whole produces this output:
As you can see, the particles are arranged in the way I wanted them to be, which means that "InstanceTranslation" property is setup correctly. The group of the particles to the left have FluidID value of 0 and the ones to the right equal to 1. The second set of particles have proper positions but index improperly into FluidColors array.
What I know:
It's not a problem with the way I set up the FluidColors uniform. If I hard-code the color selection in the shader like this:
sphereColor = FluidID == 0? FluidColors[0] : FluidColors1;
I get:
OpenGL returns GL_NO_ERROR from glGetError so there's no problem with the enums/values I provide
It's not a problem with the offsetof macro. I tried using hard-coded values and they didn't work either.
It's not a compatibility issue with GLint, I use simple 32bit Ints (checked this with sizeof(int))
I need to use FluidID as a instanced attrib that indexes into the color array because otherwise, if I were to set the color for a particle group as a simple vec3 uniform, I'd have to batch the same particle types (with the same FluidID) together first which means sorting them and it'd be too costly of an operation.
To me, this seems to be an issue of how you set up the fluidID attribute pointer. Since you use the type int in the shader, you must use glVertexAttribIPointer() to set up the attribute pointer. Attributes you set up with the normal glVertexAttribPointer() function work only for float-based attribute types. They accept integer input, but the data will be converted to float when the shader accesses them.
In oglplus, you apparently have to use VertexArrayAttrib::IPointer() instead of VertexArrayAttrib::Pointer() if you want to work with integer attributes.
I'm having trouble understanding the core concept of spaces in OpenGL. I've been reading an online book on modern 3D graphics for a couple weeks now and i often find myself confused with all of the spaces used in a program. To be specific, spaces such as: Model space , World space, Camera space, Clip space. I can't seem to wrap my mind around the order that i should be transforming the matrix from and into, an example from one of my tutorial programs:
//.vert shader of a program
#version 330
layout(location = 0) in vec4 position;
uniform mat4 cameraToClipMatrix;
uniform mat4 worldToCameraMatrix;
uniform mat4 modelToWorldMatrix;
void main()
{
vec4 temp = modelToWorldMatrix * position;
temp = worldToCameraMatrix * temp;
gl_Position = cameraToClipMatrix * temp;
}
cameraToClip , worldToCamera, XtoY, ZtoQ, how can i get an understanding of these spaces in OpenGL, websites? videos? references? Or should i just go back and re-read the information on these spaces in the tutorial until it attatches to my brain.
I really don't know how to explain it any better than I did. Especially when the matrices are named about as clearly as they can be.
Think of a matrix like a function. A function has inputs and it returns a value. You must pass the correct input or your compiler will complain.
Consider these functions:
Float intToFloat(Int i);
Double floatToDouble(Float f);
Real doubleToReal(Double d);
Where Int, Float, Double, and Real are user-defined C++ types.
Let's say I need to write this function:
Real intToReal(Int i);
So all I have is an Int. Of the above functions, there is exactly one function I can call: intToFloat. The name says it all: it takes an int and turns it into a float. Therefore, given an Int, the only thing I can do with it is call intToFloat.
Int i = ...;
Float f = intToFloat(i);
Well, now I have a Float. There is again only one function I can call: floatToDouble.
Double d = floatToDouble(d);
And with that, I can only call doubleToReal. Which means our intToReal function is:
Real intToReal(Int i)
{
Int i = ...;
Float f = intToFloat(i);
Double d = floatToDouble(d);
return doubleToReal(d);
}
Just like the matrix example.
The most important thing that a Vertex Shader does is transform positions from their original space (called model space) to the space that OpenGL defines called clip space. That's job #1 for most vertex shaders.
The matrices are just like those functions, converting the position into intermediate spaces along the way.
There can be no answer to this question until after it is solved. What will teach well enough to some people, for them to grok concepts, will not do the same for everybody. My best advice is to learn to be a 3D modeler before you become a 3D programmer. That's what I did. Once you have good familiarity with visualization of the data, then you can form mental models more easily, and code with them in mind. And when you need further visualizations to help you create algorithms, you'll be able to create them without using code.