基于Python的OpenGL 02 之着色器
1. 概述
本文基于Python语言,描述OpenGL的着色器
环境搭建以及绘制流程可参考:
笔者这里不过多描述每个名词、函数和细节,更详细的文档可以参考:
2. 着色器
着色器(Shader)是运行在GPU上的小程序,这些小程序为图形渲染管线的某个特定部分而运行
从基本意义上来说,着色器只是一种把输入转化为输出的程序
着色器也是一种非常独立的程序,因为它们之间不能相互通信,它们之间唯一的沟通只有通过输入和输出
着色器语言(英语:Shader Language)也叫着色语言(英语:Shading Language),是一类专门用来为着色器编程的编程语言
Shader Language目前主要有3种语言:
- 基于 OpenGL 的 OpenGL Shading Language,简称 GLSL
- 基于 DirectX 的 High Level Shading Language,简称 HLSL
- 还有 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定义了in
和out
两个关键字实现这种流程
前面的着色器使用out
定义的变量会传递到后面的着色器中用in
声明且类型与变量名相同的变量
例外的是:
- 顶点着色器使用
layout (location = <n>)
指定输入变量(也可以用glGetAttribLocation()查询属性位置) - 片段着色器需要
vec4
类型的颜色输出
综述就是,一般而言:
in
和out
两个关键字主要用于顶点着色器与片段着色器的数据传递- 顶点着色器需要使用
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
in
和out
两个关键字主要用于着色器的数据传递,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)
注意:
glVertexAttribPointer()
函数最后一个参数为偏移步长的指针,在C中原型为void*
,在PyOpenGL中,这里需要传入ctypes.c_void_p()
类型,具体函数解释参考:ctypes --- Python 的外部函数库 — Python 3.7.13 文档
运行程序结果:
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)
[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)
基于Python的OpenGL 02 之着色器的更多相关文章
- OpenGL官方教程——着色器语言概述
OpenGL官方教程——着色器语言概述 OpenGL官方教程——着色器语言概述 可编程图形硬件管线(流水线) 可编程顶点处理器 可编程几何处理器 可编程片元处理器 语言 可编程图形硬件管线(流水线) ...
- OpenGL之shader着色器的应用,三色渐变的三角形
学习自: https://learnopengl-cn.github.io/01%20Getting%20started/05%20Shaders/#_7 首先放一张效果图: 本次教程,将着色器单独定 ...
- OpenGl中使用着色器的基本步骤及GLSL渲染简单示例
OpenGL着色语言(OpenGL Shading Language,GLSL)是用来在OpenGL中着色编程的语言,是一种具有C/C++风格的高级过程语言,同样也以main函数开始,只不过执行过程是 ...
- 基于Unity实现油画风格的着色器
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' Shader "Cust ...
- 基于Unity实现像素化风格的着色器
Shader "MyShaderTest/SimplePixelationShader" { Properties { _MainTex ("Base (RGB)&quo ...
- 【OPENGL】第三篇 着色器基础(一)
在这一章,我们会学习什么是着色器(Shader),什么是着色器语言(OpenGL Shading Language-GLSL),以及着色器怎么和OpenGL程序交互. 首先我们先来看看什么叫着色器. ...
- Android OpenGL ES 开发(八): OpenGL ES 着色器语言GLSL
前面的文章主要是整理的Android 官方文档对OpenGL ES支持的介绍.通过之前的文章,我们基本上可以完成的基本的形状的绘制. 这是本人做的整理笔记: https://github.com/re ...
- 编写Unity3D着色器的三种方式
不管你会不会写Unity3D的shader,估计你会知道,Unity3D编写shader有三种方式,这篇东西主要就是说一下这三种东西有什么区别,和大概是怎样用的. 先来列一下这三种方式: fixed ...
- Unity3D - 图形性能优化:优化着色器载入时间
Unity官方文档之"图形性能优化-优化着色器载入时间"的翻译,E文链接. Optimizing Shader Load Time 优化着色器载入时间 Shaders are sm ...
- [GEiv]第七章:着色器 高效GPU渲染方案
第七章:着色器 高效GPU渲染方案 本章介绍着色器的基本知识以及Geiv下对其提供的支持接口.并以"渐变高斯模糊"为线索进行实例的演示解说. [背景信息] [计算机中央处理器的局限 ...
随机推荐
- [数据结构][洛谷]P3375模板题 KMP
主要还是KMP算法,上学期没学,只是考前抱了抱佛脚,也没怎么弄明白. 先放代码: //KMP #include <bits/stdc++.h>//万能头 using namespace s ...
- jmeter 之修改报告取样间隔时间以及APDEX 区间设置
1.取样间隔时间设置 在jmeter 生成的报告中取样间隔默认设置的是1分钟,而非1秒,故样本间的间隔为1分钟,如下图所示: 取样间隔时间可通过修改bin/user.properties配置文件实现自 ...
- Vue3.0 生命周期
所有生命周期钩子的this上下文都是绑定至实例的. beforeCreate:在实例初始化之后.进行数据帧听和事件/侦听器的配置之前同步调用. created:实例创建完成,主要包括数据帧听.计算属性 ...
- Transition 初步使用
Transition Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡: 条件渲染 (使用 v-if) 条件展示 (使用 v-show) 动态组件 ...
- [常用工具] mermaid学习笔记
mermaid是一个基于Javascript的图表绘制工具,类似markdown用文本语法,用于描述文档图形(流程图. 时序图.甘特图),开发者可以通过一段mermaid文本来生成SVG或者PNG形式 ...
- 【SpringBoot实战专题】「开发实战系列」从零开始教你舒服的使用RedisTemplate操作Redis数据
SpringBoot快速操作Redis数据 在SpringBoot框架中提供了spring-boot-starter-data-redis的依赖组件进行操作Redis服务,当引入了该组件之后,只需要配 ...
- js 禁用刷新快捷键
// 上代码 /** * 按键监听 * Ctrl-17,F5-116,R-82 */ var oldKeyCode = -1; document.onkeydown = function (e) { ...
- ArcEngine开发 - 打开地图读取图层
地图文档(IMapDocument)对象是ArcEngine开发最基本对象,可以说是所有操作的第一步.使用IMapDocument可以检查和打开地图文档,读取图层信息和文档信息,为源GIS并为您详细分 ...
- C#开发的资源文件程序(可国际化) - 开源研究系列文章
上次将小软件的线程池描述了,也将插件程序描述了,这次就将里面的资源文件相关的内容进行下记录,这里能够让程序做成国际化的形式(即多语言程序),主要就是通过这个资源文件的方式进行的处理.下面将对这个资源文 ...
- BBS项目 未完待续
项目开发基本流程 1.需求分析 2.架构设计 3.分组开发 4.提交测试 5.交付上线 创建项目配置 环境配置 TEMPLATES = [ { 'BACKEND': 'django.template. ...