OpenGLES demo - 2. Shader

原创文章,转载请注明连接 http://blog.csdn.net/hoytgm/article/details/25532263

Shader中文译作着色器。OpenGLES 1.x采用的是固定管线,所以不需要大家写shader程序,但是从OpenGLES 2.0开始,shader全面替换了固定管线,所以即使是画一个非常简单的三角形,也需要编写完整的shader程序。OpenGLES 2.0和3.0只有vertex shader和fragment shader,分别用于顶点变换和像素着色,vertex好理解,就是顶点,fragment可以理解为带了颜色、深度等信息的像素。OpenGLES 3.1还新增了geometry shader。我们这里先只讲前两种,并且在我们的demo中加入一个函数,用于以后方便的载入shader。


简单的讲,shader有三种带前缀的变量,前缀词为attribute,uniform和varying。

Attribute是定义在shader内部,但是从外部传入,也就是用户的OpenGLES程序来传入值。Attribute是vertex shader独有的,所以不能定义在fragment shader中,并且每个vertex shader执行的时候,都会采用不同的attribute。比如我需要画3个点,那么每个点都需要一个attribute。举个例,我们画一个三角形,三角形每个点要x,y,z信息的话,那么我们需要提供的attribute就应该是一个数组[x1, y1, z1, x2, y2, z2, x3, y3, z3]。将这个数组传入shader,并且配置好参数,那么shader执行第一次的时候,attribute就是x1,y1,z1,第二次执行的时候就是x2,y2,z2。Attribute可以理解为每个顶点的属性,所以可以将位置,颜色等信息都作为attribute传入shader。

Uniform在vertex shader和fragment shader中都可以使用,它可以认为是一个全局变量,与attribute不同的是,uniform在每次shader执行的时候是不变的。我们可以把mvp(model view projection)以uniform传入shader,这样每个顶点都会使用同样的mvp来做变换。

Varying可以看做是局部变量,它不能由外部传入shader,只能在shader内部使用。一般用法可以在vertex shader中赋值,然后fragment shader中使用。这样就相当于完成了vertex shader 到fragment shader的数据传输。


Shader程序还需要定义每个变量的精度,精度也有三个前缀,highp,mediump,lowp,分别对应高精度,中等精度和低精度。高精度不用说,精度更高,这样得到的数据更准确。一般shader中的高精度会采用32位浮点,但是高精度带来的问题就是执行效率的下降。低精度虽然数据不够准确,但是执行比高精度快。所以用什么精度,还是需要具体情况分析。

精度的定义有两种方式,一种是针对每个变量,比如

attribute highp vec4 aPosition;

这种精度定义,需要对每个变量都加,也可以使用全局的精度定义

precision mediump float;


下面我们写一个简单的vertex shader和fragment shader。vertex shader只负责传递输入进来的顶点,fragment shader只显示一个固定的颜色。

vertex shader:

"precision mediump float;\n"
"attribute vec4 aPosition;\n"
"void main() {\n"
"    gl_Position = aPosition;\n"
"}\n";

fragment shader:

"precision mediump float;\n"
"void main() {\n"
"    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
"}\n";
这里,两个shader程序都用双引号包围着,因为在程序中,我们需要以字符串的形式传入。


有了shader程序,OpenGLES要怎么识别呢?需要两个过程,1,编译shader程序,这样决定了你写的vertex shader 或fragment shader 程序是否合法,包括语法是否正确,有没有占用关键词等等。2,Link过程,将vertex shader和fragment作为一个整体结合起来形成一个程序中可以使用的program,link过程将检查vertex shader和fragment shader中公用的变量是否命名相同等等。

我们先实现一个函数,用来编译shader程序。glShaderSource用于将shader程序的字符串传给OpenGLES,然后调用glCompileShader编译传入的shader。为了知道shader编译是否成功,我们可以调用glGetShaderiv并传入GL_COMPILE_STATUS来获取编译状态。

-(BOOL)CompileShader:(char*)shadersource shader:(GLuint)shader
{
    glShaderSource(shader, 1, (const char**)&shadersource, NULL);
    glCompileShader(shader);
    
    GLint compiled = 0;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    
    if(!compiled)
    {
        int length = MAX_SIZE;
        char log[MAX_SIZE] = {0};
        
        glGetShaderInfoLog(shader, length, &length, log);
        NSLog(@"Shader compile failed");
        NSLog(@"log: %@", [NSString stringWithUTF8String:log]);
        
        return NO;
    }
    
    return YES;
}

我们再完成一个函数用于传入vertex shader,fragment shader和program句柄的指针,函数则把完成后的program返回给我们。

-(BOOL)LoadShaders:(char*)vssource fragsource:(char*)fssource program:(GLuint*)prog
{
    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
    GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
    
    if (!prog)
    {
        return NO;
    }
    
    if (!(vs && fs))
    {
        NSLog(@"Create Shader failed");
        return NO;
    }
    
    if (![self CompileShader:vssource shader:vs])
    {
        return NO;
    }
    
    if (![self CompileShader:fssource shader:fs])
    {
        return NO;
    }
    
    *prog = glCreateProgram();
    
    if (!(*prog))
    {
        NSLog(@"Create program failed");
        return NO;
    }
    
    glAttachShader(*prog, vs);
    glAttachShader(*prog, fs);
    glLinkProgram(*prog);
    
    GLint linked = 0;
    glGetProgramiv(*prog, GL_LINK_STATUS, &linked);
    
    if(!linked)
    {
        NSLog(@"Link program failed");
        return NO;
    }
    
    return YES;
}
这里我们可以看到,我们先调用了两次glCreateShader来分别产生vertex shader和fragment shader的句柄,然后调用我们刚写好的compile函数来编译,编译完成后,我们需要将两个shader和program关联,这里就是调用glAttachShader来完成的。接着我们调用glLinkProgram,则完成了两个shader之间的关联,最后调用glGetProgramiv来检查状态。

好了,shader有了,program也创建成功了,我们要怎么使用呢?

首先,我们需要在代码中去获取到shader中的变量,以便我们传递数据。上面的shader中只有vertex shader有一个attribute变量叫做aPosition,我们去拿到这个变量的句柄是通过glGetAttribLocation实现的,同时,我们还需要激活这个句柄。

    aLocPos = glGetAttribLocation(program, "aPosition");
    glEnableVertexAttribArray(aLocPos);

接着,我们先定义三个点。在OpenGLES中,一个屏幕,左下角的坐标x,y为(-1,-1),右上角为(1,1)。因为坐标表示为x,y,z,w,也就是四维的,所以我们的顶点定义为

    float vertices[] =
    {
        -0.8f, -0.8f, 0.5f, 1.0f,
        0.8f, -0.8f, 0.5f, 1.0f,
        0.0f, 0.8f, 0.5f, 1.0f,
    };

然后我们通过glVertexAttribPointer来将顶点数据传给shader中的aPosition。这里讲解一下参数,第一个是aPosition的句柄,刚刚获取的,第二个参数4是指一个顶点有四个元素,也就是x,y,z,w。那么我们可以传1,2,3,4个,都是可选的,如果我们只传x,y,z,则w默认为1.0。如果只传x,y,则z默认为0。第三个参数是指我们传的数据是浮点型。后面两个参数以后再详细说明,最后一个就是顶点数据的指针啦。

glVertexAttribPointer(aLocPos, 4, GL_FLOAT, 0, 0, vertices);

好了,一切准备就绪,调用draw来画吧!

glDrawArrays(GL_TRIANGLES, 0, 3);

这里讲的是画的类型是三角形列表,从第0个顶点开始,画3个顶点。


最后的效果!代码已上传,可以去我的资源下载, 地址 http://download.csdn.net/detail/hoytgm/7531961 。有问题请联系我~~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值