Dev:2.8/Source/OpenGL/Shaders
目次
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
Add polygon stipple support to basic GLSL shaderPort smoke shader to GLSL- Add LOD bias in GLSL basic shader for icon drawing
- Add glShadeModel(GL_FLAT) support in GLSL shaders (using dFdx / dFdy?)
- Implement texture paint multitexture masking shader in GLSL
- Optimize basic GLSL shader
Fix remaining draw errors when switching basic shader to use GLSL
(d01499a45c b9de44f458 5e063ce6c9 eaa19177e7 5bd9e83289)- Add test .blend covering most GLSL material features
- 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?
- Convert UI code to use basic shader instead of fixed function
Line width and stipple (not just a shader, requires right geometry too)
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);
}