unity 3d物体描边效果_three.js使用卷积法实现物体描边效果

法线延展法

网上使用 法线延展法 实现物体描边效果的文章比较多,这里不再描述。

但是这种方法有个缺点:当两个面的法线夹角差别较大时,两个面的描边无法完美连接。如下图所示:

81c0a2c5e7a3bdaa0be8d166ff2b9394.png

卷积法

这里使用另一种方法 卷积法 实现物体描边效果,一般机器学习使用该方法比较多。先看效果图:

b4992c079d2d559ad3ca21a301b07e53.png
18e7a6f55e734f38bffb70818520060d.png
984a1865e18cc0f4bea8b8fd0272bc55.png

使用three.js具体的实现方法如下:

  1. 创建着色器材质,隐藏不需要描边的物体进行渲染,将需要描边的位置渲染成白色,其他位置渲染成黑色。
  2. 利用片源着色器计算卷积,白色是物体内部,黑色是物体外部,灰色是边框。
  3. 设置材质透明、不融合,将边框叠加到原图上,可以使用FXAA抗锯齿。

这三步就可以实现了,很简单吧。下面我们将详细介绍实现方法,不想看的可以直接去看完整实现代码:

完整代码: https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/helper/SelectHelper.js

详细的实现过程:

1. 使用three.js正常绘制场景,得到下图,这里不介绍了。

22ceb9f8b193a11a39ebd6666a1ec3e0.png

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);}

效果图:

4e972707268e041f7803bcb3101842d7.png

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);}

效果图:

fbc5159bf105920648aca09bbcaa79b1.png

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;}

得到最终效果图:

9f7d17fc8e8e226217d32b17682dfaaa.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值