Three 之 three.js (webgl)物体描边效果(outline)三种实现方式的简单整理
目录
Three 之 three.js (webgl)物体描边效果(outline)三种实现方式的简单整理
一、简单介绍
Three js 开发的一些知识整理,方便后期遇到类似的问题,能够及时查阅使用。
本节介绍, three.js (webgl) 中实现描边效果 outline 的三种方法实现的简单介绍,其中,包括:1)后期渲染进行描边效果;2)使用 MeshBasicMaterial 略微放大比例,从而形成描边效果;3)点位置法线扩展,从而实现描边效果。其中,如果有不足之处,欢迎指出,或者你有更好的方法,欢迎留言。
二、实现原理
后期渲染实现描边:
1、具体可参见博文:
Three 之 three.js (webgl)PostProcessing/shader/EffectComposer 屏幕渲染 之 指定物体添加描边 outline 效果
MeshBasicMaterial 描边效果
1、要描边的 geometry ,然后与 MeshMeshBasicMaterial 创建一个新的 Mesh
2、新的 Mesh 适当放大比例,略大于原物体
3、把 MeshMeshBasicMaterial 的 side 设置为 THREE.BackSide,如果还没有达到效果,可以在通过合理的渲染顺序,从而视觉上形成描边效果
/**简单使用 MeshBasicMaterial 进行描边处理
* @param {Object} mesh 要描边的物体
* @param {Object} scene
*/
function outlineMeshBasicMaterial(mesh, scene){
var outlineMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff,side: THREE.BackSide } );
var outlineMesh = new THREE.Mesh( mesh.geometry, outlineMaterial );
outlineMesh.position.set(mesh.position.x,mesh.position.y,mesh.position.z)
outlineMesh.scale.set(1.2,1.2,1.2)
scene.add( outlineMesh );
}
法线扩展描边效果
1、构建 进行发现扩展的 shader
2、要描边的 geometry ,然后与 法线扩展shader 创建一个新的 Mesh
3、通过传入的描边宽度、颜色、透明度,来显示描边效果
uniform float lineWidth;
void main()
{
gl_Position = projectionMatrix * modelViewMatrix * vec4( position + normal * lineWidth, 1.0 );
}
三、注意事项
1、如果要描边的物体比较复杂,含多个 geometry ,可能需要组合多个 geometry 来创建描边效果
2、后期渲染描边,效果官方已经给了样例,效果可以,可能性能消耗较大
3、MeshMeshBasicMaterial 进行描边,如果描边要添加透明度效果,则可能需要描边的物体也要处理设置 transparent 为 true,且需要合理物体的 renderOrder (Renderer 中 sortObjects 为true) ,来控制渲染顺序
4、法线扩展的方式描边,可能对于棱角分明的物体,描边效果会出现断裂的效果
四、效果预览
五、实现步骤
后期处理进行描边的参见博文:Three 之 three.js (webgl)PostProcessing/shader/EffectComposer 屏幕渲染 之 指定物体添加描边 outline 效果
1、为了方便学习,这里是基于 Github 代码,进行开发的,大家可以下载官网代码,很多值得学习的案例
GitHub - mrdoob/three.js: JavaScript 3D Library.
gitcode:mirrors / mrdoob / three.js · GitCode
2、在上面的基础上,添加一个 html ,用来实现案例效果,引入相关包
3、 创建基础场景
4、实现 outlineMeshBasicMaterial 描边方法
5、实现法线扩展描边方法
6、场景添加描边的物体,然后给物体分别添加描边效果
7、 完成其他的基础代码编写,运行脚本,效果如下
六、关键代码
<!DOCTYPE html>
<html lang="en">
<head>
<title>22OutlineMeshBasicMaterial</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "../../../build/three.module.js"
}
}
</script>
<script type="module">
// 引入 three 基础库
import * as THREE from 'three';
import Stats from '../../jsm/libs/stats.module.js';
import { OrbitControls } from './../../jsm/controls/OrbitControls.js';
let camera, renderer, scene,controls;
let object;
let stats;
const params = {
enableFpsRender: false,
enableRightNowRender: false
};
init();
animate();
function init() {
// 渲染器
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor('#cccccc');
document.body.appendChild( renderer.domElement );
// camera
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 8;
// 场景
scene = new THREE.Scene();
// 添加环境光
scene.add( new THREE.AmbientLight( 0x222222 ) );
// 添加方向光
const light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
// 窗口尺寸变化监听
window.addEventListener( 'resize', onWindowResize );
stats = new Stats();
document.body.appendChild( stats.dom );
controls = new OrbitControls( camera, renderer.domElement );
// 添加物体到场景中
object = new THREE.Object3D();
scene.add( object );
const geometry = new THREE.SphereGeometry( 1, 4, 4 );
const material = new THREE.MeshPhongMaterial( { color: '#ff3399', flatShading: true } );
const material2 = new THREE.MeshPhongMaterial( { color: '#880088' } );
const mesh = new THREE.Mesh( geometry, material );
mesh.position.set(-2,0,0)
object.add(mesh)
const mesh2 = new THREE.Mesh( geometry, material2 );
mesh2.position.set(2,0,0)
object.add(mesh2)
// 两种方式描边
outlineMeshBasicMaterial(mesh,scene)
OutLineNormalMaterial(mesh2,scene)
}
/**简单使用 MeshBasicMaterial 进行描边处理
* @param {Object} mesh 要描边的物体
* @param {Object} scene
*/
function outlineMeshBasicMaterial(mesh, scene){
var outlineMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff,side: THREE.BackSide } );
var outlineMesh = new THREE.Mesh( mesh.geometry, outlineMaterial );
outlineMesh.position.set(mesh.position.x,mesh.position.y,mesh.position.z)
outlineMesh.scale.set(1.2,1.2,1.2)
scene.add( outlineMesh );
}
/**
* 简单使用 法线 进行描边处理
* @param {Object} mesh 要描边的物体
* @param {Object} scene
*/
function OutLineNormalMaterial(mesh, scene) {
var outlineMaterial = getOutLineNormalMaterial(0.1);
var outlineMesh = new THREE.Mesh( mesh.geometry, outlineMaterial );
outlineMesh.position.set(mesh.position.x,mesh.position.y,mesh.position.z)
outlineMesh.scale.set(1.2,1.2,1.2)
scene.add( outlineMesh );
}
/**
* 简单发现扩展描边
* @param {float} lineWidth 描边宽带
* @param {float} lineAlpha 描边的透明度
* @param {Object} lineColor 描边颜色
*/
function getOutLineNormalMaterial(lineWidth=1.0,lineAlpha = 1.0,lineColor={r:1.0,g:1.0,b:0}){
let uniforms = {
lineWidth:{value:lineWidth},
lineColor:{value: new THREE.Color(lineColor.r,lineColor.g,lineColor.b)},
lineAlpha:{value: lineAlpha},
timer:{value:3.14}
}
let vertexShader = `
uniform float lineWidth;
void main()
{
gl_Position = projectionMatrix * modelViewMatrix * vec4( position + normal * lineWidth, 1.0 );
}
`
let fragmentShader = `
uniform vec3 lineColor;
uniform float lineAlpha;
uniform float timer;
void main(){
float factor = (sin(timer * 10.0 + 3.14 / 2.0) + 1.0) / 2.0;
float alpha = lineAlpha * factor;
gl_FragColor = vec4( lineColor , alpha);
}`
let material = new THREE.ShaderMaterial({
uniforms:uniforms,
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.BackSide,
transparent: true
});
return material
}
function onWindowResize() {
// camera 更新 aspect
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
// 渲染器更新大小
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
stats.update();
controls.update()
}
</script>
</body>
</html>