以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 27-lesson 28 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=54180) |
-- 作者:一分之千 -- 发布时间:10/22/2007 8:04:00 PM -- [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 27-lesson 28 第二十七课 这是一个高级的主题,请确信你已经熟练的掌握了基本的OpenGL,并熟悉蒙板缓存。当然它会给你留下深刻的印象的。 // 定义阴影体可以延伸的距离 // 3D顶点结构 // 平面方程为: ax + by + cz + d = 0 // 描述一个模型表面的结构 struct glObject{ GLuint nPlanes, nPoints; sPoint points[100]; sPlane planes[200];}; bool readObject( const char *filename, glObject*o) file = fopen(st, "r"); 对于模型中的每一个面A 对于面A中的每一条边 如果我们不只到这条边相邻的顶点 那么对于模型中除了面A外的每一个面B 对于面B中的每一条边 如果面A的边和面B的边是同一条边,那么这两个面相邻 设置面A和面B的相邻属性 int vertA1 = pFaceA->vertexIndices[edgeA]; int vertB1 = pFaceB->vertexIndices[edgeB]; // 测试他们是否为同一边,如果是则设置相应的相邻顶点信息 //对于模型中的每一个面A p1i=o->planes[i].p[p1i]; //记录与这个边相邻的面的索引 // 绘制模型,像以前一样它绘制组成模型的三角形 for ( int j = 0; j < 3; j++ ) glNormal3f( face.normals[j].x, face.normals[j].y, face.normals[j].z ); void calculatePlane( const ShadowedObject& object, Face& face ) face.planeEquation.a = v1.y*(v2.z-v3.z) + v2.y*(v3.z-v1.z) + v3.y*(v1.z-v2.z); void castShadow( ShadowedObject& object, GLfloat *lightPosition ) GLfloat side = plane.a*lightPosition[0]+ if ( side > 0 ) glDisable( GL_LIGHTING ); // 关闭灯光 // 如果是逆时针(即面向视点)的多边形,通过了蒙板和深度测试,则把蒙板的值增加1 glFrontFace( GL_CCW ); // 把阴影绘制上颜色 下面的代码完成这些功能,它看起来并没有想象的复杂. void doShadowPass(glObject *o, float *lp) //对模型中的每一个面 //计算边的顶点到灯光的方向,并放大100倍 v2.x = (o->points[p2].x - lp[0])*100; glVertex3f(o->points[p2].x, } bool drawGLScene() // 清空缓存 glLoadIdentity(); // 设置灯光,并绘制球 glLoadIdentity(); glLoadIdentity(); glColor4f(0.7f, 0.4f, 0.0f, 1.0f); xrot += xspeed; // 增加X轴选择速度 glFlush(); // 强制OpenGL完成所有的命令 void DrawGLRoom() // 绘制房间(盒装) void VMatMult(GLmatrix16f M, GLvector4f v) int InitGLObjects() // 初始化模型对象 SetConnectivity(&obj); // 设置相邻顶点的信息 for ( int i=0;i < obj.nPlanes;i++) // 计算每个面的平面参数 return TRUE; //成功返回 最后我希望你喜欢它,如果有什么好的建议,请告诉我. |
-- 作者:一分之千 -- 发布时间:10/22/2007 8:05:00 PM -- Lesson 27 Welcome to a fairly complex tutorial on shadow casting. The effect this demo creates is literally incredible. Shadows that stretch, bend and wrap around other objects and across walls. Everything in the scene can be moved around in 3D space using keys on the keyboard. This tutorial takes a fairly different approach - It assumes you have a lot of OpenGL knowledge. You should already understand the stencil buffer, and basic OpenGL setup. If you need to brush up, go back and read the earlier tutorials. Functions such as CreateGLWindow and WinMain will NOT be explained in this tutorial. Additionally, some fundamental 3D math is assumed, so keep a good textbook handy! (I used my 1st year maths lecture notes from University - I knew they'd come in handy later on! :) First we have the definition of INFINITY, which represents how far to extend the shadow volume polygons (this will be explained more later on). If you are using a larger or smaller coordinate system, adjust this value accordingly. // Definition Of "INFINITY" For Calculating The Extension Vector For The Shadow Volume The Point3f structure holds a coordinate in 3D space. This can be used for vertices or vectors. // Structure Describing A Vertex In An Object // Structure Describing A Plane, In The Format: ax + by + cz + d = 0 // Structure Describing An Object's Face struct ShadowedObject int nFaces; bool readObject( const char *filename, ShadowedObject& object ) pInputFile = fopen( filename, "r" ); // Read Vertices // Read Faces for ( j = 0; j < 3; j++ ) for ( j = 0; j < 3; j++ ) for ( j = 0; j < 3; j++ ) void killObject( ShadowedObject& object ) delete[] object.pVertices; for each face (A) in the object int vertA1 = pFaceA->vertexIndices[edgeA]; int vertB1 = pFaceB->vertexIndices[edgeB]; // Check If They Are Neighbours - IE, The Edges Are The Same // Draw An Object - Simply Draw Each Triangular Face. for ( int j = 0; j < 3; j++ ) glNormal3f( face.normals[j].x, face.normals[j].y, face.normals[j].z ); void calculatePlane( const ShadowedObject& object, Face& face ) face.planeEquation.a = v1.y*(v2.z-v3.z) + v2.y*(v3.z-v1.z) + v3.y*(v1.z-v2.z); First up, we determine which surfaces are facing the light. We do this by seeing which side of the plane the light is on. This is done by substituting the light's position into the equation for the plane. If this is larger than 0, then it is in the same direction as the normal to the plane and visible by the light. If not, then it is not visible by the light. (Again, refer to a good Math textbook for a better explanation of geometry in 3D). void castShadow( ShadowedObject& object, GLfloat *lightPosition ) GLfloat side = plane.a*lightPosition[0]+ if ( side > 0 ) First, we push all the attributes onto the stack that will be modified. This makes changing them back a lot easier. Lighting is disabled because we will not be rendering to the color (output) buffer, just the stencil buffer. For the same reason, the color mask turns off all color components (so drawing a polygon won't get through to the output buffer). Although depth testing is still used, we don't want the shadows to appear as solid objects in the depth buffer, so the depth mask prevents this from happening. The stencil buffer is turned on as that is what is going to be used to draw the shadows into. glPushAttrib( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT | GL_POLYGON_BIT | GL_STENCIL_BUFFER_BIT ); // First Pass. Increase Stencil Value In The Shadow glFrontFace( GL_CCW ); // Draw A Shadowing Rectangle Covering The Entire Screen The brute force approach used here just draws to "infinity", and the shadow polygon is clipped against all the polygons it encounters. This causes piercing, which will stress the video hardware. For a high-performance modification to this algorithm, you should clip the polygon to the objects behind it. This is much trickier and has problems of its own, but if that's what you want to do, you should refer to this Gamasutra article. The code to do all of that is not as tricky as it sounds. To start with, here is a snippet that loops through the objects. By the end of it, we have an edge, j, and its neighbouring face, specified by neighbourIndex. void doShadowPass( ShadowedObject& object, GLfloat *lightPosition ) if ( face.visible ) // If There Is No Neighbour, Or Its Neighbouring Face Is Not Visible, Then This Edge Casts A Shadow // Get The Points On The Edge // Calculate The Two Vertices In Distance v3.x = ( v1.x-lightPosition[0] )*INFINITY; v4.x = ( v2.x-lightPosition[0] )*INFINITY; // Draw The Quadrilateral (As A Triangle Strip) bool drawGLScene() // Clear Color Buffer, Depth Buffer, Stencil Buffer glLoadIdentity(); // Reset Modelview Matrix glLoadIdentity(); // Reset Matrix glLoadIdentity(); // Reset Modelview Matrix glColor4f(0.7f, 0.4f, 0.0f, 1.0f); // Set Color To An Orange xrot += xspeed; // Increase xrot By xspeed glFlush(); // Flush The OpenGL Pipeline void DrawGLRoom() // Draw The Room (Box) void VMatMult(GLmatrix16f M, GLvector4f v) int InitGLObjects() // Initialize Objects setConnectivity(obj); // Set Face To Face Connectivity for ( int i=0;i < obj.nFaces;i++) // Loop Through All Object Faces return TRUE; // Return True void KillGLObjects() Some things to note about the tutorial: Banu Cosmin (Choko) & Brett Porter Jeff Molofee (NeHe) |
-- 作者:一分之千 -- 发布时间:10/22/2007 8:23:00 PM -- 第二十八课 这是一课关于数学运算的,没有别的内容了。来,有信心就看看它吧。 作者: David Nikdel ( ogapo@ithink.net ) 这篇教程旨在介绍贝塞尔曲面,希望有比我更懂艺术的人能用她作出一些很COOL的东东并且展示给大家。教程不能用做一个完整的贝塞尔曲面库,而是一个展示概念的程序让你熟悉曲面怎样实现的。而且这不是一篇正规的文章,为了方便理解,我也许在有些地方术语不当;我希望大家能适应这个。最后,对那些已经熟悉贝塞尔曲面想看我写的如何的,真是丢脸;-)但你要是找到任何纰漏让我或者NeHe知道,毕竟人无完人嘛?还有,所有代码没有象我一般写程序那样做优化,这是故意的。我想每个人都能明白写的是什么。好,我想介绍到此为止,继续看下文! 数学::恶魔之音::(警告:内容有点长~) 好,如果想理解贝塞尔曲面没有对其数学基本的认识是很难的,如果你不愿意读这一部分或者你已经知道了关于她的数学知识你可以跳过。首先我会描述贝塞尔曲线再介绍生成贝塞尔曲面。
这是最基础的贝塞尔曲线(长点的由很多点在一起(多到你都没发现))。这个曲线由4个点定义,有2个端点和2个中间控制点。对计算机而言这些点都是一样的,但是特意的我们通常把前后两对点分别连接,因为他们的连线与短点相切。曲线是一个参数化曲线,画的时候从曲线上平均找几点连接。这样你可以控制曲线曲面的精度(和计算量)。最通常的方法是远距离少细分近距离多细分,对视点,看上去总是很完好的曲面而对速度的影响总是最小。 t + (1 - t) = 1 看起来很简单不是?的确是的,这是最基本的贝塞尔曲线,一个一维的曲线。你也许从术语中猜到,贝塞尔曲线是多项式形式的。从线性代数知,一个一维的多项式是一条直线,没多大意思。好,因为基本方程对所有t都成立,我们可以平方,立方两边,怎么都行,等式都是成立的,对吧?好,我们试试立方。 (t + (1-t))^3 = 1^3 t^3 + 3*t^2*(1-t) + 3*t*(1-t)^2 + (1-t)^3 = 1 这是我们最常用的计算贝塞尔曲面的方程,a)她是最低维的不需要在一个平面内的多项式(有4个控制点),而且b)两边的切线互相没有联系(对于2维的只有3个控制点)。那么你看到了贝塞尔曲线了吗?呵呵,我们都没有,因为我还要加一个东西。 P1*t^3 + P2*3*t^2*(1-t) + P3*3*t*(1-t)^2 + P4*(1-t)^3 = Pnew 因为多项式是连续的,有一个很好的办法在4个点间插值。曲线仅经过P1,P4,分别当t=1,0。
恩,我认为现在已经有足够的数学背景了,看代码把! #include <math.h> // 数学库 typedef struct point_3d { // 3D点的结构 typedef struct bpatch { // 贝塞尔面片结构 BEZIER_PATCH mybezier; // 创建一个贝塞尔曲面结构 // 两个向量相加,p=p+q // 向量和标量相乘p=c*p // 创建一个3D向量 这里Ri为控制点 且00=1,[n0]=1 u'=(u-u1)/(u2-u1) 当为贝塞尔曲线时,控制点为4,相应的4个Bernstein多项式为: // 计算贝塞尔方程的值 a = pointTimes(pow(u,3), p[0]); r = pointAdd(pointAdd(a, b), pointAdd(c, d)); return r; // 生成贝塞尔曲面的显示列表 if (patch.dlBPatch != NULL) // 如果显示列表存在则删除 temp[0] = patch.anchors[0][3]; // 获得u方向的四个控制点 for (v=0;v<=divs;v++) { // 根据细分数,创建各个分割点额参数 glNewList(drawlist, GL_COMPILE); // 创建一个新的显示列表 for (u=1;u<=divs;u++) { temp[0] = Bernstein(py, patch.anchors[0]); // 计算每个细分点v方向上贝塞尔曲面的控制点 glBegin(GL_TRIANGLE_STRIP); // 开始绘制三角形带 for (v=0;v<=divs;v++) { glTexCoord2f(pyold, px); // 设置纹理坐标 last[v] = Bernstein(px, temp); // 创建下一个顶点 glEnd(); // 结束三角形带的绘制 glEndList(); // 显示列表绘制结束 free(last); // 释放分配的内存 void initBezier(void) { // 加载一个*.bmp文件,并转化为纹理 BOOL LoadGLTexture(GLuint *texPntr, char* name) glGenTextures(1, texPntr); // 生成纹理1 FILE* test=NULL; test = fopen(name, "r"); if (TextureImage != NULL) { // 邦定纹理 if (TextureImage->data) return success; int InitGL(GLvoid) // 初始化OpenGL initBezier(); // 初始化贝塞尔曲面 return TRUE; // 初始化成功 int DrawGLScene(GLvoid) { // 绘制场景 glCallList(mybezier.dlBPatch); // 调用显示列表,绘制贝塞尔曲面 if (showCPoints) { // 是否绘制控制点 return TRUE; // 成功返回 if (keys[VK_LEFT]) rotz -= 0.8f; // 按左键,向左旋转 |
-- 作者:一分之千 -- 发布时间:10/22/2007 8:27:00 PM -- Lesson 28 Bezier Patches Written by: David Nikdel ( ogapo@ithink.net ) This tutorial is intended to introduce you to Bezier Surfaces in the hopes that someone more artistic than myself will do something really cool with them and show all of us. This is not intended as a complete Bezier patch library, but more as proof of concept code to get you familiar with how these curved surfaces actually work. Also, as this is a very informal piece, I may have occasional lapses in correct terminology in favor of comprehensability; I hope this sits well with everyone. Finally, to those of you already familiar with Beziers who are just reading this to see if I screw up, shame on you ;-), but if you find anything wrong by all means let me or NeHe know, after all no one's perfect, eh? Oh, and one more thing, none of this code is optimised beyond my normal programming technique, this is by design. I want everyone to be able to see exactly what is going on. Well, I guess that's enough of an intro. On with the show! The Math - ::evil music:: (warning, kinda long section) Ok, it will be very hard to understand Beziers without at least a basic understanding of the math behind it, however, if you just don't feel like reading this section or already know the math, you can skip it. First I will start out by describing the Bezier curve itself then move on to how to create a Bezier Patch. Odds are, if you've ever used a graphics program you are already familiar with Bezier curves, perhaps not by that name though. They are the primary method of drawing curved lines and are commonly represented as a series of points each with 2 points representing the tangent at that point from the left and right. Here's what one looks like:
This is the most basic Bezier curve possible (longer ones are made by attaching many of these together (many times without the user realizing it)). This curve is actually defined by only 4 points, those would be the 2 ending control points and the 2 middle control points. To the computer, all the points are the same, but to aid in design we often connect the first and the last two, respectively, because those lines will always be tangent to the endpoint. The curve is a parametric curve and is drawn by finding any number of points evenly spaced along the curve and connecting them with straight lines. In this way you can control the resolution of the patch (and the amount of computation). The most common way to use this is to tesselate it less at a farther distance and more at a closer distance so that, to the viewer, it always appears to be a perfectly curved surface with the lowest possible speed hit. Bezier curves are based on a basis function from which more complicated versions are derived. Here's the function: t + (1 - t) = 1 Sounds simple enough huh? Well it really is, this is the Bezier most basic Bezier curve, a 1st degree curve. As you may have guessed from the terminology, the Bezier curves are polynomials, and as we remember from algebra, a 1st degree polynomial is just a straight line; not very interesting. Well, since the basis function is true for all numbers t, we can square, cube, whatever, each side and it will still be true right? Well, lets try cubing it. (t + (1-t))^3 = 1^3 t^3 + 3*t^2*(1-t) + 3*t*(1-t)^2 + (1-t)^3 = 1 This is the equation we use to calculate the most common Bezier, the 3rd degree Bezier curve. This is most common for two reasons, a) it's the lowest degree polynomial that need not necesarily lie in a plane (there are 4 control points) and b) the tangent lines on the sides are not dependant on one another (with a 2nd degree there would be only 3 control points). So do you see the Bezier curve yet? Hehe, me neither, that's because I still need to add one thing. Ok, since the entire left side is equal to 1, it's safe to assume that if you add all the components they should still equal one. Does this sound like it could be used to descide how much of each control point to use in calculating a point on the curve? (hint: just say yes ;-) ) Well you're right! When we want to calculate the value of a point some percent along the curve we simply multiply each part by a control point (as a vector) and find the sum. Generally, we'll work with 0 <= t <= 1, but it's not technically necesary. Confused yet? Here's the function: P1*t^3 + P2*3*t^2*(1-t) + P3*3*t*(1-t)^2 + P4*(1-t)^3 = Pnew Because polynomials are always continuous, this makes for a good way to morp between the 4 points. The only points it actually reaches though are P1 and P4, when t = 1 and 0 respectively. Now, that's all well and good, but how can I use these in 3D you ask? Well it's actually quite simple, in order to form a Bezier patch, you need 16 control points (4*4), and 2 variables t and v. What you do from there is calculate a point at v along 4 of the parallel curves then use those 4 points to make a new curve and calculate t along that curve. By calculating enough of these points, we can draw triangle strips to connect them, thus drawing the Bezier patch.
Well, I suppose that's enough math for now, on to the code! #include <windows.h> // Header File For Windows typedef struct point_3d { // Structure For A 3-Dimensional Point ( NEW ) typedef struct bpatch { // Structure For A 3rd Degree Bezier Patch ( NEW ) HDC hDC=NULL; // Private GDI Device Context DEVMODE DMsaved; // Saves The Previous Screen Settings ( NEW ) bool keys[256]; // Array Used For The Keyboard Routine GLfloat rotz = 0.0f; // Rotation About The Z Axis LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc // Adds 2 Points. Don't Just Use '+' ;) // Multiplies A Point And A Constant. Don't Just Use '*' // Function For Quick Point Creation // Calculates 3rd Degree Polynomial Based On Array Of 4 Points a = pointTimes(pow(u,3), p[0]); r = pointAdd(pointAdd(a, b), pointAdd(c, d)); return r; One thing we don't do is calculate the normals for lighting. When it comes to this, you basically have two options. The first is to find the center of each triangle, then use a bit of calculus and calculate the tangent on both the x and y axes, then do the cross product to get a vector perpendicular to both, THEN normalize the vector and use that as the normal. OR (yes, there is a faster way) you can cheat and just use the normal of the triangle (calculated your favorite way) to get a pretty good approximation. I prefer the latter; the speed hit, in my opinion, isn't worth the extra little bit of realism. // Generates A Display List Based On The Data In The Patch if (patch.dlBPatch != NULL) // Get Rid Of Any Old Display Lists temp[0] = patch.anchors[0][3]; // The First Derived Curve (Along X-Axis) for (v=0;v<=divs;v++) { // Create The First Line Of Points glNewList(drawlist, GL_COMPILE); // Start A New Display List for (u=1;u<=divs;u++) { temp[0] = Bernstein(py, patch.anchors[0]); // Calculate New Bezier Points glBegin(GL_TRIANGLE_STRIP); // Begin A New Triangle Strip for (v=0;v<=divs;v++) { glTexCoord2f(pyold, px); // Apply The Old Texture Coords last[v] = Bernstein(px, temp); // Generate New Point glEnd(); // END The Triangle Strip glEndList(); // END The List free(last); // Free The Old Vertices Array void initBezier(void) { // Load Bitmaps And Convert To Textures BOOL LoadGLTexture(GLuint *texPntr, char* name) glGenTextures(1, texPntr); // Generate 1 Texture FILE* test=NULL; test = fopen(name, "r"); // Test To See If The File Exists if (TextureImage != NULL) { // If It Loaded // Typical Texture Generation Using Data From The Bitmap if (TextureImage->data) return success; int InitGL(GLvoid) // All Setup For OpenGL Goes Here initBezier(); // Initialize the Bezier's Control Grid ( NEW ) return TRUE; // Initialization Went OK int DrawGLScene(GLvoid) { // Here's Where We Do All The Drawing glCallList(mybezier.dlBPatch); // Call The Bezier's Display List if (showCPoints) { // If Drawing The Grid Is Toggled On return TRUE; // Keep Going GLvoid KillGLWindow(GLvoid) // Properly Kill The Window ShowCursor(TRUE); // Show Mouse Pointer if (hRC) // Do We Have A Rendering Context? if (!wglDeleteContext(hRC)) // Are We Able To Delete The RC? if (hDC && !ReleaseDC(hWnd,hDC)) // Are We Able To Release The DC if (hWnd && !DestroyWindow(hWnd)) // Are We Able To Destroy The Window? if (!UnregisterClass("OpenGL",hInstance)) // Are We Able To Unregister Class // This Code Creates Our OpenGL Window. Parameters Are: * BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag) fullscreen=fullscreenflag; // Set The Global Fullscreen Flag hInstance = GetModuleHandle(NULL); // Grab An Instance For Our Window EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DMsaved); // Save The Current Display State ( NEW ) if (fullscreen) // Attempt Fullscreen Mode? ... Code Cut To Save Space (No Further Changes To This Function) ... return TRUE; // Success int WINAPI WinMain( HINSTANCE hInstance, // Instance // Ask The User Which Screen Mode They Prefer // Create Our OpenGL Window while(!done) // Loop That Runs While done=FALSE // Shutdown About The Author: David Nikdel is currently 18 and a senior at Bartow Senior High School. His current projects include a research paper on curved surfaces in 3D graphics, an OpenGL based game called Blazing Sands and being lazy. His hobbies include programming, football, and paintballing. He will (hopefully) be a freshman at Georgia Tech next year. David Nikdel Jeff Molofee (NeHe) |
-- 作者:liu_ying_qi -- 发布时间:4/4/2008 11:02:00 PM -- 我顶!!!!!!!!!!!!!!!!!!!!!!! |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
250.000ms |