前言:VBO(vertex Buffer Object):顶点缓冲对象。是在显卡存储空间中开辟的一块区域,在显卡存储空间中开辟一块区域,用于存放顶点的各类属性信息。如顶点坐标、纹理坐标、顶点颜色等数据。 在渲染时直接从显VBO去取数据而不必与CPU进行数据交换。而用VAO来告诉计算机这些数据分别有什么属性、起什么作用。
一、编写MainWindow.xaml
<Grid>
<wpf:OpenGLControl x:Name="openGLControl1"
RenderContextType="FBO"
Loaded="OpenGLControl1_Loaded"
></wpf:OpenGLControl>
</Grid>
二、编写MainWindow.xaml.cs后台代码
using SharpGL;
using System;
using System.Runtime.InteropServices;
using System.Windows;
namespace wpfSharpGlVao
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
OpenGL gl;
uint[] vaos = new uint[1];
uint[] vbos = new uint[2];
private uint sProgram = 0;
IntPtr pArrayIn1;
IntPtr pArrayInColor1;
//GLSL语言:顶点着色器的shader(这里填写5,6是为了演示,可取0,1,2,3...)
string vertexShaderSource = @"
#version 330 core
layout(location = 5) in vec3 positionZdy;
layout(location = 6) in vec4 colorZdy;
out vec4 coInterim;
void main()
{
gl_Position = vec4(positionZdy, 1.0);
coInterim = colorZdy;
}";
//GLSL语言:片元着色器的shader
string fragmentShaderSource = @"
#version 330 core
in vec4 coInterim;
out vec4 FragColor;
void main()
{
FragColor = coInterim;
}";
float[] vertices = new float[] //顶点位置数组
{
0.5f, 0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
};
float[] colors = //颜色数组(rgba参数-渐变)
{
0f, 1f, 0f, 1f,//白色
0f, 1f, 0f, 1f,//白色
0.94f, 0.69f, 0.45f,1f,//橙色
};
public MainWindow()
{
InitializeComponent();
}
//初始化
private void OpenGLControl1_Loaded(object sender, RoutedEventArgs e)
{
initFun();
}
//初始化内部方法
private void initFun()
{
gl = openGLControl1.OpenGL;
openGLControl1.OpenGLDraw += OpenGLControl1_OpenGLDraw_1;
openGLControl1.FrameRate = 40; //帧率为40
gl.ClearColor(0, 0, 0, 0);
gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
gl.Color(1, 0, 0, 1.0f);
// 创建并编译顶点着色器
uint vertexShader = gl.CreateShader(OpenGL.GL_VERTEX_SHADER);
gl.ShaderSource(vertexShader, vertexShaderSource);
gl.CompileShader(vertexShader);
var check = GetCompileStatus(vertexShader);
// 创建并编译片段着色器
uint fragmentShader = gl.CreateShader(OpenGL.GL_FRAGMENT_SHADER);
gl.ShaderSource(fragmentShader, fragmentShaderSource);
gl.CompileShader(fragmentShader);
var check2 = GetCompileStatus(fragmentShader);
// 创建着色器程序并链接
sProgram = gl.CreateProgram();
gl.AttachShader(sProgram, vertexShader);
gl.AttachShader(sProgram, fragmentShader);
gl.LinkProgram(sProgram);
gl.ValidateProgram(sProgram);
gl.DeleteShader(vertexShader);
gl.DeleteShader(fragmentShader);
//获取自定义输入属性的位置(对应GLSL语言语言申明的location索引)信息
//注意,若GLSL语言没有写对(或GLSL内部申明了location却没有使用),这里不报错,只是获取到的是-1
var positionLocation = gl.GetAttribLocation(sProgram, "positionZdy");
var colorLocation = gl.GetAttribLocation(sProgram, "colorZdy");
// 创建VAO并绑定
gl.GenVertexArrays(1, vaos);//创建1个VAO数组,但填入一个VAO,模拟一个VAO对应多个VBO
gl.BindVertexArray(vaos[0]); //绑定VAO到上下文
// 创建2个VBO用于存储顶点数据和颜色数据
gl.GenBuffers(2, vbos);//创建2个VBO
//绑定第一个VBO存顶点
gl.BindBuffer(OpenGL.GL_ARRAY_BUFFER, vbos[0]);
gl.BufferData(OpenGL.GL_ARRAY_BUFFER, vertices, OpenGL.GL_DYNAMIC_DRAW);
pArrayIn1 = Marshal.AllocHGlobal(vertices.Length * sizeof(float)); //申请数组的指针
gl.VertexAttribPointer((uint)positionLocation, 3, OpenGL.GL_FLOAT, false, 0, IntPtr.Zero); //申明位置解释规则。更多关于该函数的解释,见文末附录
//注意,这里第一个参数并不是像很多教程那样直接填0或1,很有歧义!实时上他是GLSL语言的location信息,只不过网上有些教程刚好使用了GLSL默认位置的0和1而已。
//作为示范,我这里故意在GLSL语言中显示声明location=5,而这里为了灵活,直接用位置变量
gl.EnableVertexAttribArray((uint)positionLocation);
//绑定第二个VBO存颜色
gl.BindBuffer(OpenGL.GL_ARRAY_BUFFER, vbos[1]);
gl.BufferData(OpenGL.GL_ARRAY_BUFFER, colors, OpenGL.GL_DYNAMIC_DRAW); //GL_DYNAMIC_DRAW GL_STATIC_DRAW
pArrayInColor1 = Marshal.AllocHGlobal(colors.Length * sizeof(float)); //申请数组的指针。
gl.VertexAttribPointer((uint)colorLocation, 4, OpenGL.GL_FLOAT, false, 0, IntPtr.Zero); //申明颜色解释规则。
gl.EnableVertexAttribArray((uint)colorLocation);
gl.BindVertexArray(0);
}
public bool GetCompileStatus(uint shar)
{
int[] parameters = new int[] { 0 };
gl.GetShader(shar, OpenGL.GL_COMPILE_STATUS, parameters);
return parameters[0] == OpenGL.GL_TRUE;
}
//实时渲染
private void OpenGLControl1_OpenGLDraw_1(object sender, SharpGL.WPF.OpenGLRoutedEventArgs args)
{
Dispatcher.Invoke(new Action(() =>
{
gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);//DEPTH_BUFFER_BIT在变化窗口大小时需要写,否则变化后不出图
gl.LoadIdentity();
gl.Viewport(0, 0, 200, 200); //设定视口宽高便于观察,因为默认管线编程时,[-1,1]是铺满控件的的,因此自己去计算百分比就好了。
gl.Translate(0f, 0f, -6f);
gl.UseProgram(sProgram); //激活着色器
gl.BindVertexArray(vaos[0]);//开启VAO
//重新设置顶点位置数据,动态改变位置坐标
//Random random = new Random();
//float randomNumber = (float)random.NextDouble();
//vertices[0] = randomNumber;
//gl.BindBuffer(OpenGL.GL_ARRAY_BUFFER, vbos[0]);
//gl.BufferData(OpenGL.GL_ARRAY_BUFFER, vertices, OpenGL.GL_DYNAMIC_DRAW);
gl.DrawArrays(OpenGL.GL_TRIANGLES, 0, 3);//绘制三角形
gl.BindVertexArray(0); //关闭VAO
gl.UseProgram(0);
gl.Flush();
}));
}
}
}
三、效果
附:对函数 VertexAttribPointer 的解释:
public void VertexAttribPointer(uint index, int size, uint type, bool normalized, int stride, IntPtr pointer);
解释:从一维数组的实际指针位置(pointer + stride * n)开始,每 stride空间长度,取前 size 个float作为要用的数据。(n是取值次数,比如三角形,n就是1~3)
案例一(假设只有一个vbo):
...
float[] vertices = new float[] //顶点位置和颜色数组分开
{
0.5f, 0.5f, 0.0f, //位置数据
0.5f, -0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
0f, 1f, 0f, 1f, //rgba颜色数据
0f, 1f, 0f, 1f,
0.94f, 0.69f, 0.45f,1f
};
...
gl.VertexAttribPointer((uint)positionLocation, 3, OpenGL.GL_FLOAT, false, 3 * sizeof(float), IntPtr.Zero); //申明位置解释规则
gl.EnableVertexAttribArray((uint)positionLocation);
gl.VertexAttribPointer((uint)colorLocation, 4, OpenGL.GL_FLOAT, false, 4 * sizeof(float), (IntPtr)(9 * sizeof(float))); //申明颜色解释规则,9是从第9个指针开始扫描
gl.EnableVertexAttribArray((uint)colorLocation);
...
//对于一维数组来说,取位置的步骤是:
// 1、从一维数组第 0 个float指针位置开始,每3(对应第5个参数)个float取前3(对应第2个参数)个float当做位置;
// 2、从一维数组第 0+3 个float指针位置开始,每3(对应第5个参数)个float取前3(对应第2个参数)个float当做位置;
// 3、从一维数组第 0+3*2 个float指针位置开始,每3(对应第5个参数)个float取前3(对应第2个参数)个float当做位置;
//对于一维数组来说,取颜色的步骤是:
// 1、从一维数组第 9 个float指针位置开始,每4(对应第5个参数)个float取前4(对应第2个参数)个float当做颜色;
// 2、从一维数组第 9+4 个float指针位置开始,每4(对应第5个参数)个float取前4(对应第2个参数)个float当做颜色;
// 3、从一维数组第 9+4*2 个float指针位置开始,每4(对应第5个参数)个float取前4(对应第2个参数)个float当做颜色
//说明:因为没有混合位置和颜色向量,也就是数据分离的很干净,解释器不需要知道步长,故第5个参数,两个也可以填0*sizeof(float)
案例二(假设只有一个vbo):
...
float[] vertices = new float[] //顶点位置和颜色数组混合
{
0.5f, 0.5f, 0.0f, 0f, 1f, 0f, 1f, //位置和颜色数据
0.5f, -0.5f, 0.0f, 0f, 1f, 0f, 1f,
-0.5f, 0.5f, 0.0f, 0.94f, 0.69f, 0.45f,1f
};
...
gl.VertexAttribPointer((uint)positionLocation, 3, OpenGL.GL_FLOAT, false, 7 * sizeof(float), IntPtr.Zero); //申明位置解释规则
gl.EnableVertexAttribArray((uint)positionLocation);
gl.VertexAttribPointer((uint)colorLocation, 4, OpenGL.GL_FLOAT, false, 7 * sizeof(float), (IntPtr)(3 * sizeof(float))); //申明颜色解释规则
gl.EnableVertexAttribArray((uint)colorLocation);
//对于一维数组来说,取位置的步骤是:
// 1、从一维数组第 0 个float指针位置开始,每7个float取前3个float当做位置;
// 2、从一维数组第 0+7 个float指针位置开始,每7个float取前3个float当做位置;
// 3、从一维数组第 0+7*2 个float指针位置开始,每7个float取前3个float当做位置;
//对于一维数组来说,取颜色的步骤是:
// 1、从一维数组第 3 个float指针位置开始,每7个float取前4个float当做颜色;
// 2、从一维数组第 3+7 个float指针位置开始,每7个float取前4个float当做颜色;
// 3、从一维数组第 3+7*2 个float指针位置开始,每7个float取前4个float当做颜色
//因为进行了位置和颜色向量混合,解释器需要知道步长,故第5个参数,需要指定步长,即这里的 7 * sizeof(float)
通过对 VertexAttribPointer 函数的理解,可以发现,案例一二虽然数组定义不同,但是通过设定不同的解析方式。最终渲染效果是一样的。
好了,本次关于sharpGL的使用着色器(更为现代的“管线编程”),一个VAO绑定多个VBO篇就讲到这里了,更多内容,请期待下次的讲述
欢迎有问题的伙伴及时留意讨论,有不足之处还望指正
祝大家生活工作愉快~