1 )建立容器对象
建立一个 lineBox 对象,作为承载多边形的容器
// lineBox.js
export default class lineBox {
constructor(gl) {
this.gl = gl
this.children = []
}
add(obj) {
obj.gl = this.gl
this.children.push(obj)
}
updateVertices(params) {
this.children.forEach(ele => {
ele.updateVertices(params)
})
}
draw() {
this.children.forEach(ele => {
ele.init()
ele.draw()
})
}
}
属性
方法
2 )场景应用
场景:鼠标点击画布,绘制多边形路径,鼠标右击,取消绘制,鼠标再次点击,绘制新的多边形
import LineBox from './lineBox'
import Poly from './poly'
// 容器
const lb = new LineBox(gl)
// 当前正在绘制的多边形
let poly = null
// 取消右击提示
canvas.oncontextmenu = function() {
return false
}
// 鼠标点击事件
canvas.addEventListener("mousedown", (event) => {
if(event.button === 2) {
popVertice()
} else {
const { x, y } = getMousePosInWebgl(event, canvas)
poly ? poly.addVertice(x,y) : crtPoly(x,y)
}
render()
})
// 鼠标移动
canvas.addEventListener("mousemove", (event) => {
if (poly) {
const { x, y } = getMousePosInWebgl(event, canvas)
poly.setVertice(poly.count - 1, x, y)
render()
}
})
// 删除最后一个顶点
function popVertice() {
poly.popVertice()
poly = null
}
// 创建多边形
function crtPoly(x,y) {
poly = new Poly({
vertices:[x,y,x,y],
types:['POINTS','LINE_STRIP']
})
lb.add(poly)
}
// 渲染方法
function render() {
gl.clear(gl.COLOR_BUFFER_BIT)
lb.draw()
}
3 )场景应用
3.1 )建立顶点着色器
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Attr;
varying float v_Alpha;
void main() {
gl_Position = vec4(a_Attr.x, a_Attr.y, 0.0, 1.0);
gl_PointSize = a_Attr.z;
v_Alpha = a_Attr.w;
}
script>
3.2 )建立片元着色器
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
varying float v_Alpha;
void main() {
float dist = distance(gl_PointCoord, vec2(0.5,0.5));
if(dist < 0.5) {
gl_FragColor = vec4(0.87, 0.91, 1.0, v_Alpha);
} else {
discard;
}
}
script>
3.3 )建立夜空对象,用于承载多边形
const lb = new lineBox(gl)
3.4 )建立合成对象,用于对顶点数据做补间运算
const compose = new Compose();
3.5 )声明两个变量,用于表示当前正在绘制的多边形和鼠标划上的点
// 当前正在绘制的多边形
let poly = null
// 鼠标划上的点
let point = null
3.6 )取消右击提示
// 取消右击提示
canvas.oncontextmenu = function() {
return false;
}
3.7 )鼠标按下事件
// 鼠标按下事件
canvas.addEventListener("mousedown", (event) => {
if(event.button === 2) {
// 右击删除顶点
poly && popVertice()
} else {
const {x,y} = getMousePosInWebgl(event, canvas)
if(poly) {
// 连续添加顶点
addVertice(x,y)
} else {
// 建立多边形
crtPoly(x, y)
}
}
});
getMousePosInWebgl() 方法是用于获取鼠标在webgl 画布中的位置,我们之前说过。
crtPoly() 创建多边形
function crtPoly(x, y) {
let o1 = point ? point : { x, y, pointSize: random(), alpha: 1 }
const o2 = { x, y, pointSize: random(), alpha: 1 }
poly = new Poly({
size: 4,
attrName: 'a_Attr',
geoData: [o1,o2],
types: ['POINTS','LINE_STRIP']
})
lb.add(poly)
crtTrack(o1)
crtTrack(o2)
}
建立两个顶点数据o1,o2,如果鼠标点击了其它顶点,o1的数据就是此顶点的数据
顶点的尺寸是一个随机数random()
function random() {
return Math.random() * 8.0 + 3.0
}
基于两个顶点数据,建立多边形对象和两个时间轨对象
crtTrack() 建立时间轨
function crtTrack(obj) {
const { pointSize } = obj
const track = new Track(obj)
track.start = new Date()
track.timeLen = 2000
track.loop = true
track.keyMap = new Map([
[
"pointSize",
[
[500, pointSize],
[1000, 0],
[1500, pointSize],
],
],
[
"alpha",
[
[500, 1],
[1000, 0],
[1500, 1],
],
],
]);
compose.add(track)
}
addVertice() 添加顶点
function addVertice(x, y) {
const { geoData } = poly
if(point) {
geoData[geoData.length-1] = point
}
let obj = { x, y, pointSize:random(), alpha: 1 }
geoData.push(obj)
crtTrack(obj)
}
如果鼠标点击了其它顶点,就让多边形的最后一个顶点数据为此顶点
建立下一个顶点的顶点数据,添加新的顶点,建立新的时间轨
popVertice() 删除最后一个顶点
function popVertice() {
poly.geoData.pop()
compose.children.pop()
poly = null
}
3.8 )鼠标移动事件
canvas.addEventListener("mousemove", (event) => {
const { x, y } = getMousePosInWebgl(event, canvas)
point = hoverPoint(x, y)
if(point) {
canvas.style.cursor = 'pointer'
} else {
canvas.style.cursor = 'default'
}
if(poly) {
const obj = poly.geoData[poly.geoData.length-1]
obj.x = x
obj.y = y
}
});
基于鼠标是否划上顶点,设置鼠标的视觉状态
设置正在绘制的多边形的最后一个顶点点位
hoverPoint() 检测所有顶点的鼠标划入,返回顶点数据
function hoverPoint(mx, my) {
for(let { geoData } of lb.children) {
for(let obj of geoData) {
if(poly && obj === poly.geoData[poly.geoData.length-1]) {
continue
}
const delta = {
x: mx - obj.x,
y: my - obj.y
}
const { x,y } = glToCssPos(delta, canvas)
const dist = x * x + y * y;
if(dist < 100) {
return obj
}
}
}
return null
}
遍历 lb 中的所有顶点数据,忽略绘图时随鼠标移动的点,获取鼠标和顶点的像素距离,若此距离小于10像素,返回此点;否则,返回null
glToCssPos() webgl坐标系转css坐标系,将之前说过的getMousePosInWebgl() 方法逆向思维即可
function glToCssPos({x,y},{width,height}){
const [halfWidth, halfHeight] = [width / 2, height / 2]
return {
x:x*halfWidth,
y:-y*halfHeight
}
}
2.9 )连续渲染方法
!(function animate() {
compose.update(new Date())
lb.updateVertices(['x', 'y', 'pointSize', 'alpha'])
render()
requestAnimationFrame(animate)
})();
function render(){
gl.clear(gl.COLOR_BUFFER_BIT)
lb.draw()
}
这个效果是由片源着色器导致的,这个片元着色器还是以前用于绘制原点的片元着色器
precision mediump float;
void main(){
float dist = distance(gl_PointCoord, vec2(0.5,0.5));
if(dist<0.5){
gl_FragColor = vec4(1,1,0,1);
} else {
discard;
}
}
这个着色器在mac系统会把一部分线条中的片元给过滤掉,也就是走了后面的discard方法放弃了一部分片元的绘制
而这个问题在window电脑里就没有
接下来咱们看一下代码实现
1)先给片元着色器添加一个uniform 变量
precision mediump float;
uniform bool u_IsPOINTS;
void main() {
if(u_IsPOINTS) {
float dist = distance(gl_PointCoord,vec2(0.5,0.5));
if(dist < 0.5) {
gl_FragColor = vec4(1,1,0,1);
} else {
discard;
}
} else {
gl_FragColor = vec4(1,1,0,1);
}
}
2)给 Poly 对象添加两个属性
const defAttr = () => ({
circleDot: false,
u_IsPOINTS: null,
...
})
3)在初始化方法中,如果是圆点,就获取一下uniform 变量
init() {
...
if (circleDot) {
this.u_IsPOINTS = gl.getUniformLocation(gl.program, "u_IsPOINTS");
}
}
4 )在渲染的时候,如果是圆点,就基于绘图方式修改 uniform 变量
draw(types = this.types) {
const {gl,count,u_IsPOINTS,circleDot} = this;
for (let type of types) {
circleDot && gl.uniform1f(u_IsPOINTS, type==='POINTS');
gl.drawArrays(gl[type],0,count);
}
}