渲染管线与shader概述

内容由该网页整理翻译得来(Translate from this website):http://in2gpu.com/2014/10/29/shaders-basics/
这个系列的目录在:http://in2gpu.com/opengl-3/

渲染管线图

这里写图片描述

解释

我们最关注的是顶点着色器碎片着色器,没有这两个着色器就无法渲染出任何东西来。着色器让我们可以自定义如何着色(???)。镶嵌着色器和几何着色器并不是必须的,可以在渲染过程中被跳过。
另外,渲染管线中的这几个步骤无法被opengl使用者直接更改:
- 获取顶点
从cpu获取顶点信息
- 光栅化

Rasterisation (or rasterization) is the task of taking an image described in a vector graphics format (shapes) and converting it into a raster image (pixels or dots) for output on a video display or printer, or for storage in a bitmap file format.
- 操作帧缓存
输出到gpu帧缓存,待显示器读取

shader交互简述

必须遵守:
- 一个shader的输入类型必须和上一个shader的输出类型相符
- shader的输出不能跳过某一个shader直到这某一个shader的下一级(即是一定要链式下来)

一般编写shader使用GLSL语言,挺像C++的,但是还有其他选择可以使用,譬如HLSL, fx等等。

读取并载入shader

我们需要新建一个loader类来专门载入loader。首先这属于一些核心类,所以我们在项目里新建一个叫做Core的文件夹,然后新建Shader_Loader.h和Shader_Loader.cpp文件。
这里先列出来头文件:

#pragma once

#include "../Dependencies/glew/glew.h"
#include "../Dependencies/freeglut/freeglut.h"
#include <iostream>

namespace Core
{
    class Shader_Loader
    {
    private:
        std::string ReadShader(char *filename);
        GLuint CreateShader(GLenum shaderType,
            std::string source,
            char* shadername);

    public:
        Shader_Loader();
        ~Shader_Loader();
        GLuint CreateProgram(char* VertexShaderFilename,
            char* FragmentShaderFilename)
    };
}

可以看到私有方法ReadShader返回的是一个string,而创建shader的输入就是上一步read出来的string,而返回了一个GLuint。
看看公有方法CreateProgram,应该就是内部调用了上面说的两个方法。
包含这两个头文件主要是为了使用GLuint和GLchar,还有我们需要glLinkProgram和glAttachShader方法。OpenGL把包含shader的容器成为program,我也不知道为啥,看起来混淆性很强。
接下来看cpp文件

#include "stdafx.h"
#include "Shader_Loader.h"
#include <fstream>
#include <vector>

using namespace Core;

using std::string;
using std::fstream;

string  Shader_Loader::ReadShader(char* filename){
    string shaderCode;
    std::ifstream file(filename, std::ios::in);

    if (!file.good()){
        std::cout << "Cannot read file" << filename << std::endl;
        std::terminate();
    }

    file.seekg(0, std::ios::end);
    shaderCode.resize((size_t)file.tellg());
    file.seekg(0, std::ios::beg);
    file.read(&shaderCode[0], shaderCode.size());
    file.close();

    return shaderCode;
}


GLuint Shader_Loader::CreateShader(GLenum shaderType, string source, char* shaderName){
    int compile_result = 0;

    GLuint shader = glCreateShader(shaderType);
    // 返回一个const的char指针
    const char* shader_code_ptr = source.c_str();
    const int shader_code_size = source.size();

    // 第三个参数是char指针的指针...
    glShaderSource(shader, 1, &shader_code_ptr, &shader_code_size);
    glCompileShader(shader);
    // 大量使用这种类似于c#里的out参数的参数列表,其实使用引用不就可以了?
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_result);

    if (compile_result == GL_FALSE){
        int info_log_length = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_log_length);
        std::vector<char> shader_log(info_log_length);
        glGetShaderInfoLog(shader, info_log_length, NULL, &shader_log[0]);
        std::cout << "ERROR compiling shader: " << shaderName << std::endl << &shader_log[0] << std::endl;
        return false;
    }

    return shader;
}

GLuint Shader_Loader::CreateProgram(char* vertexShaderFilename, char* fragmentShaderFilename){
    string vertex_shader_code = ReadShader(vertexShaderFilename);
    string fragment_shader_code = ReadShader(fragmentShaderFilename);

    // 得到GLuint格式的shader id
    GLuint vertex_shader = CreateShader(GL_VERTEX_SHADER, vertex_shader_code, vertexShaderFilename);
    GLuint fragment_shader = CreateShader(GL_FRAGMENT_SHADER, fragment_shader_code, fragmentShaderFilename);

    int link_result = 0;

    // 创建一个空的program,绑定两个shader id,连接,然后得到program id
    GLuint program = glCreateProgram();
    glAttachShader(program, vertex_shader);
    glAttachShader(program, fragment_shader);

    glLinkProgram(program);
    glGetProgramiv(program, GL_LINK_STATUS, &link_result);

    if (link_result == GL_FALSE){
        int info_log_length = 0;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &info_log_length);
        std::vector<char> program_log(info_log_length);
        glGetProgramInfoLog(program, info_log_length, NULL, &program_log[0]);
        std::cout << "Shader Loader: LINK ERROR" << std::endl << &program_log[0] << std::endl;
        return false;
    }
}

基本上都是遵循这样的一个顺序:
1. 使用gl方法创建一个空object,返回的不是object本身,而是它的id
2. 对于shader,传入的原始数据以建成shader;对于program,传入绑定的shader之后link
3. 检查resultCode
4. 返回object id

最后一步的检查错误码很重要,可以省去后面的很多迷之错误。

使用

得到这个program所对应的id之后,我们需要在我们的渲染循环里写这句话来使用它

**glUseProgram(program);**

另外

  • 顶点着色器输出的是gl_Position,决定了最后每个顶点的坐标。
  • 片段着色器的输出是颜色向量。如果没有片段着色器,那最后每个像素都是黑色。

猜你喜欢

转载自blog.csdn.net/hhh132/article/details/81633606