法线延展法
网上使用法线延展法实现物体描边效果的文章比较多, 这里不再描述.
但是这种方法有个缺点: 当两个面的法线夹角差别较大时, 两个面的描边无法完美连接. 如下图所示:
卷积法
这里使用另一种方法卷积法实现物体描边效果, 一般机器学习使用该方法比较多. 先看效果图:
使用 three.JS 具体的实现方法如下:
创建着色器材质, 隐藏不需要描边的物体进行渲染, 将需要描边的位置渲染成白色, 其他位置渲染成黑色.
利用片源着色器计算卷积, 白色是物体内部, 黑色是物体外部, 灰色是边框.
设置材质透明, 不融合, 将边框叠加到原图上, 可以使用 FXAA 抗锯齿.
这三步就可以实现了, 很简单吧. 下面我们将详细介绍实现方法, 不想看的可以直接去看完整实现代码:
完整代码:
详细的实现过程:
1. 使用 three.JS 正常绘制场景, 得到下图, 这里不介绍了.
2. 创建着色器材质, 隐藏所有不需要描边的物体. 将需要描边的物体绘制成白色, 其他地方绘制成黑色.
隐藏不需要描边的物体后, 将整个场景材质替换.
renderScene.overrideMaterial = this.maskMaterial;
着色器材质:
- const maskMaterial = new THREE.ShaderMaterial({
- vertexShader: MaskVertex,
- fragmentShader: MaskFragment,
- depthTest: false
- });
- MaskVertex:
- void main() {
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
- }
- MaskFragment:
- void main() {
- gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
- }
效果图:
3. 创建着色器材质进行卷积计算, 每四个像素颜色求平均值得到一个像素. 描边物体内部是白色, 外部是黑色, 物体边缘处会得到灰色. 灰色就是我们所需的边框.
- const edgeMaterial = new THREE.ShaderMaterial({
- vertexShader: EdgeVertex,
- fragmentShader: EdgeFragment,
- uniforms: {
- maskTexture: {
- value: this.maskBuffer.texture
- },
- texSize: {
- value: new THREE.Vector2(width, height)
- },
- color: {
- value: selectedColor
- },
- thickness: {
- type: 'f',
- value: 4
- },
- transparent: true
- },
- depthTest: false
- });
其中 texSize 是计算卷积的 canvas 宽度和高度, 为了让边框更平滑, 可以设置为原来 canvas 的两倍. color 是边框颜色, thickness 是边框粗细.
注意, 要将材质 transparent 设置为 true.
- EdgeVertex:
- varying vec2 vUv;
- void main() {
- vUv = uv;
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
- }
- EdgeFragment:
- uniform sampler2D maskTexture;
- uniform vec2 texSize;
- uniform vec3 color;
- uniform float thickness;
- varying vec2 vUv;
- void main() {
- vec2 invSize = thickness / texSize;
- vec4 uvOffset = vec4(1.0, 0.0, 0.0, 1.0) * vec4(invSize, invSize);
- vec4 c1 = texture2D( maskTexture, vUv + uvOffset.xy);
- vec4 c2 = texture2D( maskTexture, vUv - uvOffset.xy);
- vec4 c3 = texture2D( maskTexture, vUv + uvOffset.yw);
- vec4 c4 = texture2D( maskTexture, vUv - uvOffset.yw);
- float diff1 = (c1.r - c2.r)*0.5;
- float diff2 = (c3.r - c4.r)*0.5;
- float d = length(vec2(diff1, diff2));
- gl_FragColor = d> 0.0 ? vec4(color, 1.0) : vec4(0.0, 0.0, 0.0, 0.0);
- }
效果图:
4. 创建着色器材质, 将边框叠加到原来的图片上. 由于 FXAA 比较复杂, 这里使用简单的叠加方法.
着色器材质:
- const copyMaterial = new THREE.ShaderMaterial({
- vertexShader: CopyVertexShader,
- fragmentShader: CopyFragmentShader,
- uniforms: {
- tDiffuse: {
- value: edgeBuffer.texture
- },
- resolution: {
- value: new THREE.Vector2(1 / width, 1 / height)
- }
- },
- transparent: true,
- depthTest: false
- });
注意, transparent 要设置为 true, 否则会把原来的图片覆盖掉.
- CopyVertexShader:
- varying vec2 vUv;
- void main() {
- vUv = uv;
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
- }
- CopyFragmentShader:
- uniform float opacity;
- uniform sampler2D tDiffuse;
- varying vec2 vUv;
- void main() {
- vec4 texel = texture2D( tDiffuse, vUv );
- gl_FragColor = opacity * texel;
- }
得到最终效果图:
参考资料:
1. 描边实现完整代码:
2. three.JS 后期处理描边:
3. 卷积工作原理:
4. 法线延展法实现物体描边:
来源: https://www.cnblogs.com/tengge/p/11924006.html