OpenGL(3)超级宝典笔记——GLSL语言基础

2024-03-01 02:38

本文主要是介绍OpenGL(3)超级宝典笔记——GLSL语言基础,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

版权声明


本文转载自: OpenGL超级宝典笔记——GLSL语言基础

重点标注(与 C 语言不同的地方)


1.变量名不能以 gl_ 作为前缀,而且还有一些 GLSL 保留的名称是不能作为变量名称的;
2.新增向量基本类型与矩阵基本类型,命名规则:C 语言基本类型第一个字母 + vec + 长度说明,或者是 mat + 矩阵长x宽,但是这里面似乎没有看到 long、unsigned、char、short、double、long double;
3.GLSL 只能使用一维数组,不存在指针、取地址的情况,变量目前只看到形似的局部变量,没有看到堆上、全局、静态类型的变量;
4.操作符方面禁用所有位运算操作符、求模运算符及两者的复合赋值运算符;
5.切记 OpenGL 的矩阵是主列存储的,和 DirectX 以及大学线代学习的行存储矩阵有本质不同!因此将转置记做^,根据(AB)^ = B^A^,所以矩阵运算时,对于行存储中一个 m * n 的矩阵乘以一个 n * q 的矩阵必会得到 m * q 的矩阵,在 OpenGL 中的运算就会变成如下:

6.函数不允许递归。

变量


GLSL的变量命名方式与C语言类似。变量的名称可以使用字母,数字以及下划线,但变量名不能以数字开头,还有变量名不能以gl_作为前缀,这个是GLSL保留的前缀,用于GLSL的内部变量。当然还有一些GLSL保留的名称是不能够作为变量的名称的。

基本类型


除了布尔型,整型,浮点型基本类型外,GLSL还引入了一些在着色器中经常用到的类型作为基本类型。这些基本类型都可以作为结构体内部的类型。如下表:
类型描述
void跟C语言的void类似,表示空类型。作为函数的返回类型,表示这个函数不返回值。
bool布尔类型,可以是true 和false,以及可以产生布尔型的表达式。
int整型 代表至少包含16位的有符号的整数。可以是十进制的,十六进制的,八进制的。
float浮点型
bvec2包含2个布尔成分的向量
bvec3包含3个布尔成分的向量
bvec4包含4个布尔成分的向量
ivec2包含2个整型成分的向量
ivec3包含3个整型成分的向量
ivec4包含4个整型成分的向量
mat2 或者 mat2x22x2的浮点数矩阵类型
mat3或者mat3x33x3的浮点数矩阵类型
mat4x44x4的浮点矩阵
mat2x32列3行的浮点矩阵(OpenGL的矩阵是列主顺序的)
mat2x42列4行的浮点矩阵
mat3x23列2行的浮点矩阵
mat3x43列4行的浮点矩阵
mat4x24列2行的浮点矩阵
mat4x34列3行的浮点矩阵
sampler1D用于内建的纹理函数中引用指定的1D纹理的句柄。只可以作为一致变量或者函数参数使用
sampler2D二维纹理句柄
sampler3D三维纹理句柄
samplerCubecube map纹理句柄
sampler1DShadow一维深度纹理句柄
sampler2DShadow二维深度纹理句柄


结构体


结构体可以组合基本类型和数组来形成用户自定义的类型。在定义一个结构体的同时,你可以定义一个结构体实例。或者后面再定义。

struct surface {float indexOfRefraction;vec3 color;float turbulence;} mySurface;surface secondeSurface;

你可以通过=为结构体赋值,或者使用 ==,!=来判断两个结构体是否相等。

mySurface = secondSurface;

mySurface == secondSurface;

只有结构体中的每个成分都相等,那么这两个结构体才是相等的。访问结构体的内部成员使用. 来访问。

vec3 color = mySurface.color + secondSurface.color;

结构体至少包含一个成员。固定大小的数组也可以被包含在结构体中。GLSL的结构体不支持嵌套定义。只有预先声明的结构体可以嵌套其中。

struct myStruct {vec3 points[3]; //固定大小的数组是合法的surface surf;  //可以,之前已经定义了struct velocity {  //不合法float speed;vec3 direction;} velo;subSurface sub; //不合法,没有预先声明;};struct subSurface {  int id;
};


数组


GLSL中只可以使用一维的数组。数组的类型可以是一切基本类型或者结构体。下面的几种数组声明是合法的:

surface mySurfaces[];
vec4 lightPositions[8];
vec4 lightPos[] = lightPositions;const int numSurfaces = 5;
surface myFiveSurfaces[numSurfaces];float[5] values;

指定显示大小的数组可以作为函数的参数或者使返回值,也可以作为结构体的成员.数组类型内建了一个length()函数,可以返回数组的长度。

lightPositions.length() //返回数组的大小 8

最后,你不能定义数组的数组。


修饰符


变量的声明可以使用如下的修饰符。

修饰符描述
const常量值必须在声明是初始化。它是只读的不可修改的。
attribute表示只读的顶点数据,只用在顶点着色器中。数据来自当前的顶点状态或者顶点数组。它必须是全局范围声明的,不能再函数内部。一个attribute可以是浮点数类型的标量,向量,或者矩阵。不可以是数组或则结构体
uniform一致变量。在着色器执行期间一致变量的值是不变的。与const常量不同的是,这个值在编译时期是未知的是由着色器外部初始化的。一致变量在顶点着色器和片段着色器之间是共享的。它也只能在全局范围进行声明。
varying顶点着色器的输出。例如颜色或者纹理坐标,(插值后的数据)作为片段着色器的只读输入数据。必须是全局范围声明的全局变量。可以是浮点数类型的标量,向量,矩阵。不能是数组或者结构体。
centorid varying在没有多重采样的情况下,与varying是一样的意思。在多重采样时,centorid varying在光栅化的图形内部进行求值而不是在片段中心的固定位置求值。
invariant(不变量)用于表示顶点着色器的输出和任何匹配片段着色器的输入,在不同的着色器中计算产生的值必须是一致的。所有的数据流和控制流,写入一个invariant变量的是一致的。编译器为了保证结果是完全一致的,需要放弃那些可能会导致不一致值的潜在的优化。除非必要,不要使用这个修饰符。在多通道渲染中避免z-fighting可能会使用到。
in用在函数的参数中,表示这个参数是输入的,在函数中改变这个值,并不会影响对调用的函数产生副作用。(相当于C语言的传值),这个是函数参数默认的修饰符
out用在函数的参数中,表示该参数是输出参数,值是会改变的。
inout用在函数的参数,表示这个参数即是输入参数也是输出参数。


内置变量


内置变量可以与固定函数功能进行交互。在使用前不需要声明。顶点着色器可用的内置变量如下表:

名称类型描述
gl_Colorvec4输入属性-表示顶点的主颜色
gl_SecondaryColorvec4输入属性-表示顶点的辅助颜色
gl_Normalvec3输入属性-表示顶点的法线值
gl_Vertexvec4输入属性-表示物体空间的顶点位置
gl_MultiTexCoordnvec4输入属性-表示顶点的第n个纹理的坐标
gl_FogCoordfloat输入属性-表示顶点的雾坐标
gl_Positionvec4输出属性-变换后的顶点的位置,用于后面的固定的裁剪等操作。所有的顶点着色器都必须写这个值。
gl_ClipVertexvec4输出坐标,用于用户裁剪平面的裁剪
gl_PointSizefloat点的大小
gl_FrontColorvec4正面的主颜色的varying输出
gl_BackColorvec4背面主颜色的varying输出
gl_FrontSecondaryColorvec4正面的辅助颜色的varying输出
gl_BackSecondaryColorvec4背面的辅助颜色的varying输出
gl_TexCoord[]vec4纹理坐标的数组varying输出
gl_FogFragCoordfloat雾坐标的varying输出

片段着色器的内置变量如下表:

名称类型描述
gl_Colorvec4包含主颜色的插值只读输入
gl_SecondaryColorvec4包含辅助颜色的插值只读输入
gl_TexCoord[]vec4包含纹理坐标数组的插值只读输入
gl_FogFragCoordfloat包含雾坐标的插值只读输入
gl_FragCoordvec4只读输入,窗口的x,y,z和1/w
gl_FrontFacingbool只读输入,如果是窗口正面图元的一部分,则这个值为true
gl_PointCoordvec2点精灵的二维空间坐标范围在(0.0, 0.0)到(1.0, 1.0)之间,仅用于点图元和点精灵开启的情况下。
gl_FragData[]vec4使用glDrawBuffers输出的数据数组。不能与gl_FragColor结合使用。
gl_FragColorvec4输出的颜色用于随后的像素操作
gl_FragDepthfloat输出的深度用于随后的像素操作,如果这个值没有被写,则使用固定功能管线的深度值代替


表达式


操作符


GLSL语言的操作符与C语言相似。如下表(操作符的优先级从高到低排列)

操作符描述
()用于表达式组合,函数调用,构造
[]数组下标,向量或矩阵的选择器
.结构体和向量的成员选择
++ --前缀或后缀的自增自减操作符
+ – !一元操作符,表示正 负 逻辑非
* /乘 除操作符
+ -二元操作符 表示加 减操作
<> <= >= == !=小于,大于,小于等于, 大于等于,等于,不等于 判断符
&& || ^^逻辑与 ,或,  异或
?:条件判断符
= += –= *=  /=赋值操作符
,表示序列

像 求地址的& 和 解引用的 * 操作符不再GLSL中出现,因为GLSL不能直接操作地址。类型转换操作也是不允许的。 位操作符(&,|,^,~, <<, >> ,&=, |=, ^=, <<=, >>=)是GLSL保留的操作符,将来可能会被使用。还有求模操作(%,%=)也是保留的。


数组访问


数组的下标从0开始。合理的范围是[0, size - 1]。跟C语言一样。如果数组访问越界了,那行为是未定义的。如果着色器的编译器在编译时知道数组访问越界了,就会提示编译失败。

vec4 myColor, ambient, diffuse[6], specular[6];

myColor = ambient + diffuse[4] + specular[4];


构造函数


构造函数可以用于初始化包含多个成员的变量,包括数组和结构体。构造函数也可以用在表达式中。调用方式如下:

vec3 myNormal = vec3(1.0, 1.0, 1.0);

greenTint = myColor + vec3(0.0, 1.0, 0.0);

ivec4 myColor = ivec4(255);

还可以使用混合标量和向量的方式来构造,只要你的元素足以填满该向量。

vec4 color = vec4(1.0, vec2(0.0, 1.0), 1.0);

vec3 v = vec3(1.0, 10.0, 1.0);

vec3 v1 = vec3(v);

vec2 fv = vec2(5.0, 6.0);

float f = float(fv); //用x值2.5构造,y值被舍弃

对于矩阵,OpenGL中矩阵是列主顺序的。如果只传了一个值,则会构造成对角矩阵,其余的元素为0.

mat3 m3 = mat3(1.0);

构造出来的矩阵式:

1.0 0.0 0.0

0.0 1.0 0.0

0.0 0.0 1.0

mat2 matrix1 = mat2(1.0, 0.0, 0.0, 1.0);

mat2 matrix2 = mat2(vec2(1.0, 0.0), vec2(0.0, 1.0));

mat2 matrix3 = mat2(1.0); 

mat2 matrix4 = mat2(mat4(2.0)); //会取 4x4矩阵左上角的2x2矩阵。

构造函数可以用于标量数据类型的转换。GLSL不支持隐式或显示的转换,只能通过构造函数来转。其中int转为float值是一样的。float转为int则小数部分被丢弃。int或float转为bool,0和0.0转为false,其余的值转为true. bool转为int或float,false值转为0和0.0,true转为1和1.0.

float f = 1.7;

int I = int(f); // I = 1

数组的初始化,可以在构造函数中传入值来初始化数组中对应的每一个值。

ivec2 position[3] = ivec2[3]((0,0), (1,1), (2,2));

ivec2 pos2[3] = ivec2[]((3,3), (2,1), (3,1));

构造函数也可以对结构体进行初始化。其中顺序和类型要一一对应。

struct surface {  int  index;vec3 color;  float rotate;
};surface mySurface = surface(3, vec3(red, green, blue), 0.5);


成分选择


向量中单独的成分可以通过{x,y,z,w},{r,g,b,a}或者{s,t,p,q}的记法来表示。这些不同的记法用于顶点,颜色,纹理坐标。在成分选择中,你不可以混合使用这些记法。其中{s,t,p,q}中的p替换了纹理的r坐标,因为与颜色r重复了。下面是用法举例:

vec3 myVec = {0.5, 0.35, 0.7};float r = myVec.r;float myYz = myVec.yz;float myQ = myVec.q;//出错,数组越界访问,q代表第四个元素float myRY = myVec.ry; //不合法,混合使用记法

较特殊的使用方式,你可以重复向量中的元素,或者颠倒其顺序。如:

vec3 yxz = myVec.yxz; //调换顺序vec4 mySSTT = myVec.sstt; //重复其中的值

在赋值是,也可以选择你想要的顺序,但是不能重复其中的成分。

vec4 myColor = {0.0, 1.0, 2.0, 1.0};
myColor.x = -1.0;
myColor.yz = vec2(3.0, 5.0);
myColor.wx = vec2(1.0, 3.0);
myColor.zz = vec2(2.0, 3.0); //不合法

我们也可以通过使用下标来访问向量或矩阵中的元素。如果越界那行为将是未定义的。

float myY = myVec[1];

在矩阵中,可以通过一维的下标来获得该列的向量(OpenGL的矩阵是列主顺序的)。二维的小标来获得向量中的元素。

mat3 myMat = mat3(1.0);
vec3 myVec = myMat[0]; //获得第一列向量 1.0, 0.0, 0.0float f = myMat[0][0]; // 第一列的第一个向量。


控制流


循环


与C和C++相似,GLSL语言也提供了for, while, do/while的循环方式。使用continue跳入下一次循环,break结束循环。

for (l = 0; l < numLights; l++)
{if (!lightExists[l])continue;color += light[l];
}while (i < num)
{sum += color[i];i++;
}do{color += light[lightNum];lightNum--;
}while (lightNum > 0)


if / else


color = unlitColor;if (numLights > 0)
{color = litColor;
}else{color = unlitColor;
}


discard


片段着色器中有一种特殊的控制流成为discard。使用discard会退出片段着色器,不执行后面的片段着色操作。片段也不会写入帧缓冲区。


函数


在每个shader中必须有一个main函数。main函数中的void参数是可选的,但返回值是void时必须的。

void main(void)
{...
}

GLSL中的函数,必须是在全局范围定义和声明的。不能在函数定义中声明或定义函数。函数必须有返回类型,参数是可选的。参数的修饰符(in, out, inout, const等)是可选的。

//函数声明
bool isAnyNegative(const vec4 v);//函数调用void main(void)
{
    bool isNegative = isAnyNegative(gl_Color);...
}//定义
bool isAnyNegative(const vec4 v)
{if (v.x < 0.0 || v.y < 0.0 || v.z < 0.0 || v.w < 0.0)return true;elsereturn false;
}

结构体和数组也可以作为函数的参数。如果是数组作为函数的参数,则必须制定其大小。在调用传参时,只传数组名就可以了。

vec4 sumVectors(int sumSize, vec4 v[10]);void main()
{vec4 myColors[10];...vec4 sumColor = sumVectors(5, myColors);
}vec4 sumVectors(int sumSize, vec4 v[10])
{int i = 0;vec4 sum = vec4(0.0);for(; i < sumSize; ++i){sum += v[i]; }return sum;
}

GLSL的函数是支持重载的。函数可以同名但其参数类型或者参数个数不同即可。

float sum(float a, float b)
{return a + b;
}vec3 sum(vec3 v1, vec3 v2)
{return v1 + v2;
}

GLSL中函数递归是不被允许的。其行为是未定义的。

GLSL中提供了许多内建的函数,来方便我们的使用。可以在官方手册中查找相关的函数http://www.opengl.org/sdk/docs/man/

GLSL指南 http://www.opengl.org/registry/doc/GLSLangSpec.Full.1.20.8.pdf

这篇关于OpenGL(3)超级宝典笔记——GLSL语言基础的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/760935

相关文章

python操作redis基础

《python操作redis基础》Redis(RemoteDictionaryServer)是一个开源的、基于内存的键值对(Key-Value)存储系统,它通常用作数据库、缓存和消息代理,这篇文章... 目录1. Redis 简介2. 前提条件3. 安装 python Redis 客户端库4. 连接到 Re

Go语言中使用JWT进行身份验证的几种方式

《Go语言中使用JWT进行身份验证的几种方式》本文主要介绍了Go语言中使用JWT进行身份验证的几种方式,包括dgrijalva/jwt-go、golang-jwt/jwt、lestrrat-go/jw... 目录简介1. github.com/dgrijalva/jwt-go安装:使用示例:解释:2. gi

Go 语言中的 Struct Tag 的用法详解

《Go语言中的StructTag的用法详解》在Go语言中,结构体字段标签(StructTag)是一种用于给字段添加元信息(metadata)的机制,常用于序列化(如JSON、XML)、ORM映... 目录一、结构体标签的基本语法二、json:"token"的具体含义三、常见的标签格式变体四、使用示例五、使用

SpringBoot基础框架详解

《SpringBoot基础框架详解》SpringBoot开发目的是为了简化Spring应用的创建、运行、调试和部署等,使用SpringBoot可以不用或者只需要很少的Spring配置就可以让企业项目快... 目录SpringBoot基础 – 框架介绍1.SpringBoot介绍1.1 概述1.2 核心功能2

Go语言使用slices包轻松实现排序功能

《Go语言使用slices包轻松实现排序功能》在Go语言开发中,对数据进行排序是常见的需求,Go1.18版本引入的slices包提供了简洁高效的排序解决方案,支持内置类型和用户自定义类型的排序操作,本... 目录一、内置类型排序:字符串与整数的应用1. 字符串切片排序2. 整数切片排序二、检查切片排序状态:

基于Go语言实现Base62编码的三种方式以及对比分析

《基于Go语言实现Base62编码的三种方式以及对比分析》Base62编码是一种在字符编码中使用62个字符的编码方式,在计算机科学中,,Go语言是一种静态类型、编译型语言,它由Google开发并开源,... 目录一、标准库现状与解决方案1. 标准库对比表2. 解决方案完整实现代码(含边界处理)二、关键实现细

如何合理管控Java语言的异常

《如何合理管控Java语言的异常》:本文主要介绍如何合理管控Java语言的异常问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、Thorwable类3、Error4、Exception类4.1、检查异常4.2、运行时异常5、处理方式5.1. 捕获异常

Spring Boot集成SLF4j从基础到高级实践(最新推荐)

《SpringBoot集成SLF4j从基础到高级实践(最新推荐)》SLF4j(SimpleLoggingFacadeforJava)是一个日志门面(Facade),不是具体的日志实现,这篇文章主要介... 目录一、日志框架概述与SLF4j简介1.1 为什么需要日志框架1.2 主流日志框架对比1.3 SLF4

Spring Boot集成Logback终极指南之从基础到高级配置实战指南

《SpringBoot集成Logback终极指南之从基础到高级配置实战指南》Logback是一个可靠、通用且快速的Java日志框架,作为Log4j的继承者,由Log4j创始人设计,:本文主要介绍... 目录一、Logback简介与Spring Boot集成基础1.1 Logback是什么?1.2 Sprin

C语言中的常见进制转换详解(从二进制到十六进制)

《C语言中的常见进制转换详解(从二进制到十六进制)》进制转换是计算机编程中的一个常见任务,特别是在处理低级别的数据操作时,C语言作为一门底层编程语言,在进制转换方面提供了灵活的操作方式,今天,我们将深... 目录1、进制基础2、C语言中的进制转换2.1 从十进制转换为其他进制十进制转二进制十进制转八进制十进