vtk6.1 shaders in/attribute variable - c++

I have a vtkPolyData filled with points and cells that I want to draw on the screen. My polydata represents brain fibers (list of lines in 3D). A cell is a fiber. It's working, but I need to add colors between all points. We decided to color the polydata using a shader because there will be a lot of coloring methods. My vertex shader is:
vtkShader2 *shader = vtkShader2::New();
shader->SetType(VTK_SHADER_TYPE_VERTEX);
shader->SetSourceCode(R"VertexShader(
#version 120
attribute vec3 next_point;
varying vec3 vColor; // Pass to fragment shader
void main() {
float r = gl_Vertex.x - next_point.x;
float g = gl_Vertex.y - next_point.y;
float b = gl_Vertex.z - next_point.z;
if (r < 0.0) { r *= -1.0; }
if (g < 0.0) { g *= -1.0; }
if (b < 0.0) { b *= -1.0; }
const float norm = 1.0 / sqrt(r*r + g*g + b*b);
vColor = vec3(r * norm, g * norm, b * norm);
gl_Position = ftransform();
}
)VertexShader");
shader->SetContext(shader_program->GetContext());
shader_program->GetShaders()->AddItem(shader);
The goal here is, for each point, get the next point to calculate the color of the line between them. The problem is that I can't find a way to set the value of "next_point". I'm pretty sure it's always filled with 0.0 because the output image is red, blue and green on the sides.
I tried using vtkProperty::AddShaderVariable() but I never saw any change and the method's documentation hints about a "uniform variable" so it's probably not the right way.
// Splitted in 3 because I'm not sure how to pass a vtkPoints object to AddShaderVariable
fibersActor->GetProperty()->AddShaderVariable("next_x", nb_points, next_x);
fibersActor->GetProperty()->AddShaderVariable("next_y", nb_points, next_y);
fibersActor->GetProperty()->AddShaderVariable("next_z", nb_points, next_z);
I also tried using a vtkFloatArray filled with my points, then setting it as a data array.
vtkFloatArray *next_point = vtkFloatArray::New();
next_point->SetName("next_point");
next_point->SetNumberOfComponents(3);
next_point->Resize(nb_points);
// Fill next_point ...
polydata->GetPointData()->AddArray(next_point);
// Tried the vtkAssignAttribute class. Did nothing.
tl;dr Can you please tell me how to pass a list of points into a GLSL attribute variable? Thanks for your time.

Related

How many calculations can a GPU do with a given amount of FLOPS?

I have the following shader rendering voxels using raycasting:
#version 460
#extension GL_ARB_separate_shader_objects : enable
#pragma optionNV(unroll all)
layout(binding = 3, std140) uniform compVarsOb {
float time;
float phiA;
float thetaA;
vec3 camPos;
float fov;
int voxWidth;
int voxHeight;
int voxDepth;
} cvo;
layout(binding = 2, rgba8) uniform writeonly image2D img;
float hash3(vec2 xy){
xy = mod(xy, .19);
float h = dot(xy.yyx, vec3(.013, 27.15, 2027.3));
h *= h;
h *= fract(h);
return fract(h);
}
//layout(binding = 4) uniform sampler3D voxels;
layout(binding = 4, std140) buffer vData{
vec4 voxels[];
};
float greaterThan(float a, float b){
float d = a - b;
return (1. + (d / abs(d)))/2.;
}
float lesserThan(float a, float b){
float d = a - b;
return (1. - (d / abs(d)))/2.;
}
float withinBounds(ivec3 li){
vec3 l = vec3(li);
return greaterThan(l.x, 0.) * lesserThan(l.x, cvo.voxWidth) * greaterThan(l.y, 0.) * lesserThan(l.y, cvo.voxHeight) * greaterThan(l.z, 0.) * lesserThan(l.z, cvo.voxDepth);
}
vec4 quaternionMult(vec4 a, vec4 b){
return vec4(a.x * b.x - dot(a.yzw, b.yzw), a.x*b.yzw + b.x*a.yzw + cross(a.yzw, b.yzw));
}
void main()
{
vec2 iResolution = vec2(2560., 1440.);
vec2 fragCoord = gl_GlobalInvocationID.xy;
ivec2 fragI = ivec2(gl_GlobalInvocationID.xy);
vec2 iMouse = vec2(.5);
vec2 uv = fragCoord/iResolution.xy;
ivec2 uvI = ivec2(uv);
vec2 muv = iMouse.xy / iResolution.xy;
float iTime = cvo.time;
vec3 col = vec3(0.);
float screenRatio = iResolution.y / iResolution.x;
//Setting up the ray directions and other information about the point and camera
//##############################################################################
//camera direction angles phi (xy plane) and theta (xz plane)
float phi = cvo.phiA;//radians(360. * (1. - muv.x));
float theta = cvo.thetaA;//radians(180. * (1. - muv.y));
//get the camera direction as the basis for the rotation (each ray direction is a rotation of the camera direciton vector)
//it is in quarternion form here so its a vec4 instead of a vec3
vec4 camD = vec4(0., cos(phi) * sin(theta), sin(phi) * sin(theta), cos(theta));
float rad90 = radians(90.);
float fov = cvo.fov;
float xAng = radians(fov * (.5 - uv.x));
//replace "fov" with "(fov + (110. * pow(.5 - uv.x, 2.)))" below to add a counteractment to the fisheye lens effect
//it basically counteracts the artifact with quaternions that happens when you rotate by a large angle on one axis then try to rotate on another axis perpendicular, it just rotates around it thus making the new direction lesser
float yAng = radians(fov * screenRatio * (uv.y - .5));
//get the axes that the quarternions should be based around (perpendicular to the camera plane or dv)
vec3 xRotAxis = vec3(cos(phi) * sin(theta - rad90), sin(phi) * sin(theta - rad90), cos(theta - rad90));
vec3 yRotAxis = cross(xRotAxis, camD.yzw);//vec3(cos(phi - rad90) * sin(theta), sin(phi - rad90) * sin(theta), cos(theta));
//get the quarternions of the ray direction rotations
vec4 xQuat = vec4(cos(xAng / 2.), xRotAxis * sin(xAng / 2.));
vec4 yQuat = vec4(cos(yAng / 2.), yRotAxis * sin(yAng / 2.));
//combine the rotations
vec4 compQuat = quaternionMult(yQuat, xQuat);
//get the conjugate of the compQuart
vec4 conjComp = vec4(compQuat.x, -compQuat.yzw);
//ray direction
vec3 rayD = normalize(quaternionMult(quaternionMult(compQuat, camD), conjComp).yzw);
//camera location
vec3 cam = cvo.camPos;//vec3(cos(iTime), 0., 0.);
//point location and radius
//vec3 p = vec3(0., (5. * iTime) + 1., 0.);
float pr = .00001;
//############################################
//hit = 1. means that nothing has been hit or everything has been completely transparent
float hit = 1.;
vec3 locf = vec3(0.);
ivec3 loc = ivec3(0);
int locI = 0;
vec4 v = vec4(0.);
for(int i = 0; i < 20; i++){
locf = vec3((i * rayD * .4) + cam);
loc = ivec3(locf);
//adjust loc for the buffer indexing
locI = loc.x + loc.y * cvo.voxWidth + loc.z * cvo.voxWidth * cvo.voxHeight;
//vec4 v = texelFetch(voxels, loc, 0);//;imageLoad(voxels, loc);//texelFetch(voxels, ivec3((i * rayD) + cam), 0);
v = voxels[locI];// * withinBounds(loc);
if(locf.x < 0. || locf.x > cvo.voxWidth || locf.y < 0. || locf.y > cvo.voxHeight || locf.z < 0. || locf.z > cvo.voxDepth){
v = vec4(0.);
}
col += v.xyz * hit * v.w;
hit -= v.w;
if(hit <= 0.){
//col = vec3(v.w / 5.);
break;
}
}
//col = imageLoad(voxels, ivec3(uvI, 1)).xyz;//texelFetch(voxels, ivec3(fragI / 10, 1), 0).xyz;
//col = vec3(phi / radians(180.));
//col = texture(iChannel0, uv).xyz;
//col = voxels[(fragI.x / 10) + (fragI.y / 10) * cvo.voxWidth].xyz;
//col = vec3(rayD.z);
imageStore(img, fragI, vec4(col,1.0));
}
It produces this:
The problem is when I change the loop (end of main()) for the amount of voxels I want to iterate over to more than 2 (its at 20 right now), the fps absolutely tanks. Yet I feel that my GPU is capable of way more than 2 iterations of a not so demanding loop, so I'm not sure what is going on.
I am running on an RTX 2060 Super which here is said to be capable of 7.81 * 10^12 FLOPS. If I'm understanding it correctly, this means that if I want to run a compute shader at 144 fps at 1440p, I would be allowed a total of (7.81 * 10^12)/(144 * 2560 * 1440) FLOPS in my shader. That comes out to about 14712 FLOPS in the compute shader which is way more than I have in my compute shader right now, yet my code only runs at an average of 30 fps when the loop is at 20 iterations. I can only get 144+ fps when I cut the loop down to 1 or 2 iterations (which at that point is basically like not having the loop at all). Are loops just horribly unoptimized for compute shaders? Where am I going wrong?
The flops GPU is capable of is usually calculated given perfect conditions with perfect code. That is, every computation is a Fused Multiply Add, is able to start right after the previous one with data already in cache/registers, and every single core is working. Achieving such conditions is the problem of writing code on GPUs.
GPUs normally create multiple threads per core in order to reduce penalty from having to wait for memory accesses. Caches are used to work with large bandwith usage, but they are relatively small and they require memory locality(data being stored physically close) for effective usage. Some GPUs have a separate texture cache, abusing which might be wise. Drivers also try to store textures and images in an efficient way, at least by making pixels close by - also close in memory, but potentially using some special hardware.
In compute shaders, threads are created in large blocks and then assigned to Compute Units (terminology differs between vendors, basically sort of a meta-core, with own cache and a bunch of cores). The size of block a is defined in shader with layout(local_size_x = X​, local_size_y = Y​, local_size_z = Z​) in;, where total number of threads equals to the multiple of the dimensions. Threads within a block can communicate with shared variables (they are usually stored within same space as l1 cache) and synchronise with barrier() and alike. GPUs have multiple compute units, and to make them work multiple blocks should be launched(the values in vkCmdDispatch() are the number of groups launched).
Threads are also implicitly grouped in SIMD-like groups (nVidia calls it SIMT - single instruction multiple threads). Every thread in one such group performs the same instruction(but there are some differences with newer nVidia cards). In case where an if() or for(), while(), etc. makes only some threads execute a portion of code, part of cores are disabled, potentially wasting performance. Usually they have size of 32 or 64(AMD), so thread blocks should be created as a multiple of that number. Some functionalities of such groups are exposed with subgroup extensions.
Since you are working with voxels, i'd imagine that using 3D textures or images instead of a buffer may be good for caches. Figuring out how to make threads cooperate and make use of the fast shared memory should also be a good idea.

How to do dynamic loop in WebGL GLSL [duplicate]

I have the a webgl blur shader:
precision mediump float;
precision mediump int;
uniform sampler2D u_image;
uniform float blur;
uniform int u_horizontalpass; // 0 or 1 to indicate vertical or horizontal pass
uniform float sigma; // The sigma value for the gaussian function: higher value means more blur
// A good value for 9x9 is around 3 to 5
// A good value for 7x7 is around 2.5 to 4
// A good value for 5x5 is around 2 to 3.5
// ... play around with this based on what you need :)
varying vec4 v_texCoord;
const vec2 texOffset = vec2(1.0, 1.0);
// uniform vec2 texOffset;
const float PI = 3.14159265;
void main() {
vec2 p = v_texCoord.st;
float numBlurPixelsPerSide = blur / 2.0;
// Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
vec3 incrementalGaussian;
incrementalGaussian.x = 1.0 / (sqrt(2.0 * PI) * sigma);
incrementalGaussian.y = exp(-0.5 / (sigma * sigma));
incrementalGaussian.z = incrementalGaussian.y * incrementalGaussian.y;
vec4 avgValue = vec4(0.0, 0.0, 0.0, 0.0);
float coefficientSum = 0.0;
// Take the central sample first...
avgValue += texture2D(u_image, p) * incrementalGaussian.x;
coefficientSum += incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
// Go through the remaining 8 vertical samples (4 on each side of the center)
for (float i = 1.0; i <= numBlurPixelsPerSide; i += 1.0) {
avgValue += texture2D(u_image, p - i * texOffset) * incrementalGaussian.x;
avgValue += texture2D(u_image, p + i * texOffset) * incrementalGaussian.x;
coefficientSum += 2.0 * incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
}
gl_FragColor = avgValue / coefficientSum;
}
When I build, I get the following error message:
webgl-renderer.js?2eb3:137 Uncaught could not compile shader:ERROR:
0:38: 'i' : Loop index cannot be compared with non-constant expression
I have also tried to use just the uniform float blur to compare i to. Is there any way to fix this?
The problem is further detailed here: https://www.khronos.org/webgl/public-mailing-list/archives/1012/msg00063.php
The solution that I've found looking around is to only use a constant expression when comparing a loop var. This doesn't fit with what I need to do which is vary how many times I'm looping based on the blur radius.
Any thoughts on this?
This happens because on some hardware, GLSL loops are un-rolled into native GPU instructions. This means there needs to be a hard upper limit to the number of passes through the for loop, that governs how many copies of the loop's inner code will be generated. If you replace numBlurPixelsPerSide with a const float or even a #define directive, and the shader compiler can then determine the number of passes at compile time, and generate the code accordingly. But with a uniform there, the upper limit is not known at compile time.
There's an interesting wrinkle in this rule: You're allowed to break or call an early return out of a for loop, even though the max iterations must be discernible at compile time. For example, consider this tiny Mandelbrot shader. This is hardly the prettiest fractal on GLSL Sandbox, but I chose it for its small size:
precision mediump float;
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
varying vec2 surfacePosition;
const float max_its = 100.;
float mandelbrot(vec2 z){
vec2 c = z;
for(float i=0.;i<max_its;i++){ // for loop is here.
if(dot(z,z)>4.) return i; // conditional early return here.
z = vec2(z.x*z.x-z.y*z.y,2.*z.x*z.y)+c;
}
return max_its;
}
void main( void ) {
vec2 p = surfacePosition;
gl_FragColor = vec4(mandelbrot(p)/max_its);
}
In this example, max_its is a const so the compiler knows the upper limit and can un-roll this loop if it needs to. Inside the loop, a return statement offers a way to leave the loop early for pixels that are outside of the Mandelbrot set.
You still don't want to set the max iterations too high, as this can produce a lot of GPU instructions and possibly hurt performance.
Try something like this:
const float MAX_ITERATIONS = 100.0;
// Go through the remaining 8 vertical samples (4 on each side of the center)
for (float i = 1.0; i <= MAX_ITERATIONS; i += 1.0) {
if (i >= numBlurPixelsPerSide){break;}
avgValue += texture2D(u_image, p - i * texOffset) * incrementalGaussian.x;
avgValue += texture2D(u_image, p + i * texOffset) * incrementalGaussian.x;
coefficientSum += 2.0 * incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
}
Sometimes you can use my very simple solving of issue.
My fragment of the shader source code:
const int cloudPointsWidth = %s;
for ( int i = 0; i < cloudPointsWidth; i++ ) {
//TO DO something
}
You can see '%' : syntax error above. But I am replace %s to a number in my javascript code before use my shader. For example:
vertexCode = vertexCode.replace( '%s', 10 );
vertexCode is my shader source code.
Everytime if I want to change cloudPointsWidth, I am destroying my old shader and creating new shader with new cloudPointsWidth .
Hope sometimes my solving can to help you.
You can just do a for loop with large constant number and use a break.
for(int i = 0; i < 1000000; ++i)
{
// your code here
if(i >= n){
break;
}
}
I've had similar problem with image downsampling shader. The code is basically the same:
for (int dx = -2 * SCALE_FACTOR; dx < 2 * SCALE_FACTOR; dx += 2) {
for (int dy = -2 * SCALE_FACTOR; dy < 2 * SCALE_FACTOR; dy += 2) {
/* accumulate fragment's color */
}
}
What I've ended up doing is using preprocessor and creating separate shader programs for every SCALE_FACTOR used (luckily, only 4 was needed). To achieve that, a small helper function was implemented to add #define ... statements to shader code:
function insertDefines (shaderCode, defines) {
var defineString = '';
for (var define in defines) {
if (defines.hasOwnProperty(define)) {
defineString +=
'#define ' + define + ' ' + defines[define] + '\n';
}
}
var versionIdx = shaderCode.indexOf('#version');
if (versionIdx == -1) {
return defineString + shaderCode;
}
var nextLineIdx = shaderCode.indexOf('\n', versionIdx) + 1;
return shaderCode.slice(0, nextLineIdx) +
defineString +
shaderCode.slice(nextLineIdx);
}
The implementation is a bit tricky because if the code already has #version preprocessor statement in it, all other statements have to follow it.
Then I've added a check for SCALE_FACROR being defined:
#ifndef SCALE_FACTOR
# error SCALE_FACTOR is undefined
#endif
And in my javascript code I've done something like this:
var SCALE_FACTORS = [4, 8, 16, 32],
shaderCode, // the code of my shader
shaderPrograms = SCALE_FACTORS.map(function (factor) {
var codeWithDefines = insertDefines(shaderCode, { SCALE_FACTOR: factor });
/* compile shaders, link program, return */
});
I use opengl es3 on android and solve this problem by using extension above the beginning of program like this:
#extension GL_EXT_gpu_shader5 : require
I don't know whether it work on webGL, but you can try it.
Hope it can help.
You can also use template litterals to set the length of the loop
onBeforeCompile(shader) {
const array = [1,2,3,4,5];
shader.uniforms.myArray = { value: array };
let token = "#include <begin_vertex>";
const insert = `
uniform float myArray[${array.length}];
for ( int i = 0; i < ${array.length}; i++ ) {
float test = myArray[ i ];
}
`;
shader.vertexShader = shader.vertexShader.replace(token, token + insert);
}

Deriving uncertainty values from a noise texture?

I'm trying to implement Sketchy Drawings. I'm at the part of the process which calls for the use of the noise texture to derive uncertainty values that will provide an offset into the edge map.
Here is a picture of my edge map for a torus:
And here is the noise texture I've gotten using the Perlin function as suggested:
I have these saved as textures in edgeTexture and noiseTexture respectively.
Now I'm stuck on the section where you have to offset the texture coordinates of the edge map by uncertainty values derived from the noise texture. This image is from the book:
offs = turbulence(s, t);
offt = turbulence(1 - s, 1 - t);
I'm ignoring the 2x2 matrix for the time being. Here is my current fragment shader attempt and the result it produces:
#version 330
out vec4 vFragColor;
uniform sampler2D edgeTexture;
uniform sampler2D noiseTexture;
smooth in vec2 vTexCoords;
float turbulence(float s, float t)
{
float sum = 0;
float scale = 1;
float s1 = 1;
vec2 coords = vec2(s,t);
for (int i=0; i < 10; i++)
{
vec4 noise = texture(noiseTexture, 0.25 * s1 * coords);
sum += scale * noise.x;
scale = scale / 2;
s1 = s1 * 2;
}
return sum;
}
void main( void )
{
float off_s = turbulence(vTexCoords.s, vTexCoords.t);
float off_t = turbulence(1 - vTexCoords.s, 1 - vTexCoords.t);
vFragColor = texture(edgeTexture, vTexCoords + vec2(off_s, off_t));
}
Clearly my addition to the vTexCoords is way off, but I can't see why. I have tried several other turbulence function definitions but none were close to the desired output so I'm thinking my overall approach is flawed somewhere. Any help here is greatly appreciated, and please comment if I haven't been clear. The desired output for a torus would just look like a roughly drawn circle I would imagine.
Your turbulence function will return values in the range (0,1). Firstly you need to change this to get values centered on 0. This should be done inside the loop in the function or you'll end up with a strange distribution. So firstly, I think you should change the line:
vec4 noise = texture(noiseTexture, 0.25 * s1 * coords);
to
vec4 noise = texture(noiseTexture, 0.25 * s1 * coords) * 2.0 - 1.0;
You then need to scale the offset so that you're not sampling the edge texture too far away from the fragment being drawn. Change:
vFragColor = texture(edgeTexture, vTexCoords + vec2(off_s, off_t));
to
vFragColor = texture(edgeTexture, vTexCoords + vec2(off_s, off_t) * off_scale);
where off_scale is some small value (perhaps around 0.05) chosen by experimentation.

Cell-Shading Outlines: edge mesh writer does not define all desired edges

The program that I am writing takes in the vertex data of a 3D mesh, performs a series of calculations (forgive the vagueness, I'll try to explain in better detail later), and outputs a binary file that defines where the edges are on the mesh. My program then draws a colored line where the edge is. Without the appropriate vertex shader, this would look like a regular triangulated mesh, but once the appropriate vertex shader is applied, only the edges that are "sharp" (the dot product of their normals is greater than something close to zero) have lines drawn on them, along with the edges on the outside of the figure. My implementation for the outline is not correct, as I made the assumption that if an edge wasn't behind the edge, and didn't define a sharp edge, it would be an outline edge. I haven't found a satisfactory answer to this elsewhere, and I didn't want to rely on the old trick of re-drawing the mesh as a solid color, and rendering it to be slightly larger than the original mesh. This approach was to be entirely math-based, relying only on the vertex data of a mesh. I am writing a program that uses the following vertex shader:
uniform mat4 worldMatrix;
uniform mat4 projMatrix;
uniform mat4 viewProjMatrix;
uniform vec4 eyepos;
attribute vec3 a;
attribute vec3 b;
attribute vec3 n1;
attribute vec3 n2;
attribute float w;
void main()
{
float a_vertex = dot(eyepos.xyz - a, n1);
float b_vertex = dot(eyepos.xyz - a, n2);
if (a_vertex * b_vertex > 0.0) // signs are different, edge is behind the object
{
gl_Position = vec4(2.0,2.0,2.0,1.0);
}
else // the outline of the figure
{
if(w == 0.0)
{
vec4 p = vec4(a.x, a.y, a.z, 1.0);
p = p * worldMatrix * viewProjMatrix;
gl_Position = p;
}
else
{
vec4 p = vec4(b.x, b.y, b.z, 1.0);
p = p * worldMatrix * viewProjMatrix;
gl_Position = p;
}
}
if(dot(n1, n2) <= 0.2) // there is a sharp edge
{
if(w == 0.0)
{
vec4 p = vec4(a.x, a.y, a.z, 1.0);
p = p * worldMatrix * viewProjMatrix;
gl_Position = p;
}
else
{
vec4 p = vec4(b.x, b.y, b.z, 1.0);
p = p * worldMatrix * viewProjMatrix;
gl_Position = p;
}
}
}
... to take information from a binary file that is written using this program in C++:
#include <iostream>
#include "llgl.h"
#include <fstream>
#include <vector>
#include "SuperMesh.h"
using namespace std;
using namespace llgl;
struct Vertex
{
float x,y,z,w;
float s,t,p,q;
float nx,ny,nz,nw;
};
bool isFileAlright(string fName)
{
ifstream in(fName.c_str());
if(!in.good())
return false;
return true;
}
int main(int argc, char* argv[])
{
// INPUT FILE NAME //
string fName;
cout << "Enter the path to your spec.mesh file here: ";
cin >> fName;
while(!isFileAlright(fName))
{
cout << "Enter the path to your spec.mesh file here: ";
cin >> fName;
}
SuperMesh* Model = new SuperMesh(fName.c_str());
// END INPUT //
Model->load();
Model->draw();
string fname = Model->fname;
string FileName = fname.substr(0, fname.size() - 10); // supposed to slash the last 10 characters off of the string, removing ".spec.mesh"...
FileName = FileName + ".bin"; //... and then we make it a .bin file*/
cout << FileName << endl;
ofstream out(FileName.c_str(), ios::binary);
for (unsigned w = 0; w < Model->m.size(); w++)
{
vector<float> &vdata = Model->m[w]->vdata;
vector<char> &idata = Model->m[w]->idata;
//Create a vertex and index variable, a map for Edge Mesh, perform two loops to analyze all triangles on a mesh and write out their vertex values to a file.//
Vertex* V = (Vertex*)(&vdata[0]);
unsigned short* I16 = (unsigned short*)(&idata[0]);
unsigned char* I8 = (unsigned char*)(&idata[0]);
unsigned int* I32 = (unsigned int*)(&idata[0]);
map<set<int>, vector<vec3> > EM;
for(unsigned i = 0; i < Model->m[w]->ic; i += 3) // 3 because we're looking at triangles //
{
Mesh* foo = Model->m[w];
int i1;
int i2;
int i3;
if( Model->m[w]->ise == GL_UNSIGNED_BYTE)
{
i1 = I8[i];
i2 = I8[i + 1];
i3 = I8[i + 2];
}
else if( Model->m[w]->ise == GL_UNSIGNED_SHORT)
{
i1 = I16[i];
i2 = I16[i + 1];
i3 = I16[i + 2];
}
else
{
i1 = I32[i];
i2 = I32[i + 1];
i3 = I32[i + 2];
}
vec3 p = vec3(V[i1].x, V[i1].y, V[i1].z); // to represent the point in 3D space of each vertex on every triangle on the mesh
vec3 q = vec3(V[i2].x, V[i2].y, V[i2].z);
vec3 r = vec3(V[i3].x, V[i3].y, V[i3].z);
vec3 v1 = p - q;
vec3 v2 = r - q;
vec3 n = cross(v2,v1); //important to make sure the order is correct here, do VERTEX TWO dot VERTEX ONE//
set<int> tmp;
tmp.insert(i1); tmp.insert(i2);
EM[tmp].push_back(n);
set<int> tmp2;
tmp2.insert(i2); tmp2.insert(i3);
EM[tmp2].push_back(n);
set<int> tmp3;
tmp3.insert(i3); tmp3.insert(i1);
EM[tmp3].push_back(n);
//we have now pushed every needed point into our edge map
}
int edgeNumber = 0;
cout << "There should be 12 edges on a lousy cube." << endl;
for(map<set<int>, vector<vec3> >::iterator it = EM.begin(); it != EM.end(); ++it)
{
//Now we will take our edge map and write its data to the file!//
/* Information is written to the file in this form:
Vertex One, Vertex Two, Normal One, Normal Two, r (where r, depending on its value, determines whether one edge is on top of the other in the case
where two edges are aligned with one another)
*/
set<int>::iterator tmp = it->first.begin();
int pi = *tmp;
tmp++;
int qi = *tmp;
Vertex One = V[pi];
Vertex Two = V[qi];
vec3 norm1 = it->second[0];
vec3 norm2;
if(it->second.size() == 1)
norm2 = -1 * norm1;
else
norm2 = it->second[1];
out.write((char*) &One, 12);
out.write((char*) &Two, 12);
out.write((char*) &norm1, 12);
out.write((char*) &norm2, 12);
float r = 0;
out.write((char*) &r, 4);
out.write((char*) &One, 12);
out.write((char*) &Two, 12);
out.write((char*) &norm1, 12);
out.write((char*) &norm2, 12);
r = 1;
out.write((char*) &r, 4);
edgeNumber++;
cout << "Wrote edge #" << edgeNumber << endl;
}
}
return 0;
}
The problem that this program has is that it does neither of these two essential things in the test case where I use it to draw a simple box with outlines:
It does not draw outlines. The vertex shader is not sufficient to determine anything more than where the edges of the object are. The binary file that makes this happen is pre-computed in a separate program using code from the second snippet posted above, and then it is saved as a .bin file along with the mesh assets to which it belongs. However, raw vertex data would only take me so far, and I seek a way to draw a line around the outside of the mesh without using more traditional methods.
It does not draw ALL of the edges that I need. In my test case, two of the edges are missing, and I cannot figure out for the life of me why. I figure I must have done something wrong in writing the edge map.
A couple notes about the above code:
llgl is an OpenGL wrapper that I have used to simplify many elements of OpenGL. It is not used extensively here, but rather in the creation of meshes, done elsewhere.
Things like Mesh and SuperMesh (a collection of meshes into one rigid body) are meant to be 3D objects in my scene. In my test case, there is only one Mesh in my scene, and defining a SuperMesh of a single Mesh is essentially just creating a single Mesh.
The "draw" call in the second snippet, which pre-computes a Mesh's edge map, does not actually draw anything. It is necessary to gain access to the Mesh's vertex data.
The variable "ise" is taken from the individual Meshes in the SuperMesh, and is a variable found by reading it in from the original Blender .OBJ file. It is related to how much memory should be used to store the important vertex data. It generally isn't a good idea to allocate more space than is needed for these values, as I've been told by friends and mentors who work with Blender.
It isn't well-commented, as I'm not the only one who has worked on this code, and I, unfortunately, have a limited understanding of how the second snippet could iterate through all of the triangles on a mesh and somehow miss the last two edges. Once I understand better what this code should do when properly written, I plan on heavily commenting it and using it in future applications.
Order of multiplication between matrix and vector is not comutative, so
your vertex shader have to output Projection * Model * Vertex and not the opposite.
I solved the mystery of the undrawn lines by allocating more space to write vertex data in a different part of my code. As for my other problems, although the order of multiplication being done in my vertex shader was actually alright, I had messed up another fundamental concept of vector math. The dot product of two face normals will be a negative number when the normals make an obtuse angle... the way a sharp point on my model would. Also, there is the faulty logic above that basically says that if the face is visible, draw all of the lines on it. I re-wrote my shader to test first if a face was visible, and then in that same conditional block I did the test for sharp edges. Now, if a face is visible BUT it doesn't create a sharp edge, the shader will ignore that edge. Also, outlines appear now, just not perfectly. Here is a modified version of the above vertex shader:
uniform mat4 worldMatrix; /* the matrix that defines how to project a point from
object space to world space.*/
uniform mat4 viewProjMatrix; // the view (pertaining to screen size) matrix times the projection (how to project points to 3D) matrix.
uniform vec4 eyepos; // the position of the eye, given by the program.
attribute vec3 a; // one vertex on an edge, having an x,y,z, and w coordinate.
attribute vec3 b; // the other edge vertex.
attribute vec3 n1; // the normal of the face the edge is on.
attribute vec3 n2; // another normal in the case that an edge shares two faces... otherwise, this is the same as n1.
attribute float w; // an attribute given to make a binary choice between two edges when they draw on top of one another.
void main()
{
// WORLD SPACE ATTRIBUTES //
vec4 eye_world = eyepos * worldMatrix;
vec4 a_world = vec4(a.x, a.y,a.z,1.0) * worldMatrix;
vec4 b_world = vec4(b.x, b.y,b.z,1.0) * worldMatrix;
vec4 n1_world = normalize(vec4(n1.x, n1.y,n1.z,0.0) * worldMatrix);
vec4 n2_world = normalize(vec4(n2.x, n2.y,n2.z,0.0) * worldMatrix);
// END WORLD SPACE ATTRIBUTES //
// TEST CASE ATTRIBUTES //
float a_vertex = dot(eye_world - a_world, n1_world);
float b_vertex = dot(eye_world - b_world, n2_world);
float normalDot = dot(n1_world.xyz, n2_world.xyz);
float vertProduct = a_vertex * b_vertex;
float hardness = 0.0; // this would be the value for an object made of sharp angles, like a box. Take a look at its use below.
// END TEST CASE ATTRIBUTES //
gl_Position = vec4(2.0,2.0,2.0,1.0); // if all else fails, keeping this here will discard unwanted data.
if (vertProduct >= 0.1) // NOTE: face is behind the viewable portion of the object, normally uses 0.0 when not checking for silhouette
{
gl_Position = vec4(2.0,2.0,2.0,1.0);
}
else if(vertProduct < 0.1 && vertProduct >= -0.1) // NOTE: face makes almost a right angle with the eye vector
{
if(w == 0.0)
{
vec4 p = vec4(a_world.x, a_world.y, a_world.z, 1.0);
p = p * viewProjMatrix;
gl_Position = p;
}
else
{
vec4 p = vec4(b_world.x, b_world.y, b_world.z, 1.0);
p = p * viewProjMatrix;
gl_Position = p;
}
}
else // NOTE: this is the case where you can very clearly see a face.
{ // NOTE: the number that normalDot compares to should be its "hardness" value. The more negative the value, the smoother the surface.
// a.k.a. the less we care about hard edges (when the normals of the faces make an obtuse angle) on the object, the more negative
// hardness becomes on a scale of 0.0 to -1.0.
if(normalDot <= hardness) // NOTE: the dot product of the two normals is obtuse, so we are looking at a sharp edge.
{
if(w == 0.0)
{
vec4 p = vec4(a_world.x, a_world.y, a_world.z, 1.0);
p = p * viewProjMatrix;
gl_Position = p;
}
else
{
vec4 p = vec4(b_world.x, b_world.y, b_world.z, 1.0);
p = p * viewProjMatrix;
gl_Position = p;
}
}
else // NOTE: not sharp enough, just throw the vertex away
{
gl_Position = vec4(2.0,2.0,2.0,1.0);
}
}
}

how to implement grayscale rendering in OpenGL?

When rendering a scene of textured polygons, I'd like to be able to switch between rendering in the original colors and a "grayscale" mode. I've been trying to achieve this using blending and color matrix operations; none of it worked (with blending I couldn't find a glBlendFunc() that achieved something remotely resembling to what I wanted, and color matrix operations ...are discussed here).
A solution that comes to mind (but also is rather expensive) is to capture the screen every frame and convert the resulting texture to a grayscale one and display that instead... (Where I said grayscale I actually meant anything with a low saturation, but I'm guessing for most of the possible solutions it won't differ all that much from grayscale).
What other options do I have?
The default OpenGL framebuffer uses the RGB colour-space, which doesn't store an explicit saturation. You need an approach for extracting the saturation, modifying it, and change it back again.
My previous suggestion which simply used the RGB vector length to represent 0 in luminance was incorrect, as it didn't take scaling into account, I apologize.
Credit for the new short snippet goes to the regular user "RTFM_FTW" from ##opengl and ##opengl3 on FreeNode/IRC, and it lets you modify the saturation directly without computing the costly RGB->HSV->RGB conversion, which is exactly what you want. Though the HSV code is inferior with respect to your question, I let it stay.
void main( void )
{
vec3 R0 = texture2DRect( S, gl_TexCoord[0].st ).rgb;
gl_FragColor = vec4( mix( vec3( dot( R0, vec3( 0.2125, 0.7154, 0.0721 ) ) ),
R0, T ), gl_Color.a );
}
If you want more control than just the saturation, you need to convert to HSL or HSV colour-space. As shown below by using a GLSL fragment shader.
Read the OpenGL 3.0 and GLSL 1.30 specification available on http://www.opengl.org/registry to learn how to use GLSL v1.30 functionality.
#version 130
#define RED 0
#define GREEN 1
#define BLUE 2
in vec4 vertexIn;
in vec4 colorIn;
in vec2 tcoordIn;
out vec4 pixel;
Sampler2D tex;
vec4 texel;
const float epsilon = 1e-6;
vec3 RGBtoHSV(vec3 color)
{
/* hue, saturation and value are all in the range [0,1> here, as opposed to their
normal ranges of: hue: [0,360>, sat: [0, 100] and value: [0, 256> */
int sortindex[3] = {RED,GREEN,BLUE};
float rgbArr[3] = float[3](color.r, color.g, color.b);
float hue, saturation, value, diff;
float minCol, maxCol;
int minIndex, maxIndex;
if(color.g < color.r)
swap(sortindex[0], sortindex[1]);
if(color.b < color.g)
swap(sortindex[1], sortindex[2]);
if(color.r < color.b)
swap(sortindex[2], sortindex[0]);
minIndex = sortindex[0];
maxIndex = sortindex[2];
minCol = rgbArr[minIndex];
maxCol = rgbArr[maxIndex];
diff = maxCol - minCol;
/* Hue */
if( diff < epsilon){
hue = 0.0;
}
else if(maxIndex == RED){
hue = ((1.0/6.0) * ( (color.g - color.b) / diff )) + 1.0;
hue = fract(hue);
}
else if(maxIndex == GREEN){
hue = ((1.0/6.0) * ( (color.b - color.r) / diff )) + (1.0/3.0);
}
else if(maxIndex == BLUE){
hue = ((1.0/6.0) * ( (color.r - color.g) / diff )) + (2.0/3.0);
}
/* Saturation */
if(maxCol < epsilon)
saturation = 0;
else
saturation = (maxCol - minCol) / maxCol;
/* Value */
value = maxCol;
return vec3(hue, saturation, value);
}
vec3 HSVtoRGB(vec3 color)
{
float f,p,q,t, hueRound;
int hueIndex;
float hue, saturation, value;
vec3 result;
/* just for clarity */
hue = color.r;
saturation = color.g;
value = color.b;
hueRound = floor(hue * 6.0);
hueIndex = int(hueRound) % 6;
f = (hue * 6.0) - hueRound;
p = value * (1.0 - saturation);
q = value * (1.0 - f*saturation);
t = value * (1.0 - (1.0 - f)*saturation);
switch(hueIndex)
{
case 0:
result = vec3(value,t,p);
break;
case 1:
result = vec3(q,value,p);
break;
case 2:
result = vec3(p,value,t);
break;
case 3:
result = vec3(p,q,value);
break;
case 4:
result = vec3(t,p,value);
break;
default:
result = vec3(value,p,q);
break;
}
return result;
}
void main(void)
{
vec4 srcColor;
vec3 hsvColor;
vec3 rgbColor;
texel = Texture2D(tex, tcoordIn);
srcColor = texel*colorIn;
hsvColor = RGBtoHSV(srcColor.rgb);
/* You can do further changes here, if you want. */
hsvColor.g = 0; /* Set saturation to zero */
rgbColor = HSVtoRGB(hsvColor);
pixel = vec4(rgbColor.r, rgbColor.g, rgbColor.b, srcColor.a);
}
If you're working against a modern-enough OpenGL, I would say pixel shaders is a very suitable solution here. Either by hooking into each polygon's shading as they render, or by doing a single full-screen quad in a second pass that just reads each pixel, converts to grayscale, and writes it back. Unless your resolution, graphics hardware, and target framerate are somehow "extrem", that should be doable these days in most cases.
For most Desktops Render-To-Texture isn't that expensive anymore, all of compiz, aero, etc and effects like bloom or depth of field seen in recent titles depend on it.
Actually you don't convert the screen texture per se to grayscale, you would want to draw a scree-sized quad with the texture and a fragment shader transforming the valures to grayscale.
Another option is to have two sets of fragment shaders for your triangles, one just copying the gl_FrontColor attribute as the fixed function pieline would, and another that writes grayscale values to the screen buffer.
A third option might be indexed color modes, if you set uüp a grayscale palette, but that mode might be deprecated and poorly supported by now; plus you lose a lot of functionality like blending, if I remember correctly.