之前已经接触过Vertex Shader和Fragment Shader,这次学习如何编写Shader并封装成类。

Shader源码主要有四部分:

  1. 版本声明 #version xxx core
  2. 使用in和out关键字定义输入输出变量,上一个Shader的输出变量必须和下一个Shader的输入变量保持一致;
  3. 有时使用uniform关键字定义全局变量;
  4. main主函数。

看一个Vertex Shader的例子:

#version 330 core
layout (location = 0) in vec3 Pos;
out vec4 Color;
void main()
{
gl_Position = vec4(Pos, 1.0f);
Color = vec4(1.0f, 0.0f, 0.0f, 1.0f);
}

首先定义一个location为0的三维输入变量Pos,表示顶点的位置属性。之后用一个四维的Color变量表示输出的颜色。

在main函数中需要将Pos变换成齐次坐标,输出变量Color直接设置为红色。

Fragment Shader就很容易编写了,只需要把Vertex Shader的输出变量作为输入:

#version 330 core
in vec4 Color;
out vec4 fragColor;
void main()
{
fragColor = Color;
}

深入一点,考虑如何实现三角形颜色渐变的效果。基本想法是颜色变量在渲染循环中随着时间的变化而变化,这就需要uniform定义一个全局变量来表示颜色。

首先是Vertex Shader:

#version 330 core
layout (location = 0) in vec3 Pos;
void main()
{
gl_Position = vec4(Pos, 1.0f);
}

在Fragment Shader中使用uniform关键字取代in关键字:

#version 330 core
uniform vec4 Color;
out vec4 fragColor;
void main()
{
fragColor = Color;
}

在渲染循环中,调用glfwGetTime函数获取从GLFW被初始化后经过的时间,使用sin函数根据时间计算绿色分量的值,并映射到[0, 1]。不要忘记添加cmath头文件。

接下来调用glGetUniformLocation函数获取Color变量在Shader Program中的位置,并调用glUniform函数更新Color的值。注意glUniform函数后的后缀4f,表示Color的值是一个float类型的四维向量:

glUseProgram(shaderProgram);
float timeValue = glfwGetTime();
float greenValue = sin(timeValue)/2.0f+0.5f;
int uniformlocation = glGetUniformLocation(shaderProgram, "Color");
glUniform4f(uniformlocation, 0.0f, greenValue, 0.0f, 1.0f);

运行代码后会发现三角形颜色逐渐由绿变成黑,再变成绿。


之前所有的代码都只涉及了一个顶点属性,即Pos用来表示顶点的位置属性。如果在Vertex Shader中再定义一个location为1的三维输入变量,表示顶点的颜色属性,那么该如何配置VBO和顶点属性指针,并和VAO绑定呢?

首先在顶点数据中添加颜色数据,位置(0.5f, 0.5f, 0.0f)的顶点颜色为(1.0f, 0.0f, 0.0f),(0.5f, -0.5f, 0.0f)的顶点颜色为(0.0f, 1.0f, 0.0f),(-0.5f, 0.5f, 0.0f)的顶点颜色为(0.0f, 0.0f, 1.0f):

float vertices[] = {0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f};

编写Vertex Shader,Pos的属性位置为0,表示位置,Col的属性位置为1,表示颜色:

#version 330 core
layout (location = 0) in vec3 Pos;
layout (location = 1) in vec3 Col;
out vec4 Color;
void main()
{
gl_Position = vec4(Pos, 1.0f);
Color = vec4(Col, 1.0f);
}

不需要更改Fragment Shader:

#version 330 core
in vec4 Color;
out vec4 fragColor;
void main()
{
fragColor = Color;
}

最后配置VBO和顶点属性指针并绑定VAO:

glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)0); //配置位置
glEnableVertexAttribArray(0); //使能属性位置0
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3*sizeof(float))); //配置颜色
glEnableVertexAttribArray(1); //使能属性位置1

Shadr的创建,源码编译和链接成Shader Program需要编写很多重复代码,为了减少工作量,将这部分代码封装成类。

这个Shader类很简单,只包含了两个函数:

  1. shader函数负责处理Shader的创建,源码编译和链接;
  2. use函数负责激活Shader Program。

代码如下:

#ifndef SHADER_H
#define SHADER_H #include <glad/glad.h>
#include <iostream>
#include <fstream>
#include <sstream> using namespace std; class Shader{
public:
unsigned int ID;
void shader(const char *vertexPath, const char *fragmentPath)
{
string vertexCode;
string fragmentCode;
ifstream vertexShaderFile;
ifstream fragmentShaderFile;
stringstream vertexStream;
stringstream fragmentStream;
const char *vertexShaderSource;
const char *fragmentShaderSource;
vertexShaderFile.open(vertexPath);
fragmentShaderFile.open(fragmentPath);
vertexStream << vertexShaderFile.rdbuf();
fragmentStream << fragmentShaderFile.rdbuf();
vertexShaderFile.close();
fragmentShaderFile.close();
vertexCode = vertexStream.str();
fragmentCode = fragmentStream.str();
vertexShaderSource = vertexCode.c_str();
fragmentShaderSource = fragmentCode.c_str(); int vertexShader;
int fragmentShader;
int success;
char infoLog[512];
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if(!success){
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
cout << "ERROR::VERTEXSHADER::COMPILATION_FAILED\n" << infoLog << endl;
}
else cout << "VERTEXSHADER_COMPILATION_SUCCESS" << endl;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success){
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
cout << "ERROR::FRAGMENTSHADER::COMPILATION_FAILED\n" << infoLog << endl;
}
else cout << "FRAGMENTSHADER_COMPILATION_SUCCESS" << endl; ID = glCreateProgram();
glAttachShader(ID, vertexShader);
glAttachShader(ID, fragmentShader);
glLinkProgram(ID);
glGetProgramiv(ID, GL_LINK_STATUS, &success);
if(!success){
glGetProgramInfoLog(ID, 512, NULL, infoLog);
cout << "ERROR::LINKING_FAILED\n" << infoLog << endl;
}
else cout << "LINKING_SUCCESS" << endl; glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
}
void use()
{
glUseProgram(ID);
}
}; #endif // SHADER_H

这样在使用时,只需要将其添加到工程中,并添加头文件“shader.h”。

补:今天看书时有提到,定义class最好不要使用using namespace xx。懒得改了。。。

OpenGL学习(3)——Shader的更多相关文章

  1. OpenGL学习——自定义Shader工具类

    从文件读取Vertex Shader 和 Fragment Shader的工具类. 代码如下: Shader.h #ifndef Shader_h #define Shader_h // GLEW # ...

  2. OpenGL学习之路(三)

    1 引子 这些天公司一次次的软件发布节点忙的博主不可开交,另外还有其它的一些事也占用了很多时间.现在坐在电脑前,在很安静的环境下,与大家分享自己的OpenGL学习笔记和理解心得,感到格外舒服.这让我回 ...

  3. OpenGL学习笔记3——缓冲区对象

    在GL中特别提出了缓冲区对象这一概念,是针对提高绘图效率的一个手段.由于GL的架构是基于客户——服务器模型建立的,因此默认所有的绘图数据均是存储在本地客户端,通过GL内核渲染处理以后再将数据发往GPU ...

  4. OpenGL学习进程(12)第九课:矩阵乘法实现3D变换

    本节是OpenGL学习的第九个课时,下面将详细介绍OpenGL的多种3D变换和如何操作矩阵堆栈.     (1)3D变换: OpenGL中绘制3D世界的空间变换包括:模型变换.视图变换.投影变换和视口 ...

  5. OpenGL学习进程(11)第八课:颜色绘制的详解

        本节是OpenGL学习的第八个课时,下面将详细介绍OpenGL的颜色模式,颜色混合以及抗锯齿.     (1)颜色模式: OpenGL支持两种颜色模式:一种是RGBA,一种是颜色索引模式. R ...

  6. OpenGL学习笔记:拾取与选择

    转自:OpenGL学习笔记:拾取与选择 在开发OpenGL程序时,一个重要的问题就是互动,假设一个场景里面有很多元素,当用鼠标点击不同元素时,期待作出不同的反应,那么在OpenGL里面,是怎么知道我当 ...

  7. OpenGL学习之路(一)

    1 引子 虽然是计算机科班出身,但从小对几何方面的东西就不太感冒,空间想象能力也较差,所以从本科到研究生,基本没接触过<计算机图形学>.为什么说基本没学过呢?因为好奇(尤其是惊叹于三维游戏 ...

  8. OpenGL学习之路(四)

    1 引子 上次读书笔记主要是学习了应用三维坐标变换矩阵对二维的图形进行变换,并附带介绍了GLSL语言的编译.链接相关的知识,之后介绍了GLSL中变量的修饰符,着重介绍了uniform修饰符,来向着色器 ...

  9. OpenGL学习之路(五)

    1 引子 不知不觉我们已经进入到读书笔记(五)了,我们先对前四次读书笔记做一个总结.前四次读书笔记主要是学习了如何使用OpenGL来绘制几何图形(包括二维几何体和三维几何体),并学习了平移.旋转.缩放 ...

  10. OpenGL学习之windows下安装opengl的glut库

    OpenGL学习之windows下安装opengl的glut库 GLUT不是OpenGL所必须的,但它会给我们的学习带来一定的方便,推荐安装.  Windows环境下的GLUT下载地址:(大小约为15 ...

随机推荐

  1. CreateProjectFormat——初始项目目录格式

    百度云链接:https://pan.baidu.com/s/1f_2YaMf9619Rt5AlC6nryQ 密码:v2cb 修改中...

  2. 【转】Java学习---快速掌握RPC原理及实现

    [原文]https://www.toutiao.com/i6592365493435236872/ ​RPC概述 RPC(Remote Procedure Call)即远程过程调用,也就是说两台服务器 ...

  3. November 11th, 2017 Week 45th Saturday

    Happiness is a direction, not a place. 快乐是一个方向,不是一个目的. Do you remember those moments in your life wh ...

  4. 写博的第一天 0x00

    作为一个大学生,我觉得自己除了睡觉,打游戏,吃饭更应该做点有意义的事情,作为计算机专业的学生,更应该勤奋点,主动去学习.已经要大三了,我认识到这个暑假我必须学点什么了,为了更好的学习,我决定学语言的同 ...

  5. Case Study: 技术和商业的结合点在哪里?

    如果要制作过去两三年里最火爆的企业级产品创业公司名单,里面一定少不了一家叫做Docker的公司.先别管Docker是做什么的,只要记住这是这几年企业级IT产品里最重要的热点技术,有一半的大型企业已经在 ...

  6. [python]关于在python中模块导入问题追加总结

    [背景] 最近在写程序时,我使用的eclipse编辑器运行都没有问题,然后部署到自动化环境上却偏偏报找不到相应模块问题,现在对该问题在之前的贴子上追加总结 原帖子:[python]关于python中模 ...

  7. Scala学习之路 (九)Scala的上界和下届

    一.泛型 1.泛型的介绍 泛型用于指定方法或类可以接受任意类型参数,参数在实际使用时才被确定,泛型可以有效地增强程序的适用性,使用泛型可以使得类或方法具有更强的通用性.泛型的典型应用场景是集合及集合中 ...

  8. 8、JVM--虚拟机字节码执行引擎

    8.1.概述 执行引擎是Java虚拟机最核心的组成部分之一.“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上 ...

  9. [转]numpy中的np.max 与 np.maximum区别

    转自:https://blog.csdn.net/lanchunhui/article/details/52700895

  10. 使用java实现hex和ascii码的转换

    几乎很少写JAVA代码,第一是确实不会,第二感觉JAVA写起来不爽(较python.golang),但总有万不得已必须要用java的时候.这里记录下使用java实现的hex十六进制和acsii码之间的 ...