什么是shader?
一、什么是shader?
shader是一段GLSL(openGL着色语言)小程序,运行在GPU(图形处理器),而非CPU使用GLSL语言编写,看上去像c或c++,但却是另外一种不同的语言。使用shader就像写个普通程序一样,写代码-->编译-->链接在一起才能生成最终的程序。
着色器类似一个函数调用的方式--数据传输进来,经过处理,然后再传输出去。每个着色器看起来像一个完整的c程序,它的输入点就是一个名为main()的函数,但与c不同的是,GLSL的main函数没有任何参数,也没有返回值。
着色器的建立:
1>创建着色器(顶点和片元)在程序启示的位置,总是要用#version来声名所使用的版本。
glCreatShader(GLenum type),type为GL_VERTEX_SHADER或者是GL_FRAGMENT_SHADER
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
我们把需要创建的着色器类型以参数形式提供给glCreateShader。由于我们正在创建一个顶点着色器,传递的参数是GL_VERTEX_SHADER。
下一步我们把这个着色器源码附加到着色器对象上,然后编译它:void glShaderSource(GLuint shader,GLsizei count,const GLchar * const *string,const GLint *length);
顶点着色器源码(储存在一个C的字符串中),所以第二个参数为1.第三个参数是顶点着色器真正的源码。
glShaderSource(vertexShader, , &vertexShaderSource, NULL);
glCompileShader(vertexShader);
2>编译着色器
glCompileShader(Gluint shader)。结果查询使用glGetShaderiv(),例如:glGetShaderiv(Gluint shader,GL_COMPILE_STATUS,&compiled)
int success;
char infoLog[];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
首先我们定义了一个整型变量来表示是否成功编译,还定义了一个存储错误消息的容器(如果有的话)。然后用glGetShaderiv检查是否编译成功。如果编译失败,用glGetShaderInfoLog获取错误消息,然后打印出来。
if(!success)
{
glGetShaderInfoLog(vertexShader, , NULL, infoLog);
std::cout << "顶点着色器编译错误\n" << infoLog << std::endl;
}
3>把着色器添加到程序中
GLuint glCreatPogram();创建一个空的程序
void glAttachShader(GLuint program,GLuint shader);把着色器加到程序中
4>链接你的程序
链接程序的时候会把顶点着色器的输出作为片段着色器的输入。
void glLinkProgram(GLuint program);
void glGetProgramiv(program,GL_LINK_STATUS,&linked);
5>使用程序
void glUseProgram(GLuint program)
那shader到底干了什么?这取决于是哪种shader.
二、Vertex Shader
顶点着色器主要用来将点(x,y,z)变换成不同的点。顶点只是几何形状中的一个点,一个点叫vectex,多个点叫vertices。在本教程中,我们的三角形需要三个顶点(vertices)组成。
Vertex Shader的GLSL代码如下:
#version 330 core
layout(location = 0) in vec3 vert;
void main() {
// does not alter the vertices at all
gl_Position = vec4(vert, 1.0);//或者是gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
第一行#version 330
告诉OpenGL这个shader使用GLSL版本3.3核心版本
第二行 layout(location = 0)设定输入变量的位置值,之后会有用到
in vec3 vert;
告诉shader有一个输入变量(in)vert
第三行定义函数main
,这是shader运行入口。这看上去像C,(区别:)但GLSL中main
不需要带任何参数,并且不用返回,用void。
第四行gl_Position = vec4(vert, 1);
将输入的顶点直接输出,变量gl_Position
是OpenGL定义的全局变量,用来存储vertex shader的输出。所有vertex shaders都需要对gl_Position
进行赋值。
gl_Position
是4D坐标(vec4),但vert
是3D坐标(vec3),所以我们需要将vert
转换为4D坐标vec4(vert, 1)
。第二个的参数1
是赋值给第四维坐标。(因为矩阵变换均是利用齐次坐标)
Vertex Shader在本文中没有做任何事,后续我们会修改它来处理动画,摄像机和其它东西。
顶点着色器源码(储存在一个C的字符串中)
三、Fragment Shader
片元着色器的主要功能是计算每个需要绘制的像素点的颜色。
一个”fragment”基本上就是一个像素,所以你可以认为片段着色器(fragment shader)就是像素着色器(pixel shader)。在本文中每个片段都是一像素,但这并不总是这样的。你可以更改某个OpenGL设置,以便得到比像素更小的片段。
#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);//橘黄色
}
创建并编译:
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, , &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
现在两个着色器都编译过了,剩下的事情是把两个着色器对象链接到一个用来渲染的着色器程序中。(shader program)
四、着色器程序
着色器程序对象是多个着色器合并之后最终链接完成的,若要使用刚才编译的着色器,我们必须把它们链接为一个着色器程序对象,然后再渲染对象的时候激活这个着色器程序。
//创建
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
//依附并链接
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
得到的结果就是一个程序对象,我们可以调用glUseProgram函数,用刚创建的程序对象作为它的参数,以激活这个程序对象。
glUseProgram(shaderProgram);
就像着色器的编译一样,我们也可以检测链接着色器程序是否失败,并获取相应的日志
glGetProgramiv(shaderProgram,GL_LINK_STATUS,&success);
if (!success) {
glGetShaderInfoLog(shaderProgram, , NULL, infoLog);
cout << "着色器程序链接出错" << infoLog << endl;
}
在把着色器对象链接到程序对象以后,记得删除着色器对象,我们不再需要它们了:
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
现在,我们已经把输入顶点数据发送给了GPU,并指示了GPU如何在顶点和片段着色器中处理它。就快要完成了,但还没结束,OpenGL还不知道它该如何解释内存中的顶点数据,以及它该如何将顶点数据链接到顶点着色器的属性上。我们需要告诉OpenGL怎么做。
五、链接顶点属性
使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据.
glVertexAttribPointer(,,GL_FLOAT,GL_FALSE,*sizeof(float),);
glEnableVertexAttribArray();
第一个参数指定我们要配置的顶点属性.之前我们在顶点着色器中使用layout(location = 0)
定义了position顶点属性的位置值(Location)。我们希望把数据传递到这一个顶点属性中,所以这里我们传入0
。
第二个参数指定顶点属性的大小。顶点属性是一个vec3
,它由3个值组成,所以大小是3。
第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*
都是由浮点数值组成的)。
第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float
之后,我们把步长设置为3 * sizeof(float)
。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)
最后一个参数的类型是void*
,所以需要我们进行这个奇怪的强制类型转换。表示偏移量,设为0.
什么是shader?的更多相关文章
- OpenGL shader 中关于顶点坐标值的思考
今天工作中需要做一个事情: 在shader内部做一些空间距离上的计算,而且需要对所有的点进行计算,符合条件的显示,不符合条件的点不显示. 思路很简单,在vertex shader内知道顶点坐标,进行计 ...
- CSharpGL(14)用geometry shader渲染模型的法线(normal)
+BIT祝威+悄悄在此留下版了个权的信息说: CSharpGL(14)用geometry shader渲染模型的法线(normal) +BIT祝威+悄悄在此留下版了个权的信息说: 2016-08-13 ...
- 【译】Unity3D Shader 新手教程(6/6) —— 更好的卡通Shader
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 动机 如果你想了解以下几件事,我建议你阅读以下这篇教程: 想知道如何写一个multipass的toon shade ...
- 【译】Unity3D Shader 新手教程(5/6) —— Bumped Diffuse Shader
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 动机 如果你满足以下条件,我建议你阅读这篇教程: 你想学习片段着色器(Fragment Shader). 你想实现 ...
- 【译】Unity3D Shader 新手教程(4/6) —— 卡通shader(入门版)
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 暗黑系 动机 如果你满足以下条件,我建议你阅读这篇教程: 你想了解更多有关表面着色器的细节知识. 你想实现一个入门 ...
- 【译】Unity3D Shader 新手教程(3/6) —— 更加真实的积雪
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 如果你满足以下条件,我建议你阅读这篇教程: 你想知道如何在表面着色器中进行混色(blend colour) 你想实 ...
- 【译】Unity3D Shader 新手教程(2/6) —— 积雪Shader
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 如果你是一个shader编程的新手,并且你想学到下面这些酷炫的技术,我觉得你可以看看这篇教程: 实现一个积雪效果的 ...
- 【译】Unity3D Shader 新手教程(1/6)
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 刚开始接触Unity3D Shader编程时,你会发现有关shader的文档相当散,这也造成初学者对Unity3D ...
- 多材质(Shader)实现
最近在cocos creator上打算写个U3D中shader功能的插件(能在属性面板调整shader属性). 对其中一个功能有点疑惑,就是U3D中一个渲染物体上可以挂多个材质,后来查询了下,一个物体 ...
- unity的固定管线shader
最近shader学习中,看的视频. 练习的固定管线的shader如下: ps.在unity5中半透明不好用,其他的还好 //不区分大小写 //这是固定管线的Shader Shader "Sh ...
随机推荐
- Planting Trees
Planting Trees 给定N*N矩阵,求子矩形满足里面最大元素最小元素之差不超过M 单调队列 枚举上边界,下边界,及右边界, 用两个单调队列,一个维护最大值,一个维护最小 求左边界 #incl ...
- es之分词器和分析器
Elasticsearch这种全文搜索引擎,会用某种算法对建立的文档进行分析,从文档中提取出有效信息(Token) 对于es来说,有内置的分析器(Analyzer)和分词器(Tokenizer) 1: ...
- [CERC2016]:凸轮廓线Convex Contour(模拟+数学)
题目描述 一些几何图形整齐地在一个网格图上从左往右排成一列.它们占据了连续的一段横行,每个位置恰好一个几何图形.每个图形是以下的三种之一:$1.$一个恰好充满单个格子的正方形.$2.$一个内切于单个格 ...
- android发送udp,tcp消息
发送方创建步骤: 1. 创建一个DatagramSocket对象 DatagramSocket socket = new DatagramSocket (4567); 2. 创建一个 InetA ...
- 从Mybatis中#和$的区别到SQL预编译
#和$的区别 Mybatis中参数传递可以通过#和$设置.它们的区别是什么呢? # Mybatis在解析SQL语句时,sql语句中的参数会被预编译为占位符问号? $ Mybatis在解析SQL语句时, ...
- The JavaScript this Keyword
https://www.w3schools.com/js/js_this.asp What is this? The JavaScript this keyword refers to the obj ...
- vue 拖动调整左右两侧div的宽度
原文链接:https://www.cnblogs.com/layaling/p/11009570.html 原文是左中右三种情况的拖动.由于项目需要,我删除掉了右边的,直接左右区域拖动调整div宽度 ...
- tar 打包文件
tar支持通配符, 可以用* ?等来指定多个文件 在指明压缩文件名的时候, 一定要带上 -f选项 压缩文件名中间 最好不要带特殊符号, 如& ? * +等, shell bash 会作一些特殊 ...
- JS 引擎
最早的 JS 引擎是纯解释器,现代 JS 引擎已经使用 JIT(Just-in-time compilation:结合预编译(ahead-of-time compilation AOT)和解释器的优点 ...
- Websocket如何建立连接
前面提到,WebSocket复用了HTTP的握手通道.具体指的是,客户端通过HTTP请求与WebSocket服务端协商升级协议.协议升级完成后,后续的数据交换则遵照WebSocket的协议. 1.客户 ...