新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     >>计算机科学论坛<<     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 计算机科学论坛计算机技术与应用『 C/C++编程思想 』 → [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 47-lesson 48【本教程全部传完】 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 29406 个阅读者浏览上一篇主题  刷新本主题   平板显示贴子 浏览下一篇主题
     * 贴子主题: [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 47-lesson 48【本教程全部传完】 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客楼主
    发贴心情 

    Lesson: 47
       
    Using vertex and fragment (or pixel) shaders to do some rendering dirty work can have numerous benefits. The most obvious is the movement of some of the graphics related CPU load off the CPU and onto the GPU. Cg provides a (reasonably) simple language for writing very powerful shaders.
    This tutorial has multiple aims. The first is to present a simple vertex shader that actually does something, without introducing unnecessary lighting etc… The second is to provide the basic mechanism for running the vertex shader with visible results using OpenGL. As such, it is aimed at the beginner interested in Cg who has a little experience in OpenGL.

    This tutorial is based on the Latest NeHeGL Basecode. For more information on Cg check out nVidia’s website (developer.nvidia.com) and www.cgshaders.org for some cool shaders.

    NOTE: This tutorial is not intended to teach you everything you need to know about writing vertex shaders using Cg. It is intended to teach you how to successfully load and run vertex shaders within OpenGL.


    Setup:

    The first step (if it hasn’t been done already) is to download the Cg Compiler from nVidia. It is important that you download version 1.1, as nVidia appear to have made changes between version 1.0 and 1.1 (different variable naming, replaced functions, etc…), and code compiled for one may not necessarily work with the other.

    The next step is to setup the header files and library files for Cg to a place where Visual Studio can find them. Because I am inherently distrustful of installers working the way they are supposed to, I copy the library files...
      
       

    From: C:\Program Files\NVIDIA Corporation\Cg\libTo:   C:\Program Files\Microsoft Visual Studio\VC98\Lib
       
    and the header files (Cg sub-directory and GLext.h into the GL sub-directory)...  
       

    From: C:\Program Files\NVIDIA Corporation\Cg\includeTo:   C:\Program Files\Microsoft Visual Studio\VC98\Include
       
    We’re now ready to get on with the tutorial.
    Cg tutorial:

    The information regarding Cg contained in this tutorial was obtained mostly from the Cg Toolkit User’s Manual.

    There are a few important points that you need to remember when dealing with vertex (and later fragment) programs. The first thing to remember is that a vertex program will execute in its entirety on EVERY vertex. The only way to run the vertex program on selected vertices is to either load/unload the vertex program for each individual vertex, or to batch vertices into a stream that are affected by the vertex program, and a stream that isn’t.

    The output of a vertex program is passed to a fragment shader, regardless of whether you have implemented and activated a fragment shader.

    Finally, remember that a vertex program is executed on the vertices before primitive assembly, while a fragment program is executed after rasterization. On with the tutorial.

    First, we need to create a blank file (save as “wave.cg”). We then create a structure to contain the variables and information that we want available to our shader. This code is added to the wave.cg file.
      
       

    struct appdata { float4 position : POSITION; float4 color : COLOR0; float3 wave : COLOR1;};

       
    Each of the 3 variables (position, color and wave) are bound to predefined names (POSITION, COLOR0 and COLOR1 respectively). These predefined names are referred to as the binding semantics. In OpenGL, these predefined names implicitly specify the mapping of the inputs to particular hardware registers. The main program must supply the data for each of these variables. The position variable is REQUIRED, as it is used for rasterization. It is the only variable that is required as an input to the vertex program.
    The next step is to create a structure to contain the output which will be passed on to the fragment processor after rasterization.
      
       

    struct vfconn{ float4 HPos : POSITION; float4 Col0 : COLOR0;};
       
    As with the inputs, each of the output variables is bound to a predefined name. Hpos represents the position transformed into homogenous clip-space. Col0 represents the color of the vertex after changes made to it by the vertex program.
    The only thing left to do is to write the actual vertex program, utilizing both of our newly defined structures.
      
       

    vfconn main(appdata IN, uniform float4x4 ModelViewProj){ vfconn OUT;         // Variable To Handle Our Output From The Vertex           // Shader (Goes To A Fragment Shader If Available
       
    Much as in C, we define our function as having a return type (struct vfconn), a function name (main, but can be anything we want), and the parameters. In our example, we take our struct appdata as an input (containing the current position of the vertex, the color of the vertex, and a wave value for moving the sine wave across the mesh).
    We also pass in a uniform parameter, which is the current modelview matrix from OpenGL (in our main program). This value typically does not change as we manipulate our vertices, and is therefore referred to as uniform. This matrix is required to transform the vertex position into homogenous clip-space.

    We declare a variable to hold our modified values from the vertex shader. These values are returned at the end of the function, and are passed to the fragment shader (if it exists).

    We now need to perform our modifications to the vertex data.

    .   
       

    // Change The Y Position Of The Vertex Based On Sine Waves IN.position.y = ( sin(IN.wave.x + (IN.position.z / 4.0) ) + sin(IN.wave.x + (IN.position.x / 5.0) ) ) * 2.5f;
       
    We change the Y position of the vertex depending on the current X / Z position of the vertex. The X and Z positions of the vertex are divided by 4.0 and 5.0 respectively to make them smoother (to see what I mean, change both of these values to 1.0).
    Our IN.wave variable contains an ever-increasing value which causes the sine waves to move across our mesh. This variable is specified within our main program. Therefore, we calculate the Y position of the X / Y position of the mesh as sin of the wave value + the current X or Z position. Finally, we multiple the value by 2.5 to make the waves more noticeable (higher).

    We now perform the required operations to determine the values to output to the fragment program.
      
       

    // Transform The Vertex Position Into Homogenous Clip-Space (Required) OUT.HPos = mul(ModelViewProj, IN.position);
    // Set The Color To The Value Specified In IN.color
    OUT.Col0.xyz = IN.color.xyz;

    return OUT;
    }


       
    First we transform the new vertex position into homogenous clip-space. We then set our output color to the input color, which is specified in our main program. Finally, we return our values for use by a fragment shader (if we have one).
    We’ll now move on the main program which creates a triangle mesh and runs our shader on each vertex to produce a nice wave effect.

    OpenGL tutorial:

    The main sequence of steps for dealing with our Cg shader is to generate our mesh, load up and compile our Cg program and then run this program on each vertex as it is being drawn.

    First we must get some of the necessary setup details out of the way. We need to include the necessary header files to run Cg shaders with OpenGL. After our other #include statements, we need to include the Cg and CgGL headers.
      
       

    #include <cg\cg.h>         // NEW: Cg Header#include <cg\cggl.h>         // NEW: Cg OpenGL Specific Header

       
    Now we should be ready to setup our project and get to work. Before we start, we need to make sure that Visual Studio can find the correct libraries. The following code will do the trick!   
       

    #pragma comment( lib, "cg.lib" )       // Search For Cg.lib While Linking#pragma comment( lib, "cggl.lib" )       // Search For CgGL.lib While Linking

       
    Next we’ll create some global variables for our mesh and for toggling the CG program on / off  
       

    #define  SIZE 64        // Defines The Size Of The X/Z Axis Of The Meshbool  cg_enable = TRUE, sp;       // Toggle Cg Program On / Off, Space Pressed?GLfloat  mesh[SIZE][SIZE][3];       // Our Static MeshGLfloat  wave_movement = 0.0f;       // Our Variable To Move The Waves Across The Mesh
       
    We define the size as 64 points on each edge of our mesh (X and Z axes). We then create an array for each vertex of our mesh. The final variable is required to make the sine waves ‘move’ across our mesh.
    We now need to define some Cg specific global variables.

      
       

     CGcontext cgContext;        // A Context To Hold Our Cg Program(s)

       
    The first variable we need is a CGcontext. A CGcontext variable is a container for multiple Cg programs. In general, you require only one CGcontext variable regardless of the number of vertex and fragment programs you have. You can select different programs from the same CGcontext using the functions cgGetFirstProgram and cgGetNextProgram.
    We next define a CGprogram variable for our vertex program.
      
       

    CGprogram cgProgram;        // Our Cg Vertex Program
       
    Our CGprogram variable is used to store our vertex program. A CGprogram is essentially a handle to our vertex (or fragment) program. This is added to our CGcontext.
    We next need to have a variable to store our vertex profile.
      
       

    CGprofile cgVertexProfile;       // The Profile To Use For Our Vertex Shader

       
    Our CGprofile defines the most suitable profile. We next need variables that provide a connection between variables in our main program and variables in our shader.  
       

    CGparameter position, color, modelViewMatrix, wave;     // The Parameters Needed For Our Shader

       
    Each CGparameter is essentially a handle to the corresponding parameter in our shader.
    Now that we’ve taken care of our global variables, it’s time to get to work on setting up our mesh and vertex program.

    In our Initialize function, before we call “return TRUE;”, we need to add our custom code.
      
       

     glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);     // Draw Our Mesh In Wireframe Mode
    // Create Our Mesh
    for (int x = 0; x < SIZE; x++)
    {
    for (int z = 0; z < SIZE; z++)
    {
    mesh[x][z][0] = (float) (SIZE / 2) - x; // We Want To Center Our Mesh Around The Origin
    mesh[x][z][1] = 0.0f; // Set The Y Values For All Points To 0
    mesh[x][z][2] = (float) (SIZE / 2) - z; // We Want To Center Our Mesh Around The Origin
    }
    }


       
    We first call glPolygonMode to change the display to wireframe (flatshaded looks awful without correct lighting). We then traverse through our mesh, setting the X and Z values around the origin. The Y value for each point is set to 0.0f. It is interesting to note that the values generated in this step at no point change during executing.
    With our mesh initialization out of the way, we’re now ready to initialize our Cg stuff.
      
       

    // Setup Cg cgContext = cgCreateContext();       // Create A New Context For Our Cg Program(s)
    // Validate Our Context Generation Was Successful
    if (cgContext == NULL)
    {
    MessageBox(NULL, "Failed To Create Cg Context", "Error", MB_OK);
    return FALSE; // We Cannot Continue
    }


       
    We first try to create a new CGcontext to store our Cg programs. If our return value is NULL, then our context creation fails. This will usually only fail due to memory allocation errors.   
       

    cgVertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);    // Get The Latest GL Vertex Profile
    // Validate Our Profile Determination Was Successful
    if (cgVertexProfile == CG_PROFILE_UNKNOWN)
    {
    MessageBox(NULL, "Invalid profile type", "Error", MB_OK);
    return FALSE; // We Cannot Continue
    }

    cgGLSetOptimalOptions(cgVertexProfile); // Set The Current Profile

       
    We now determine the last vertex profile for use. To determine the latest fragment profile, we call cgGLGetLatestProfile with the CG_GL_FRAGMENT profile type. If our return value is CG_PROFILE_UNKNOWN, there was no appropriate profile available. With a valid profile, we can set call cgGLSetOptimalOptions. This function sets compiler arguments based on available compiler arguments, GPU and driver. These functions are used each time a new Cg program is compiler. (Essentially optimizes the compilation of the shader dependent on the current graphics hardware and drivers).   
       

    // Load And Compile The Vertex Shader From File cgProgram = cgCreateProgramFromFile(cgContext, CG_SOURCE, "CG/Wave.cg", cgVertexProfile, "main", 0);
    // Validate Success
    if (cgProgram == NULL)
    {
    // We Need To Determine What Went Wrong
    CGerror Error = cgGetError();

    // Show A Message Box Explaining What Went Wrong
    MessageBox(NULL, cgGetErrorString(Error), "Error", MB_OK);
    return FALSE; // We Cannot Continue
    }


       
    We now attempt to create our program from our source file. We call cgCreateProgramFromFile, which will load and compile our Cg program from the specified file. The first parameter defines which CGcontext variable our program will be attached to. The second parameter define whether our Cg code is assumed to be in a file that contains source code (CG_SOURCE), or a file which contains the object code from a pre-compiled Cg program (CG_OBJECT). The third parameter is the name of the file containing our Cg program. The fourth parameter is the latest profile for the particular type of program (use a vertex profile for vertex programs, fragment profiles for fragment programs). The fifth parameter determines the entry function of our Cg program. This function can be arbitrarily specified, and often should be something other than “main”. The last parameter provides for additional arguments to be passed to the Cg compiler. This is often left as NULL.
    If cgCreateProgramFromFile fails for any reason, we retrieve the last error by calling cgGetError. We can then get a human-readable string of the error contained in our CGerror variable by calling cgGetErrorString.

    We’re almost finished our initialization.
      
       

    // Load The Program cgGLLoadProgram(cgProgram);
       
    The next step to do is to actually load our program, and prepare it for binding. All programs must be loaded before they can be bound to the current state  
       

     // Get Handles To Each Of Our Parameters So That // We Can Change Them At Will Within Our Code position = cgGetNamedParameter(cgProgram, "IN.position"); color  = cgGetNamedParameter(cgProgram, "IN.color"); wave  = cgGetNamedParameter(cgProgram, "IN.wave"); modelViewMatrix = cgGetNamedParameter(cgProgram, "ModelViewProj");
    return TRUE; // Return TRUE (Initialization Successful)


       
    The final step of initialization requires our program to get handles to the variables which we wish to manipulate in our Cg program. For each CGparameter we attempt to retrieve a handle to the corresponding Cg program parameter. If a parameter does not exist, cgGetNamedParameter will return NULL.
    If the parameters into the Cg program are unknown, cgGetFirstParameter and cgGetNextParameter can be used to traverse the parameters of a given CGprogram.

    We’ve finally finished with the initialization of our Cg program, so now we’ll quickly take care of cleaning up after ourselves, and then it’s on to the fun of drawing.

    In the function Deinitialize, we need clean up our Cg program(s).
      
       

    cgDestroyContext(cgContext);       // Destroy Our Cg Context And All Programs Contained Within It
       
    We simply call cgDestroyContext for each of our CGcontext variables (we can have multiple, but there’s usually only one). You can individually delete all of your CGprograms by calling cgDestoryProgram, however calling cgDestoryContext deletes all CGprograms contained by the CGcontext, and then deletes the CGcontext itself.
    Now we will add some code to our Update function. The following code checks to see if the spacebar is pressed and not held down. If space is press and not held down, we toggle cg_enable from true to false or from false to true.
      
       

    if (g_keys->keyDown [' '] && !sp) {  sp=TRUE;  cg_enable=!cg_enable; }
       
    The last bit of code checks to see if the spacebar has been released, and if so, it sets sp (space pressed?) to false.  
       

    if (!g_keys->keyDown [' '])  sp=FALSE;
       
    Now that we’ve dealt with all of that, it’s time to get down to the fun of actually drawing our mesh and running our vertex program.
    The final function we need to modify is the Draw function. We’re going to add our code after glLoadIdentity and before glFlush.
      
       

    // Position The Camera To Look At Our Mesh From A Distance gluLookAt(0.0f, 25.0f, -45.0f, 0.0f, 0.0f, 0.0f, 0, 1, 0);
       
    First, we want to move our viewpoint far enough away from the origin to view our mesh. We move the camera 25 units vertically, 45 units away from the screen, and center our focal point at the origin.  
       

    // Set The Modelview Matrix Of Our Shader To Our OpenGL Modelview Matrix cgGLSetStateMatrixParameter(modelViewMatrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
       
    The next thing we want to do is set the model view matrix of our vertex shader to the current OpenGL modelview matrix. This needs to be done, as the position which is changed in our vertex shaders needs to transform the new position into homogenous clip-space, which is done by multiplying the new position by our modelview matrix.   
       

    if (cg_enable) {  cgGLEnableProfile(cgVertexProfile);     // Enable Our Vertex Shader Profile
    // Bind Our Vertex Program To The Current State
    cgGLBindProgram(cgProgram);


       
    We then have to enable our vertex profile. cgGLEnableProfile enables a given profile by making the relevant OpenGL calls. cgGLBindProgram binds our program to the current state. This essentially activates our program, and subsequently runs our program on each vertex passed to the GPU. The same program will be run on each vertex until we disable our profile.   
       

     // Set The Drawing Color To Light Green (Can Be Changed By Shader, Etc...)  cgGLSetParameter4f(color, 0.5f, 1.0f, 0.5f, 1.0f); }
       
    Next we set the drawing color for our mesh. This value can be dynamically changed while drawing the mesh to create cool color cycling effects.
    Notice the check to see if cg_enable is true? If it is not, we do not deal with any of the Cg commands above. This prevents the CG code from running.

    We’re now ready to render our mesh!
      
       

    // Start Drawing Our Mesh for (int x = 0; x < SIZE - 1; x++) {  // Draw A Triangle Strip For Each Column Of Our Mesh  glBegin(GL_TRIANGLE_STRIP);  for (int z = 0; z < SIZE - 1; z++)  {   // Set The Wave Parameter Of Our Shader To The Incremented Wave Value From Our Main Program   cgGLSetParameter3f(wave, wave_movement, 1.0f, 1.0f);   glVertex3f(mesh[x][z][0], mesh[x][z][1], mesh[x][z][2]); // Draw Vertex   glVertex3f(mesh[x+1][z][0], mesh[x+1][z][1], mesh[x+1][z][2]); // Draw Vertex   wave_movement += 0.00001f;     // Increment Our Wave Movement   if (wave_movement > TWO_PI)     // Prevent Crashing    wave_movement = 0.0f;  }  glEnd(); }
       
    To render our mesh, we simply loop along the Z axis for each X axis (essentially we work in columns from one side of our mesh to the other). For each column, we begin a new triangle strip.
    For each vertex we render, we dynamically pass the value of our wave parameter of our vertex program. Because this value is determined by the wave_movement variable in our main program, which is incremented continuously, our sine waves appear to move across and down our mesh.

    We then pass the vertices we are currently drawing to our GPU, while the GPU will handle automatically running the our vertex program on each vertex. We slowly increment our wave_movement variable so as to get slow and smooth movement.

    If the value of wave_movement gets to high, we reset it back to 0 to prevent crashing. TWO_PI is defined at the top of the program.
      
       

    if (cg_enable)  cgGLDisableProfile(cgVertexProfile);     // Disable Our Vertex Profile
       
    Once we’ve finished our rendering, we check to see if cg_enable is true and if so, we disable our vertex profile and continue to render anything else that we wish.
    Owen Bourne

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/11/1 11:51:00
     
     GoogleAdSense射手座1984-11-30
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/5/10 15:24:43

    本主题贴数6,分页: [1]

     *树形目录 (最近20个回帖) 顶端 
    主题:  [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 4..(7311字) - 一分之千,2007年11月1日
        回复:  这些都学会 差不多就入门了 谢谢楼主了!!(40字) - emc2010,2011年12月16日
        回复:  谢谢你!没了吗?(18字) - 长风万里,2007年11月1日
        回复:  Lesson: 48 ArcBall Rotation Control, Revisi..(8041字) - 一分之千,2007年11月1日
        回复:  第四十八课[IMG]http://www.owlei.com/DancingWind/P..(4302字) - 一分之千,2007年11月1日
        回复:  Lesson: 47 Using vertex and fragment (or pi..(20272字) - 一分之千,2007年11月1日

    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    109.375ms