Three.js-描边
思路
根据模型的顶点,构建线条
这是我们初始想到的办法,实际上并不合适,性能差,效果也达不到预期。
/**
* 绘制模型的线框
* @param obj - 模型对象
* @returns
*/
export function renderFrameMesh(obj: Mesh) {
const edges = new EdgesGeometry(obj.geometry);
const color = new Color(1, 0, 0);
const lineBaseMaterial = new LineBasicMaterial({
color,
side: FrontSide,
linecap: 'round',
linejoin: 'round',
});
const line = new LineSegments(edges, lineBaseMaterial);
// 针对整体一个模型的情况,需要对其进行坐标转换
const objWorldPosition = new Vector3();
const objWorldQuaternion = new Quaternion();
obj.getWorldPosition(objWorldPosition);
obj.getWorldQuaternion(objWorldQuaternion);
line.position.x = objWorldPosition.x;
line.position.y = objWorldPosition.y;
line.position.z = objWorldPosition.z;
line.quaternion.x = objWorldQuaternion.x;
line.quaternion.y = objWorldQuaternion.y;
line.quaternion.z = objWorldQuaternion.z;
line.quaternion.w = objWorldQuaternion.w;
return line;
}
卷积
three.js使用卷积法实现物体描边效果 - tengge - 博客园
这个方法尝试了下,感觉还是麻烦了一些,然后发现官方居然有描边的Demo,就先放弃这个方案了。
后来发现官方的描边Demo,和这个原理是很类似的,二者的Shader代码都很像。
直接使用官方的OutlinePass
参考官方这个示例:
https://threejs.org/examples/#webgl_postprocessing_outline
这个方法是最简单的,也是我们最终使用的方案。
该方案不用我们自己手动分层处理物体,并且还有呼吸效果。
看了下源码,发现和第二种卷积的方式很类似,也是mask + edge,整体流程如下:
-
将未选中的物体置为invisible,绘制选中物体(通过对比未选中物体的depth buffer)
-
降低采样率到分辨率的一半
-
将描边线Pass应用上去
-
应用Blur效果(二分之一分辨率)
-
应用Blur效果(四分之一分辨率)
-
混合(Blend)
每一个操作都会执行类似这样的流程代码:
// fsQuard是什么:this.fsQuad = new FullScreenQuad( null );
this.fsQuad.material = this.edgeDetectionMaterial;
this.edgeDetectionMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskDownSampleBuffer.texture;
this.edgeDetectionMaterial.uniforms[ 'texSize' ].value.set( this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height );
this.edgeDetectionMaterial.uniforms[ 'visibleEdgeColor' ].value = this.tempPulseColor1;
this.edgeDetectionMaterial.uniforms[ 'hiddenEdgeColor' ].value = this.tempPulseColor2;
renderer.setRenderTarget( this.renderTargetEdgeBuffer1 );
renderer.clear();
this.fsQuad.render( renderer );
这里应用的材质都是着色器,比如edgeDetectionMaterial、separableBlurMaterial1等。
混合代码也很有参考性:
// Blend it additively over the input texture
this.fsQuad.material = this.overlayMaterial;
this.overlayMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskBuffer.texture;
this.overlayMaterial.uniforms[ 'edgeTexture1' ].value = this.renderTargetEdgeBuffer1.texture;
this.overlayMaterial.uniforms[ 'edgeTexture2' ].value = this.renderTargetEdgeBuffer2.texture;
this.overlayMaterial.uniforms[ 'patternTexture' ].value = this.patternTexture;
this.overlayMaterial.uniforms[ 'edgeStrength' ].value = this.edgeStrength;
this.overlayMaterial.uniforms[ 'edgeGlow' ].value = this.edgeGlow;
this.overlayMaterial.uniforms[ 'usePatternTexture' ].value = this.usePatternTexture;
思考:我们可以将常用特效,封装为Pass类进行复用。这样积累多了,我们的开发效率就上来了。并且今后还可以应用到可视化编辑器中,以类似Unity的组件化的形式附加到场景中。
如何理解WebGLRenderTarget?
官方定义:
A render target is a buffer where the video card draws pixels for a scene that is being rendered in the background. It is used in different effects, such as applying postprocessing to a rendered image before displaying it on the screen.
再来看看程序中是如何初始化的:
let resx = Math.round( width / this.downSampleRatio );
let resy = Math.round( height / this.downSampleRatio )
;this.renderTargetBlurBuffer1 = new WebGLRenderTarget( resx, resy );
this.renderTargetBlurBuffer1.texture.name = 'OutlinePass.blur1';
this.renderTargetBlurBuffer1.texture.generateMipmaps = false;
this.renderTargetBlurBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ) );
this.renderTargetBlurBuffer2.texture.name = 'OutlinePass.blur2';
this.renderTargetBlurBuffer2.texture.generateMipmaps = false;
所以RenderTarget是一个GPU的帧缓冲,或者直接叫离屏渲染更好理解一些。我们可以设置其大小。
通过WebGL渲染目标WebGLRenderTarget的纹理属性.texture可以获得WebGL渲染器的渲染结果,该属性返回的结果是一个纹理对象THREE.Texture,可以作为材质Material对象颜色贴图属性map的属性。
参考资料:Three.js WebGLRenderTarget(离屏渲染)_郭隆邦技术博客的博客-CSDN博客_three. webglrendertarget
如何理解卷积?
参考:
https://www.zhihu.com/question/22298352
这就是为什么代码中有一些二分之一、四分之一的缘故。
所谓两个函数的卷积,本质上就是先将一个函数翻转,然后进行滑动叠加。
在图像处理的中,卷积处理的结果,其实就是把每个像素周边的,甚至是整个图像的像素都考虑进来,对当前像素进行某种加权处理。所以说,“积”是全局概念,或者说是一种“混合”,把两个函数在时间或者空间上进行混合。