|
|
E文地址:http://www.lighthouse3d.com/opengl/glsl/
进度:未完成. 版本:07.8.8
最近看书时抱着锻炼E文的目地随便翻译了下来.由于是第一次干这种事.希望大家不要打击我打击得太厉害.
未作任何校对以及专业知识的贫乏,所以大家还是尽量看原文比较好..........
GLSL Tutorial
翻译:Mao!
导言
GL Shader Language将覆盖本文章内所有Shader教程.作为3D游戏的一个热门话题,着色技术如果善加利用,将可以获得非常显著的画面效果.而本篇文章旨在引领读者进入着色技术的世界.
假如你对此非常认真严肃,那么我推荐你首先看看OpenGL 2.0 与 GLSL 的官方规格文档.另外一点,作为学习此篇指南的基础,你必须能够熟练的使用OpenGL进行3D图形编程.
缩写GLSL代表了Graphics Library Shading Language,由非盈利性质的OpenGL官方组织ARB进行定义管理.
我将不会把GLSL与CG进行对比.因为Nvidia关于着色语言的建议与OpenGL是几乎一致的.而唯一选择GLSL非CG的理由便是,GLSL更加的接近OpenGL.
首先,对于学写任何的着色语言之前,了解图形管线的基础总是必要的.它将给你一个写着色语言的背景知识.着色语言可以干什么,干得好什么,不能干什么等等.
然后,关于使用GLSL的OpenGL细节问题将会被讨论.随后就展示一个OpenGL程序如何传递着色,以使其的使用更加灵活和强大.
最后,由于此篇文章同时包含了ARB扩展和OpenGL2.0的一些内容.我会使用不同的颜色来帮助读者区别他们.ARB扩展使用绿色,OpenGL2.0使用橙色
由于这篇文章仍处于不断的编写之中,所以错误必然存在.如果你发现了,不管是多么微小的,请尽快让我知道,谢谢.
希望你能够享受此篇教程.
/////////////////////////////////一.图形管线部分/////////////////////////////
管线总观
下面是关于数据于管线各阶段中传递的简图.虽然简单,但它会指出着色编程一些重要概念.此片段是关于固定编程管线的.注意了,这只是一个抽象的过程,所以并不一定符合任意特定执行的所有步骤.

顶点转换(Vertex Transformation)
在这里传入顶点包含一系列的属性集合比如位置,颜色,法线,纹理坐标等等.此阶段的输入是单独的顶点属性.一些通过固定管线在此阶段实现的操作包括:
- 顶点坐标转换
- 顶点光照计算
- 产生并转换纹理坐标
图元装配和光栅化(Primitive Assembly and Rasterization)
此阶段传入已经变换的顶点,以及连接信息.连接信息将告诉管线如何连接顶点使其成为一个图元.图元在这里被装配.
此阶段同时依靠视锥体负责Clipping操作和背面剔除.
光栅确定片段和象素的原始位置.这里的片段是一块用于更新特定位置帧缓存象素数据的数据.当然一个片段并不仅仅包含颜色而已,还包括了法线,纹理坐标以及其他一些用于计算新象素颜色的属性.
阶段输出则分为两个部分:
- 片段于帧缓存中的位置
- 在顶点转换的阶段以插值的方式用于每一片段的属性计算
值在顶点转换阶段计算之后,可以与顶点连接信息结合来为片段的属性进行计算.
举例来将:每一顶点都有个转换之后的坐标.考虑到图元是由顶点构成,那么计算图元片段的位置是有可能的.
另一个例子是颜色的用法,假如一个三角形的顶点拥有不同的颜色,那么此颜色片段将会以插值方式得到三角形的顶点颜色加权,相对以顶点距离,决定该片段颜色.
逐个片段的纹理与着色(Fragment Texturing and Coloring)
以内插值替换的片段信息将会在此阶段输入.由于颜色和纹理坐标早已在上个阶段通过插值求得,所以在这里就可以用来与纹理元素相结合.同时,雾的处理也是在此阶段.综合输出的结果变是逐个片段的颜色与深度值.
光栅操作(Raster Operations)
此阶段需要输入这些内容:
- 象素坐标
- 片段深度与颜色值
作为图形管线的最后一个阶段.这里将会完成一系列的片段测试:
- 模版测试
- 深度测试
- Alpha测试
- 截取测试
假如测试成功,片段信息就将通过当前的混合方式用于更新象素.注意一点,混合只可能发生于这一阶段,因为片段纹理和颜色不具备访问帧缓存的权限.帧缓存只可在这一阶段中被访问.
固定管线功能图象摘要(Visual Summary of the Fixed Functionality)
现在给你一个形象的描述,请看下图.

替代固定管线功能
近来,显卡给了程序员们一个定义以下两个阶段功能的权利:
- 顶点着色 可于 顶点变换 阶段被写入
- 片段着色 可替代 片段的纹理与着色 阶段功能
在下一个章节.由于可编程阶段的加入,顶点处理与片段处理功能将被我们所描述.
顶点处理器
顶点处理器为运行中的着色处理程序负责.顶点数据输入顶点着色,也就是它的位置\颜色\法线等等依靠OpenGL程序发送的东西.
下面的OpenGL程序将会把拥有颜色位置信息的顶点传送给顶点处理器.
glBengin(…);
glColor3f( 0.2f, 0.4f, 0.6f);
glVertex3f( -1.0f, 1.0f, 2.0f);
glColor3f( 0.2f, 0.4f, 0.8f);
glVertex3f( 1.0f, -1.0f, 2.0f);
glEnd();
在顶点着色中,你可以分配以下的任务:
- 片段着色 可替代 片段的纹理与着色 阶段功能
- 使用模型和投影矩阵进行顶点位置变换.
- 法线变换
- 纹理坐标生成与变换
- 逐顶点或逐象素的光照计算
- 颜色计算
完成上面的操作没有任何必要的东西,你的程序也许不会在实例中使用光照,但是!一旦你写了一个顶点着色程序,你是替代了整个顶点处理器的功能.因此你就不能对法线进行变换,或是期望固定管线可以进行纹理坐标生成. 当一个顶点着色程序被使用,此阶段所有功能都将被替代.
和你早先看到的一个章节一样,顶点处理器没有任何的连接信息.因此,一些需要拓扑信息的操作不能被完成.举个例:想在顶点着色程序里实现背面剔除便是不可能的.
顶点着色程序需要最少的一个变量:gl_Position,大抵在模型和投影矩阵中变换顶点.
因为顶点处理器是能够查询OpenGL状态,所以它可以使用材质完成光照等操作.他同样也可以访问纹理(只在最近几年的显卡上).但没有访问帧缓存的能力.
片段处理器
片段着色程序运行于片段处理器上,这个单元为以下的操作负责:
- 逐象素的计算颜色,纹理坐标.
- 纹理运用
- 雾计算
- 假如你想采用逐象素光照,那么计算法线
此单元接受上一个单元插值计算出的一些顶点坐标\颜色\法线等等信息.
顶点着色程序为每一个顶点计算值.现在我们要涉及片段内部的基本图元了.
假如你写了一个片段着色程序在顶点处理器用以替换所有的固定管线功能.那么要给予一个片段以纹理或是想实现固定管线上的雾功能都将是不可能的.再重复一次,程序员需要替代的是整个单元功能.
片段处理器操作于单个片段上.没有任何片段的相临信息.着色程序有权访问OpenGL状态,就跟顶点着色一样.因此可以访问一个OpenGL程序实例中雾指定点的颜色.
片段着色中比较重要的一点是它不可以改变前一个管线阶段计算出的象素坐标.回想一下.在顶点处理器中,模型和投影矩阵可以用于变换顶点.在那之后视点就会发挥作用.但在片段处理器里,却是在那之前.片段着色程序访问象素位置,却不可以改变.
一个片段着色可以选择两个输出方式:
- 干脆放弃片段,那么就不会输出任何东西
- 当渲染多个对象时计算gl_FragColor(片段的最终颜色),或是gl_FragData.
虽然那在上一个单元已经计算了,深度也可以被写入.
注意,片段着色程序也没有访问帧缓存的权利.这意味着比如混合之类的操作只能在片段着色程序后运行.
//////////////////////////////二.在OpenGL中配置GLSL//////////////////////////
概括
在这个章节,我们假定你已经写好了顶点着色和片段着色器,并且想在OpenGL程序中使用他们.如果你并没有真正准备好写,那么就到Internet上随便下一段.
设置你的程序跟写一段C代码非常相似.但必须分开编译.
由于这里使用了ARB扩展和OpenGL2.0.所以假如你刚刚接触扩展或是一直使用OpenGL1.1以下版本(比如微软的实现版本).那么我建议你看一看关于GLEW的文章.GLEW简化了扩展和包含了OpenGL新版本内容与扩展的使用.
下面两个扩展是必须的:
GL_ARB_fragment_shader
GL_ARB_vertex_shader
下面是使用GLUT的测试是否支持扩展的小程序:
#include <glew.h>
#include <glut.h>
void main (int argc, char **argv)
{
glutInit( &argc, argv );
……
glewInit();
if( GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader )
printf(“Rendy for GLSL \n”);
else
{
printf(“Not totally ready \n”);
exit(1);
}
setShaders();
glutMainLoop();
}
测试是否支持OpenGL 2.0:
#include <GL/glew.h>
#include <GL/glut.h>
void main(int argc, char **argv)
{
glutInit(&argc, argv);
...
glewInit();
if (glewIsSupported("GL_VERSION_2_0"))
printf("Ready for OpenGL 2.0\n");
else
{
printf("OpenGL 2.0 not supported\n");
exit(1);
}
setShaders();
glutMainLoop();
}
下面显示了在OpenGL2.0中使用着色程序的必要步骤,详细的函数将会在随后介绍.

创造一个着色器
下面是创建一个着色器的必要步骤:
首先是创建一个扮演着色器容器的物件.这个函数将会返回指向容器的一个句柄.
ARB扩展的语法:
GLhandleARB glCreateShaderObjectARB(Glenum shaderType
参数:
shaderType ? GL_VERTEX_SHADER or GL_FRAGMENT_SHADER
OpenGL2.0的语法:
Gluint glCreateShader(Glenum shaderType);
参数:
shaderType ? GL_VERTEX_SHADER or GL_FRAGMENT_SHADER
你可以创造任何数量的着色器,只要你想.但是只能有一个main函数用于设置顶点或片段着色器在每一段着色程序里.
下面的步骤用于增加源代码.着色器的源代码是以流数组形式传入的.
ARB扩展版本:
void glShaderSourceARB(GLhandleARB shader, int numOfStrings,
const char **strings, int *lenOfStrings);
参数:
shader ? 着色器的句柄
numOfString ? 数组里面流的数量
strings ? 流数组
lenOfString ? 保存着每一个流的长度的数组,或者NULL,表示流在NULL时终止.
OpenGL2.0版本:
void glShaderSource( Gluint shader, int numOfString,
const char **strings, int *lenOfString);
参数:
shader ? 着色器的句柄
numOfString ? 数组里面流的数量
strings ? 流数组
lenOfString ? 保存着每一个流的长度的数组,或者NULL,表示流在NULL时终止.
最后,着色器必须被编译.
ARB扩展版本:
void glCompileShaderARB(GLhandleARB shader);
参数:
shader ? 着色器的句柄
OpenGL2.0版本
glCompileShader(Gluint shader);
参数:
shader ? 着色器的句柄
创建一个着色程序
下面显示了准备和运行一个着色程序的必要步骤:

第一步是创建一个程序容器.
此函数返回一个容器的句柄.
ARB扩展版本
GLhandleARB glCreateProgramObjectARB(void);
OpenGL2.0版本
Gluint glCreateProgram(void);
你可以创建任何数量的着色程序,只要你想.每次渲染,你可以选择不同的着色程序,甚至在某一帧使用固定管线.比如,你可能会想使用折射和反射光着色器来渲染一个茶壶,又可能想只使用固定管线来绘制一个正方体.
下一步配置于上一步创建好的着色程序.着色器并不需要在此时进行编译,它们甚至没有源代码.所有必须的只是把一个着色器匹配给一个着色程序.
ARB扩展版本
void glAttachObjectARB(GLhandleARB program, GLhandleARB shader);
参数:
program ? 着色程序的句柄
shader ? 着色器的句柄
OpenGL 2.0版本
glAttachShader(Gluint program, Gluint shader);
参数:
program ? 着色程序的句柄
shader ? 着色器的句柄
假如你有一对顶点与片段着色器,你必须依次调用上面的函数将他们与着色程序进行匹配.你可以拥有去多同样的着色器匹配给同样的着色程序,就像C语言可以拥有许多模块一样.但对于每一种着色器只能有一个着色器作为main函数,也跟C一样.
你可以将一个着色器匹配给多个着色程序,比如当你想给几个着色程序使用相同的顶点着色器的时候.
最后一步是连接着色程序.
ARB扩展版本:
void glLinkProgramARB(GLhandleARB program);
函数:
program ? 着色程序的句柄
OpenGL2.0版本:
void glLinkProgram(Gluint program);
函数:
program ? 着色程序的句柄
当连接操作完成之后,着色器的代码将会被优化.
如同下面你所看到的一样,有一个函数用于负载和调用着色程序.你可以连接很多着色程序,并使用此函数运用任何你想使用的着色程序.
ARB扩展版本:
void glUseProgramObjectARB(GLhandleARB prog);
参数:
prog ? 任何你想要使用的着色程序的句柄,或者是0以返回固定管线
OpenGL2.0版本:
void glUseProgram(Gluint prog);
参数:
prog ? 任何你想要使用的着色程序的句柄,或者是0以返回固定管线
假如一个着色程序被使用,它会被再次连接.它同样会自动的又一次使用.所以你不必多次调用函数.
源码
下面的代码包含了前面几个章节描述的所有步骤.
ARB扩展版本:
void setShaders()
{
char *vs,*fs;
v = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
f = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
vs = textFileRead("toon.vert");
fs = textFileRead("toon.frag");
const char * vv = vs;
const char * ff = fs;
glShaderSourceARB(v, 1, &vv,NULL);
glShaderSourceARB(f, 1, &ff,NULL);
free(vs);free(fs);
glCompileShaderARB(v);
glCompileShaderARB(f);
p = glCreateProgramObjectARB();
glAttachObjectARB(p,v);
glAttachObjectARB(p,f);
glLinkProgramARB(p);
glUseProgramObjectARB(p);
}
OpenGL 2.0版本:
void setShaders()
{
char *vs,*fs;
v = glCreateShader(GL_VERTEX_SHADER);
f = glCreateShader(GL_FRAGMENT_SHADER);
vs = textFileRead("toon.vert");
fs = textFileRead("toon.frag");
const char * vv = vs;
const char * ff = fs;
glShaderSource(v, 1, &vv,NULL);
glShaderSource(f, 1, &ff,NULL);
free(vs);free(fs);
glCompileShader(v);
glCompileShader(f);
p = glCreateProgram();
glAttachShader(p,v);
glAttachShader(p,f);
glLinkProgram(p);
glUseProgram(p);
}
|
|