Dev:2.8/Source/OpenGL/Shaders

提供: wiki
移動先: 案内検索

OpenGL Refactoring: Shaders

Overview

Shaders must be used for all drawing in the core profile, so we must write our own GLSL shaders with support for drawing textured polgyons, solid lighting, stippling, and so on.

For some drawing code it makes sense to write a custom GLSL shader, but a lot of code only needs basic constant color or textured drawing, and for those cases we should have some shared shaders available that can easily be bound for drawing.

We have APIs for dynamically generating shader code, binding vertex attributes, etc. However this is too much tied to the GLSL material draw mode, and should be made more generic so that it is possible to write custom shaders for various purposes.

Tasks


  • Implement proposal for low level shader API
    • Move GPUVertexAttribs into GPUShader
    • Implement GPUShaderParams
    • Update existing users of GPUShader for API changes
      • Smoke shaders
      • Basic shader
      • GLSL materials
      • Viewport FX shaders
      • ...
  • Figure out where to put blend modes, culling, depth tests, .. maybe in GPUShader?

Design Proposal

The shader API would have 4 main types.

  • GPUShader: a set of compiled GLSL shaders and information about the uniforms, textures, vertex attributes they use. It can represent many types of shaders, for GLSL node materials, matcaps, UI buttons, selected faces, wireframes, etc.
  • GPUShaderParams: set of parameters like uniforms and textures for a particular GPUShader. Parameters are stored separately from the GPUShader, so that for example a wireframe shader can be used multiple times but with different colors.
  • GPUShaderInstance: GPUShader + list of GPUShaderParams, ready to use for drawing.
  • GPUShaderVertexAttribs: a set of vertex attributes required for drawing with a particular GPUShader.

A basic shader could be used like this:

GPUShaderInstance shader = GPU_basic_color_shader(rgb_value);
draw(&shader, vertex_buffers, transformation);

Or in a more advanced case:

// init material related parameters
FunkyMaterialParams funky;
funky.diffuse_color = ...;
funky.specular_roughness = ...;
funky.alpha_texture = ...;

// retrieve shared light related parameters
GPUShaderParams *light_params = view3d_light_parameters(...);

// create shader instance
GPUShaderInstance shader = funky_shader_new(&funky, light_params);

// create vertex buffers
GPUVertexBuffers vertex_buffers = vertex_buffers_new();
for attrib in GPU_shader_vertex_attribs(shader):
    vertex_buffers.add(...)

// ready to draw
draw(&shader, vertex_buffers, transformation);

Note that the above code does not rely on any global state. We do not bind shaders or set a current color, all the information needed is passed explicitly to the draw function.

In GLSL shader code the current transformation is a uniform like any other, but for convenience the draw function can automatically add it to the shader instance.

Notes

  • In most cases the shaders, shader parameters and vertex buffers would be created once and cached for subsequent redraws. Some of those caches would be global (UI drawing shaders) and others tied to specific datablocks and freed along with them (GLSL node materials).
  • In Vulkan terminology, a GPUShader is a Pipeline, and GPUShaderParames corresponds to a Descriptor Set.
  • With Vulkan draw calls would all go into a command buffer, and multiple such command buffers might be created by multiple threads at the same time. This is something we should aim for in API design, but it will take a while before we get there.

Basic Shader Example

The implementation of a basic shader would looking something like this. The GPUShader would be cached, not actually compiled every time.

GPUShaderInstance GPU_basic_color_shader(float color[3])
{
    GPUShader *shader = GPU_shader_new(vertex_code, fragment_code, ...);

    GPUShaderParams *params = GPU_shader_params_new(...);
    GPU_shader_params_set_uniform_vec3(params, "color", color);
    GPU_shader_params_finalize(params);

    return GPUShaderInstance(shader, params);
}

Advanced Shader Example

A more complicated shader might look like this:

struct FunkyLight
{
    float intensity;
};

GPUShaderParams *funky_light_params_new(FunkyLight lights[], int num_lights);
{
    GPUShaderParams *params = GPU_shader_params_new(...);
    for (int i = 0; i < num_lights; i++)
        GPU_shader_params_set_float(params, "intensity_" + i, lights[i].intensity);

    return params;
}

struct FunkMaterialParams
{
    float diffuse_color[3];
    float specular_roughness;
    GPUTexture *alpha_texture;
};

GPUShaderInstance funky_shader_new(FunkyMaterialParams *funky, GPUShaderParams *light_params)
{
    GPUShader *shader = GPU_shader_new(vertex_code, fragment_code, ...);

    GPUShaderParams *material_params = GPU_shader_params_new(...);
    GPU_shader_params_set_uniform_vec3(material_params, "diffuse_color", funk->diffuse_color);
    ....
    GPU_shader_params_finalize(params);

    return GPU_shader_instance(shader, material_params, light_params);
}