//main.cpp
int u_vertexColorLocation = glGetUniformLocation(shaderProgram, "u_color");
glUseProgram(shaderProgram);
glUniform4f(u_vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
//VertexShader.glsl
#version 330 core
layout (location = 0) in vec3 aPos; // the position variable has attribute position 0
out vec4 vertexColor; // specify a color output to the fragment shader
uniform vec4 u_color;
void main()
{
gl_Position = vec4(aPos, 1.0); // see how we directly give a vec3 to vec4's constructor
vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // set the output variable to a dark-red color
}
//FragmentShader.glsl
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // the input variable from the vertex shader (same name and same type)
uniform vec4 u_color;
void main()
{
FragColor = u_color;
}
(上面三段程序是理解GLSL运行用的,主要是关于uniform变量)
GLSL是如何从程序获取和传递数据的?
每个stage所需要的数据逻辑上有三个来源:由OpenGL管理的数据、前一stage、由render loop传递。(硬编码数据来源的实现就是普通的编译实现)
由于渲染管线可以非常复杂,所以每个stage都可能三个通道共同使用。
-
OpenGL管理一部分数据,是因为这部分数据必要且操作机械,所以可以处于半封装的形式。
-
如VertexShader中必须给
gl_Position赋值,但是后面stages使用它但不需要直接写出,因为对三维点的处理方式是默认的。这就是.glsl第一种数据通道。 -
还有layout(location = n) in从OpenGL的VAA中获取对应顶点属性,这里的location其实是与
glVertexAttribPointer()中第一个参数关联的。
-
-
数据的前后传递使用in/out加同一变量名的形式,直观模型就是同接口拼图或加工传送带。这是第二种
-
由render loop提供的数据,使用的是.glsl中uniform变量和render loop中
glGetUniformLocation()和glUniformxx()共同实现的对接。这个是最难理解的。glGetUniformLocation()能获取与一个uniform变量对应的虚拟地址,这个地址就能作为glUniformxx()的参数实现在render loop中修改shader内变量的值。其实现方式是shader program在需要使用uniform变量时,会从uniform变量表中查找它的值。这是第三种。
(虽然有些功能看似能合写成一个函数但是作为api不应该这么做,这样才能为程序员提供更多封装方式)
第一和第三种其实可以不作区分,因为这三种传输数据的形式都是由OpenGL管理、并建议在render loop中进行传递的。所以可以四种都分开记。
记忆:gl_、layout、in/out、uniform
uniform变量如何很好地使用和封装
由于在render loop中又添加了一个新功能,所以其布局再次修改:
while(!glfwWindowShouldClose(window)){
//process the device input
processInput(window);
//render the background
glClearColor(0.2f,0.3f,0.3f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//use the program
glUseProgram(shaderProgram);
//update the uniform variables
...//calculate the value
type xxLocation = glGetUniformLocation(shaderProgram,"u_xx");
glUniformxx(xxLocation,...);//assign the value
//select object and render the triangles
glBindVertexArray(VAO);
glDrawxx(...);
//swap and poll
glfwSwapBuffers(window);
glfwPollEvents();
}
cpp流对象异常处理进阶
明确需要理解使用的成员函数:exceptions、clear、rdstate。
位掩码的使用都会。
如果你希望在使用文件流时将整个文件传入,通常可能会选择一行一行加在std::string上,但是也可以:
ifstream xxFile;
stringstream xxSStream;
string xxStr;
xxFile.open("...");
xxStream << xxFile.rdbuf();
xxStr = xxStream.str();
const char* xxSource = xxStr.c_str();
.rdbuf()成员返回的是filebuf类型,所以使用字符串流,再用.str()过渡一下到字符串类型,再.c_str()到C字符串。其中每个变量都不能是临时的,通过了实践的检验。
前两个好理解,xxStr、xxSource必须具名的原因是在使用
glShaderSource()进行一次字符串复制前,字符串必须要有被分配一个内存空间。以及如果不想预定义一个超大的字符数组,就要在初始化时给常字符指针赋字符串值。
(C风格字符串复制真挺麻烦的)
以std::ifstream流对象代指所有流对象,流对象可能有四种状态:
-
std::ifstream::goodbit -
std::ifstream::eofbit -
std::ifstream::failbit -
std::ifstream::badbit
都是位掩码形式。但是这些状态本身不会引起抛出异常,通常是可以继续运行的。
.exceptions(std::ifstream::failbit|std::ifstream::badbit)设置了会抛出异常的状态。
.rdstate()返回当前状态。
.clear(std::ifstream::badbit)设置当前状态,默认参数为goodbit相当于清楚异常状态。
.good()``.eof()``.fail()``.bad()当处于某状态时返回true。
如果你需要让流对象在出问题时能中断程序或进行处理等,请使用该方法。
void sourceInput(unsigned int vertexShader, unsigned int fragmentShader, const char* vertexShaderPath, const char* fragmentShaderPath)
{
std::ifstream vertexShaderFile;
std::ifstream fragmentShaderFile;
std::string vertexShaderStr;
std::string fragmentShaderStr;
vertexShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fragmentShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try
{
std::stringstream vertexShaderStringStream;
std::stringstream fragmentShaderStringStream;
vertexShaderFile.open(vertexShaderPath);
fragmentShaderFile.open(fragmentShaderPath);
vertexShaderStringStream << vertexShaderFile.rdbuf();
fragmentShaderStringStream << fragmentShaderFile.rdbuf();
vertexShaderFile.close();
fragmentShaderFile.close();
vertexShaderStr = vertexShaderStringStream.str();
fragmentShaderStr = fragmentShaderStringStream.str();
}
catch (const std::exception&)
{
std::cerr << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}
const char* vertexShaderSource = vertexShaderStr.c_str();
const char* fragmentShaderSource = fragmentShaderStr.c_str();
std::cout << vertexShaderSource << std::endl;
std::cout << fragmentShaderSource << std::endl;
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
}
多个VAA是如何处理的?
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
//......
float vertices[] = {
// first triangle
-0.9f, -0.5f, 0.0f, 1.0f,0.0f, 0.0f, // left
-0.0f, -0.5f, 0.0f, 0.0f,1.0f, 0.0f, // right
-0.45f, 0.5f, 0.0f, 0.0f,0.0f, 1.0f, // top
};
unsigned int VBO;
glGenBuffers(1, &VBO);
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);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
//......
glBindVertexArray(0);
第二个参数为3,从这里可以更容易看出3指的是在由offset和stride确定的每个位置要取多大一段数据。
也可以看出一个VAO对应多个VAA。

comment 评论区
star_outline 咱快来抢个沙发吧!