以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 C/C++编程思想 』  (http://bbs.xml.org.cn/list.asp?boardid=61)
----  [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 47-lesson 48【本教程全部传完】  (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=54730)


--  作者:一分之千
--  发布时间:11/1/2007 11:50:00 AM

--  [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 47-lesson 48【本教程全部传完】

第四十七课  四十八课源码


第四十七课


按此在新窗口浏览图片CG 顶点脚本

nVidio的面向GPU的C语言,如果你相信它就好好学学吧,同样这里也只是个入门。记住,类似的语言还有微软的HLSL,OpenGL的GLSL,ATI的shaderMonker。不要选错哦:)

  
   
   
使用顶点和片断脚本去做渲染工作可以得到额外的好处,最大的好处就是把CPU的一些工作交给了GPU,Cg提供了书写这些强大的脚本的一种手段。
这篇教程有许多目的,第一向你展现了一个非常简单的顶点脚本,第二向你说明如何在OpenGL中使用Cg编写的脚本。

这个教程是基于最新的NeHeGL的基本代码,为了获得更多的信息,你可以访问nVidia的官方网站(developer.nvidia.com),它会给你一个完整的答案。

注意:这个教程不是叫你如何去写一个完整的Cg脚本,而是教你在OpenGL中载入并运行脚本。

开始:
第一步,从nVidia的网站上下载Cg Compiler库,最好去下载1.1版本的,因为nvidia各个版本的变化很大,为了让程序不出现任何问题,最好这样做,因为我们用的是1.1版本的。

下一步,包含编译需要的头文件和库文件。


  
   
   
我已经帮你把它们拷贝到了工程的文件夹里了。

Cg介绍
你必须有以下几个概念:
1、顶点脚本会作用于你输入的每一个顶点,如果你想要作用于一些顶点,那么你必须在作用前加载顶点脚本,并于作用后释放顶点脚本。
2、顶点脚本输出的结果被送入到片断处理器中,你不用管这其中是如何实现的。
最后,记住顶点脚本在图元装配前被执行,片断脚本在光栅化后被执行。

好了,现在我们创建一个空白的文件吧(保存为wave.cg),接着我们创建一个数据结构,它被我们得脚本使用。下面的代码被加入到wave.cg文件中。
  
   

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

   
上面的结果说明,我们输入的顶点包含一个位置坐标,一个颜色和我们自定义的波的颜色
下面的代码定义一个输出顶点的数据,包括一个顶点和颜色
  
   

struct vfconn{ float4 HPos : POSITION; float4 Col0 : COLOR0;};
   
下面的代码是Cg的主函数,每个顶点都会被以下函数执行:

  
   

vfconn main(appdata IN, uniform float4x4 ModelViewProj)
{
vfconn OUT; // 保存我们输出顶点的数据

// 计算顶点y的坐标
IN.position.y = ( sin(IN.wave.x + (IN.position.x / 5.0) ) + sin(IN.wave.x + (IN.position.z / 4.0) ) ) * 2.5f;

// 保存到输出数据中
OUT.HPos = mul(ModelViewProj, IN.position);

// 不改变输入的颜色
OUT.Col0.xyz = IN.color.xyz;

return OUT;
}

   
完成了上面的代码,记得保存一下.

下面我们到了程序中,首先包含使用cg需要的头文件,和库文件
  
   

#include <cg\cg.h>         #include <cg\cggl.h>         
#pragma comment( lib, "cg.lib" )       #pragma comment( lib, "cggl.lib" )       

   
下面我们定义一些全局变量,用来计算我们得网格和控制cg程序的开关  
   

#define  SIZE 64        // 定义网格的大小bool  cg_enable = TRUE, sp;       // 开关Cg程序GLfloat  mesh[SIZE][SIZE][3];       // 保存我们的网格GLfloat  wave_movement = 0.0f;       // 记录波动的移动
   
下面我们来定义一些cg相关的全局变量
  
   

 CGcontext cgContext;        // 用来保存cg脚本

   
我们需要的第一个变量是CGcontext,这个变量是多个Cg脚本的容器,一般来说,你获得你可以用函数从这个容器中获得你想要的脚本
接下来我们定义一个CGprogram变量,它用来保存我们得顶点脚本
  
   

CGprogram cgProgram;        // 我们得顶点脚本
   
接下来我们需要一个变量来设置如何编译这个顶点脚本  
   

CGprofile cgVertexProfile;       // 被顶点脚本使用

   
下面我们需要一些参数用来把Cg脚本使用的数据从程序中传送过去。  
   

CGparameter position, color, modelViewMatrix, wave;     // 脚本中需要的参数

   
在初始化阶段我们先要创建我们网格数据  
   

 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);     
for (int x = 0; x < SIZE; x++)
{
for (int z = 0; z < SIZE; z++)
{
mesh[x][z][0] = (float) (SIZE / 2) - x;
mesh[x][z][1] = 0.0f;
mesh[x][z][2] = (float) (SIZE / 2) - z;
}
}


   
我们设置多边形的现实模式为线框图,接着遍历没有顶点,设置其高度。
接下来,我们初始化Cg程序


  
   

// 设置Cg cgContext = cgCreateContext();       // 创建一个Cg容器
// 测试是否创建成功
if (cgContext == NULL)
{
MessageBox(NULL, "Failed To Create Cg Context", "Error", MB_OK);
return FALSE;
}


   
我们创建一个Cg程序的容器,并检查它是否创建成功  
   

cgVertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);    // 配置在OpenGL中使用顶点缓存
// 检测Cg程序的是否创建成功
if (cgVertexProfile == CG_PROFILE_UNKNOWN)
{
MessageBox(NULL, "Invalid profile type", "Error", MB_OK);
return FALSE;
}

cgGLSetOptimalOptions(cgVertexProfile); // 启用配置文件

   
如果你想使用片断脚本,使用CG_GL_FRAGMENT变量。如果返回的变量为CG_PROFILE_UNKNOW表示没有可用的配置文件,则不能编译你需要的Cg程序。  
   

// 从文件中载入Cg程序 cgProgram = cgCreateProgramFromFile(cgContext, CG_SOURCE, "CG/Wave.cg", cgVertexProfile, "main", 0);
// 检测是否成功
if (cgProgram == NULL)
{
CGerror Error = cgGetError();
MessageBox(NULL, cgGetErrorString(Error), "Error", MB_OK);
return FALSE;
}


   
我们尝试从源文件中创建一个Cg程序,并返回编译后的结果。   
   

// 载入脚本 cgGLLoadProgram(cgProgram);
   
下面我们把顶点脚本载入到显存,并准备帮定给GPU。所有的脚本在使用前必须加载。  
   

 // 把数据变量地址发送给Cg程序 position = cgGetNamedParameter(cgProgram, "IN.position"); color  = cgGetNamedParameter(cgProgram, "IN.color"); wave  = cgGetNamedParameter(cgProgram, "IN.wave"); modelViewMatrix = cgGetNamedParameter(cgProgram, "ModelViewProj");
return TRUE;


   
在初始化的最后,我们必须告诉Cg脚本在那里去获得输入的数据,我们需要把变量的指针传递过去,下面的函数完成了这个功能。
程序结束时,记得释放我们创建的内容。
  
   

cgDestroyContext(cgContext);       
   
下面的代码使用空格切换是否使用Cg程序  
   

if (g_keys->keyDown [' '] && !sp) {  sp=TRUE;  cg_enable=!cg_enable; }
if (!g_keys->keyDown [' '])  sp=FALSE;
   
现在我们已经完成了所有的准备工作了,到了我们实际绘制网格的地方了,按照惯例我们还是先设置我们得视口。

  
   

 gluLookAt(0.0f, 25.0f, -45.0f, 0.0f, 0.0f, 0.0f, 0, 1, 0);
// 把当前Cg程序的模型变化矩阵告诉当前程序 cgGLSetStateMatrixParameter(modelViewMatrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
   
上面我们要做的事就是把当前Cg程序的模型变化矩阵告诉当前程序。

结下来如果使用cg程序,则把顶点的颜色设置为绿色
  
   

 // 如果使用Cg程序 if (cg_enable) {  // 使用顶点脚本配置文件  cgGLEnableProfile(cgVertexProfile);          // 帮定到当前的顶点脚本  cgGLBindProgram(cgProgram);  // 设置绘制颜色  cgGLSetParameter4f(color, 0.5f, 1.0f, 0.5f, 1.0f); }
   
下面我们来绘制我们的网格。   
   

 // 开始绘制我们的网格 for (int x = 0; x < SIZE - 1; x++) {  glBegin(GL_TRIANGLE_STRIP);  for (int z = 0; z < SIZE - 1; z++)  {   // 设置Wave参数   cgGLSetParameter3f(wave, wave_movement, 1.0f, 1.0f);   //设置输入的顶点   glVertex3f(mesh[x][z][0], mesh[x][z][1], mesh[x][z][2]);     glVertex3f(mesh[x+1][z][0], mesh[x+1][z][1], mesh[x+1][z][2]);    wave_movement += 0.00001f;            if (wave_movement > TWO_PI)             wave_movement = 0.0f;  }  //经过Cg程序处理,进行绘制  glEnd(); }
   
上面的代码完成具体的绘制操作,对于每一个顶点,我们动态的传入波动系数和输入原始的顶点数据。在绘制开始前,顶点脚本接受所有的顶点数据并处理,接着进行光栅华操作。
别忘了在绘制完成后,关闭我们启用的顶点脚本,否则在绘制其它的模型时会让你得到不想要的结果。
  
   

if (cg_enable)  cgGLDisableProfile(cgVertexProfile);     // 禁用顶点脚本配置文件
   
好了上面就是所有的内容了,简单吧.Cg就是这么简单



--  作者:一分之千
--  发布时间:11/1/2007 11:51:00 AM

--  
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



--  作者:一分之千
--  发布时间:11/1/2007 11:52:00 AM

--  

第四十八课

按此在新窗口浏览图片轨迹球实现的鼠标旋转

使用鼠标旋转物体,很简单也有很多实现方法,这里我们教会你模拟轨迹球来实现它.

  
   
   
轨迹球控制
By Terence J. Grant (tjgrant@tatewake.com)
如果只用鼠标来控制你的模型是不是很酷?轨迹球可以帮你做到这一点,我将告诉你我的实现,你可以把它应用在你的工程里。

我的实现是基于Bretton Wade’s,它是基于Ken Shoemake’s 实现的,最初的版本,你可以从游戏编程指南这本图上找到。但我还是修正了一些错误,并优化了它。

轨迹球实现的内容就是把二维的鼠标点映射到三维的轨迹球,并基于它完成旋转变化。

为了完成这个设想,首先我们把鼠标坐标映射到[-1,1]之间,它很简单:



  
   

MousePt.X  =  ((MousePt.X / ((Width  -1) / 2)) -1);MousePt.Y  = -((MousePt.Y / ((Height -1) / 2))-1);

   
这只是为了数学上的简化,下面我们计算这个长度,如果它大于轨迹球的边界,我们将简单的把z轴设为0,否则我们把z轴设置为这个二维点映射到球面上对应的z值。
一旦我们有了两个点,就可以计算它的法向量了和旋转角了。

下面我们从构造函数开始,完整的讲解这个类:


  
   

ArcBall_t::ArcBall_t(GLfloat NewWidth, GLfloat NewHeight)

   
当点击鼠标时,记录点击的位置   
   

void    ArcBall_t::click(const Point2fT* NewPt)
   
当拖动鼠标时,记录当前鼠标的位置,并计算出旋转的量。   
   

void ArcBall_t::drag(const Point2fT* NewPt, Quat4fT* NewRot)


   
如果窗口大小改变,设置鼠标移动的范围  
   

void    ArcBall_t::setBounds(GLfloat NewWidth, GLfloat NewHeight)
   
下面是完成计算所要用到的数据结果,都是一些矩阵和向量  
   

Matrix4fT Transform = {  1.0f,  0.0f,  0.0f,  0.0f,          0.0f,  1.0f,  0.0f,  0.0f,          0.0f,  0.0f,  1.0f,  0.0f,          0.0f,  0.0f,  0.0f,  1.0f };
Matrix3fT LastRot = { 1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f };

Matrix3fT ThisRot = { 1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f };

ArcBallT ArcBall(640.0f, 480.0f);
Point2fT MousePt;
bool isClicked = false; // 是否点击鼠标
bool isRClicked = false; // 是否右击鼠标
bool isDragging = false; // 是否拖动


   
在上面定义的变量中,transform是我们获得的最终的变换矩阵,lastRot是上一次鼠标拖动得到的旋转矩阵,thisRot为这次鼠标拖动得到的旋转矩阵。
当我们点击鼠标时,创建一个单位旋转矩阵,当我们拖动鼠标时,这个矩阵跟踪鼠标的变化。

为了更新鼠标的移动范围,我们在函数ReshapeGL中加入下面一行:


  
   

void ReshapeGL (int width, int height){ . . . ArcBall.setBounds((GLfloat)width, (GLfloat)height);    // 更新鼠标的移动范围}
// 处理鼠标的按键操作
LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
. . .
case WM_MOUSEMOVE:
MousePt.s.X = (GLfloat)LOWORD(lParam);
MousePt.s.Y = (GLfloat)HIWORD(lParam);
isClicked = (LOWORD(wParam) & MK_LBUTTON) ? true : false;
isRClicked = (LOWORD(wParam) & MK_RBUTTON) ? true : false;
break;

case WM_LBUTTONUP: isClicked = false; break;
case WM_RBUTTONUP: isRClicked = false; break;
case WM_LBUTTONDOWN: isClicked = true; break;
case WM_RBUTTONDOWN: isRClicked = true; break;
. . .
}


   
为了随着输入更新我们的的状态,在Update函数中需要处理更新参数  
   

if (isRClicked)          // 如果右键按下,这重置所有的变量{ Matrix3fSetIdentity(&LastRot);
  Matrix3fSetIdentity(&ThisRot);
  Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot);
  }
if (!isDragging) // 如果没有拖动
{
if (isClicked) // 第一次按下
{
isDragging = true; // 设置拖动为变量为true
LastRot = ThisRot;
ArcBall.click(&MousePt);
}
}
else
{
if (isClicked) //如果按住拖动
{
Quat4fT ThisQuat;

ArcBall.drag(&MousePt, &ThisQuat); // 更新轨迹球的变量
Matrix3fSetRotationFromQuat4f(&ThisRot, &ThisQuat); // 计算旋转量
Matrix3fMulMatrix3f(&ThisRot, &LastRot);
Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot);
}
else // 如果放开鼠标,设置拖动为false
isDragging = false;
}


   
好了,完成了上面的内容。我们到了绘制的阶段。
记住在绘制前,把我们得到的矩阵乘以当前的模型变换矩阵。  
   

glPushMatrix();         // 保存当前的矩阵 glMultMatrixf(Transform.M);       // 应用我们的变换矩阵
glBegin(GL_TRIANGLES); // 绘制模型
. . .
glEnd();

glPopMatrix(); // 弹出保存的矩阵

   
我已经在上面给掩饰了所有的技巧,你可以不使用我告诉你的数学技巧,因为我想你会有更好的。现在你已经看到了,这是多么的简单,你完全可以按你的风格创造出更好的轨迹球。



--  作者:一分之千
--  发布时间:11/1/2007 11:53:00 AM

--  
Lesson: 48
   
ArcBall Rotation Control, Revisited
By Terence J. Grant (tjgrant@tatewake.com)
Wouldn’t it be great to rotate your model at will, just by using the mouse? With an ArcBall you can do just that. In this document, I’ll touch on my implementation, and considerations for adding it to your own projects.

My implementation of the ArcBall class is based on Bretton Wade’s, which is based on Ken Shoemake’s from the Graphic Gems series of books. However, I did a little bug fixing and optimization for our purposes.

The ArcBall works by mapping click coordinates in a window directly into the ArcBall’s sphere coordinates, as if it were directly in front of you.

To accomplish this, first we simply scale down the mouse coordinates from the range of [0...width), [0...height) to [-1...1], [1...-1] – (Keep in mind that we flip the sign of Y so that we get the correct results in OpenGL.) And this essentially looks like:
  
   

MousePt.X  =  ((MousePt.X / ((Width  – 1) / 2)) – 1);MousePt.Y  = -((MousePt.Y / ((Height – 1) / 2)) – 1);

   
The only reason we scale the coordinates down to the range of [-1...1] is to make the math simpler; by happy coincidence this also lets the compiler do a little optimization.
Next we calculate the length of the vector and determine whether or not it’s inside or outside of the sphere bounds. If it is within the bounds of the sphere, we return a vector from within the inside the sphere, else we normalize the point and return the closest point to outside of the sphere.

Once we have both vectors, we can then calculate a vector perpendicular to the start and end vectors with an angle, which turns out to be a quaternion. With this in hand we have enough information to generate a rotation matrix from, and we’re home free.

The ArcBall is instantiated using the following constructor. NewWidth and NewHeight are essentially the width and height of the window.
  
   

ArcBall_t::ArcBall_t(GLfloat NewWidth, GLfloat NewHeight)

   
When the user clicks the mouse, the start vector is calculated based on where the click occurred.   
   

void    ArcBall_t::click(const Point2fT* NewPt)
   
When the user drags the mouse, the end vector is updated via the drag method, and if a quaternion output parameter is provided, this is updated with the resultant rotation.   
   

void ArcBall_t::drag(const Point2fT* NewPt, Quat4fT* NewRot)


   
If the window size changes, we simply update the ArcBall with that information:  
   

void    ArcBall_t::setBounds(GLfloat NewWidth, GLfloat NewHeight)
   
When using this in your own project, you’ll want some member variables of your own.  
   

// Final TransformMatrix4fT Transform = {  1.0f,  0.0f,  0.0f,  0.0f,          0.0f,  1.0f,  0.0f,  0.0f,          0.0f,  0.0f,  1.0f,  0.0f,          0.0f,  0.0f,  0.0f,  1.0f };
Matrix3fT LastRot = { 1.0f, 0.0f, 0.0f, // Last Rotation
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f };

Matrix3fT ThisRot = { 1.0f, 0.0f, 0.0f, // This Rotation
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f };

ArcBallT ArcBall(640.0f, 480.0f); // ArcBall Instance
Point2fT MousePt; // Current Mouse Point
bool isClicked = false; // Clicking The Mouse?
bool isRClicked = false; // Clicking The Right Mouse Button?
bool isDragging = false; // Dragging The Mouse?


   
Transform is our final transform- our rotation and any optional translation you may want to provide. LastRot is the last rotation we experienced at the end of a drag. ThisRot is the rotation we experience while dragging. All are initialized to identity.
When we click, we start from an identity rotation state. When we drag, we are then calculating the rotation from the initial click point to the drag point. Even though we use this information to rotate the objects on screen, it is important to note that we are not actually rotating the ArcBall itself. Therefore to have cumulative rotations, we must handle this ourselves.

This is where LastRot and ThisRot come into play. LastRot can be defined as “all rotations up till now”, whereas ThisRot can be defined by “the current rotation.” Every time a drag is started, ThisRot is modified by the original rotation. It is then updated to the product of itself * LastRot. (Then the final transformation is also updated.) Once a drag is stopped, LastRot is then assigned the value of ThisRot.

If we didn’t accumulate the rotations ourselves, the model would appear to snap to origin each time that we clicked. For instance if we rotate around the X-axis 90 degrees, then 45 degrees, we would want to see 135 degrees of rotation, not just the last 45.

For the rest of the variables (except for isDragged), all you need to do is update them at the proper times based on your system. ArcBall needs its bounds reset whenever your window size changes. MousePt gets updated whenever your mouse moves, or just when the mouse button is down. isClicked / isRClicked whenever the left/right mouse button is clicked, respectively. isClicked is used to determine clicks and drags. We’ll use isRClicked to reset all rotations to identity.

The additional system update code under NeHeGL/Windows looks something like this:
  
   

void ReshapeGL (int width, int height){ . . . ArcBall.setBounds((GLfloat)width, (GLfloat)height);    // Update Mouse Bounds For ArcBall}
// Process Window Message Callbacks
LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
. . .
// Mouse Based Messages For ArcBall
case WM_MOUSEMOVE:
MousePt.s.X = (GLfloat)LOWORD(lParam);
MousePt.s.Y = (GLfloat)HIWORD(lParam);
isClicked = (LOWORD(wParam) & MK_LBUTTON) ? true : false;
isRClicked = (LOWORD(wParam) & MK_RBUTTON) ? true : false;
break;

case WM_LBUTTONUP: isClicked = false; break;
case WM_RBUTTONUP: isRClicked = false; break;
case WM_LBUTTONDOWN: isClicked = true; break;
case WM_RBUTTONDOWN: isRClicked = true; break;
. . .
}


   
Once we have the system update code in place, its time to put the click logic in place. This is very self-explanatory once you know everything above.  
   

if (isRClicked)          // If Right Mouse Clicked, Reset All Rotations{ // Reset Rotation Matrix3fSetIdentity(&LastRot);
// Reset Rotation
Matrix3fSetIdentity(&ThisRot);

// Reset Rotation
Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot);
}

if (!isDragging) // Not Dragging
{
if (isClicked) // First Click
{
isDragging = true; // Prepare For Dragging
LastRot = ThisRot; // Set Last Static Rotation To Last Dynamic One
ArcBall.click(&MousePt); // Update Start Vector And Prepare For Dragging
}
}
else
{
if (isClicked) //Still clicked, so still dragging
{
Quat4fT ThisQuat;

ArcBall.drag(&MousePt, &ThisQuat); // Update End Vector And Get Rotation As Quaternion
Matrix3fSetRotationFromQuat4f(&ThisRot, &ThisQuat); // Convert Quaternion Into Matrix3fT
Matrix3fMulMatrix3f(&ThisRot, &LastRot); // Accumulate Last Rotation Into This One
Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot); // Set Our Final Transform's Rotation From This One
}
else // No Longer Dragging
isDragging = false;
}


   
This takes care of everything for us. Now all we need to do is apply the transformation to our models and we’re done. It’s really simple:   
   

glPushMatrix();         // Prepare Dynamic Transform glMultMatrixf(Transform.M);       // Apply Dynamic Transform
glBegin(GL_TRIANGLES); // Start Drawing Model
. . .
glEnd(); // Done Drawing Model

glPopMatrix(); // Unapply Dynamic Transform

   
I have included a sample, which demonstrates everything above. You’re not locked in to using my math types or functions; in fact I would suggest fitting this in to your own math system if you’re confident enough. However, everything is self-contained otherwise and should work on its own.
Now after seeing how simple this is, you should be well on your way to adding ArcBall to your own projects. Enjoy!

Terence J. Grant



--  作者:长风万里
--  发布时间:11/1/2007 7:06:00 PM

--  
谢谢你!
没了吗?
--  作者:emc2010
--  发布时间:12/16/2011 4:21:00 PM

--  
这些都学会 差不多就入门了 谢谢楼主了!!
W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
187.500ms