自定义着色器

业务中可能有一些特殊的渲染需求,例如水流特效,这时候就需要通过自定义着色器 (Shader)去实现。

创建着色器

Shader 类 封装了顶点着色器、片元着色器、着色器预编译、平台精度、平台差异性。他的创建和使用非常方便,用户只需要关注 shader 算法本身,而不用纠结于使用什么精度,亦或是使用 GLSL 哪个版本的写法。下面是一个简单的 demo:

import { Material, Shader, Color } from "@galacean/engine";
 
//-- Shader 代码
const vertexSource = `
  uniform mat4 renderer_MVPMat;
 
  attribute vec3 POSITION; 
 
  void main() {
    gl_Position = renderer_MVPMat * vec4(POSITION, 1.0);
  }
  `;
 
const fragmentSource = `
  uniform vec4 u_color;
 
  void main() {
    gl_FragColor = u_color;
  }
  `;
 
// 创建自定义 shader(整个 runtime 只需要创建一次)
Shader.create("demo", vertexSource, fragmentSource);
 
// 创建材质
const material = new Material(engine, Shader.find("demo"));

Shader.create()用来将 shader 添加到引擎的缓存池子中,因此整个 runtime 只需要创建一次,接下来就可以通过 Shader.find(name) 来反复使用.

注:引擎已经预先 create 了 blinn-phong、pbr、shadow-map、shadow、skybox、framebuffer-picker-color、trail。用户可以直接使用这些内置 shader,并且不能重名创建。

在上述的案例中,因为我们没有上传 u_color 变量,所以片元输出还是黑色的(uniform 默认值),接下来我们来介绍下引擎内置的 shader 变量以及如何上传自定义变量。

内置 shader 变量

在上面,我们给 material 赋予了 shader,这个时候程序已经可以开始渲染了。

需要注意的是,shader 代码中有两种变量,一种是逐顶点attribute 变量,另一种是逐 shaderuniform 变量。(在 GLSL300 后,统一为 in 变量)

引擎已经自动上传了一些常用变量,用户可以直接在 shader 代码中使用,如顶点数据和 mvp 数据,下面是引擎默认上传的变量。

顶点输入

逐顶点数据attribute name数据类型
顶点POSITIONvec3
法线NORMALvec3
切线TANGENTvec4
顶点颜色COLOR_0vec4
骨骼索引JOINTS_0vec4
骨骼权重WEIGHTS_0vec4
第一套纹理坐标TEXCOORD_0vec2
第二套纹理坐标TEXCOORD_1vec2

属性

渲染器

名字类型解释
renderer_LocalMatmat4模型本地坐标系矩阵
renderer_ModelMatmat4模型世界坐标系矩阵
renderer_MVMatmat4模型视口矩阵
renderer_MVPMatmat4模型视口投影矩阵
renderer_NormalMatmat4法线矩阵

相机

名字类型解释
camera_ViewMatmat4视口矩阵
camera_ProjMatmat4投影矩阵
camera_VPMatmat4视口投影矩阵
camera_ViewInvMatmat4视口逆矩阵
camera_Positionvec3相机位置
camera_DepthTexturesampler2D深度信息纹理
camera_DepthBufferParamsVec4相机深度缓冲参数:(x: 1.0 - far / near, y: far / near, z: 0, w: 0)
camera_ProjectionParamsVec4投影矩阵相关参数:(x: flipProjection ? -1 : 1, y: near, z: far, w: 0)

时间

名字类型解释
scene_ElapsedTimevec4引擎启动后经过的总时间:(x: t, y: sin(t), z:cos(t), w: 0)
scene_DeltaTimevec4距离上一帧的间隔时间:(x: dt, y: 0, z:0, w: 0)

名字类型解释
scene_FogColorvec4雾的颜色
scene_FogParamsvec4雾的参数:(x: -1/(end-start), y: end/(end-start), z: density / ln(2), w: density / sqr(ln(2))

上传着色器数据

attribute 逐顶点数据的上传请参考 网格渲染器,这里不再赘述。

除了内置的变量,我们可以在着色器中上传任何自定义名字的变量,我们唯一要做的就是根据着色器数据类型,使用正确的接口。上传的接口全部保存在 ShaderData 中,而 shaderData 实例对象又分别保存在引擎的四大类 SceneCameraRendererMaterial 中,我们只需要分别往这些 shaderData 中调用接口,上传变量,引擎便会在底层自动帮我们组装这些数据,并进行判重等性能的优化。

着色器数据分开的好处

着色器数据分别保存在引擎的四大类 SceneCameraRendererMaterial 中,这样做的好处之一就是底层可以根据上传时机上传某一块 uniform,提升性能;另外,将材质无关的着色器数据剥离出来,可以实现共享材质,比如两个 renderer ,共享了一个材质,虽然都要操控同一个 shader,但是因为这一部分 shader 数据的上传来源于两个 renderer 的 shaderData,所以是不会影响彼此的渲染结果的。

如:

const renderer1ShaderData = renderer1.shaderData;
const renderer2ShaderData = renderer2.shaderData;
const materialShaderData = material.shaderData;
 
materialShaderData.setColor("material_color", new Color(1, 0, 0, 1));
renderer1ShaderData.setFloat("u_progross", 0.5);
renderer2ShaderData.setFloat("u_progross", 0.8);

调用接口

着色器数据的类型和分别调用的 API 如下:

shader 类型ShaderData API
boolintsetInt( value: number )
floatsetFloat( value: number )`
bvec2ivec2vec2setVector2( value:Vector2 )
bvec3ivec3vec3setVector3( value:Vector3 )
bvec4ivec4vec4setVector4( value:Vector4 )
mat4setMatrix( value:Matrix )
float[]vec2[]vec3[]vec4[]mat4[]setFloatArray( value:Float32Array )
bool[]int[]bvec2[]bvec3[]bvec4[]ivec2[]ivec3[]ivec4[]setIntArray( value:Int32Array )
sampler2DsamplerCubesetTexture( value:Texture )
sampler2D[]samplerCube[]setTextureArray( value:Texture[] )

代码演示如下:

// shader
 
uniform float u_float;
uniform int u_int;
uniform bool u_bool;
uniform vec2 u_vec2;
uniform vec3 u_vec3;
uniform vec4 u_vec4;
uniform mat4 u_matrix;
uniform int u_intArray[10];
uniform float u_floatArray[10];
uniform sampler2D u_sampler2D;
uniform samplerCube u_samplerCube;
uniform sampler2D u_samplerArray[2];
 
// GLSL 300:
// in float u_float;
// ...
// shaderData 可以分别保存在 scene 、camera 、renderer、 material 中。
const shaderData = material.shaderData;
 
shaderData.setFloat("u_float", 1.5);
shaderData.setInt("u_int", 1);
shaderData.setInt("u_bool", 1);
shaderData.setVector2("u_vec2", new Vector2(1, 1));
shaderData.setVector3("u_vec3", new Vector3(1, 1, 1));
shaderData.setVector4("u_vec4", new Vector4(1, 1, 1, 1));
shaderData.setMatrix("u_matrix", new Matrix());
shaderData.setIntArray("u_intArray", new Int32Array(10));
shaderData.setFloatArray("u_floatArray", new Float32Array(10));
shaderData.setTexture("u_sampler2D", texture2D);
shaderData.setTexture("u_samplerCube", textureCube);
shaderData.setTextureArray("u_samplerArray", [texture2D, textureCube]);

:为了性能考虑,引擎暂不支持 结构体数组上传、数组单个元素上传。

宏开关

除了 uniform 变量之外,引擎将 shader 中的宏定义也视为一种变量,因为宏定义的开启/关闭 将生成不同的着色器变种,也会影响渲染结果。

如 shader 中有这些宏相关的操作:

#ifdef DISCARD
	discard;
#endif
 
#ifdef LIGHT_COUNT
	uniform vec4 u_color[ LIGHT_COUNT ];
#endif

也是通过 ShaderData 来操控宏变量:

// 开启宏开关
shaderData.enableMacro("DISCARD");
// 关闭宏开关
shaderData.disableMacro("DISCARD");
 
// 开启变量宏
shaderData.enableMacro("LIGHT_COUNT", "3");
 
// 切换变量宏。这里底层会自动 disable 上一个宏,即 “LIGHT_COUNT 3”
shaderData.enableMacro("LIGHT_COUNT", "2");
 
// 关闭变量宏
shaderData.disableMacro("LIGHT_COUNT");

封装自定义材质

这部分的内容是结合上文所有内容,给用户一个简单的封装示例,希望对您有所帮助:

import { Material, Shader, Color, Texture2D, BlendFactor, RenderQueueType } from "@galacean/engine";
 
//-- Shader 代码
const vertexSource = `
  uniform mat4 renderer_MVPMat;
 
  attribute vec3 POSITION; 
  attribute vec2 TEXCOORD_0;
  varying vec2 v_uv;
 
  void main() {
    gl_Position = renderer_MVPMat * vec4(POSITION, 1.0);
    v_uv = TEXCOORD_0;
  }
  `;
 
const fragmentSource = `
  uniform vec4 u_color;
  varying vec2 v_uv;
 
  #ifdef TEXTURE
    uniform sampler2D u_texture;
  #endif
 
  void main() {
    vec4 color = u_color;
 
    #ifdef TEXTURE
      color *= texture2D(u_texture, v_uv);
    #endif
 
    gl_FragColor = color;
 
  }
  `;
 
Shader.create("demo", vertexSource, fragmentSource);
 
export class CustomMaterial extends Material {
  set texture(value: Texture2D) {
    if (value) {
      this.shaderData.enableMacro("TEXTURE");
      this.shaderData.setTexture("u_texture", value);
    } else {
      this.shaderData.disableMacro("TEXTURE");
    }
  }
 
  set color(val: Color) {
    this.shaderData.setColor("u_color", val);
  }
 
  // make it transparent
  set transparent() {
    const target = this.renderState.blendState.targetBlendState;
    const depthState = this.renderState.depthState;
 
    target.enabled = true;
    target.sourceColorBlendFactor = target.sourceAlphaBlendFactor = BlendFactor.SourceAlpha;
    target.destinationColorBlendFactor = target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha;
    depthState.writeEnabled = false;
    this.renderQueueType = RenderQueueType.Transparent;
  }
 
  constructor(engine: Engine) {
    super(engine, Shader.find("demo"));
  }
}
最后更新于 七月 11, 2024

这篇文档对您有帮助吗?