咨询一下首屏优化问题

引擎版本:3.7.4

看了论坛大概有两种方案:

  • HTML IMG标签定一个图片,进度条什么的到场景完成,在场景里关闭隐藏。
  • WEB GL渲染 基于WX
    环境现状:
    使用引擎3.7.4,游戏横屏,附2个DEMO末尾。麻烦大佬帮我看看啥问题。

方案一:

我用HTML IMG标签,在竖屏的状态下进入游戏,webmobile平台,会先看到竖屏再横屏显示。

html代码如下:

 <div id="GameDiv" cc_exact_fit_screen="true">
    <div class="custom_splash-screen" id="custom_splash-screen"> 
      <img class="splash-screen-image" src="./splash.jpg" alt="" id="splash-screen-image"> 
    </div>
     
    <div id="Cocos3dGameContainer">
      <!-- <canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="99"></canvas> -->
      <canvas id="GameCanvas" oncontextmenu="event.preventDefault()" tabindex="99"></canvas>
    </div>
  </div>

CSS代码:

.custom_splash-screen {
	z-index: 99;
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
}

.splash-screen-image {
  position: absolute;
	width: 100%;
	height: 100%;
  top: 50%; 
  left: 50%;
  transform: translate(-50%, -50%);
  object-fit: cover;
}

其中splash.jpg是一张图横屏。

测试发现:

  • 在纯横屏模式没有问题;
  • 在竖屏模式,游戏启动后会先竖屏显示某一部分,然后横屏正常显示。
    总觉得有些奇怪,毕竟习惯拿微信扫,或者说手机系统竖向锁定来玩游戏,看到这个情况就多少有点难接受了。
    竖屏模式:

方案二:

感觉上述走不通,我尝试用webGL解决,在生成的index.html里,嵌入顶点和片段着色器代码:

代码如下:

  <!-- vertex shader -->
<script  id="vertex-shader-2d" type="x-shader/x-vertex">
  attribute vec2 a_position;
  uniform mat3 u_matrix;
  uniform vec2 u_resolution;
  
  attribute vec2 a_texCoord;
  varying vec2 v_texCoord;
  
  void main() {
     // Multiply the position by the matrix.
     vec2 position = (u_matrix * vec3(a_position, 1)).xy;

     // convert the rectangle from pixels to 0.0 to 1.0
     vec2 zeroToOne = position / u_resolution;
  
     // convert from 0->1 to 0->2
     vec2 zeroToTwo = zeroToOne * 2.0;
  
     // convert from 0->2 to -1->+1 (clipspace)
     vec2 clipSpace = zeroToTwo - 1.0;
  
     gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
  
     // pass the texCoord to the fragment shader
     // The GPU will interpolate this value between points.
     v_texCoord = a_texCoord;
  }
  </script>
  <!-- fragment shader -->
  <script  id="fragment-shader-2d" type="x-shader/x-fragment">
  precision mediump float;
  
  // our texture
  //为什么u_image没有设置还能正常运行?
  //全局变量默认为 0 所以 u_image 默认使用纹理单元 0 。 纹理单元 0 默认为当前活跃纹理,所以调用 bindTexture 会将纹理绑定到单元 0 。
  uniform sampler2D u_image;
  
  // the texCoords passed in from the vertex shader.
  varying vec2 v_texCoord;
  
  void main() {
    // 在纹理上寻找对应颜色值
     gl_FragColor = texture2D(u_image, v_texCoord);
  }
  </script>
  <script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
  
  <script src="./firstScreen.js"></script> 

firstScreen.js部分,我取的是GameCanvas画布,直接贴代码:

"use strict";

/**
 * create by kevin
 * Date:2024年9月4日 17:12:31
*/
function main() {
  var image = new Image();
  image.src = "splash.jpg";
  image.onload = function () {
    render(image);
  };
}


function render(image) {
  var canvas = document.querySelector("#GameCanvas");
  var gl = canvas.getContext("webgl");
  
  if (!gl) {
    return;
  }

  // setup GLSL program
  var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-2d", "fragment-shader-2d"]);

  // look up where the vertex data needs to go.
  var positionLocation = gl.getAttribLocation(program, "a_position");
  var texcoordLocation = gl.getAttribLocation(program, "a_texCoord");
  var matrixLocation = gl.getUniformLocation(program, "u_matrix");

  // Create a buffer to put three 2d clip space points in
  var positionBuffer = gl.createBuffer();

  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  setRectangle(gl, 0, 0, image.width, image.height);

  // provide texture coordinates for the rectangle.
  var texcoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    0.0, 0.0,
    1.0, 0.0,
    0.0, 1.0,
    0.0, 1.0,
    1.0, 0.0,
    1.0, 1.0,
  ]), gl.STATIC_DRAW);

  // Create a texture.
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  /**
   * 这段代码是WebGL API的一部分,用于设置2D纹理对象的参数。WebGL是一个JavaScript API,用于在网页上渲染交互式3D和2D图形,无需使用插件。下面是对这段代码的解释:

    gl.texParameteri(target, pname, param): 这是一个设置纹理参数的函数,其中:

    target 是纹理的目标类型,这里是 gl.TEXTURE_2D,表示2D纹理。
    pname 是要设置的参数的名称。
    param 是要设置的参数值。
    gl.TEXTURE_WRAP_S 和 gl.TEXTURE_WRAP_T: 这两个参数控制纹理在S轴(水平方向)和T轴(垂直方向)的包裹方式。设置为 gl.CLAMP_TO_EDGE 表示纹理坐标超出[0.0, 1.0]范围时,纹理将被拉伸到最近的边缘像素的颜色。

    gl.TEXTURE_MIN_FILTER 和 gl.TEXTURE_MAG_FILTER: 这两个参数控制纹理的过滤方式。gl.NEAREST 表示使用最接近的纹理元素(像素)进行过滤,这通常用于点采样,可以提供最快的渲染速度,但可能在纹理放大时出现明显的锯齿。

    这段代码设置了一个2D纹理,使其在纹理坐标超出边界时不重复纹理图案,而是拉伸到边缘,并使用最近邻点采样,这通常用于需要快速渲染而不追求高质量纹理效果的场景。
   */
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

  /**
   * 参数列表:
    gl.TEXTURE_2D: 表示这是2D纹理。
    0: 这是纹理的mipmap级别,0表示基本级别,也就是最大的图像尺寸。
    gl.RGBA: 这指定了纹理的内部格式,RGBA表示纹理包含红色、绿色、蓝色和透明度通道。
    gl.RGBA: 这指定了传入的数据格式,与内部格式相同,表示传入的数据包含红色、绿色、蓝色和透明度通道。
    gl.UNSIGNED_BYTE: 这指定了数据类型,UNSIGNED_BYTE表示数据是以无符号字节格式提供的,这是WebGL中最常见的数据类型。
    image: 这是一个HTMLImageElement对象,表示要上传到纹理的图像。
   */
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

  // lookup uniforms
  var resolutionLocation = gl.getUniformLocation(program, "u_resolution");


  webglUtils.resizeCanvasToDisplaySize(gl.canvas);

  // Tell WebGL how to convert from clip space to pixels
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  // Set a rectangle the same size as the image.
  var newWidth = canvas.width;
  var newHight = canvas.height;

  console.log("result :", newWidth, newHight);
  // let ratioW = gl.canvas.width / image.width;
  // let ratioH = gl.canvas.height / image.height;
  // image.width = gl.canvas.width 
  // image.height = gl.canvas.height

  // Clear the canvas
  gl.clearColor(0, 0, 0, 0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  // Tell it to use our program (pair of shaders)
  gl.useProgram(program);

  // Turn on the position attribute
  gl.enableVertexAttribArray(positionLocation);

  // Bind the position buffer.
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
  var size = 2;          // 2 components per iteration
  var type = gl.FLOAT;   // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0;        // start at the beginning of the buffer
  gl.vertexAttribPointer(
    positionLocation, size, type, normalize, stride, offset);

  // Turn on the texcoord attribute
  gl.enableVertexAttribArray(texcoordLocation);

  // bind the texcoord buffer.
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);

  // Tell the texcoord attribute how to get data out of texcoordBuffer (ARRAY_BUFFER)
  var size = 2;          // 2 components per iteration
  var type = gl.FLOAT;   // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0;        // start at the beginning of the buffer
  gl.vertexAttribPointer(
    texcoordLocation, size, type, normalize, stride, offset);

  // set the resolution
  gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
  let scale = [1, 1];
  //计算缩放
  let fixHeight = image.width > image.height;
  let visibleSize = gl.canvas;
  // if (fixHeight) {
  //   if (image.width < visibleSize.width) {
  //     let ratio = visibleSize.width / image.width;
  //     console.log("ratio width1:", ratio);
  //     scale = [ratio, ratio];
  //   }
  // } else {
  //   if (image.height < visibleSize.height) {
  //     let ratio = visibleSize.height / image.height;
  //     console.log("ratio width2:", ratio);
  //     scale = [ratio, ratio];
  //   }
  // }
  let raidio = Math.max(visibleSize.width/image.width,visibleSize.height/image.height)
  scale = [raidio,raidio];

  //平移到屏幕中心
  var translation = [gl.canvas.width / 2, gl.canvas.height / 2];
  var translationMatrix = m3.translation(translation[0], translation[1]);
  var scaleMatrix = m3.scaling(scale[0], scale[1]);

  // make a matrix that will move the origin of the 'image' to its center.
  var moveOriginMatrix = m3.translation(-image.width / 2, -image.height / 2);

  var matrix = m3.multiply(translationMatrix, scaleMatrix);
  matrix = m3.multiply(matrix, moveOriginMatrix);
  // Set the matrix.
  gl.uniformMatrix3fv(matrixLocation, false, matrix);

  // Draw the rectangle.
  var primitiveType = gl.TRIANGLES;
  var offset = 0;
  var count = 6;
  console.log("开始画..");
  gl.drawArrays(primitiveType, offset, count);
}

function setRectangle(gl, x, y, width, height) {
  var x1 = x;
  var x2 = x + width;
  var y1 = y;
  var y2 = y + height;
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    x1, y1,
    x2, y1,
    x1, y2,
    x1, y2,
    x2, y1,
    x2, y2,
  ]), gl.STATIC_DRAW);
}

var m3 = {
  translation: function (tx, ty) {
    return [
      1, 0, 0,
      0, 1, 0,
      tx, ty, 1,
    ];
  },

  rotation: function (angleInRadians) {
    var c = Math.cos(angleInRadians);
    var s = Math.sin(angleInRadians);
    return [
      c, -s, 0,
      s, c, 0,
      0, 0, 1,
    ];
  },

  scaling: function (sx, sy) {
    return [
      sx, 0, 0,
      0, sy, 0,
      0, 0, 1,
    ];
  },

  multiply: function (a, b) {
    var a00 = a[0 * 3 + 0];
    var a01 = a[0 * 3 + 1];
    var a02 = a[0 * 3 + 2];
    var a10 = a[1 * 3 + 0];
    var a11 = a[1 * 3 + 1];
    var a12 = a[1 * 3 + 2];
    var a20 = a[2 * 3 + 0];
    var a21 = a[2 * 3 + 1];
    var a22 = a[2 * 3 + 2];
    var b00 = b[0 * 3 + 0];
    var b01 = b[0 * 3 + 1];
    var b02 = b[0 * 3 + 2];
    var b10 = b[1 * 3 + 0];
    var b11 = b[1 * 3 + 1];
    var b12 = b[1 * 3 + 2];
    var b20 = b[2 * 3 + 0];
    var b21 = b[2 * 3 + 1];
    var b22 = b[2 * 3 + 2];
    return [
      b00 * a00 + b01 * a10 + b02 * a20,
      b00 * a01 + b01 * a11 + b02 * a21,
      b00 * a02 + b01 * a12 + b02 * a22,
      b10 * a00 + b11 * a10 + b12 * a20,
      b10 * a01 + b11 * a11 + b12 * a21,
      b10 * a02 + b11 * a12 + b12 * a22,
      b20 * a00 + b21 * a10 + b22 * a20,
      b20 * a01 + b21 * a11 + b22 * a21,
      b20 * a02 + b21 * a12 + b22 * a22,
    ];
  },
};

main();

测试如下:

  • 在浏览器跑提示:This device does not support WebGL.
    image
    原因在于GameCanvas这个节点不知道为啥不支持。如果使用canvas不会有这个问题,但是会有两个画布同时显示,这就尴尬了。论坛的很多方案都是基于微信取得的微信都canvas。但我这个web-mobile平台。
  • 横屏模式下没有问题,图片可以正常渲染,虽然显示了This device does not support WebGL,但是渲染后,会闪一下黑屏画面后,切入了正常的场景背景图。
  • 竖屏模式依然表现糟糕:图片先竖屏显示,然后横屏,跟着再黑一下,最后显示了正常的场景背景。

横屏:
webgl

DEMO-IMG:
HelloWorld-IMG-TEST.zip (4.0 MB)
DEMO-WEBGL
HelloWorld-WEBGL-TEST.zip (4.0 MB)

求大佬看看 :sweat_smile:

css的问题,没有旋转。
要不然你就不写css直接把图片本身旋转一下。
不过H5游戏,最好把首屏用Html实现,速度最快。

已经解决了。谢谢。HTML昨天找web前端的一起研究搞定了横屏,早上自己也把竖屏的也支持上。现在在手机来回旋转,不管竖屏还是横屏,亦或是横屏图片还是竖屏图片,刚的很 :smile:

HTML版本:里面含部分webgl代码,可自行删了。webgl代码没有调用。
HelloWorld.zip (2.6 MB)
回头我再折腾个webgl版本 :grinning:

H5的话 直接在html里写dom就好了,首屏肯定是最快的

我感觉比webgl快

那肯定的啊,webgl要起个canvas画布,canvas加载webgl,然后还要下载图片,图片下载完了才能加载
dom的话这些底层优化了好多年,肯定比你自己实现的那一坨加载的快

再说了:
webgl总实现流程相当于canvas+webgl+img dom下载+shader绘制
那肯定没有直接 img dom 绘制快

领教了,我WEBGL版本刚好也折腾好了哈哈 :grinning: 话说,微信平台用webgl是不是比较好点。

微信不是浏览器,只能用webgl

微信小游戏有个封面插件技术吧,应该可以用,比你自己写webgl靠谱

1赞