1. 概述

本文基于Python语言,描述OpenGL的着色器

环境搭建以及绘制流程可参考:

笔者这里不过多描述每个名词、函数和细节,更详细的文档可以参考:

2. 着色器

着色器(Shader)是运行在GPU上的小程序,这些小程序为图形渲染管线的某个特定部分而运行

从基本意义上来说,着色器只是一种把输入转化为输出的程序

着色器也是一种非常独立的程序,因为它们之间不能相互通信,它们之间唯一的沟通只有通过输入和输出

着色器语言(英语:Shader Language)也叫着色语言(英语:Shading Language),是一类专门用来为着色器编程的编程语言

Shader Language目前主要有3种语言:

  1. 基于 OpenGL 的 OpenGL Shading Language,简称 GLSL
  2. 基于 DirectX 的 High Level Shading Language,简称 HLSL
  3. 还有 NVIDIA 公司的 C for Graphic,简称 Cg 语言

3. GLSL

OpenGL着色器是使用一种叫GLSL的类C语言写成的,为图形计算量身定制的,包含一些针对向量和矩阵操作的有用特性

着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数

每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中

uniform可以理解为globe,即全局变量

一个典型的OpenGL着色器的GLSL结构如下:

#version version_number
in type in_variable_name;
in type in_variable_name; out type out_variable_name; uniform type uniform_name; int main()
{
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = result_we_processed;
}

4. 数据类型

GLSL包含的基础变量类型:

  • int
  • uint
  • float
  • double
  • bool

GLSL包含的容器类型:

  • Vector(向量)
  • Matrix(矩阵)

4.1 向量

GLSL中的向量可以是包含2-4个分量的容器,分量的类型可以是任意基础类型

向量的类型大致可以表示为:<基础类型首字母>+vec+<分量个数>

如包含3个double分量的向量:dvec3

例外的是float类型,float向量是GLSL中最常用的变量,其类型为:vec+<分量个数>

向量的分量支持.x.y.z的方式获取,向量支持重组,例如:

vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;

5. 输入与输出

OpenGL中着色器是独立的小程序,每个着色器具有输入与输出,从而进行数据传递

GLSL定义了inout两个关键字实现这种流程

前面的着色器使用out定义的变量会传递到后面的着色器中用in声明且类型与变量名相同的变量

例外的是:

  • 顶点着色器使用layout (location = <n>)指定输入变量(也可以用glGetAttribLocation()查询属性位置)
  • 片段着色器需要vec4类型的颜色输出

综述就是,一般而言:

  • inout两个关键字主要用于顶点着色器与片段着色器的数据传递
  • 顶点着色器需要使用location进行输入,输出中要指定gl_Position
  • 片段着色器需要指定输出颜色

一个简单的示例代码如下:

顶点着色器:

#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0 out vec4 vertexColor; // 为片段着色器指定一个颜色输出 void main()
{
gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数
vertexColor = vec4(1.0, 0.0, 0.0, 1.0); // 把输出变量设置为红色
}

片段着色器:

#version 330 core
out vec4 FragColor; in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同) void main()
{
FragColor = vertexColor;
}

这里只是修改GLSL,环境代码在这片文章的结尾:

实现的效果:

6. Uniform

inout两个关键字主要用于着色器的数据传递,Uniform则主要用于CPU与GPU的数据传递

Uniform是全局变量,变量名不可重复,可以被任何着色器随时读取

以下是在片段着色器中声明一个Uniform的颜色变量:

#version 330 core
out vec4 FragColor; uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量 void main()
{
FragColor = ourColor;
}

接下来是在渲染中动态设置这个Uniform变量的代码:

timeValue = glfw.get_time()
greenValue = (np.sin(timeValue) / 2.0) + 0.5
vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor")
glUseProgram(shaderProgram)
glUniform4fv(vertexColorLocation, 1, np.array([0.0, greenValue, 0.0, 1.0], dtype='float32'))

一切正常的话,将会出现一个三角形逐渐由绿变黑再变回绿色:

一些想法:

  • 既然说Uniform是全局变量,那是否可以在没有声明Uniform的着色器中访问呢?经过笔者的实验,似乎不可以

7. 绑定更多属性

利用Uniform可以实现颜色从CPU到GPU的传递,但是变量多时声明很多Uniform就不那么合适,另一个方案是将颜色属性绑定到顶点数据中,从顶点着色器传递到片段着色器中,代码如下

设置顶点与对应的颜色:

vertices = np.array([   # 位置               颜色
0.5, -0.5, 0.0, 1.0, 0.0, 0.0, # 右下
-0.5, -0.5, 0.0, 0.0, 1.0, 0.0, # 左下
0.0, 0.5, 0.0, 0.0, 0.0, 1.0 # 顶部
])

在顶点着色器中配置颜色属性:

#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为 0
layout (location = 1) in vec3 aColor; // 颜色变量的属性位置值为 1 out vec3 ourColor; // 向片段着色器输出一个颜色 void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor; // 将ourColor设置为我们从顶点数据那里得到的输入颜色
}

在片段着色器中接收颜色属性:

#version 330 core
out vec4 FragColor;
in vec3 ourColor; void main()
{
FragColor = vec4(ourColor, 1.0);
}

绑定属性:

现在的VBO内存布局如下:

绑定VBO的顶点格式:

# 位置属性
glVertexAttribPointer(0, 3, GL_DOUBLE, GL_FALSE, int(8 * 6), None)
glEnableVertexArrayAttrib(VAO, 0)
# 颜色属性
glVertexAttribPointer(1, 3, GL_DOUBLE, GL_FALSE, int(8 * 6), ctypes.c_void_p(8*3))
glEnableVertexArrayAttrib(VAO, 1)

注意

运行程序结果:

8. 封装着色器

着色器程序的生成步骤大致都是:

  • 编写GLSL
  • 创建Shader
  • 加载GLSL
  • 编译Shader
  • 创建着色器程序
  • 附加Shader到着色器程序
  • 链接着色器程序

封装一个着色器类,可以有效简化创建一个着色器程序的步骤

这是封装的shader.py:

from OpenGL.GL import *

class Shader:
def __init__(self, vertex_path, fragment_path):
with open(vertex_path, mode='r', encoding='utf-8') as vertex_stream:
vertex_code = vertex_stream.readlines()
with open (fragment_path, mode='r', encoding='utf-8') as fragment_stream:
fragment_code = fragment_stream.readlines() vertex_shader = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vertex_shader, vertex_code)
glCompileShader(vertex_shader)
status = glGetShaderiv(vertex_shader, GL_COMPILE_STATUS)
if not status:
print("[ERROR]: " + bytes.decode(glGetShaderInfoLog(vertex_shader))) fragment_shader = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fragment_shader, fragment_code)
glCompileShader(fragment_shader)
status = glGetShaderiv(fragment_shader, GL_COMPILE_STATUS)
if not status:
print("[ERROR]: " + bytes.decode(glGetShaderInfoLog(fragment_shader))) shader_program = glCreateProgram()
glAttachShader(shader_program, vertex_shader)
glAttachShader(shader_program, fragment_shader)
glLinkProgram(shader_program)
status = glGetProgramiv(shader_program, GL_LINK_STATUS )
if not status:
print("[ERROR]: " + bytes.decode(glGetProgramInfoLog(shader_program))) glDeleteShader(vertex_shader)
glDeleteShader(fragment_shader)
self.shaderProgram = shader_program def use(self):
glUseProgram(self.shaderProgram) def delete(self):
glDeleteProgram(self.shaderProgram)

以下是一个简单的调用程序test.py

import glfw
from OpenGL.GL import *
import numpy as np import shader as shader glfw.init()
window = glfw.create_window(800, 600, "shader", None, None)
glfw.make_context_current(window) VAO = glGenVertexArrays(1)
glBindVertexArray(VAO) vertices = np.array([ # 位置 颜色
0.5, -0.5, 0.0, 1.0, 0.0, 0.0, # 右下
-0.5, -0.5, 0.0, 0.0, 1.0, 0.0, # 左下
0.0, 0.5, 0.0, 0.0, 0.0, 1.0 # 顶部
])
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, 8 * vertices.size, vertices, GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_DOUBLE, GL_FALSE, int(8 * 6), None)
glEnableVertexArrayAttrib(VAO, 0)
glVertexAttribPointer(1, 3, GL_DOUBLE, GL_FALSE, int(8 * 6), ctypes.c_void_p(8 * 3))
glEnableVertexArrayAttrib(VAO, 1) shaderProgram = shader.Shader("./glsl/test.vs.glsl", "./glsl/test.fs.glsl") while not glfw.window_should_close(window):
glClearColor(0.2, 0.3, 0.3, 1.0)
glClear(GL_COLOR_BUFFER_BIT) shaderProgram.use()
glBindVertexArray(VAO)
glDrawArrays(GL_TRIANGLES, 0, 3) glfw.swap_buffers(window)
glfw.poll_events() shaderProgram.delete()

test.vs.glsl如下:

#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为 0
layout (location = 1) in vec3 aColor; // 颜色变量的属性位置值为 1 out vec3 ourColor; // 向片段着色器输出一个颜色 void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor; // 将ourColor设置为我们从顶点数据那里得到的输入颜色
}

test.fs.glsl如下:

#version 330 core
out vec4 FragColor;
in vec3 ourColor; void main()
{
FragColor = vec4(ourColor, 1.0);
}

9. 参考资料

[1]着色器 - LearnOpenGL CN (learnopengl-cn.github.io)

[2]着色器语言_百度百科 (baidu.com)

[3]三大 Shader 编程语言(CG/HLSL/GLSL) - 知乎 (zhihu.com)

[4]OpenGL学习笔记(四)着色器 - 知乎 (zhihu.com)

[5]着色器语言 GLSL (opengl-shader-language)入门大全 - 善未易明 - 博客园 (cnblogs.com)

[6]ctypes --- Python 的外部函数库 — Python 3.7.13 文档

[7]PyOpenGL 3.1.0 Function Reference (sourceforge.net)

[8]基于C++的OpenGL 02 之着色器 - 当时明月在曾照彩云归 - 博客园 (cnblogs.com)

最新文章

  1. MIS性能优化常见问题与方案(辅助项目组性能优化的总结贴)
  2. Elasticsearch 2.X 版本Java插件开发简述
  3. Python快速上手JSON指南
  4. iis浏览网页时提示无法显示 XML 页
  5. iOS常用正则表达式验证(手机号、密码格式、身份证号等)
  6. spark中streamingContext的使用详解
  7. MongoDB学习笔记-查询
  8. Web App之一
  9. java中的对象
  10. 数据库服务器构建和部署列表(For SQL Server 2012)
  11. Object-Relational Structural Patterns
  12. Swagger: 一个restful接口文档在线生成+功能测试软件
  13. 记一次产品需求:图片等比缩放和CSS自适应布局16:9
  14. 如何用ABP框架快速完成项目(7) - 用ABP一个人快速完成项目(3) - 通过微服务模式而不是盖楼式来避免难度升级和奥卡姆剃刀原理
  15. 2004 ACM 成绩转换 两种方法
  16. jmeter 如何发送上传文件接口请求
  17. SQL Server 基本UPDATE和DELETE语句
  18. Layer 弹出页面 在点击保存关闭弹出层
  19. [图解tensorflow源码] 入门准备工作附常用的矩阵计算工具[转]
  20. 图解SVN的branch合并到trunk的过程

热门文章

  1. 错误:Required request parameter &#39;XXX&#39; for method parameter type String is not present
  2. Django框架模板语法传值-过滤器-标签-自定义过滤器,标签,inclusion_tag
  3. vue引入高德地图
  4. nuxt.js中登录、注册(密码登录和手机验证码登录)
  5. anaconda peompt 、labalimg 数据标注
  6. Kali Win-KeX SL
  7. 【Redis 技术探索】「数据迁移实战」手把手教你如何实现在线 + 离线模式进行迁移Redis数据实战指南(离线同步数据)
  8. C语言函数值传递问题
  9. python进阶之路19 地狱之门购物车!!!!
  10. [cocos2d-x]关于声音和音效