注册 登录
主题 : Apple's OpenGL——Vertex Shader基础
级别: 天使

状态: 连续签到 - [7天]
UID: 5513
精华: 19
发帖: 90659
可可豆: 922081 CB
威望: 921946 点
在线时间: 5508(时)
注册时间: 2009-05-06
最后登录: 2019-09-16
0 楼:  发表于: 2010-10-25 15:53    发自: Web Page

Apple's OpenGL——Vertex Shader基础    (在iOS代码库中浏览本帖)

前言:
Apple's OpenGL系列与“深入了解OpenGL”系列分开,意在想把一些基础性的东西能说明得更透彻。“深入了解OpenGL”系列主要讲述OpenGL基础性架构以及基于OpenGL的图形处理流水线和一些常用的绘图方法。
“Apple's OpenGL”系列将讲解更现代化的OpenGL使用方式——利用目前现代GPU的可编程单元对OpenGL流水线的某些阶段以编程的方式进行实现。
OpenGL3.3以及更高版本将OpenGL本来与顶点着色器(Vertex Shader)和片断着色器(Fragment Shader)所对应的固定功能流水线部分的接口全都剔除掉了,取而代之的是这些着色器配置相关的接口。
OpenGL3.3又添加了几何着色器(Geometry Shader)。
目前最新的OpenGL4.1又添加了Tessellation(细分曲面),这里又有几个着色器被添加进去。
OpenGL在最近两年里发展迅猛,一下子就从3.3跳到了4.1。
我们目前所使用的OpenGL Shading Language的版本是1.20。


好。下面我们将切入正题。之前在这个贴中描述了如何创建创建着色器,并且对它们进行编译、构建、连接,以及如何把着色器源代码加入到应用的资源中。
这里,我们将正式对着色器进行讲解。
顶点着色器取代的是OpenGL固定功能流水线中对每个顶点的变换阶段。这部分的操作包括:顶点变换(投影变换、视图模型变换)、法线变换与规格化、纹理坐标生成、纹理坐标变换、光照、颜色材质应用。
下面看一个简单的Vertex Shader的代码:

//
//  Shader.vsh
//  GLSLTest
//
//  Created by Zenny Chen on 4/11/10.
//  Copyright GreenGames Studio 2010. All rights reserved.
//


attributevec4position;
attributevec4color;
varying vec4 colorVarying;


uniform mat4 translate;


void main()
{
gl_Position = position * translate; //mat4(0.816, 0.433, 0.0, 0.0,  -0.5, 0.616, 0.0, 0.0,  0.0, 0.0, -0.5, 0.0,  0.0, 0.0, -0.5, 1.0);
    colorVarying = color;
}



最上面两个attribute变量的数据是从主机端输入。attribute在后期Shader Language版本中被废弃,取而代之的是in关键字。主机端通过以下接口把相关数据与vertex shader所对应的attribute变量进行绑定:
复制代码
  1. void glBindAttribLocation( GLuint   program,
  2.   GLuint   index,
  3.   const GLchar *  name);


program:你已经构建好的shader程序
index:自己定义的一个id,与下面的name进行绑定
name:指定顶点着色器中所要绑定的变量名
在我们的主机端程序中,与上述position变量进行绑定的代码为:
// attribute index
enum{
ATTRIB_VERTEX,
ATTRIB_COLOR,
NUM_ATTRIBUTES
};



glBindAttribLocation(program, ATTRIB_VERTEX, "position");



与color绑定的代码为:
glBindAttribLocation(program, ATTRIB_COLOR, "color");



这里要注意的是,顶点着色器是可以与主机端应用进行直接交互的。而片断着色器则不能与主机端程序进行直接交互。因此,我们可以把片断着色器所要用到的数据先通过顶点着色器输入进来,然后通过varying属性传给片断着色器(Fragment Shader)。注意,在以后版本中varying关键字也被废弃。取而代之的是在顶点着色器部分用out关键字,而在片断着色器中相应地使用in关键字。
因此,attribute不仅仅是指顶点属性,也可以是颜色属性或用户自定义的一些变量,而我们用主机端的数据与这些attribute变量绑定时,都用glBindAttribLocation这一个接口。
而我们通过以下接口把主机端的数据发送给Shader端:
复制代码
  1. void glVertexAttribPointer( GLuint   index,
  2.   GLint   size,
  3.   GLenum   type,
  4.   GLboolean   normalized,
  5.   GLsizei   stride,
  6.   const GLvoid *   pointer);

index:我们刚才所指定的attribute变量所绑定的id。
size:指定每个元素有多少分量,可以是1,2,3,4
type:指定数据类型
normalized:指定定点数据是否被规格化
stride:相邻两个元素之间的跨度(字节数)
pointer:指向数组首地址
下面将给出示例代码:
首先定义顶点坐标和颜色值:
    static const GLfloat squareVertices[] = {
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f,
        -0.5f0.5f, 0.0f,
        0.5f0.5f, 0.0f
    };

    static const GLubyte squareColors[] = {
        255, 255,   0, 255,
        0,   255, 255, 255,
        0,     0,   0,   0,
        255,   0, 255, 255,
    };



然后,下面就是分别传送顶点坐标数据和颜色数据:
// Update attribute values
    glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, 0, 0, squareVertices);
glEnableVertexAttribArray(ATTRIB_VERTEX);
    glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, 1, 0, squareColors);
glEnableVertexAttribArray(ATTRIB_COLOR);



下面附赠完整的工程。

OpenGL3_2.zip包是OS X Lion(10.7)下,对OpenGL 3.2 Core Profile的基本使用样例。
[ 此帖被zenny_chen在2013-04-11 21:04重新编辑 ]

附件: OpenGLShaderBasic.zip (34 K) 下载次数:532
附件: OpenGL3_2.zip (1269 K) 下载次数:105
新浪围脖地址:http://t.sina.com.cn/1181389417
CPU Dasher for OS X: https://itunes.apple.com/cn/app/cpu-dasher/id1013487510?mt=12

级别: 精灵王
UID: 27247
精华: 0
发帖: 773
可可豆: 5911 CB
威望: 5856 点
在线时间: 2267(时)
注册时间: 2010-08-10
最后登录: 2017-04-26
1 楼:  发表于: 2010-10-25 16:19    发自: Web Page
占板凳~终于等到了~哈~谢谢zenny大
应该早点放出来,上次没教程,害我自己研究了好久才懂。。。
级别: 圣骑士

UID: 2406
精华: 1
发帖: 139
可可豆: 8145 CB
威望: 8145 点
在线时间: 1406(时)
注册时间: 2009-02-25
最后登录: 2015-08-25
2 楼:  发表于: 2010-10-25 17:25    发自: Web Page
很好呀. 呵呵.
The greatest test of courage on earth is to bear defeat without losing heart.
级别: 天使

状态: 连续签到 - [7天]
UID: 5513
精华: 19
发帖: 90659
可可豆: 922081 CB
威望: 921946 点
在线时间: 5508(时)
注册时间: 2009-05-06
最后登录: 2019-09-16
3 楼:  发表于: 2010-10-25 18:00    发自: Web Page
下面谈谈uniform变量。
uniform变量与attribute类似,都是通过主机端传递到shader,而且它们都不能在Shader端被修改。但是,attribute变量数据依赖于glDrawArray等绘制函数所提供的顶点个数,并且类型不能是矩阵类型,而uniform变量类型可以非常灵活,你甚至能通过uniform block定义自己的数据结构。并且uniform变量能够在Vertex Shader端与Fragment Shader端共享,这点,attribute则做不到。attribute只能把数据传给varying变量才能在Fragment Shader端使用。

要使用uniform变量,首先在主机端对shader中所用的uniform变量进行绑定,通过以下接口:
复制代码
  1. GLint glGetUniformLocation(    GLuint      program,
  2.      const GLchar *     name);

这里,name就是uniform变量在shader中的标识符。
返回的是指定的uniform变量在shader中的索引,可以认为是一个id。
在本示例程序中,对我们定义的uniform mat4 translate;变量所绑定的代码如下:
复制代码
  1. // uniform index
  2. enum {
  3.     UNIFORM_TRANSLATE,
  4.     NUM_UNIFORMS
  5. };
  6. GLint uniforms[NUM_UNIFORMS];
  7.     // get uniform locations
  8.     uniforms[UNIFORM_TRANSLATE] = glGetUniformLocation(program, "translate");

我们然后可以通过glUniform接口把指定的数据传输到shader的uniform变量中。由于glUniform的变体非常多,这里就不一一列出,详细可参考OpenGL官网的Reference Page。
这里,将主机端的数据传递到shader的uniform的代码是:
复制代码
  1.     GLfloat transformMatrix[4 * 4] = {
  2.         1.0f, 0.0f, 0.0f, 0.0f,
  3.         0.0f, 1.0f, 0.0f, 0.0f,
  4.         0.0f, 0.0f, 1.0f, 0.0f,
  5.         0.0f, 0.0f, 0.0f, 1.0f
  6.     };
  7.     // Update uniform value
  8.     glUniformMatrix4fv(uniforms[UNIFORM_TRANSLATE], 1, GL_FALSE, transformMatrix);




另外要强调一点的是,glUniformMatrix4fv是默认将数组以列向量的形式存放在矩阵中的,即对于四个向量——(a, b, c, d);(e, f, g, h);(i, j, k, l);(m, n, o, p),存放在一个数组中,构成——{ a, b, c, d,  e, f, g, h,  i, j, k, l,  m, n, o, p },那么通过调用glUniformMatrix4fv函数,并且第三个参数传递为GL_FALSE,那么获得的矩阵其实是:[a, e, i, m; b, f, j, n; c, g, k, o; d, h, l, p]。如果要将数组中的元素作为行向量传递到uniform的矩阵中,那么第三个形参transpose应该传给它GL_TRUE值。
[ 此帖被zenny_chen在2011-02-07 14:27重新编辑 ]

新浪围脖地址:http://t.sina.com.cn/1181389417
CPU Dasher for OS X: https://itunes.apple.com/cn/app/cpu-dasher/id1013487510?mt=12

级别: 天使

状态: 连续签到 - [7天]
UID: 5513
精华: 19
发帖: 90659
可可豆: 922081 CB
威望: 921946 点
在线时间: 5508(时)
注册时间: 2009-05-06
最后登录: 2019-09-16
4 楼:  发表于: 2010-10-25 19:45    发自: Web Page
上面要注意的是glVertexAttribPointer接口只指定attribute属性变量每个元素的长度和类型,但并没有指定元素个数。
元素个数都是通过glDrawArrays这样的绘制接口传递的。因此如果是自己自定义的attribute变量必须注意顶点个数,元素个数必须与顶点个数挂钩。

以上程序,我们是事先计算好变换矩阵,然后在顶点着色器中直接与顶点坐标值相乘。下面将提供实时计算坐标顶点的方法。



首先我们把vertex shader改成如下形式:

//
//  Shader.vsh
//  GLSLTest
//
//  Created by Zenny Chen on 4/11/10.
//  Copyright GreenGames Studio 2010. All rights reserved.
//


attribute vec4 position;
attribute vec4 color;
varying vec4 colorVarying;
uniform mat4 translate;
uniform mat4 projection;
uniform float degree;


mat4 rotation = mat4(
                    0.0, 0.0, 0.0, 0.0,
                    0.0, 0.0, 0.0, 0.0,
                    0.0, 0.0, 1.0, 0.0,
                    0.0, 0.0, 0.0, 1.0
                    );


void main()
{
    float radian = radians(degree);
//gl_Position = position * translate; //mat4(0.816, 0.433, 0.0, 0.0,  -0.5, 0.616, 0.0, 0.0,  0.0, 0.0, -0.5, 0.0,  0.0, 0.0, -0.5, 1.0);

    mat4 transMatrix = translate;

    rotation[0][0] = cos(radian);
    rotation[0][1] = -sin(radian);
    rotation[1][0] = sin(radian);
    rotation[1][1] = cos(radian);

    transMatrix = rotation * transMatrix;
    transMatrix = projection * transMatrix;

    gl_Position = position * transMatrix;

    colorVarying = color;
}



我们可以看到,这里增加了两个uniform变量,一个用于投影变换,一个用于输入角度。另外,这里又增加了一个shader普通的全局变量rotation,并且为其初始化。
在main()函数中,我们先将输入的角度换成弧度,然后按照模型视图变换-》投影变换这一次序构成变换矩阵。最后用顶点坐标与变换矩阵相乘,获得变换后的顶点坐标值,输出给内建的gl_Position变量,这个内建属性将会被传递给光栅化阶段进行使用。


而在代码中,我们需要为添加的uniform变量进行绑定:
// uniform index
enum {
UNIFORM_TRANSLATE,
UNIFORM_PROJECTION,
UNIFORM_DEGREE,
NUM_UNIFORMS
};
GLint uniforms[NUM_UNIFORMS];



在方法中,在- (BOOL) loadShaders原来的地方添加:
// get uniform locations
uniforms[UNIFORM_TRANSLATE] = glGetUniformLocation(program, "translate");
uniforms[UNIFORM_PROJECTION] = glGetUniformLocation(program, "projection");
uniforms[UNIFORM_DEGREE] = glGetUniformLocation(program, "degree");



然后,我们再添加一个定时器相应方法:
static GLfloat degree = 0.0f;


- (void)timerFireMethod:(NSTimer*)theTimer
{
    degree += 1.0f;
    if(degree >= 360.0f)
        degree = 0.0f;

    [self setNeedsDisplay:YES];
}



接着,在- (void)prepareOpenGL方法中绑定新的uniform变量:
#if 0
    my_transform(transformMatrix);

// Update uniform value
glUniformMatrix4fv(uniforms[UNIFORM_TRANSLATE], 1, GL_FALSE, transformMatrix);

#else
    GLfloat translation[4 * 4] = {
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
// x,  y,    z,    w
0.0f, 0.0f, -2.0f, 1.0f
    };

    GLfloat projection[4 * 4] = {
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
        0.0f, 0.0f, -0.5f, 0.0f,
0.0f, 0.0f, -1.5f, 1.0f
    };

    glUniformMatrix4fv(uniforms[UNIFORM_TRANSLATE], 1, GL_FALSE, translation);
    glUniformMatrix4fv(uniforms[UNIFORM_PROJECTION], 1, GL_FALSE, projection);

#endif



由于角度是在每个单位时间内变化的,因此我们在- (void)drawRect:(NSRect)dirtyRect方法中修改degree变量:
- (void)drawRect:(NSRect)dirtyRect {

glClear(GL_COLOR_BUFFER_BIT);

glUniform1f(uniforms[UNIFORM_DEGREE], degree);

// Draw
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

glFlush();

}
于是,我们就能看到一个旋转的正方形。
[ 此帖被zenny_chen在2010-10-25 21:15重新编辑 ]

新浪围脖地址:http://t.sina.com.cn/1181389417
CPU Dasher for OS X: https://itunes.apple.com/cn/app/cpu-dasher/id1013487510?mt=12

级别: 天使

状态: 连续签到 - [7天]
UID: 5513
精华: 19
发帖: 90659
可可豆: 922081 CB
威望: 921946 点
在线时间: 5508(时)
注册时间: 2009-05-06
最后登录: 2019-09-16
5 楼:  发表于: 2010-10-26 10:20    发自: Web Page
引用
引用第1楼borisyue于2010-10-25 16:19发表的  :
占板凳~终于等到了~哈~谢谢zenny大
应该早点放出来,上次没教程,害我自己研究了好久才懂。。。


呵呵,没办法。前一阵子做公司的一个大项目,俺亲自负责设计和实现,所以没空整理。
现在项目做完了,并且取得巨大成功,所以最近一阵比较轻松,呵呵。所以把这个补完。

新浪围脖地址:http://t.sina.com.cn/1181389417
CPU Dasher for OS X: https://itunes.apple.com/cn/app/cpu-dasher/id1013487510?mt=12

级别: 精灵王
UID: 27247
精华: 0
发帖: 773
可可豆: 5911 CB
威望: 5856 点
在线时间: 2267(时)
注册时间: 2010-08-10
最后登录: 2017-04-26
6 楼:  发表于: 2010-10-26 10:34    发自: Web Page
回 5楼(zenny_chen) 的帖子
什么项目啊?透露一下嘛,咱瞧瞧Zenny做的项目是嘛样儿?有什么难度有什么好玩儿的?
不方便透露也就算喽,理解理解。
级别: 天使

状态: 连续签到 - [7天]
UID: 5513
精华: 19
发帖: 90659
可可豆: 922081 CB
威望: 921946 点
在线时间: 5508(时)
注册时间: 2009-05-06
最后登录: 2019-09-16
7 楼:  发表于: 2010-10-26 11:31    发自: Web Page
Re:回 5楼(zenny_chen) 的帖子
引用
引用第6楼borisyue于2010-10-26 10:34发表的 回 5楼(zenny_chen) 的帖子 :
什么项目啊?透露一下嘛,咱瞧瞧Zenny做的项目是嘛样儿?有什么难度有什么好玩儿的?
不方便透露也就算喽,理解理解。  


哦,是关于ARM连接器的,呵呵。

新浪围脖地址:http://t.sina.com.cn/1181389417
CPU Dasher for OS X: https://itunes.apple.com/cn/app/cpu-dasher/id1013487510?mt=12

级别: 天使

状态: 连续签到 - [7天]
UID: 5513
精华: 19
发帖: 90659
可可豆: 922081 CB
威望: 921946 点
在线时间: 5508(时)
注册时间: 2009-05-06
最后登录: 2019-09-16
8 楼:  发表于: 2010-10-28 02:10    发自: Web Page
最后把着色器的编程模型再强调一下。
Shader与OpenCL的编程模型类似,都是采用SPMD(Single Program Multiple Data,即单程序多数据)的方式执行的。也就是说同一个程序对多个数据进行操作。
就拿上面4楼的顶点着色器而言,一个Shader.vsh源代码中的程序main()对我们在主机端定义的正方形的每个顶点进行执行。我们看到一个attribute变量position,你可以认为你的GPU有足够多的核心,使得对每个传进来的position(即顶点坐标值)调用同一个main函数,并且并发执行。这样,每个attribute变量(以后版本是in变量)、varying变量(以后版本是out变量)还有我们自定义的全局数据rotation都是每个顶点私有的。你可以认为rotation变量为每个顶点的操作分配相应的空间并做相同的初始化。而只有uniform变量是所有顶点共享的,它们的值对于每个顶点而言都是相同的——因为在做顶点变换操作时,uniform变量不能被修改,而它只能在主机端,在调glDrawArray等绘制接口前被修改。
当然,对于某些GPU的实现而言可能会与上述描述的过程有些出入,但是概念上用SPMD来理解更方便些。
新浪围脖地址:http://t.sina.com.cn/1181389417
CPU Dasher for OS X: https://itunes.apple.com/cn/app/cpu-dasher/id1013487510?mt=12

级别: 天使

状态: 连续签到 - [7天]
UID: 5513
精华: 19
发帖: 90659
可可豆: 922081 CB
威望: 921946 点
在线时间: 5508(时)
注册时间: 2009-05-06
最后登录: 2019-09-16
9 楼:  发表于: 2010-10-29 01:44    发自: Web Page
我们看到在默认情况下,顶点的着色模型是GL_SMOOTH。在OpenGL2.1中尚能用glShadeMode()来设定着色模式,但是在iOS的OpenGL ES2.0中就不能设了。这个时候,我们可以通过采用单色来填充多边形:


    glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, 0, 0, squareVertices);
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    //glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, 1, 0, squareColors);
    //glEnableVertexAttribArray(ATTRIB_COLOR);
    glVertexAttrib4f(ATTRIB_COLOR, 1.0f, 0.0f, 0.0f, 1.0f);

我们看到上述代码,将原来的颜色数组换成了单个颜色值(红色)。由于此颜色将作用于所有顶点,这样,我们就能看到一个红色的矩形了。
新浪围脖地址:http://t.sina.com.cn/1181389417
CPU Dasher for OS X: https://itunes.apple.com/cn/app/cpu-dasher/id1013487510?mt=12

CocoaChina社区转载内容已尽可能注明出处,如未能核实来源或转发内容图片有权利瑕疵的,请及时联系社区进行修改或删除【联系方式QQ : 3442093904 邮箱:support@cocoachina.com】文章内容为作者独立观点,不代表CocoaChina社区立场。版权归原作者所有,如申请授权请联系作者,因文章侵权CocoaChina社区不承担任何法律及连带责任。

描述
快速回复

关注本帖(如果有新回复会站内信通知您)

发帖、回帖都会得到可观的积分奖励。查看论坛积分规则

按"Ctrl+Enter"直接提交
    顶部