前言: 如果大家对webgl图形学感兴趣请留言回复,多的话我可能会写一些系列教程文章。
微信2.7版本新增webgl支持,开发版工具nightly已可调试
试用了一下 暂时发现几个问题:
1.移动端应避免大尺寸纹理 我的机器似乎2048以上就不支持, 没具体测试。
2.不支持 OES_standard_derivatives 拓展,这个拓展可以在webgl1下shader中使用以下函数
genType dFdx(genType)
genType dFdy(genType) genType fwidth(genType)
,所以我们不去加载该拓展就好了 shader中也不要使用这些函数(这个问题在小游戏中也存在),
提到webgl,很多伙伴就想到Three.js,
实际上还有很多 像Babylon.js playcanvas scene.js等等 优秀的框架
整体使用下来,本人推荐playcanvas。
playcanvas整个框架逻辑完全照搬unity,有强大的网页编辑工具。比起Three.js有这更好的游戏功能支持,以及更优秀的资源脚本系统。
有丰富的拓展插件功能。
回到主题,小程序中使用webgl需要一个canvas
<canvas type="webgl" id="application" style="width:100%;height:{{boxHeight}}px;" wx:if="{{canvasready}}"></canvas>
const query = wx.createSelectorQuery()
query.select('#application').node().exec((res) => {
const canvas = res[0].node ;
})
通过createSelectorQuery拿到canvas实例,我们很多的方法都是 挂载在canvas上的,
像创建Image实例 使用canvas.createImage
像raf 使用 canvas.requestAnimationFrame
所以我们在改造引擎使得能够在微信小程序中工作,这些地方都要做相应的改动,我们改造的时候依然暴露出全局对象pc;
首先给canvas补上类dom元素的一些方法属性
canvas加上getBoundingClientRect方法,以及宽高
canvas.devicePixelRatio = systemInfo.pixelRatio;
canvas.clientWidth = systemInfo.windowWidth;
canvas.clientHeight = systemInfo.windowHeight;
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
_defineProperty(canvas, 'getBoundingClientRect', {
value: function () {
return {
top: 0,
left: 0,
width: canvas.width,
height: canvas.height
}
},
writable: false,
configurable: true
})
pc对象上添加创建图片对象的方法,我这里加上instanceof用来判断资源类型(有时候需要区分canvas还是image)
pc.createImage = function () {
var image = canvas.createImage();
image.instanceof = 'HTMLImageElement';
return image;
}
app.canvas.requestAnimationFrame(app.tick);
var application = new pc.Application(canvas, { assetPrefix: 'anonymous'});
application.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
application.setCanvasResolution(pc.RESOLUTION_AUTO);
//在web中 Image配置crossOrigin 在小程序中似乎没有必要了 ,但是不影响我们依然加上 assetPrefix
创建一个相机,这个示例主要演示shader,不是3d模型,这里用正交投影相机
var camera = new pc.Entity();
camera.addComponent('camera', {
nearClip: 0,
farClip: 2,
projection: pc.PROJECTION_ORTHOGRAPHIC,
clearColor: new pc.Color(0, 0, 0)
});
camera.setPosition(0, 0, 1);
var CameraAspect = pc.createScript('cameraAspect');
CameraAspect.prototype.initialize = function () {
this.currentOrthoHeight = this.entity.camera.orthoHeight;
};
CameraAspect.prototype.update = function (t) {
var canvas = this.app.graphicsDevice.canvas;
var aspectRatio = canvas.width / canvas.height;
var orthoHeight = pc.math.clamp(0.72 / aspectRatio, 1, 1.28);
if (orthoHeight !== this.currentOrthoHeight) {
this.entity.camera.orthoHeight = orthoHeight;
this.currentOrthoHeight = orthoHeight;
}
};
camera.addComponent('script');
camera.script.create('cameraAspect');
application.root.addChild(camera);
//创建一个平面
var plane = new pc.Entity();
plane.addComponent('model', {type: "plane"});
//TRS
plane.setLocalPosition(0, -0.006,0);
plane.setEulerAngles(90, 0, 0);
plane.setLocalScale(1.54, 1.05, 2.61);
//给平面引入脚本系统
plane.addComponent('script');
//shader脚本 cardManShader 如果在web端我们其实可以当成资源引入,可惜小程序中不支持动态脚本
var CardManShader = pc.createScript('cardManShader');
CardManShader.attributes.add('vs', {
type: 'asset',
assetType: 'shader',
title: 'Vertex Shader'
});
CardManShader.attributes.add('fs', {
type: 'asset',
assetType: 'shader',
title: 'Fragment Shader'
});
CardManShader.attributes.add('uTexture', {
type: 'asset',
assetType: 'texture',
title: '火焰纹理'
});
CardManShader.attributes.add('uNormalTexture', {
type: 'asset',
assetType: 'texture',
title: '宝剑的remap 纹理'
});
CardManShader.attributes.add('uFlowTexture', {
type: 'asset',
assetType: 'texture',
title: 'wave纹理'
});
CardManShader.attributes.add('uCardTexture', {
type: 'asset',
assetType: 'texture',
title: '卡片纹理'
});
CardManShader.attributes.add('uMaskTexture', {
type: 'asset',
assetType: 'texture',
title: 'man遮罩纹理'
});
CardManShader.attributes.add('uMainTexture', {
type: 'asset',
assetType: 'texture',
title: 'man纹理'
});
CardManShader.prototype.initialize = function () {
this.time = 0;
this.material = new pc.Material();
this.uTextureMap = this.uTexture.resource;
this.uNormalTextureMap = this.uNormalTexture.resource;
this.uFlowTextureMap = this.uFlowTexture.resource;
this.uCardTextureMap = this.uCardTexture.resource;
this.uMaskTextureMap = this.uMaskTexture.resource;
this.uMainTextureMap = this.uMainTexture.resource;
this.entity.model.model.meshInstances[0].material = this.material;
this.createShader();
};
CardManShader.prototype.update = function (dt) {
this.time += dt * 2;
this.material.setParameter('uTime', this.time);
};
CardManShader.prototype.createShader = function () {
var app = this.app;
var model = this.entity.model.model;
var vertexShader = this.vs.resource;
var fragmentShader = this.fs.resource;
var shaderDefinition = {
attributes: {
aPosition: pc.SEMANTIC_POSITION,
aNormal: pc.SEMANTIC_NORMAL,
aUv: pc.SEMANTIC_TEXCOORD0
},
vshader: vertexShader,
fshader: fragmentShader
};
var gd = app.graphicsDevice;
this.shader = new pc.Shader(gd, shaderDefinition);
this.material.shader = this.shader;
this.material.setParameter('uTime', 0);
this.material.setParameter('uTexture', this.uTextureMap);
this.material.setParameter('uNormalTexture', this.uNormalTextureMap);
this.material.setParameter('uFlowTexture', this.uFlowTextureMap);
this.material.setParameter('uCardTexture', this.uCardTextureMap);
this.material.setParameter('uMaskTexture', this.uMaskTextureMap);
this.material.setParameter('uMainTexture', this.uMainTextureMap);
};
CardManShader.prototype.updateShader = function (t1, t2, t3, t4, t5, t6) {
if (t1) this.uTexture = t1;
this.uTextureMap = this.uTexture.resource;
if(t2) this.uNormalTexture = t2;
this.uNormalTextureMap = this.uNormalTexture.resource;
if (t3) this.uFlowTexture = t3;
this.uFlowTextureMap = this.uFlowTexture.resource;
if(t4) this.uCardTexture = t4;
this.uCardTextureMap = this.uCardTexture.resource;
if(t5) this.uMaskTexture = t5;
this.uMaskTextureMap = this.uMaskTexture.resource;
if (t6) this.uMainTexture = t6;
this.uMainTextureMap = this.uMainTexture.resource;
var material = this.entity.model.model.meshInstances[0].material;
material.setParameter('uTexture', this.uTextureMap);
material.setParameter('uNormalTexture', this.uNormalTextureMap);
material.setParameter('uFlowTexture', this.uFlowTextureMap);
material.setParameter('uCardTexture', this.uCardTextureMap);
material.setParameter('uMaskTexture', this.uMaskTextureMap);
material.setParameter('uMainTexture', this.uMainTextureMap);
};
//给shader脚本 加上着色器 以及贴图资源
var vsglsl = new pc.Asset("vs", "shader");
vsglsl.resource = `precision ${application.graphicsDevice.precision} float;
attribute vec4 aPosition;
attribute vec3 aNormal;
attribute vec2 aUv;
uniform mat4 matrix_viewProjection;
uniform mat4 matrix_model;
uniform mat4 matrix_view;
uniform mat3 matrix_normal;
uniform vec3 uLightPos;
// Color to fragment program
varying float vertOutTexCoord;
varying vec2 texCoord;
void main(void)
{
mat4 modelView = matrix_view * matrix_model;
mat4 modelViewProj = matrix_viewProjection * matrix_model;
vec3 eyeNormal = normalize(matrix_normal * aNormal);
vec4 vertexPos = modelView * aPosition;
vec3 vertexEyePos = vertexPos.xyz / vertexPos.w;
vec3 lightDir = normalize(uLightPos - vertexEyePos);
vertOutTexCoord = max(0.0, dot(eyeNormal, lightDir));
texCoord = aUv;
gl_Position = modelViewProj * aPosition;
}`;
var fsglsl = new pc.Asset("fs", "shader");
fsglsl.resource = `precision ${application.graphicsDevice.precision} float;
uniform sampler2D uTexture;
uniform sampler2D uNormalTexture;
uniform sampler2D uFlowTexture;
uniform sampler2D uCardTexture;
uniform sampler2D uMaskTexture;
uniform sampler2D uMainTexture;
uniform float uDistortPower;
uniform float uTime;
varying float vertOutTexCoord;
varying vec2 texCoord;
vec3 unpackNormal(sampler2D nmap, vec2 uv) {
return texture2D(nmap,uv).xyz*2.0-vec3(1.0);
}
vec3 unpackNormal(vec4 color) {
return color.xyz*2.0-vec3(1.0);
}
vec3 packNormal(vec3 n) {
return (n+vec3(1.0))*0.5;
}
vec2 distort(float mask,sampler2D distTex, vec2 distUV, float distPower) {
vec4 distColor = texture2D(distTex, distUV + vec2(uTime) * 0.5);
vec2 offsetUV = (mask * unpackNormal(distColor) * distPower).st;
return offsetUV;
}
vec2 rotate(vec2 uv, vec4 param,float time) {
float cosx = cos(param.z + param.w * time);
float sinx = sin(param.z + param.w * time);
mat2 m2 = mat2(cosx, sinx, -sinx, cosx);
return m2 * (uv.xy - param.xy) + param.xy;
}
vec2 UV_RotateAround(vec2 center,vec2 uv,float rad){
vec2 fuv = uv - center;
mat2 ma = mat2(cos(rad),sin(rad),-sin(rad),cos(rad));
fuv = ma * fuv + center;
return fuv;
}
vec2 UV_STD2Rect(vec2 uv,vec4 rect){
uv.x = mix(rect.x,rect.x+rect.z, uv.x);
uv.y = mix(rect.y,rect.y+rect.w, uv.y);
return uv;
}
vec4 remap(sampler2D remapTex,vec2 uv){
vec2 uv_remap2 = uv;
vec4 color_remap2 = texture2D(remapTex,uv_remap2);
if(color_remap2.b >= 0.5) {
color_remap2 = vec4(0,color_remap2.gba);
}
return color_remap2;
}
const vec4 _Color_image1 = vec4(1.,1.,1.,1.);
vec2 imagenodeBase(vec2 uv_image,vec4 uv_tint,vec3 uv_move,float uv_rotate){
vec2 tilling = uv_tint.xy;
vec2 offset = uv_tint.zw;
vec2 move = uv_move.xy;
float moveFactor = uv_move.z;
float rotaterad = uv_rotate;
vec2 center_image = vec2(0.5,0.5);
uv_image = uv_image-center_image;
uv_image = uv_image+offset;
//trs
uv_image = uv_image+move*moveFactor;
uv_image = UV_RotateAround(vec2(0.,0.),uv_image,rotaterad);
uv_image = uv_image/tilling;
// vec2 dir_image = uv_image/length(uv_image);
// uv_image = uv_image-dir_image*vec2(0.,0.)*(_Time.y); //spin
// uv_image = UV_RotateAround(vec2(0.,0.),uv_image,0.*(_Time.y)); //radial
uv_image = uv_image + center_image;
return uv_image;
}
vec2 uvnode(sampler2D _waveTex,vec2 uv){
vec2 _Time = vec2(uTime,uTime);
vec2 tilling = vec2(0.60546,0.60913);
vec2 offset = vec2(0.,4.17232);
vec2 move = vec2(0.07617,0.);
float moveFactor = _Time.y;
float rotaterad = 0.;
vec2 ruv = vec2(0.,0.1503901);
vec2 guv = vec2(0.24501,0.00114);
vec2 buv = vec2(0.,0.);
vec2 auv = vec2(0.,0.);
vec2 uv_uv = uv;
uv_uv = imagenodeBase(uv_uv, vec4(tilling,offset),vec3(move,moveFactor),rotaterad);
vec4 rect_uv = vec4(1,1,1,1);
vec4 color_uv = texture2D(_waveTex,uv_uv);
uv_uv = -(color_uv.r*ruv + color_uv.g*guv + color_uv.b*buv + color_uv.a*auv);
return uv_uv;
}
vec2 uvnodehair(sampler2D _waveTex,vec2 uv){
vec2 _Time = vec2(uTime,uTime);
vec2 tilling = vec2(0.34375,0.3339844);
vec2 offset = vec2(0.2,0.);
vec2 move = vec2(0.,-0.1328125);
float moveFactor = _Time.y;
float rotaterad = 0.;
vec2 ruv = vec2(-0.02734375,0.02539063);
vec2 guv = vec2(0.02148438,0.02539063);
vec2 buv = vec2(0.,0.);
vec2 auv = vec2(0.,0.);
vec2 uv_uv = uv;
uv_uv = imagenodeBase(uv_uv, vec4(tilling,offset),vec3(move,moveFactor),rotaterad );
vec4 rect_uv = vec4(1,1,1,1);
vec4 color_uv = texture2D(_waveTex,uv_uv);
uv_uv = -(color_uv.r*ruv + color_uv.g*guv + color_uv.b*buv + color_uv.a*auv);
return uv_uv;
}
vec4 imagenode(sampler2D _flameTex, vec4 color_remap,vec2 newuv,vec2 olduv){
vec2 _Time = vec2(uTime,uTime);
vec2 tilling = vec2(0.5,1.);
vec2 offset = vec2(0.,0.);
vec2 move = vec2(-0.06640625,0);
float moveFactor = _Time.y;
float rotaterad = 0.;
vec2 uv_image = color_remap.rg;
uv_image = imagenodeBase(uv_image,vec4(tilling,offset),vec3(move,moveFactor),rotaterad);
uv_image = uv_image + newuv*1.*((1.));
vec4 rect_image = vec4(1.,1.,1.,1.);
vec4 color_image = texture2D(_flameTex,uv_image);
//color_image = color_image * _Color_image3;
//blendadd
color_image = vec4(color_image.rgb,color_image.a*color_remap.a);
return color_image;
}
void main(void)
{
vec2 _Time = vec2(uTime,uTime);
// float v = vertOutTexCoord;
// v = float(int(v * 6.0)) / 6.0;
vec4 color_xman_mask0 = texture2D(uMaskTexture,texCoord);
vec2 uv = texCoord;
vec4 color = texture2D (uTexture, uv);
vec2 color_ROOTUV = imagenodeBase(texCoord, vec4(0.9550781,0.5,0.02246094,-0.2177734),
vec3(0.,0.,uTime),0.);
vec4 remapuv = remap(uNormalTexture,color_ROOTUV);
vec2 newuv = uvnode(uFlowTexture,color_ROOTUV);
vec4 flowcolor = imagenode(uTexture,remapuv,newuv,color_ROOTUV);
vec2 newuvhair = uvnodehair(uFlowTexture,color_ROOTUV);
vec4 offsetx_color_xman_mask0 = texture2D(uMaskTexture,texCoord + vec2(-0.1,-0.02));
vec2 color_ROOT2 = color_ROOTUV + newuvhair *1.*((1.)) * offsetx_color_xman_mask0.b;
vec4 color_ROOT = texture2D(uMainTexture,color_ROOT2);
color = mix(color_ROOT,vec4(flowcolor.rgb,1.),clamp(flowcolor.a*1.*((1.)),0.,1.));
vec4 cardcolor = texture2D(uCardTexture,texCoord);
color = mix(cardcolor,vec4(color.rgb,1.),clamp(color.a*1.*((1.)) * color_xman_mask0.r,0.,1.));
//gl_FragColor = color_ROOT;
gl_FragColor = color;
}`
var uTextureAsset = new pc.Asset("uTexture", "texture", {
url: "http://p1.qhimgs3.com/t01cd1ec98a32680eba.png"
});
var uNormalTextureAsset = new pc.Asset("uNormalTexture", "texture", {
url: "http://p1.qhimgs3.com/t01396cc020f83752f8.png"
});
var uFlowTextureAsset = new pc.Asset("uFlowTexture", "texture", {
url: "http://p1.qhimgs3.com/t01a07e2120f6d6e227.png"
});
var uCardTextureAsset = new pc.Asset("uCardTexture", "texture", {
url: "http://p1.qhimgs3.com/t0106a31ec8b61c2cb5.png"
});
var uMaskTextureAsset = new pc.Asset("uMaskTexture", "texture", {
url: "http://p1.qhimgs3.com/t0163659206d15e5cb5.jpg"
});
var uMainTextureAsset = new pc.Asset("uMainTexture", "texture", {
url: "http://p1.qhimgs3.com/t01dbc7e56d6af1643d.png"
});
application.assets.add(uTextureAsset);
application.assets.add(uNormalTextureAsset);
application.assets.add(uFlowTextureAsset);
application.assets.add(uCardTextureAsset);
application.assets.add(uMaskTextureAsset);
application.assets.add(uMainTextureAsset);
plane.script.on('create:cardManShader', function (_shader) {
_shader.vs = vsglsl;
_shader.fs = fsglsl;
_shader.uTexture = uTextureAsset;
_shader.uNormalTexture = uNormalTextureAsset;
_shader.uFlowTexture = uFlowTextureAsset;
_shader.uCardTexture = uCardTextureAsset;
_shader.uMaskTexture = uMaskTextureAsset;
_shader.uMainTexture = uMainTextureAsset;
})
plane.script.create('cardManShader');
//shader
application.assets.load(uTextureAsset);
application.assets.load(uNormalTextureAsset);
application.assets.load(uFlowTextureAsset);
application.assets.load(uCardTextureAsset);
application.assets.load(uMaskTextureAsset);
application.assets.load(uMainTextureAsset);
application.assets.on("load",function(){
plane.script.cardManShader.updateShader();
})
application.root.addChild(plane);
//最后再开始tick ,提前开始tick会在真机上有未查明的问题
application.start();
这样就能在小程序中展现我们动图中的特效了。
具体代码,稍后提交到github。请关注songshujitu
结尾打个广告 下面是我们目前创业在做的小程序,大家进来玩下吧
大佬是创业失败了吗?小程序用不了啊
代码格式太乱了
过分,你太直接了啊
image.instanceof = 'HTMLImageElement';
... 还能这样写的么
github没有更新呀,小程序点进去也什么都没有,是不维护了么