View Full Version : Tutorial Proofreading and Correcting.

Alfonse Reinheart

06-24-2010, 07:14 PM

I have started work on a series of OpenGL tutorials, available here online (http://www.arcsynthesis.org/gltut/) and here for download (http://bitbucket.org/alfonse/gltut/downloads?highlight=8970).

I only have 5 complete thus far, but more will be added in the future. I'm primarily interested in getting feedback as to how useful these are currently. Is anything unclear, does it all make sense, and most importantly do the tutorial projects work on your machine (currently restricted to Visual Studio builds)?

pjcozzi

06-25-2010, 11:56 AM

Wow. This looks great; I like your shader-based, close to the metal, approach. I also like the use of small code samples. Looks like you put an incredible amount of work into this!

There is a small typo in On Vertex Shader Performance (http://www.arcsynthesis.org/gltut/Positioning/Tut03%20On%20Vertex%20Shader%20Performance.html):

The second vertex shader we use, the one that computes the offset itself, does a lot of complex map.

I believe map should be math.

Also, you may want to consider simplifying the build process - having to build a single .sln would be ideal for many readers. I'm sure a lot of people will be ok with downloading Premake 4 and following the necessary steps but anything you can do to simplify this will lower the barrier to entry.

I'm looking forward to the later chapters!

Regards,

Patrick

Alfonse Reinheart

06-25-2010, 01:58 PM

There is a small typo in On Vertex Shader Performance:

Thanks.

Also, you may want to consider simplifying the build process - having to build a single .sln would be ideal for many readers.

There are two problems with this.

1: What kind of .sln should it be? VS 2002, 2003, 2005, 2008, 2010? VC6? All of these? Using Premake makes it easy to avoid the question altogether; just build the one for your version of Visual Studio.

2: I do fully intend to get the tutorials working on non-Visual Studio/non-Windows platforms. Eventually. When that happens, Premake will be able to handle these platforms.

That being said, I could have a single Premake file in the root that creates a project that builds all of the tutorials.

Well, depending on how much additional work you want to put into it, you could of course use premake to create ALL visual studio projects and supply different downloads for each version.

I agree that it is much easier to use a build-tool (personally i use cmake), but for a beginner this is indeed something that may sound to complicated and thus scares them away.

Other than that, i skipped over the tutorials and was also very impressed by what i saw.

Keep at it!

Jan.

pjcozzi

06-26-2010, 05:18 AM

That being said, I could have a single Premake file in the root that creates a project that builds all of the tutorials.

Maybe even include Premake in the download if its license/install process allows you to do so. Similarly, I have a package that includes unit tests written with NUnit, so I include NUnit in the package so users can build and run the tests without downloading anything else.

Regards,

Patrick

ugluk

06-26-2010, 04:53 PM

I like it, nice to see you want to teach. I wonder what motivated you to do this?

tanzanite

06-28-2010, 04:24 AM

Looks very nice so far, but one suggestion if i may - drop the ridiculous feet/inches and use SI units (for obvious reasons).

knackered

06-28-2010, 04:35 AM

good stuff, but the Clip Space Transformation section is a bit vague. The W coordinate ends up being the Z coordinate, you miss that bit. Maybe you're leaving the perspective stuff till later - didn't read it all.

Dark Photon

06-28-2010, 05:05 AM

good stuff, but the Clip Space Transformation section is a bit vague. The W coordinate ends up being the Z coordinate, you miss that bit. Maybe you're leaving the perspective stuff till later - didn't read it all.

wclip = -zeye for perspective, but = 1 for orthographic (both assuming weye = 1, of course).

Haven't browsed the tutorials yet so apologies if this isn't applicable.

skynet

06-28-2010, 05:53 AM

clip[/sub] = -zeye for perspective, but = 1 for orthographic (both assuming weye = 1, of course).

Well, that might be the most common projection matrices in use, but not the only ones possible. The dot product of the last row of the projection matrix (which might in fact have arbitrary values) and the incoming 4D eyespace vector determines W.

If you further think about it, the last row is the plane-equation of the image plane.

Alfonse Reinheart

06-28-2010, 08:36 AM

good stuff, but the Clip Space Transformation section is a bit vague. The W coordinate ends up being the Z coordinate, you miss that bit. Maybe you're leaving the perspective stuff till later - didn't read it all.

If you're talking about the introduction section on clip-space, I wanted to keep that one general at that point. For similar reasons, I don't introduce camera space until the tutorial on perspective projection, because camera space is an arbitrarily-defined construct of the vertex shader. In the intro, I only want to talk about what OpenGL requires from the user.

pjcozzi

06-28-2010, 02:37 PM

Here's a few more minor things...

Figure 5.5 is missing in the Depth Precision (http://www.arcsynthesis.org/gltut/Positioning/Tut05%20Overlap%20and%20Depth%20Buffering.html#d0e 4403) section.

A bit later, the text cuts off after:

There are certainly ways around it (and we will discuss some later), but if you need a camera-space range that

Finally, I'm curious about this:

By reducing detail in distant objects, you are also less likely to have z-fighting

How exactly does this work? A lower resolution model doesn't mean it is going to take up less total camera space? Do you mean that it will have less triangles so the flicker is less noisy? Or are you talking about using imposters?

Regards,

Patrick

Alfonse Reinheart

06-28-2010, 07:36 PM

How exactly does this work?

It depends on the source of the z-fighting. If you have z-fighting within the same model (because some part of the model overlaps with itself), then having a lower-detail version where the details are abstracted can help. Think of a window-sill or something of that nature. Something that, in a low-LOD would just be flat.

I'll clarify this in the text.

Thanks everyone for the feedback so far.

pjcozzi

06-29-2010, 05:30 AM

Something that, in a low-LOD would just be flat.

Right, I see. This is also true when models are replaced with imposters regardless of the shape of the original model.

Regards,

Patrick

knackered

07-01-2010, 09:56 AM

If you're talking about the introduction section on clip-space, I wanted to keep that one general at that point. For similar reasons, I don't introduce camera space until the tutorial on perspective projection, because camera space is an arbitrarily-defined construct of the vertex shader. In the intro, I only want to talk about what OpenGL requires from the user.

Yes, thanks, I realised that as I read more. Should have kept my trap shut.

I think it's a fantastic bit of work. Wish something like that had been around when I was learning this stuff.

Groovounet

07-01-2010, 07:01 PM

Nice work Alfonse!

I quite agree with a couple of people here, CMake could accomplish what you need in a much easier way for yours readers!

For the developer CMake isn't perfect I think but so far I don't know a better cross-platform build system...

EDIT: Oh actually the build system is pretty good I think. I am going to have a closer look at it actually, it seems interesting!

ugluk

07-03-2010, 01:33 PM

Well, I say try bjam from the boost project for the build system. I build everything with bjam. If not, just write a custom make file build system that will work on linux and windows and forget the other OSes.

david_f_knight

07-04-2010, 01:01 PM

I just found and downloaded your tutorials yesterday. Though I have only just finished reading and working through chapter one, I wanted to provide a little feedback now rather than wait until I have finished everything.

First, THANK YOU VERY MUCH for undertaking this task. You have a made a tremendous start. I think MANY people will appreciate what you're doing. I certainly do. I have been searching in vain for weeks trying to find anything that teaches "modern" (non-deprecated) OpenGL.

I believe I noticed one small error in your text. In Chapter One, Following the Data page, Vertex Transfer section, in your discussion of the code fragment for glBindBuffer(), glEnableVertexAttribArray(), and glVertexAttribPointer(), you wrote:

"This means that our data of 24 floats represents enough information for the 3 vertices of a single triangle; this is exactly what we want."

I believe you should say that "... our data of 12 floats..." rather than 24 floats.

Here's the feedback I have for you that I think is very significant, though. In your program code tut1.cpp, in function init(), you call glGenVertexArrays() and glBindVertexArrays(). However, I don't believe you have discussed the significance of those two functions, what they do, or why they are where they are. Nor have they been included among the Chapter One, In Review page, OpenGL Functions of Note section.

Here's why I think those two functions are so noteworthy. I am just starting to learn OpenGL, and want to learn proper techniques for core profile OpenGL 4.0. Before finding your tutorial, I have read parts of the OpenGL 4.0 core spec including section 2.10 (Vertex Array Objects), the entire book Beginning OpenGL Game Programming, second edition (2009), by Benstead, Astle, and Hawkins (which purports to teach "modern" (non-deprecated) OpenGL as in more-or-less core OpenGL 3.0), and the only other tutorial I have found that attempts to teach "modern" OpenGL.

The OpenGL 4.0 core spec doesn't provide any useful information (at least that I can understand) as to Vertex Array Objects: are they required, and how, where, & why are they used?

Vertex Array Objects were not mentioned even once in the 290+ pages of the Benstead book. The only other "modern" OpenGL tutorial on the internet I have found also does not use Vertex Array Objects. I have spent two weeks trying to make the simplest OpenGL 4.0 core profile program display a single triangle without success. I have tried to write my own based on what I learned from the previously mentioned educational sources, I have tried to recreate the demo program from the other "modern" OpenGL tutorial I mentioned. None have worked.

Then, only after suffering extreme frustration and desperation, I found your tutorials. Your demo code uses a Vertex Array Object and it's the first OpenGL 4.0 core profile program I have ever witnessed that actually works. I added a Vertex Array Object to my other programs, and now they also work. But I don't really understand why. It isn't clear to me how Vertex Array Objects connect or interact with other OpenGL objects. I don't know whether or when I should use more than one VAO. All I know is, they are essential but not obvious.

I'm grateful your tutorial has finally set me on the correct path. Given that it seems other OpenGL experts don't really seem to understand Vertex Array Objects, but that they are essential, it seems that VAOs really do deserve some discussion in chapter one of your tutorial.

Alfonse Reinheart

07-04-2010, 01:22 PM

Given that it seems other OpenGL experts don't really seem to understand Vertex Array Objects, but that they are essential, it seems that VAOs really do deserve some discussion in chapter one of your tutorial.

VAOs are discussed, at some length, in Tutorial 5. I don't bring them up in Tutorial 1 for a couple of reasons.

1: Tutorial 1 is already packed with information as it is. For someone who is new to OpenGL and rendering in general, it's simply adding complexity to something that's already borderline overwhelming.

2: Until you are actually rendering more than one set of geometry, VAOs don't really function any differently from the non-VAO case. So explaining what they do doesn't really make sense at that point.

Dan Bartlett

07-04-2010, 04:12 PM

david_f_knight, there's some more info about VAOs at:

http://www.opengl.org/wiki/Vertex_Array_Object

They are basically a collection of all the vertex array state, and binding another VAO changes to another collection of vertex array state.

The currently bound VAO will store information set when:

a) Enabling different attributes

glEnableVertexAttrib(i);

b) Setting up attribute info

// note: buffer bound to GL_ARRAY_BUFFER is not part of VAO state, it only

// becomes part of VAO state when you perform the call to glVertexAttribPointer.

glBindBuffer(GL_ARRAY_BUFFER, bufferID);

glVertexAttribPointer(index, size, type, normalized, stride, &offset);

c) Binding index buffer

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID);

Binding a different VAO gives you a different set of state. A brand new VAO generated with glGenVertexArrays will have all attributes disabled, no buffers bound etc.

VAOs are *required* to get a functioning program with OpenGL 3+ using only core profile features, since the default VAO is deprecated in OpenGL 3.0, removed in 3.1, so you should get errors when calling glEnableVertexAttrib/glVertexAttribPointer/glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID) or any drawing commands without a VAO bound.

Unfortunately NVidia still allows you to use the default VAO in a core profile, whereas ATI do not, which is a potential cause of failure when developing on NVidia hardware, then running on other hardware that follows the spec. If you want a quick workaround to get a program running using core profile, then at the start of your app you can just do:

glGenVertexArrays(1, &dummyVAO);

glBindVertexArray(dummyVAO);

I personally don't understand why the default VAO was deprecated, but there was probably a reason, and if you want your program to work on both NVidia + ATI, then you need to make sure a VAO is bound. The main spec body should really include error messages that occur with each function call if no VAO is bound, rather than only being mentioned once in the appendices.

This is all mentioned in the chapter 5 of the tutorial, except that NVidia currently allows use of default VAO in core profile, which shouldn't be allowed according to spec.

david_f_knight

07-04-2010, 06:59 PM

Alfonse, I agree with your rationale for postponing the discussion of VAOs from chapter one until a bit later. (I knew I was taking a risk commenting before I finished reading everything you wrote. Sure enough, you had already covered the issue I raised.)

Dan, you provided a lot of information that really helps explains things. As it turns out, I do have an ATI graphics card in my computer. I have the ATI beta release driver that first supported OpenGL 4.0 installed. However, my OpenGL 4.0 core profile programs that didn't have any VAO executed without any errors being raised, but all I got was a blank window. Incredibly frustrating. My guess now is that other tutorial authors must have had NVIDIA graphics cards in their computers, and consequently their demos have worked on their computers even though they haven't had any explicitly created VAOs, or they didn't restrict their context to core functionality and basically got away with unwittingly writing faulty code.

Thanks for the helpful insights.

ugluk

07-05-2010, 12:48 AM

Strange, I have a Nvidia (crappy Ge 9300M) and ATI (crappy Radeon HD5450), but VAOs worked out of the box on both. It's a good thing to have 2 video cards to play with and compare.

Dan Bartlett

07-05-2010, 06:20 AM

Strange, I have a Nvidia (crappy Ge 9300M) and ATI (crappy Radeon HD5450), but VAOs worked out of the box on both. It's a good thing to have 2 video cards to play with and compare.

Yeah, VAOs work fine on both, it's just when you try to use the default VAO in a core profile where the difference occurs:

If you try the following code in a core profile:

glBindVertexArray(0);

glBindBuffer(GL_ARRAY_BUFFER, bufferID);

glVertexAttribPointer(index, size, type, normalized, stride, &offset);

glDrawArrays(GL_TRIANGLES, first, count);

then ATI throws a GL_INVALID_OPERATION error after the glVertexAttribPointer + glDrawArrays calls since no VAO is bound (as mentioned in appendix E of spec), but NVidia doesn't throw this error (at least the last time I tested).

People developing on NVidia might write code/demos that use the default VAO in a core profile that works when testing on NVidia but when it comes to running on ATI it fails.

Alfonse's tutorial already shows how to get it to work on both ATI + NVidia, by including:

glGenVertexArrays(1, &vao);

glBindVertexArray(vao);

david_f_knight

07-05-2010, 11:58 AM

Dan is right, of course.

To set the record straight, I had stated previously that ATI didn't throw any error even though I didn't have any VAO bound. Turns out I screwed that check up. I called glGetError() only after I had released my rendering context at the very end of my test programs -- since the OpenGL error status is sticky, I figured if there was no error at the end of the program, there was no error anywhere before. I didn't realize that the OpenGL error status is part of the rendering context, too.

Anyway, I just moved my glGetError() call to before the point where I release my rendering context and sure enough, ATI had thrown the error all along just as the spec requires. So, I created a lot of the frustration I suffered for the past two weeks. If I had properly checked for errors, I would have quickly narrowed my problem down to my call to glVertexAttribPointer(). What was wrong still wouldn't have been obvious to me, but at least I would have been looking narrowly at the right location.

ugluk

07-05-2010, 06:01 PM

I like to use this macro in my code:

#ifndef NDEBUG

# define GL_DEBUG(x)\

do {\

GLenum error(glGetError());\

if (GL_NO_ERROR != error)\

{\

std::cerr << gl_error_string(error) << std::endl;\

BOOST_ASSERT(0);\

}\

} while(0)

#else

# define GL_DEBUG(x)

#endif // NDEBUG

GLubyte const* gl_error_string(GLenum error)

{

return gluErrorString(error);

}

Alfonse Reinheart

07-05-2010, 06:11 PM

Why not use glIntercept? You don't have to ugly up your code or anything, and you can simply use it periodically as needed.

Dark Photon

07-05-2010, 07:31 PM

Was scanning random bits of this. Good stuff. Great to have a tutorial geared toward GL4 to point folks too.

One thing I just passed on the Depth Clamping (http://www.arcsynthesis.org/gltut/Positioning/Tut05%20Depth%20Clamping.html) page...:

If you're wondering what happens when you have depth clamping and a clip-space W <= 0 and you don't do clipping, then... well, OpenGL doesn't say. At least, it doesn't say specifically. All it says is that clipping against the near and far planes stops and that fragment depth values generated outside of the expected depth range are clamped to that range. No, really, that's all it says.

The wording I don't think is quite right here and is a bit confusing. Since I understand and have implemented homogenous clipping, I suspect this might confuse others. Might deserve a tweak.

For reference, Homogenous clipping is:

-w <= x,y,z <= w

The above verbage is talking about the case where:

clip.w <= 0

Geometrically, what is this? Not obvious. Well, for perspective projections, clip.w = -eye.z (assuming eye.w = 1), so plugging in, we have:

eye.z >= 0

So this is (somewhat cryptically) talking about the class of all points at or "behind" the plane of the eye. Why is the verbage singling out this case? Not obvious.

However, L/R/B/T clipping still occur, so (for perspective projections only! which is what you're assuming here) the points behind the eye will be culled by the L/R/B/T clipping planes. So the handling of these points is specified. The main class of points we add here with depth clamping are those in the pyramid formed by the eyepoint and the near plane corners, as well as those beyond the far clip still within the L/R/B/T planes. And OpenGL is clear about that clipping behavior.

Now what about for orthographic projections. That's where the tutorial verbage above doesn't make sense. For ortho, clip.w = 1. So there are no cases where clip.w <= 0. The verbage is geared toward some derived property of the perspective projection, and is kinda confusing anyway why it's doing that.

Think it's probably simpler to just say that homogenous clipping goes from:

-w <= x,y,z <= w

to just:

-w <= x,y <= w

when depth clamp is enabled, with Z values just being clamped after the x/y clip.

And if you continue to mention this special case, you might caveat that you're talking about points behind the eye with a perspective projection, rather than just say cryptically clip-space W <= 0.

david_f_knight

07-05-2010, 11:06 PM

This brings up what might be another valuable, but rarely presented, chapter to add to your OpenGL tutorial: tips on how to debug OpenGL programs.

A couple years ago I dabbled with OpenGL a bit and also experienced considerable trouble getting beyond a blank window due to some error of mine from not really understanding how OpenGL worked. Once data enters the blackbox of the GL, it seems pretty hard to debug when some things go wrong (especially when the resulting window is blank). Anyway, I'm sure you've learned a number of useful techniques over the years, such as using glIntercept, that help you debug your OpenGL programs. It could be very helpful for people learning OpenGL to read some discussion of debugging techniques.

Dark Photon

07-06-2010, 05:43 AM

If you're wondering what happens when you have depth clamping and a clip-space W <= 0 and you don't do clipping, then... well, OpenGL doesn't say...

... Homogenous clipping is:

-w <= x,y,z <= w

A slightly different take on this that occurred to me (and doesn't require any geometric intuition). When clip.w < 0, there are no points that satisfy the above relation. This is true whether or not z is clipped or not. So these points are always clipped. When clip.w == 0, the perspective divide ill-defined so that should be clipped as well.

So clip.w > 0 is really the only class of points that stand a shot at not being clipped. This is always the case in ortho (clip.w == 1), but depends on eye.z for perspective (clip.w = -eye.z).

kRogue

07-06-2010, 02:01 PM

I would present the perspective and clipping section differently, though it is debatable if it is better.

I would just repeat what the GL specification says on clipping:

-w <= x,y,z <= w where gl_Position=vec4(x,y,z,w). From there, talk about projection matrices and as an example give the typical frustum and ortho matrices. From there, one can talk about the "w" divide and another key feature: the affect of the "w" values on interpolation of out's of a vertex (or geometry) shader. A nice discussion with an example of using linear interpolation vs perspective correct interpolation I think would be invaluable to new comers a great deal. Along the same lines, a nice explanation of why z-clip is interpolated linearly but vertex shader out's are (default) interpolated perspective correct.

On the discussion of frustum perspective matrices, the use of the ratio [near:far] might lead a new comer to believe that the ratio of the 2 is important, where as the real beans in how small zNear is.. you do address this but perhaps take the discussion from a different point of view: given zNear, zFar, and given z in that range, calculate delta_z (as a function of z, zNear and zFar) where delta_z is the smallest delta on z to trigger a different value on 24-bit depth buffer. That I think would be quite useful for all new comers to see, and it also exposes them early to letting zFar=infinity as well (Ahhh, the days when DEPTH_CLAMP was only NV_DEPTH_CLAMP). That discussion also nicely leads to polygon offset to do decals.

Lastly, for the Unix users, maybe make a unix makefile too, with the assumption that the Unix system has FreeImage, GIL and some version of GLUT. FreeGLUT has glutGetProcAddress so in theory all the code could be platform agnostic.. in theory..

Don't know if this is a bad or good idea: use #version 330 in the shaders.

All in all, I am glad to see a new OpenGL tutorial popup, especially one that is GL3+ centric. Keep it up and I look forward to reading it more!

P.S. A wish list of topics that have few, if any tutorials:

Uniform Buffer Objects

Framebuffer Objects Layered Rendering (for example drawing to all 6 faces of a cubemap in one draw call, this will then imply a geometry shader write up...)

GL3's instance drawing API

Transform Feedback

Alfonse Reinheart

07-06-2010, 04:12 PM

the affect of the "w" values on interpolation of out's of a vertex (or geometry) shader.

Originally, I intended to hold off the discussion of perspective-correct interpolation until the tutorials involving textures. The examples are usually more clear when you can see the effect of switching it on and off with textures. However, after thinking about what you said, I've decided to move it up to the first lighting tutorial. It's the first place where there is a physically-based need to have perspective-correct interpolation of values.

I don't think it's good to discuss perspective-correct interpolation in the perspective projection tutorial. The perspective tutorial is pretty dense as is, and any examples would be fairly artificial.

On the discussion of frustum perspective matrices, the use of the ratio [near:far] might lead a new comer to believe that the ratio of the 2 is important, where as the real beans in how small zNear is..

But it is based on the ratio. 1:100 is no different than 10:1000, in terms of how close something must be (relative to the near/far planes) before you run out of precision. Increase both near and far by a factor of 10, and you increase that value by a factor of 10 as well. It is only when you keep the far plane fixed that the near plane's absolute value is important.

I think that what needs emphasizing more than I do is that clipping is a real problem for moving the near/far planes around. Since I talk about the ratio before actually talking about clipping, it creates an issue with the ordering of things. The far plane determines the absolute maximum extent of what you can see, and it is the most likely to be limited by absolute need. So the question becomes how big of a near plane you can choose and live with, given the far plane you have to live with.

I think I can move things around a bit to make this work better.

Lastly, for the Unix users, maybe make a unix makefile too, with the assumption that the Unix system has FreeImage, GIL and some version of GLUT. FreeGLUT has glutGetProcAddress so in theory all the code could be platform agnostic.. in theory..

All of the libraries I use are cross platform. Premake4 is a cross-platform build system. The only things standing in my way of just having the whole thing be cross-platform are:

1: My extension loading code, which I auto-generate with some Lua scripts, is Windows-only. The main issue there is with getting function pointers for core functions. With the Windows loading code, it simply includes the usual "gl/gl.h" and copies the function pointers when needed. This works for all of the 1.1 entrypoints. But I don't know what version that the various X-Windows implementations work with, so I don't know what their "gl/gl.h" uses. And I don't know if glutGetProcAddress works for statically-linked entrypoints.

2: I don't have a Linux install to test it on.

Admittedly, #2 is easier to solve than #1.

Don't know if this is a bad or good idea: use #version 330 in the shaders.

I started these before GL 3.3, so I just kept using 3.2-centric code. I'll update the tutorials to 3.3 when I feel that ATI and NVIDIA's 3.3 drivers are sufficiently stable. There are still one or two issues I've seen about ATi's 3.3 drivers that I want to see fixed before switching over to them. Maybe in a couple of months or so. At which point, I'll also add text to introduce useful 3.3 functionality like attribute index assignment in the shaders and so forth.

A wish list of topics that have few, if any tutorials:

I can post my current outline of future topics and tutorials, if you want. There's a partial outline (http://bitbucket.org/alfonse/gltut/wiki/Tutorial_Outline) up on BitBucket (which, btw, has without question the worst wiki software in the history of wikis). My full outline is in the Mercurial repository (though it also gets caught up in the distribution), in the documents directory.

I find that one of the most difficult things about planning tutorials is ordering. This is also one of the problems with NeHe's tutorials; everything's all over the place. I want each of my tutorials to properly build on knowledge from the previous ones.

The other problem is just finding an example that doesn't look too artificial, so that it inspires people to use it for the right purposes. Indeed, that's something I'm really going to push when I get into texturing. So many texturing tutorials and documents always talk about it in terms of images.

Take transform feedback for example. It's easy enough to explain what it does. But how do you explain it in such a way that the reader understands when to use it? The most obvious usage of it I can think of is as a performance optimization for multi-pass rendering. That's easy enough to describe, but talking about it requires having some need to talk about multi-pass rendering. So how would that come up?

david_f_knight

07-06-2010, 06:59 PM

I still haven't finished reading everything, but I've got enough comments that I don't want to wait to post them all at once.

-------------

Chapter 3. OpenGL's Moving Triangle page, Moving the Vertices section:

"The cosf and sinf functions compute the cosine and sine respectively. It isn't important to know exactly how these functions work, but they effectively compute a circle of radius 2. By multiplying by 0.5f, it shrinks the circle down to a radius of 1."

You need to replace the word radius with diameter.

-------------

Chapter 4. Objects at Rest, Perspective Correction page, Mathematical Perspective section:

"A perspective projection essentially shifts vertices towards the eye, based on the location of that particular vertex. Vertices farther in Z from the front of the projection are shifted less than those closer to the eye."

I believe you mean to say that vertices farther in Z (...) are shifted more than those closer to the eye. Or maybe I am not interpreting in the same way what you mean by the front of the projection. I'm assuming you just mean that vertices farther from the projection plane are shifted more than vertices nearer the projection plane; there's no need to distinguish which side of the projection plane is being referenced.

-------------

Figure 4.6 shows the eye at the origin, but has the following caption:

"The projection of the point P onto the projection plane, located at the origin. R is the projection and E is the eye point."

The projection plane isn't located at the origin in the figure, however.

Beneath that, there is the text:

"What we have are two similar right triangles; the triangle formed by E, R and the origin; ..."

But E (the eye) is at the origin, so there are only two distinct points.

-------------

Equation 4.1 is given as:

R = P * (Ez / Pz)

But if P is on the projection plane, then Pz is zero and equation 4.1 is indeterminate. I believe you need equation 4.1 to be something more like:

R = P * (Ez / (Ez + Pz))

-------------

Same chapter and page, The Perspective Divide section:

"You might notice that the scaling can be expressed as a division operation (dividing by the reciprocal). And you may recall that the difference between clip space and normalized device coordinate space is a division by the W coordinate. So instead of doing the divide in the shader, we can simply set the W coordinate of each vertex correctly and let the hardware handle it."

I've got a few problems with this. First sentence: division is multiplying by the reciprocal, not dividing by the reciprocal. Third sentence, re: setting the W coordinate of each vertex correctly. I don't know what you mean by "correctly" or by "letting the hardware handle it." With shaders, isn't it the programmer's responsibility to make the hardware handle it?

It's here that I have some issues basically surrounding the use and explanation of the W coordinate. At this time I need to think more about this because it's intertwined with lots of things, so I'll try to come back to it later. In the meantime, I'll just point out that you have defined W coordinates in all your geometry passed to OpenGL, but that you never use those W coordinates in example 4.2 (ManualPerspective Vertex Shader); it appears that in Example 4.4 (MatrixPerspective Vertex Shader) you assume and require all W coordinates have a value of one (i.e., that the input geometry be in Euclidean coordinates), though there is nothing to verify or force the programmer to comply.

As background information, the way homogeneous coordinates are converted to Euclidean coordinates is by dividing all coordinates of any point by its W coordinate. As long as W is not zero, then W will be one after the conversion. Presumably, to allow homogeneous coordinate geometry is the reason that OpenGL allows the programmer to pass W coordinates to the OpenGL pipeline. Otherwise, a value of one could have been assigned to W within the OpenGL pipeline. (The world we live in consists of Euclidean geometry, so at some point geometry in the OpenGL pipeline must be represented as Euclidean geometry if we hope to represent reality.)

Basically, the perspective where I am coming from is to set the stage for eventually getting to an OpenGL 4.0 pipeline with all five shader stages in use. Using them to render rational geometry, such as rational Bezier curves and surfaces, and by extension NURBS curves and surfaces, requires that homogeneous coordinate geometry (i.e., W not all the same) be used. I know you're nowhere near that at this stage in the tutorials (and might choose never to go that far), but beginning with OpenGL 4.0 and the use of tesselation shaders, homogeneous coordinate geometry will be important and descriptions of W and the transformations here ought not lead to contradictions or confusion later.

Alfonse Reinheart

07-06-2010, 11:10 PM

The projection plane isn't located at the origin in the figure, however.

When I first started doing the writeup for the tutorial, I had the projection plane at the origin, for some reason. I got pretty far into the tutorial, until I couldn't make the math work out. I thought I had fixed all of that, but apparently, I missed some.

First sentence: division is multiplying by the reciprocal, not dividing by the reciprocal.

No, I mean division by the reciprocal.

R = P * (Ez / Pz) is equivalent to R = P / (Pz / Ez). Assuming that Ez and Pz are non-zero.

I could probably make that clearer, though.

Third sentence, re: setting the W coordinate of each vertex correctly. I don't know what you mean by "correctly" or by "letting the hardware handle it."

From the perspective of simply performing the act of perspective projection, the shader is perfectly capable of outputting coordinates in NDC space, with a W of 1. However, since there is a division in the transform from clip-space to NDC space, we can "let the hardware handle it" by setting the W coordinate of each vertex "correctly" (as in, setting W to the value that, when divided by P will produce R. IE: Pz/Ez).

Remember: at this point, we're not ready to talk about why this division step exists in hardware at all, when the shader is perfectly capable of doing it by itself. And we're not ready to talk about the reasons for doing it, like homogeneous space clipping or perspective correct interpolation, let alone GL 4.0 homogeneous math.

Dark Photon

07-07-2010, 06:58 AM

On the discussion of frustum perspective matrices, the use of the ratio [near:far] might lead a new comer to believe that the ratio of the 2 is important, where as the real beans in how small zNear is..

But it is based on the ratio. 1:100 is no different than 10:1000, in terms of how close something must be (relative to the near/far planes) before you run out of precision.

I almost made this same comment yesterday too. Near is where it's at. You can push far out to infinity, and allegedly you don't lose that much precision in doing so with a perspective projection, despite the fact that the near:far ratio drops to 0. That's why this technique was sometimes used for shadow volumes (that or depth clamp).

It makes some intuitive sense when you consider that the bulk of your precision is clustered up close to the near plane. The further you go out into the scene, the less precision is used there, to where pushing the far clip out to infinity doesn't lose you that much.

kRogue

07-07-2010, 01:19 PM

I almost made this same comment yesterday too. Near is where it's at. You can push far out to infinity, and allegedly you don't lose that much precision in doing so with a perspective projection, despite the fact that the near:far ratio drops to 0. That's why this technique was sometimes used for shadow volumes (that or depth clamp).

This is the exact reason why I suggested to do the calculation given zNear and zFar (infinity is ok) and z in that range (or really -z) to calculate how much z has to change to be picked up by a 24 bit depth buffer.

Alfonse Reinheart

07-07-2010, 02:54 PM

You can push far out to infinity, and allegedly you don't lose that much precision in doing so with a perspective projection, despite the fact that the near:far ratio drops to 0.

I'm going to assume that by "zFar at infinity" you don't actually mean plugging the floating-point +INF value into the equation. As doing so would cause both terms of the Z calculations to become 0. Instead, I'll assume you just mean "really big".

So the near:far ratio never reaches 0. Doubling the zNear is the same as halving the zFar, in terms of the overall precision distribution of the depth buffer. The only difference is one of distance: doubling a zNear of 1 makes it 2, while halving the zFar of 10,000,000 makes it 5,000,000. The back half of the world is lost to get the same gains from losing only one unit off the front.

They both say the same thing, but talking about a zFar at infinity (which is not a legitimate concept) would be confusing.

This is the exact reason why I suggested to do the calculation given zNear and zFar (infinity is ok) and z in that range (or really -z) to calculate how much z has to change to be picked up by a 24 bit depth buffer.

Most people don't get to choose the Z values of their scene; that's necessitated by what is being rendered. What a user has a choice over is zNear/zFar. Because of that, the most useful question you can ask is, "how much usable depth range does a given zNear/zFar pairing give me?" Just because you put your zFar out at 50,000,000 doesn't mean that 50,000,000 units of space is actually usable.

In this case, I choose half-precision as the limit of "usable depth range". The equation you're suggesting has two unknowns: zNear/zFar, and the target Z value. The equation I use only has one unknown: zNear/zFar. This makes it much more obvious as to what's going on and how the zNear/zFar ratio affects the resulting depth buffer precision.

kRogue

07-07-2010, 03:08 PM

Oh boy.

Lets write some equations down ok?

Lets take the standard frustum projection matrix with left=-right and bottom=-top, then:

z_clip = (n+f)/(n-f) * z_eye + 2nf/(n-f) * w_eye

w_clip = -z_eye

by "f=infinity" it is meant to let f--> infinity as a mathematical limit, the expression then becomes:

z_clip = -z_eye - 2n*w_eye

w_clip = -z_eye

performing w-divide and for the common situation where w_eye=1.0,

z_n = 1 + 2n/z_eye

Take a look at that expression now. Given z_eye, that is the normalized device co-ordinate when the far plane is infinity. Notice:

z_eye= -n gives: z_n = -1 and letting z_eye limit to negative infinity, you get z_n --> +1.

Now for the interesting questions that are worth asking:

Using a 24 bit depth buffer, given z_eye (negative), find delta_z (negative) so that the values written to the depth buffer are distinct for z_eye and delta_z. Do a similar exercise having a finite f as well. Call this function

delta_z(z_eye, n, f).

and here is the surprise for you:

delta_z(t*z_eye, t*n, t*f) does not equal t*delta_z(z_eye, n, f) i.e. the affects of scaling n and f do not mean just pretend to scale z_eye too.

The concept of having the far plane out at negative infinity for shadow volumes is explained in the pdf: "Practical and Robust Shadow Volumes" here: Robust Shadow Volumes (http://developer.nvidia.com/object/robust_shadow_volumes.html) which is quite old.

ugluk

07-07-2010, 04:48 PM

You made an interesting suggestion. Well, I develop on Linux, and glIntercept has not been ported there yet. So I use the GL_DEBUG() macro. It's not so bad really, even though it is ugly. Works in Windows too.

Alfonse Reinheart

07-07-2010, 04:52 PM

The concept of having the far plane out at negative infinity for shadow volumes

But we're not dealing with uncapped shadow volumes; we're dealing with projecting a scene for the purpose of rasterizing it. By thinking of the limit as zFar approaches infinity as the expected way to generate your projection matrices, you're potentially throwing away valuable depth buffer precision.

Taking the limit of zFar as it approaches infinity may in fact be useful, but it's not something you need to be introduced to when first doing perspective projections. And it's certainly no solution for z-fighting.

delta_z(t*z_eye, t*n, t*f) does not equal t*delta_z(z_eye, n, f) i.e. the affects of scaling n and f do not mean just pretend to scale z_eye too.

That's because delta_z is a function of variables other than the near and far distances. The function I was using, determining the camera-space distance where 99.98% of the precision gets used, is purely a function of zNear and zFar. It does scale directly with the zNear/zFar ratio.

And I still think that it is easier to look at where 99.98% of the precision is going than to try to discuss a multi-variate function of camera z, zNear, and zFar. You can make a simple table or even a graph of the former, while the latter would require a 3D (or greater) graph.

kRogue

07-07-2010, 09:57 PM

I should never, ever post so late at night when so tired.

Many of my values for the coefficients are negated, everything is negated.. sighs.. and no one said anything.. the correction is this:

z_clip = -z_eye - 2n

w_clip = -z_eye

so

z_n = 1 + 2n/z_eye

and this makes sense:

z_eye=-n --> z_n = -1 (in front)

z_eye=-infinity --> z_n = +1 (far in back)

Now missing details: it is not z_n that is important but z_w which is given by (unless glDepthRange has been called):

z_w= 0.5 + 0.5*z_n

and we had for zFar=infinity, z_n= 1 + 2n/z_eye so:

z_w = 1 + n/z_eye

Plugging in z_eye =-n we get z_w=0 so sanity check passes.

The interesting and important bit: z_w is really just a function of the ratio zNear to z_eye, this also makes sense too.

The more sadistic can do the case for zFar is not infinity:

z_clip = (n+f)/(n-f)*z_eye + 2nf/(n-f)

so

z_n = (n+f)/(f-n) + 2nf/(z_eye*(f-n))

so

z_w= 0.5 + 0.5*( (n+f)/(f-n) + 2nf/(z_eye*(f-n)) )

after a little simplifying :

z_w = (1 + n/z_eye)*f/(f-n)

thus the only difference is that factor f/(n-f), for n=1 and f=1000 the ratio is 1000/999, and taking f to 10,0000 it is 10000/9999 do the maths, you will see the precision loss on the bits of the depth buffer are minimal. Also on this subject one sees quite clearly that the ratio of zNear to zFar is not the important issue, but the ratios zNear to z and zFar to (zFar-zNear). The above also guides one very well on how to use a floating pointer depth buffer well (which all GL3 hardware can do AND many GLES2 hardware can do too!)

The above tell you exactly how z_eye must vary in order to prevent z-fighting.

Please THINK. Presenting projection matrices with the little bits of math behind it (we are talking high school algebra here) will take the magic out of the entire process which is critical to creating a good tutorial.

kRogue

07-08-2010, 05:54 AM

I should also not post in the morning when I am tired either :o

Apparently I did not mess up my maths of z_n, z_w, whatever.. no wonder no one corrected it... but I did make one mistake: how delta_z(z,zNear,zFar) varies, it is homogeneous since the expression for z_w is, i.e.

delta_z(t*z, t*zNear, t*zFar) = t*delta_z(z,zNear,zFar)

but the real important bit on the derivation: the value of zNear to zFar is not important, the value of zNear to z_eye and to a much lesser extent zFar to (zFar-zNear) are what determines what happens.

Alfonse Reinheart

07-08-2010, 11:42 AM

The interesting and important bit: z_w is really just a function of the ratio zNear to z_eye, this also makes sense too.

Of course it is; you took the zFar plane out of the equation. I'm not doubting the ability to do this; I'm doubting:

1: The utility of doing it. Particularly for rendering geometry.

2: The practical need to talk about it at this point in the tutorial series.

Also on this subject one sees quite clearly that the ratio of zNear to zFar is not the important issue, but the ratios zNear to z and zFar to (zFar-zNear).

Given a particular Z value NDC space (Zndc), the function to compute the equivalent value in camera space is as follows:

Zcamera = -2 * N * F /(((N - F) * Zndc) + N + F)

If you multiply both N and F by the same factor t, then you get:

Zcamera = -2 * N * F * t^2/(((Nt - Ft) * Zndc) + Nt + Ft)

or

Zcamera = t * (-2 * N * F /(((N - F) * Zndc) + N + F))

Did I screw up my math somewhere? This suggests that it is proportional to the ratio of N and F.

The above also guides one very well on how to use a floating pointer depth buffer well

This only turns a 24-bit depth buffer into a 31-bit one (unless you throw in the sign bit to make 32). Though until that depth range issue is fixed, it only provides 30-bits of precision (positive exponents are never used). That's useful, and it does change the bit distribution to a degree. But the distribution of bits is still heavily weighted towards the front, and generally in the same proportions.

Presenting projection matrices with the little bits of math behind it (we are talking high school algebra here) will take the magic out of the entire process which is critical to creating a good tutorial.

I do present the projection transformations with the math behind them.

kRogue

07-08-2010, 12:50 PM

This only turns a 24-bit depth buffer into a 31-bit one (unless you throw in the sign bit to make 32). Though until that depth range issue is fixed, it only provides 30-bits of precision (positive exponents are never used). That's useful, and it does change the bit distribution to a degree. But the distribution of bits is still heavily weighted towards the front, and generally in the same proportions.

Um, er no. A floating pointer number is not 31 bits of precision + one sign bit, there is the exponent in there too.

When you use a floating point depth buffer in unextended GL3 (NV_depth_buffer_float has an unclamped glDepthRange, so in there the discussion is different), you reverse the role of 1.0 and 0.0 i.e. you call:

glDepthRange(1.0, 0.0);

and reverse the depth test direction too, the nature of accuracy of floating point biz makes this the better thing to do.

Now onto the maths:

(1) you should not be doing the precision question with z_n (z normalized device co-ordinates) since that is not the value stored in the depth buffer, it is z_window, so that is what you need to futz with, there we get:

z_w = (1+n/z_eye)*f/(f-n)

which then becomes

z_eye = n/( z_w*(f-n)/f - 1 )

simplifying becomes

z_eye = nf/( z_w*(f-n) - f)

You are correct that replaying n by t*n and f by t*f and you can futz to see that z_eye/n is a function of z_w and f/n:

z_eye/n = R*R/( z_w*(R-1) - R)

where R=(f/n).

Ick gross and actually useless for this discussion. The discussion is given projective matrix and z_eye, what is stored in the depth buffer. Using the depth buffer to get z_eye is never a good idea and is pointless here. We are only worried about given z_eye, what is then stored in depth buffer. The above gives a good reason why differed shaders store z_eye linearly and do not use the depth buffer to calculate z_eye. For that calculation what does matter is f/n, but the round off error gets real bad real fast.

Of course it is; you took the zFar plane out of the equation. I'm not doubting the ability to do this; I'm doubting:

1: The utility of doing it. Particularly for rendering geometry.

2: The practical need to talk about it at this point in the tutorial series.

I agree with (1), mostly, i.e. taking zFar to infinity.

What is important, in my opinion, is to give a detailed analysis of the effect of zFar and zNear. Saying that precision is based from zNear/zFar is dead wrong. I and others took this up by pointing out that one can make zFar=infinity and the loss of precision is tiny. The correct thing to do for a tutorial is to write out z_w given z_eye, zNear and zFar and then notice what matters are the ratios zNear to z_eye and to a very small extant zFar to (zFar-zNear). It is you tutorial, so you can do whatever you like, but considering the effort you have placed into it, putting a good, accurate discussion on projective matrices would make it stand above many others.

Just to state again, the issue is this: given a z_eye and a perspective projection matrix, how much does z_eye have to change so that the fixed 24-bit depth buffer will produce different values? That is the exact question to answer to know how to avoid z-fights. In that line, then one needs to know z_w given z_eye and the projection matrix, in this case having zNear and zFar is all that is needed. As I wrote adnauseum.

Also, why are you ignoring the derivation I wrote down previously? It clearly shows that z_w is a function of the ratios z/zNear and zFar/(zFar-zNear), which in turn states precisely what happens to the bits in the depth buffer.

Alfonse Reinheart

07-09-2010, 10:27 AM

A floating pointer number is not 31 bits of precision + one sign bit, there is the exponent in there too.

The power of floating-point numbers comes from the distribution of those bits of precision, not how many there are. A 32-bit float can deal with a larger range of values than a 32-bit integer. But there are still only 2^32 possible different values for either case.

Because of the way floats are distributed, you would get more effective use out of a 32-bit floating-point z-buffer than a 32-bit normalized integer one. But that doesn't change the fact that, given a [0, 1] range, you only get to use 30 of those bits (1 from the sign bit, and one from only using one sign of the exponent).

you should not be doing the precision question with z_n (z normalized device co-ordinates) since that is not the value stored in the depth buffer

The value stored in the depth buffer ultimately depends on the depth range. But since the depth range is a linear transform, and since we're assuming the full use of the depth range, it can be freely ignored. The entire [-1, 1] NDC range will be mapped to the entire [0, 1] depth range. And the mapping is linear, so the precision distribution in window space directly matches the precision distribution in NDC space.

And since this section is all about analyzing the precision distribution, I don't see the need to make already complex equations more complex.

Just to state again, the issue is this: given a z_eye and a perspective projection matrix, how much does z_eye have to change so that the fixed 24-bit depth buffer will produce different values? That is the exact question to answer to know how to avoid z-fights.

And to state again, this is not a useful metric for showing someone how the distribution of depth precision changes with depth range changes. The goal of this section is to show how changes to the zNear/zFar values affect the distribution of precision in the depth buffer relative to camera space.

I'm using a function of a single variable: the depth range. You're talking about a 2 variable function: depth range and a particular Z. This is inherently more difficult to present to the user. One can be presented with simple tables or even graphs. The other requires 3D graphing and is much more difficult to visualize.

Don't forget that these are tutorials, not the definitive work on a subject. These exist to be learning aids. That means information needs to be properly simplified for user consumption. Multivariate functions are not conducive to that.

Also, why are you ignoring the derivation I wrote down previously? It clearly shows that z_w is a function of the ratios z/zNear and zFar/(zFar-zNear), which in turn states precisely what happens to the bits in the depth buffer.

Because I derived an equation that clearly shows that the ratio of zNear to zFar is what is important. Is there something wrong with the math for that equation?

kRogue

07-09-2010, 11:06 AM

The power of floating-point numbers comes from the distribution of those bits of precision, not how many there are. A 32-bit float can deal with a larger range of values than a 32-bit integer. But there are still only 2^32 possible different values for either case.

Because of the way floats are distributed, you would get more effective use out of a 32-bit floating-point z-buffer than a 32-bit normalized integer one. But that doesn't change the fact that, given a [0, 1] range, you only get to use 30 of those bits (1 from the sign bit, and one from only using one sign of the exponent).

I cannot figure out anymore if Alfonse is serious or just having a laugh. Isn't is clear that I already know how floats work? Actually, looking at the above, he seems kind of hazy on it. Here is a pop quiz question for you Alfonse: why do you think that when using a floating point depth buffer, one should call glDepthRange(1.0, 0.0) (and thus change the direction of the depth tests)?

And to state again, this is not a useful metric for showing someone how the distribution of depth precision changes with depth range changes. The goal of this section is to show how changes to the zNear/zFar values affect the distribution of precision in the depth buffer relative to camera space.

I'm using a function of a single variable: the depth range. You're talking about a 2 variable function: depth range and a particular Z. This is inherently more difficult to present to the user. One can be presented with simple tables or even graphs. The other requires 3D graphing and is much more difficult to visualize.

How about this: write down your function, f(t) where t=zFar/zNear, and tell me what it evaluates precisely. Please do.

Because I derived an equation that clearly shows that the ratio of zNear to zFar is what is important. Is there something wrong with the math for that equation?

I'd bet a lot there is something really wrong. Here is why: myself and others have given you evidence that letting zFar tend to infinity does not incur universal z-fighting. Moreover, the loss of precision of letting zFar tend to infinity has also been demonstrated to be tiny too. This directly contradicts saying it only depends on zFar/zNear. What we have stated, repeatedly, is the ratio of zNear/z_eye is where most of the action is.

Don't forget that these are tutorials, not the definitive work on a subject. These exist to be learning aids. That means information needs to be properly simplified for user consumption. Multivariate functions are not conducive to that.

Well if you had read something for a change, you would have noticed that what matters is the ratio zNear/z_eye, which is one value and determines what is placed in the depth buffer, which with some thinking could then be used to make a plot: zNear/z_eye against discretized z_w to show the thresholds of when the depth buffer changes.

Those tutorials are yours and you can write whatever you want in them. However, you are now at a cross roads: do you write real tutorials and learning material or just the same junk plastered all over the place: copy-paste-able code which new comers copy and don't understand why it works. Your tutorials, your choice.

ZbuffeR

10-28-2010, 02:23 AM

Alfonse, where is the download link for the code in your tutorials ?

I can not find it anywhere ?

Alfonse Reinheart

10-28-2010, 03:47 AM

Does the download link (http://bitbucket.org/alfonse/gltut/downloads?highlight=8970) from the first page not work anymore?

I just uploaded a more recent version.

ZbuffeR

10-28-2010, 04:05 AM

Well I have not seen how to go from the tutorial itself : http://www.arcsynthesis.org/gltut/ to the project page http://bitbucket.org/alfonse/gltut/downloads?highlight=8970 ...

Alfonse Reinheart

10-28-2010, 07:21 AM

Good point.

ZbuffeR

11-03-2010, 10:39 AM

Alfonse, you really should put a link from the website to the code, or at least add the link in your signature ...

Alfonse Reinheart

11-03-2010, 01:56 PM

It's done now.

Powered by vBulletin® Version 4.2.2 Copyright © 2014 vBulletin Solutions, Inc. All rights reserved.