Unity 插件 Texture 是不可变的

Unity plugin Texture is immutable

我有一个复杂的问题。在 unity WebGL 中,加载纹理(使用 LoadImage)会导致引擎冻结几毫秒,导致游戏卡顿,加载大型纹理时更糟。这是一个已知问题。

为了避免冻结,我决定尝试让浏览器加载纹理,并将该纹理应用于游戏对象。这样应该不会冻结,因为浏览器是在线程上执行的。

这样做有点复杂,所以我将此解决方案基于 WebGLMovieTexture,它是资产商店中的免费资产,允许您使用浏览器内置的播放器(而不是 unitys VideoPlayer)播放电影,将其应用于纹理,然后应用于游戏对象。我经常使用它并且它很有效,所以我决定尝试用图像做同样的事情。

为此必须在 Javascript 中创建一个插件,在 c# 中为该插件创建一个接口 class,然后创建一个使用该接口 class 的 class .

首先是 Javascript 插件,我在这里只包含了重要的部分

var LibraryWebGLImageTexture = {

$imageInstances: [],

WebGLImageTextureCreate: function(url)
{
  var str = Pointer_stringify(url);
  var img = document.createElement('img');
  img.onload=function() {
      console.log("image load completed"); <<<-------------
  }
  img.style.display = 'none';
  img.src = str;
  return imageInstances.push(img) - 1;
},

WebGLImageTextureRefresh: function(img, tex)
{
  GLctx.bindTexture(GLctx.TEXTURE_2D, GL.textures[tex]);
  GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, true);
  GLctx.texImage2D(GLctx.TEXTURE_2D, 0, GLctx.RGBA, GLctx.RGBA,GLctx.UNSIGNED_BYTE, imageInstances[img]);
  GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, false);
}

这是插件的 C# 接口 class,同样只包含重要部分

public class WebGLImageTexture 
{
[DllImport("__Internal")]
private static extern int WebGLImageTextureCreate (string url);

[DllImport("__Internal")]
private static extern void WebGLImageTextureRefresh (int img, int texture);

public Texture2D m_Texture=null;
int m_Instance; 
bool m_Loop;

public WebGLImageTexture (string url)
{
    Debug.Log("creating image element");
    m_Instance = WebGLImageTextureCreate(url);
    imgInfo();
    Debug.Log("image element created:"+m_Instance);
}

public void imgInfo()
{
    Debug.Log("trying to get width and height...=" + m_Instance);
    var width = 672;
    var height = 420;
    m_Texture = new Texture2D(width, height, TextureFormat.ARGB32, false);
    m_Texture.wrapMode = TextureWrapMode.Clamp;
    Debug.Log("IMAGE:"+m_Texture);
}
public void Refresh()
{
    Debug.Log("Image Update IN");
    WebGLImageTextureRefresh(m_Instance, m_Texture.GetNativeTextureID());
}

static public implicit operator Texture2D(WebGLImageTexture tex)
{
    Debug.Log("IMPLICIT TEXTURE 2D");
    return tex.m_Texture;
}   

下面的class是使用上面的插件接口来创建插件的实例,将URL传递给图像。然后等待图像加载一小段时间,然后调用插件刷新函数将纹理传输到游戏对象。

WebGLImageTexture it;   // plugin interface

void Start () {
    it = new WebGLImageTexture("http://interfacelift.com/wallpaper/previews/04194_pagview_672x420.jpg");
    gameObject.GetComponent<Renderer>().material.mainTexture = it;
    Invoke("loaded", 20); // wait for image to load then invoke this
} 


public void loaded()
{
    it.Refresh();
}

//Spin the cube

void Update () {
    transform.Rotate(new Vector3(1, 2, 3) * Time.deltaTime * 10);
}

正如您从最顶部的 javascript 插件代码中看到的那样,当图像加载时它会打印到控制台 "image load completed"。这有效!

不久之后调用超时并调用插件刷新函数,该函数将图像放入纹理中,但在刷新函数中它因错误而崩溃

[.WebGL-0000005F0C18E320] GL_INVALID_OPERATION: Texture is immutable.

插件刷新功能似乎正在使用我不知道的 OpenGL,它在刷新功能中给出了这个错误,这是使这项工作成功的关键。

有人知道如何解决这个错误吗?

我认为出现该错误的唯一原因是纹理是使用 gl.texStorage2D 分配的,这意味着您只能使用 gl.texSubImage2D 来更新纹理。

gl.texStorage2D 在一次调用中分配纹理及其所有 mip 级别。从那时起,纹理的大小无法更改。 gl.texImage2D 重新分配单个 mip 级别,因此您不能使用它来更新分配给 gl.texStorage2D 的纹理,但您可以使用 gl.texSubImage2D

更新现有纹理的内容

换句话说改变这一行

GLctx.texImage2D(GLctx.TEXTURE_2D, 0, GLctx.RGBA, GLctx.RGBA,GLctx.UNSIGNED_BYTE, imageInstances[img]);

至此

GLctx.texSubImage2D(GLctx.TEXTURE_2D, 0, 0, 0, GLctx.RGBA, GLctx.UNSIGNED_BYTE, imageInstances[img]);

仅供参考,尽管您的代码会因不等待纹理实际加载而出现问题。如果用户的连接速度较慢,仅等待 "awhile" 是不够的。你需要重构,这样你就可以在加载图像时从 C# 中的 JavaScript 获取事件,或者偶尔从游戏中轮询

只是猜测类似

var LibraryWebGLImageTexture = {

$imageInstances: [],

WebGLImageTextureCreate: function(url)
{
  var str = Pointer_stringify(url);
  var img = new Image();
  img.src = str;
  return imageInstances.push(img) - 1;
},

WebGLImageTextureLoaded: function(img)
{
  return imageInstances[img].complete;
},

WebGLImageTextureWidth: function(img)
{
  return imageInstances[img].width;
},

WebGLImageTextureHeight: function(img)
{
  return imageInstances[img].height;
},

WebGLImageTextureRefresh: function(img, tex)
{
  GLctx.bindTexture(GLctx.TEXTURE_2D, GL.textures[tex]);
  GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, true);
  GLctx.texSubImage2D(GLctx.TEXTURE_2D, 0, 0, 0, GLctx.RGBA, GLctx.UNSIGNED_BYTE, imageInstances[img]);
  GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, false);
}
public class WebGLImageTexture 
{
[DllImport("__Internal")]
private static extern int WebGLImageTextureCreate (string url);

[DllImport("__Internal")]
private static extern bool WebGLImageTextureLoaded (int img);

[DllImport("__Internal")]
private static extern int WebGLImageTextureWidth (int img);

[DllImport("__Internal")]
private static extern int WebGLImageTextureHeight (int img);

[DllImport("__Internal")]
private static extern void WebGLImageTextureRefresh (int img, int texture);

...

我会告诉你是否要在图像已加载时检查更新或使用协程检查图像是否已加载

如果你还想检查错误,那么可能是

var LibraryWebGLImageTexture = {

$imageInstances: [],

WebGLImageTextureCreate: function(url)
{
  var str = Pointer_stringify(url);
  var img = new Image();
  var info = {img: img, error: false}
  img.onerror = function() {
    info.error = true;
  };
  img.src = str;
  return imageInstances.push(info) - 1;
},

WebGLImageTextureLoaded: function(img)
{
  return imageInstances[img].img.complete;
},

WebGLImageTextureError: function(img)
{
  return imageInstances[img].error;
},


WebGLImageTextureWidth: function(img)
{
  return imageInstances[img].img.width;
},

WebGLImageTextureHeight: function(img)
{
  return imageInstances[img].img.height;
},

WebGLImageTextureRefresh: function(img, tex)
{
  GLctx.bindTexture(GLctx.TEXTURE_2D, GL.textures[tex]);
  GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, true);
  GLctx.texSubImage2D(GLctx.TEXTURE_2D, 0, 0, 0, GLctx.RGBA, GLctx.UNSIGNED_BYTE, imageInstances[img].img);
  GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, false);
}
public class WebGLImageTexture 
{
[DllImport("__Internal")]
private static extern int WebGLImageTextureCreate (string url);

[DllImport("__Internal")]
private static extern bool WebGLImageTextureLoaded (int img);

[DllImport("__Internal")]
private static extern bool WebGLImageTextureError (int img);

[DllImport("__Internal")]
private static extern int WebGLImageTextureWidth (int img);

[DllImport("__Internal")]
private static extern int WebGLImageTextureHeight (int img);

[DllImport("__Internal")]
private static extern void WebGLImageTextureRefresh (int img, int texture);

...

现在你可以检查你的轮询如果WebGLImageTextureError returns true 那么你得到一个错误,如果WebGLImageTextureLoaded returns true 图像加载完成。