A 型框架 + pdf.js:无法两次更新画布纹理,仅在 VR 模式下

发布于 2025-01-11 21:53:45 字数 4766 浏览 2 评论 0原文

我正在使用 A-Frame 和 pdf.js 创建一个应用程序来在 VR 中查看 PDF 文件。该应用程序在桌面上按预期工作,包括前进到 PDF 文档的下一页并重新渲染到画布。

当在 VR 浏览器中运行时(使用 Quest 2 进行测试),第一次尝试会按预期呈现。但是,当将第二个文档页面渲染到画布时,新图像将无法显示,除非退出 VR 模式,此时新纹理将按预期显示。

我尝试重复设置关联的材质map.needsUpdate(在A-Frame组件滴答循环中),但没有效果。在实验过程中,我还注意到,如果您尝试第二次渲染一个新的 PDF 页面,然后第三次渲染另一个页面(在 VR 模式下前进两页),则退出 VR 模式时,只会出现第二次的纹理;第三次渲染的数据似乎丢失了 - 我想知道这是否与主要问题有关。

下面是一个最小示例的代码,实时版本可在 http://stemkoski 获取.net/test/quest-pdf-min.html 。在桌面模式下查看示例时,您可以通过打开浏览器 JavaScript 控制台并输入 testBook.nextPage(); 前进到下一页,并看到图像按预期更改。您可以使用Quest耳机在VR模式下进行测试;按 A 按钮应前进到下一页。

问题是:如何在 A-Frame 的 VR 模式下多次将 PDF 文档的页面渲染到同一画布?

<!DOCTYPE html>
<html>

<head>
    <title>A-Frame + PDF.js</title>
    <script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.6.347/pdf.js"></script>
</head>

<body>
<script>
AFRAME.registerComponent("interactive-pdf", {

    schema:
    {   
        fileName:   {type: 'string', default:"assets/test10.pdf"},
        pageWidth:  {type: 'float',  default: 1200},
        pageHeight: {type: 'float',  default: 1500},
    },

    init: function () 
    {
        this.displayPlane = document.getElementById("plane1");
        this.displayMaterial = this.displayPlane.getObject3D('mesh').material;
        this.canvas = this.displayPlane.getAttribute("material").src
        this.canvas.setAttribute("width", this.data.pageWidth);
        this.canvas.setAttribute("height", this.data.pageHeight);
        this.context = this.canvas.getContext('2d');
        this.textArea = document.querySelector("#textArea");

        this.pdf = null;
        this.currentPage = 1;

        pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.6.347/pdf.worker.js";

        let self = this;
        pdfjsLib.getDocument(this.data.fileName).promise.then( function(pdf) {
            self.pdf = pdf;
            self.render(1);
        });
    },

    render(pageNumber = 1)
    {
        if (!this.pdf)
            return;

        let self = this;

        let text = "rendering page " + pageNumber + " of " + this.pdf.numPages + " on canvas";
        this.textArea.setAttribute("text", "value", text);

        this.pdf.getPage(pageNumber).then( function(page) {

            const pageViewport = page.getViewport({ scale: 2 });
            const context = self.canvas.getContext("2d");
            const renderContext = {
              canvasContext: context,
              viewport: pageViewport
            };

            const renderTask = page.render(renderContext);

            renderTask.promise.then( function() {
                self.displayMaterial.map.needsUpdate = true;
            });    
        });
    },

    nextPage: function()
    {
        this.currentPage++;
        this.render( this.currentPage );
    },

    prevPage: function()
    {
        this.currentPage--;
        this.render( this.currentPage );
    },

    tick: function()
    {
        // this.displayMaterial.map.needsUpdate = true;
    }

});

AFRAME.registerComponent('page-turner', {
  
  init: function () 
  {
      let bookComponent = document.getElementById("interactive-pdf").components["interactive-pdf"];
      this.el.addEventListener('abuttondown', function(event) { bookComponent.nextPage(); } );
  }

});
</script>

<a-scene>
        
    <a-assets>
        <!-- use to display pdf pages -->
        <canvas id="canvas1"></canvas>
    </a-assets>

    <a-camera></a-camera>

    <a-sky color = "#000337"></a-sky>

    <a-plane id="plane1" material="shader: flat; src: #canvas1;" position="0 1 -2"></a-plane>

    <a-entity id="interactive-pdf" position="0 1 -2" 
        interactive-pdf="fileName: assets/test10.pdf; pageWidth: 1224; pageHeight: 1584;">
    </a-entity>
        
    <a-entity id="left-controller" oculus-touch-controls="hand: left"></a-entity>
    <a-entity id="right-controller" oculus-touch-controls="hand: right" page-turner></a-entity>   

    <!-- text area to print debug text -->
    <a-entity id="textArea" position="0 2 -2"
        geometry="primitive: plane;  width: 3; height: auto"
        material="color: #444444;"
        text="anchor: center; color: #8888FF; value: debug text here">
    </a-entity>

</a-scene>

<script>
var testBook = document.getElementById("interactive-pdf").components["interactive-pdf"];
</script>

</body>
</html>

I am using A-Frame and pdf.js to create an application to view PDF files in VR. The application works as expected on desktop, including advancing to the next page of the PDF document and re-rendering to a canvas.

When running in a browser in VR (tested with Quest 2), the first attempt renders as expected. However, when rendering a second document page to the canvas, the new image fails to appear unless exiting VR mode, at which point the new texture appears as expected.

I have tried setting the associated material map.needsUpdate repeatedly (in an A-Frame component tick loop) to no effect. While experimenting, I also noticed that if you try to render a new PDF page a second time and then another page a third time (advancing by two pages while in VR mode), when exiting VR mode, only the texture from the second time appears; the data from the third render appears to be lost - I wonder if this is related to the primary issue.

Code for a minimal example is below, and a live version is available at http://stemkoski.net/test/quest-pdf-min.html . When viewing the example in desktop mode, you can advance to the next page by opening the browser JavaScript console and entering testBook.nextPage(); and see that the image changes as expected. You can test in VR mode with a Quest headset; pressing the A button should advance to the next page.

The question is: how can I render pages of a PDF document to the same canvas, multiple times, while in VR mode in A-Frame?

<!DOCTYPE html>
<html>

<head>
    <title>A-Frame + PDF.js</title>
    <script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.6.347/pdf.js"></script>
</head>

<body>
<script>
AFRAME.registerComponent("interactive-pdf", {

    schema:
    {   
        fileName:   {type: 'string', default:"assets/test10.pdf"},
        pageWidth:  {type: 'float',  default: 1200},
        pageHeight: {type: 'float',  default: 1500},
    },

    init: function () 
    {
        this.displayPlane = document.getElementById("plane1");
        this.displayMaterial = this.displayPlane.getObject3D('mesh').material;
        this.canvas = this.displayPlane.getAttribute("material").src
        this.canvas.setAttribute("width", this.data.pageWidth);
        this.canvas.setAttribute("height", this.data.pageHeight);
        this.context = this.canvas.getContext('2d');
        this.textArea = document.querySelector("#textArea");

        this.pdf = null;
        this.currentPage = 1;

        pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.6.347/pdf.worker.js";

        let self = this;
        pdfjsLib.getDocument(this.data.fileName).promise.then( function(pdf) {
            self.pdf = pdf;
            self.render(1);
        });
    },

    render(pageNumber = 1)
    {
        if (!this.pdf)
            return;

        let self = this;

        let text = "rendering page " + pageNumber + " of " + this.pdf.numPages + " on canvas";
        this.textArea.setAttribute("text", "value", text);

        this.pdf.getPage(pageNumber).then( function(page) {

            const pageViewport = page.getViewport({ scale: 2 });
            const context = self.canvas.getContext("2d");
            const renderContext = {
              canvasContext: context,
              viewport: pageViewport
            };

            const renderTask = page.render(renderContext);

            renderTask.promise.then( function() {
                self.displayMaterial.map.needsUpdate = true;
            });    
        });
    },

    nextPage: function()
    {
        this.currentPage++;
        this.render( this.currentPage );
    },

    prevPage: function()
    {
        this.currentPage--;
        this.render( this.currentPage );
    },

    tick: function()
    {
        // this.displayMaterial.map.needsUpdate = true;
    }

});

AFRAME.registerComponent('page-turner', {
  
  init: function () 
  {
      let bookComponent = document.getElementById("interactive-pdf").components["interactive-pdf"];
      this.el.addEventListener('abuttondown', function(event) { bookComponent.nextPage(); } );
  }

});
</script>

<a-scene>
        
    <a-assets>
        <!-- use to display pdf pages -->
        <canvas id="canvas1"></canvas>
    </a-assets>

    <a-camera></a-camera>

    <a-sky color = "#000337"></a-sky>

    <a-plane id="plane1" material="shader: flat; src: #canvas1;" position="0 1 -2"></a-plane>

    <a-entity id="interactive-pdf" position="0 1 -2" 
        interactive-pdf="fileName: assets/test10.pdf; pageWidth: 1224; pageHeight: 1584;">
    </a-entity>
        
    <a-entity id="left-controller" oculus-touch-controls="hand: left"></a-entity>
    <a-entity id="right-controller" oculus-touch-controls="hand: right" page-turner></a-entity>   

    <!-- text area to print debug text -->
    <a-entity id="textArea" position="0 2 -2"
        geometry="primitive: plane;  width: 3; height: auto"
        material="color: #444444;"
        text="anchor: center; color: #8888FF; value: debug text here">
    </a-entity>

</a-scene>

<script>
var testBook = document.getElementById("interactive-pdf").components["interactive-pdf"];
</script>

</body>
</html>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

月下凄凉 2025-01-18 21:53:45

此处找到了答案:

WebXR 启用时 requestAnimationFrame 不会触发

它非常相关,因为在将图像渲染到画布上时,pdf.js 默认使用 requestAnimationFrame

它可以切换 - 页面渲染功能 有一个选项intent,稍后用于确定是否使用 requestAnimationFrame


您使用的源略有不同(较旧?),但如果您搜索在 ProxyPDFPagerender 函数中创建 InternalRenderTask - 它是全部都在那里:

const internalRenderTask = new InternalRenderTask({
  callback: complete,
  params: {
    canvasContext,
    viewport,
    transform,
    imageLayer,
    background
  },
  objs: this.objs,
  commonObjs: this.commonObjs,
  operatorList: intentState.operatorList,
  pageIndex: this._pageIndex,
  canvasFactory: canvasFactoryInstance,
  webGLContext,
  useRequestAnimationFrame: renderingIntent !== "print",
  pdfBug: this._pdfBug
});

您可以在 renderContext 对象中将 intent 设置为 print

// 'interactive-pdf'.render()
const renderContext = {
          canvasContext: context,
          viewport: pageViewport,
          intent: "print"
};
const renderTask = page.render(renderContext);

并且它似乎工作正常:

在此处输入图像描述

我不确定将 Intent 设置为 print 是否不会导致不良效果(它也会影响其他设置),因此禁用 useRequestAnimationFrame在source 也能达到这个目的。

查看具有“打印”意图的版本,或者带有 修改后的pdfjs源代码

Found an answer here:

requestAnimationFrame doesn't fire when WebXR is engaged

Its quite relevant because pdf.js uses requestAnimationFrame by default when rendering the image onto the canvas.

It can be toggled though - the pages render function has an option intent, which is later on used to determine whether to use requestAnimationFrame.


The sources You use are slightly different (older?), but if you search for creating the InternalRenderTask in the render function of the ProxyPDFPage - it's all there:

const internalRenderTask = new InternalRenderTask({
  callback: complete,
  params: {
    canvasContext,
    viewport,
    transform,
    imageLayer,
    background
  },
  objs: this.objs,
  commonObjs: this.commonObjs,
  operatorList: intentState.operatorList,
  pageIndex: this._pageIndex,
  canvasFactory: canvasFactoryInstance,
  webGLContext,
  useRequestAnimationFrame: renderingIntent !== "print",
  pdfBug: this._pdfBug
});

You can set the intent to print in your renderContext object:

// 'interactive-pdf'.render()
const renderContext = {
          canvasContext: context,
          viewport: pageViewport,
          intent: "print"
};
const renderTask = page.render(renderContext);

And it seems to be working properly:

enter image description here

I'm not sure, if setting the intent to print won't cause undesired effects (it affects other settings as well), so disabling useRequestAnimationFrame in the source does the trick as well.

Check out the version with the 'print' intent, or the one with the modified pdfjs source code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文