第二章 着色器基础

第二章 着色器基础

做色漆语言概述

变量的声明

GLSL中的基本数据类型:float double int uint bool
GLSL中的不透明类型包括采样器(sampler)图像(image)以及原子计数器(atomic counter)

变量的初始化

所有变量必须在声明的同时进行初始化。

构造函数

GLSL中支持的隐式类型

所需类型可以从这些类型隐式转换
uintint
floatint uint
doubleint uint float

除此之外的其他数值转换都需要提供显示的转换构造函数,例如

float f = 10.0
int ten = int(f);

聚合类型

在这里插入图片描述

矩阵类型需要给出两个维度的信息,例如mat4x3,其中第一个值标识列数,第二个值表示行数

如果矩阵的构造函数只穿入了一个值,表示该矩阵的对角线元素全部为这个值。
m = m a t 3 ( 4.0 ) = ( 4.0   0.0   0.0 0.0   4.0   0.0 0.0   0.0   4.0 ) m = mat3(4.0) = \begin{pmatrix} 4.0 \ 0.0 \ 0.0 \\ 0.0 \ 4.0 \ 0.0 \\ 0.0 \ 0.0 \ 4.0\\ \end{pmatrix} m=mat3(4.0)=4.0 0.0 0.00.0 4.0 0.00.0 0.0 4.0

矩阵也可以指定每一个元素的值,矩阵的指定需要遵循列住序的原则,也就是数据首先填充列,在填充行,例如:

mat M = mat3(1.0, 2.0, 3.0 ,4.0, 5.0, 6.0, 7.0, 8.0, 9.0);

或者是

vec3 col1 = vec3(1.0, 2.0, 3.0);
vec3 col2 = vec3(4.0, 5.0, 6.0);
vec3 col3 = vec3(7.0, 8.0, 9.0);

mat3 M = (col1, col2, col3);

都表示同一个矩阵
( 1.0   4.0   7.0 2.0   5.0   8.0 3.0   6.0   9.0 ) \begin{pmatrix} 1.0 \ 4.0 \ 7.0 \\ 2.0 \ 5.0 \ 8.0 \\ 3.0 \ 6.0 \ 9.0 \\ \end{pmatrix} 1.0 4.0 7.02.0 5.0 8.03.0 6.0 9.0

访问向量和矩阵中的元素

分量访问符符号描述
(x, y, z, w)与位置相关的分量
(r, g, b, a)与颜色相关的分量
(s, t, p, q)与纹理坐标相关的分量

数组

数组属于GLSL的first-class类型,所以需要构造函数

float coeff[3] = float[3](2.3, 3.1, 42.0);

其中构造函数的维度数值可以不填。
数组的长度可以通过length()方法取得。

存储限制符

类型修饰符描述
const讲一个变量定义为只读形式,如果他初始化时用的是一个编译时常量,那么他本身也会成为编译时常量
in设置这个变量为着色器阶段的输入变量
out设置这个变量为着色器阶段的输出变量
uniform设置这个变量为用户应用程序传递给着色器的数据,他对于给定的图元而言是一个常量
buffer设置应用程序共享的一块可读写的内存。这块内存也作为着色器中的存储缓存(storage buffer)使用
shared设置变量是本地工作组(local work group)中共享的。只能用于计算着色器中

获取uniform索引
uniform变量在所有可用着色器阶段是共享的,只能在应用程序中设置,着色器无法写入到uniform变量,也不能更改他的值。
要设置uniform的只需要首先获取uniform的索引:

GLint glGetUniformLocation(GLuint program, const char* name);

获取着色器程序中uniform变量name对应的索引值,如果name没有在着色器中查找到或者是一个内部变量名称,则返回-1。
除非重新链接着色器程序(调用glLinkProgram),否则对于同一个着色器变量,该返回值是不变的。

设置uniform值

void glUniform{1234}{fdi ui}(GLuint location, TYPE value);
void glUniform{1234}{fdi ui}v(GLuint location, GLsizei count, const TYPE* values);
void glUniformMatrix{234}{fd}v(GLuint location, GLsizei count, GLboolean tranpose, const GLfloat * values);
void glUniformMatrix{2x3,2x4,3x2,3x4,4x2,4x3}{fd}v(GLuint location, GLsizei count, GLboolean transpose, const GLfloat* value);

{1234}指定变量需要几个{fdi ui}类型的值,例如GLSL中vec3需要3个float变量。注意:如果一个uniform在GLSL中定义但是未使用,编译器会默认移除这个变量,如果在OpenGL端设置它就会出现错误。

流控制语句

可以使用break, continue, return, discard来终止相关层级代码的进行。
其中discard只能用于片元着色器。

函数参数限制符

包括四种限制符:in(默认),const in,out,inout

计算不变性

invariant限制符:可以确保如果两个着色器的输出变量使用了相同的表达式,且表达式中的变量也是相同值,那么计算产生的结果是相同的。对于着色器的自定义输出以及内置输出都可以使用invariant修饰:

invariant gl_position;
invariant centroid out vec3 Color;

如果需要将所有输出变量全部指定为invariance,可以使用预编译命令:

#pragma STDGL invariant(all)

设置invariant会影响性能。

precise限制符:可以设置任何计算中的变量或者是函数的返回值。通常使用在细分着色器中,防止几何体形状裂缝。如果要保证某个表达式产生的结果是一致的,即使表达式中的数据发生了变化(但是在数学上不影响结果)也是如此。

Location = a * b + c * d

如果a与b,c与d之间互换数据理论上Location的结果不发生变化,那么Location的结果就应该是相同的。
priceise限制符可以修饰内置变量用户变量或者是函数返回值:

precise gl_Position;
precise out vec3 Location;
precise vec3 subdivide(vec3 p1, vec3 p2){ ... }

预编译指令

编译器优化:#pragma optimize(on/off)
编译器调试选项:#pragma debug(on/off)

着色器扩展处理

#extension extension_name : <directive>

extension_name与glGetString(GL_EXTENSIONS)获取到的相同,如果要使用所有扩展可以使用:

#extension all : <directive>

directive可以使用的值:

命令描述
require如果无法支持给定的扩展功能,或者是被设置为all,则提示错误
enable如果无法支持给定的扩展功能,则给出警告;如果被设置为all,则提示错误
warn如果无法支持给定的扩展功能,或者在编译过程中使用了任何扩展,则给出警告
diable禁止给定的扩展(即强制编译器不提供对扩展功能的支持),或者如果设置为all则禁止所有的扩展支持,之后的代码如果涉及到这个扩展,提示警告或者错误

uniform块

uniform块的写法:

uniform b{      // 限定符可以是uniform、in、out或则是buffer
    vec4 v1;
    bool v2;
};              // 访问时直接使用v1、v2

或者是

uniform b{
    vec4 v1;
    bool v2;
} name;

b是外部访问时的接口名称,name是着色器代码中访问的变量。

注意:uniform块中只能包含透明类型的变量(不透明类型包括采样器、图像和原子计数器),并且必须在全局作用域中声明。

uniform块的布局控制

布局限制符描述
shared设置uniform块是多个程序之间共享的(默认值)
packed设置uniform块占有最小的内存空间,但是会禁止程序之间共享这个块
std140使用标准布局方式来设置uniform块或者是着色器储存的buffer块
std430使用标准布局方式来设置buffer块
row_major使用行主序的方式来储存uniform块中的矩阵
column_major使用列主序的方式来储存uniform块中的矩阵(默认值)

多个限制符可以使用逗号分开:

layout (shared, row_major) uniform{...};

如果要对之后的所有uniform块使用相同的限制符,可以使用:

layout (packed, column_major) uniform;

uniform块不能作为uniform变量的命名空间,也就是说多个uniform块中也不能包含同名的uniform变量,访问uniform时,不一定非要使用块的名字。

查找uniform块在着色器中的索引

GLuint glGetUniformBlockIndex(GLuint program, const char* uniformBlockName);

返回名为uniformBlockName的uniform块的索引值。如果不是一个合法uniform块则返回GL_INVALID_INDEX。

关联缓存对象与uniform块

void glBindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
void glBindBufferBase(GLenum target, GLuint index, GLuint buffer);

将缓存对象buffer与索引为index的uniform块关联起来。target可以是GL_UNIFORM_BUFFER或者GL_TRANSFORM_FEEDBACK_BUFFER。
glBindBufferBase相当于将offset设置为0,size设置为缓存对象的大小来调用glBindBufferRange。

建立关联之后就可以通过缓存命令对uniform块内的数据进行初始化。

显示指定uniform块的索引

GLint glUniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);

需要在glLinkProgram之前调用。显示将uniformBlockIndex绑定到uniformBlockBinding上。也可以在layout中指定uniformBlockBinding的值。

http://www.voidcn.com/article/p-dposszej-kv.html

获取指定名称uniform变量的索引位置

void glGetUniformIndices(GLuint program, GLsizei uniformCount, const char** uniformNames, GLuint* uniformIndices);

in/out块

作为输入输出的块必须是匹配的

着色器的编译

编译流程:

在这里插入图片描述

对于每一个着色器对象需要:

  • 创建一个着色器对象
  • 将着色器源代码编译为对象
  • 验证着色器编译是否成功

对于着色器程序:

  • 创建一个着色器程序
  • 将着色器对象关联到着色器程序
  • 链接着色器程序
  • 判断链接是否完成
  • 使用着色器程序

创建着色器对象

GLuint glCreateShader(GLenum type);

关联着色器代码

void glShaderSource(GLuint shader, GLsizei count, const GLchar** string, const GLint* length);

编译着色器对象代码

void glCompileShader(GLuint shader);

编译结果查询可以使用glGetShaderiv(GL_COMPILE_STATUS)进行确认。如果不为true,可以使用glGetShaderInfoLog获取编译日志。

获取编译日志

void glGetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei* length, char* infoLog);

创建着色器程序

GLuint glCreateProgram();

关联着色器对象

void glAttachShader(GLuint program, GLuint shader);

着色器对象可以在任何时候关联到着色器程序,但是他们的功能只有在程序链接之后才能使用。着色器对象可以关联到多个不同的着色器程序上。

解除着色器对象关联

void glDetachShader(GLuint program, GLuint shader);

移除着色器对象的关联,如果着色器对象被标记为删除的对象(调用glDeleteShader()),然后被解除关联则会立即被删除。

链接着色器程序

void glLinkProgram(GLuint program);

通过glGetProgramiv()并使用GL_LINK_STATUS来查看是否链接成功。

获取日志信息

void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei* length, char* infoLog);

使用着色器程序

void glUseProgram(GLuint program);

如果已经启用了一个程序,而它需要关联新的着色器对象,或者解除之前关联的对象,那么需要对它重新链接。如果链接成功,那么新的程序会直接替代之前启动的程序。如果链接失败,那么当前绑定的着色器程序依然是可用的,不会被替代,知道链接成功或者使用glUseProgram指定了新的程序为止。

删除着色器对象

void glDeleteShader(GLuint shader);

删除着色器对象shader。如果shader已经链接到一个或多个激活的程序上,那么将其标记为可删除,当它不再使用时就会自动删除。

删除着色器程序

void glDeleteProgram(GLuint program);

立即删除一个没有被使用的着色器程序,如果程序正被使用,那么等到它空闲时再删除。

着色器对象是否存在

GLboolean glIsShader(GLuint shader);

着色器程序是否存在

GLboolean glIsProgram(GLuint program);

着色器子程序

设置子程序池

  • 通过关键字subroutine来定义子程序的类型:

      subroutine returnType subroutineType(type param, ...);
    
  • 定义子程序集合的内容:

      subroutine (subroutineType) returnType functionName(...);
    
  • 指定一个子程序uniform变量,保存子程序的选择信息:

      subroutine uniform subroutineType variableName;
    

子程序并不只能属于一个子程序类型,可以为同一子程序函数指定多个子程序类型。

获取子程序uniform位置

GLint glGetSubroutineUniformLocation(GLuint program, GLenum shadertype, const char* name);

如果name不是一个激活的子程序则返回-1, 如果program不是一个可用的着色器程序,则生成一个GL_INVALID_OPERATION错误。

获取子程序的索引号

GLuint glGetSubroutineIndex(GLuint program, GLenum shaderType, const char* name);

如果name不是shaderType的一个活动子程序,返回GL_INVALID_INDEX。

设置当前使用的子程序执行函数

GLuint glUniformSubroutinesuiv(GLenum shaderType, GLsizei count, const GLuint* incides);

使用示例:

GLint matShaderLoc;
GLuint ambientIndex;
GLuint diffIndex;

glUseProgram(program);
matShaderLoc = glGetSubroutineUniformLocation(program, GL_VERTEX_SHADER, "matShader");
if (matShaderLoc < 0){
    // error
} 

ambientIndex = glGetSubroutineIndex(program, GL_VERTEX_SHADER, "ambient");
diffIndex = glGetSubroutineIndex(program, GL_VERTEX_SHADER, "diffuse");

if (ambientIndex == GL_INVALID_INDEX || diffIndex = GL_INVALID_INDEX)
{
    //error
}
else
{
    GLsizei n;
    glGetIntegerv(GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS, &n);
    GLuint *indices = new GLuint[n];
    indices[matShaderLoc] = ambientIndex;
    glUniformSubroutinesuiv(GL_VERTEX_SHADER, indices);
    delete [] indices;
}

独立的着色器对象

使用glUseProgram只能指定一个当前使用的着色器程序,因此不能够在链接之后动态指定不同阶段使用的着色器对象。

第一步 创建用于着色器管线的着色器程序

有两种方式:

  • 通过glCreateProgram创建一个着色器程序,之后设定GL_PROGRAM_SEPARABLE为true

      glProgramParameter(program, GL_PROGRAM_SEPARABLE, GL_TRUE);
    
  • 使用glCreateShaderProgramv创建独立的着色器程序

      GLuint glCreateShaderProgramv(GLenum type, GLsizei count, const char** strings);
    

注意4.4之前,transform feedback不能够作为pre-link直接设置到shader当中

https://www.khronos.org/opengl/wiki/Shader_Compilation#Separate_programs

第二步 使用独立的着色器程序

通过着色器管线来关联着色器程序,管线对象的创建、绑定、删除函数分别是glGenProgramPipelines(),glBindProgramPipeline(),glDeleteProgramPipeline().

注意:glUseProgram()会覆盖管线对象,所以要保证调用glUseProgram(0)

绑定一个管线对象之后使用glUseProgramStages()将之前标记为独立的程序对象关联到管线上。

void glUseProgramStages(GLuint pipeline, GLbitfield stages, GLuint program);

设置着色器uniform的方式有两种,第一种是利用glActiveShaderProgram()选择一个活动的着色器程序,之后利用glUniform*()进行设置,这种方式不推荐。第二种方式是调用glProgramUniform*()进行设置:

void glProgramUniform{1234}{fdi ui}(GLuint program, GLint location, TYPE value);
void glProgramUniform{1234}{fdi ui}v(GLuint program, GLint location, GLsizei count, const TYPE* values);
void glProgramUniformMatrix{234}{fd}v(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* values);
void glProgramUniformMatrix(2x3,2x4,3x2,3x4,4x2,4x3){fd}v(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* values);

其中program可以不是当前绑定的程序。

示例1:

// Create two separable program objects from a 
// single source string respectively (vertSrc and fragSrc)
GLuint vertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertSrc);
GLuint fragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragSrc);

// CHECK FOR ERRORS HERE!.

// Generate a program pipeline and attach the programs to their respective stages
glGenProgramPipelines(1, &pipeline);
glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, vertProg);
glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, fragProg);

// Query and set any uniforms
GLint colorLoc = glGetUniformLocation(fragProg, "Color");
glProgramUniform4f(fragProg, colorLoc, 1.f, 0.f, 0.f, 1.f);

示例2:

// Create two programs. One with just the vertex shader, and 
// one with both geometry and fragment stages.
GLuint vertexProgram   = glCreateProgram();
GLuint geomFragProgram = glCreateProgram();

// Declare that programs are separable - this is crucial!
glProgramParameteri(vertexProgram, GL_PROGRAM_SEPARABLE, GL_TRUE);
glProgramParameteri(geomFragProgram, GL_PROGRAM_SEPARABLE, GL_TRUE);

// Generate and compile shader objects, as normal.
GLuint vertShader  = glCreateShader(GL_VERTEX_SHADER);
GLuint geomShader  = glCreateShader(GL_GEOMETRY_SHADER);
GLuint fragShader  = glCreateShader(GL_FRAGMENT_SHADER);

glShaderSource(vertShader, 1, &vertSrc, NULL);
glShaderSource(geomShader, 1, &geomSrc, NULL);
glShaderSource(fragShader, 1, &fragSrc, NULL);

glCompileShader(vertShader);
glCompileShader(geomShader);
glCompileShader(fragShader);

// Attach the shaders to their respective programs
glAttachShader(vertexProgram, vertShader);
glAttachShader(geomFragProgram, geomShader);
glAttachShader(geomFragProgram, fragShader);

// Perform any pre-linking steps.
glBindAttribLocation(vertexProgram, 0, "Position");
glBindFragDataLocation(geomFragProgram, 0, "FragColor");

// Link the programs
glLinkProgram(vertexProgram);
glLinkProgram(geomFragProgram);

// Detach and delete the shader objects
glDetachShader(vertexProgram, vertShader);
glDeleteShader(vertShader);

glDetachShader(geomFragProgram, geomShader);
glDetachShader(geomFragProgram, fragShader);
glDeleteShader(geomShader);
glDeleteShader(fragShader);

// Generate a program pipeline
glGenProgramPipelines(1, &pipeline);

// Attach the first program to the vertex stage, and the second program
// to the geometry and fragment stages
glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, vertexProgram);
glUseProgramStages(pipeline, GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, geomFragProgram);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值