kanzi 2D节点绘制到3D模型上

本文介绍了如何使用Node2Dto3D插件将2D图像渲染到3D纹理,通过定时器处理首次渲染问题,并优化了贴图的高斯模糊效果,以便在3D场景中显示清晰的2D内容。

背景:3d场景可以通过render pass渲染到一张纹理上,然后在2d图片上展示。反过来,无法实现,本次探究了可行性。

1. 创建插件工程

先创建一个插件工程Node2Dto3D
创建插件工程参考 使用kanzi开发仪表HMI插件

2. 实现插件功能

打开vs,修改node2dto3d.hpp

#ifndef NODE2DTO3D_HPP
#define NODE2DTO3D_HPP

#include <kanzi/kanzi.hpp>

class Node2Dto3D;
typedef kanzi::shared_ptr<Node2Dto3D> Node2Dto3DSharedPtr;

// The template component.
class NODE2DTO3D_API Node2Dto3D : public kanzi::Node2D
{
public:

    static kanzi::PropertyType<kanzi::ResourceSharedPtr> RenderTextureProperty;

    KZ_METACLASS_BEGIN(Node2Dto3D, Node2D, "Node2Dto3D")
        KZ_METACLASS_PROPERTY_TYPE(RenderTextureProperty)
    KZ_METACLASS_END()

    // Creates a Node2Dto3D.
    static Node2Dto3DSharedPtr create(kanzi::Domain* domain, kanzi::string_view name);

    virtual void onAttached() KZ_OVERRIDE;
    virtual void onDetached() KZ_OVERRIDE;
    void onNodePropertyChanged(kanzi::AbstractPropertyType propertyType, kanzi::PropertyNotificationReason reason) KZ_OVERRIDE;
    void renderOverride(kanzi::Renderer3D& renderer, kanzi::CompositionStack& compositionStack) KZ_OVERRIDE;

protected:
    // Constructor.
    explicit Node2Dto3D(kanzi::Domain* domain, kanzi::string_view name);
    void initialize();
    void createTexture();
    void onTimeOutEventHandler();
private:
    int m_framebufferID = -1;
    kanzi::FramebufferSharedPtr m_framebuffer;
    kanzi::TextureSharedPtr m_texture = NULL;

    kanzi::Renderer* m_renderer = NULL;
    kanzi::Renderer3D* m_renderer3d = NULL;

    int m_width=0;
    int m_height=0;

    kanzi::TimerSubscriptionToken timerid;
    unsigned int m_counter = 0;
};

#endif

修改node2dto3d.cpp

#include "node2dto3d.hpp"
using namespace kanzi;
Node2Dto3DSharedPtr Node2Dto3D::create(Domain* domain, string_view name)
{
    Node2Dto3DSharedPtr enginePlugin(new Node2Dto3D(domain, name));
    enginePlugin->initialize();
    return enginePlugin;
}
void Node2Dto3D::onAttached()
{
    kzLogDebug(("onAttached~"));
    Node2D::onAttached();  
    m_counter = 0;
    timerid = addTimerHandler(this->getMessageDispatcher(), kanzi::chrono::milliseconds(20), KZU_TIMER_MESSAGE_MODE_REPEAT, bind(&Node2Dto3D::onTimeOutEventHandler, this));
}

void Node2Dto3D::onDetached()
{
    Node2D::onDetached();
    removeTimerHandler(this->getMessageDispatcher(), timerid);
}
void Node2Dto3D::onNodePropertyChanged(kanzi::AbstractPropertyType propertyType, kanzi::PropertyNotificationReason reason)
{    
    if (RenderTextureProperty == propertyType) {
        //kzLogDebug(("onNodePropertyChanged~"));
    }
    Node2D::onNodePropertyChanged(propertyType, reason);
}
void Node2Dto3D::initialize()
{
    // Initialize base class.
    Node2D::initialize();
}

void Node2Dto3D::createTexture() {
    int w = getProperty(Node::WidthProperty); 
    int h = getProperty(Node::HeightProperty);

    static int lastW = 0;
    static int lastH = 0;

    if (w != lastW || h != lastH) {
        lastW = w;
        lastH = h;

        m_width = w;
        m_height = h;

        m_width = m_width <= 0 ? 100 : m_width;
        m_height = m_height <= 0 ? 100 : m_height;

        kzLogDebug(("createTexture=,w={},h={}", w, h));

        kanzi::Texture::CreateInfoNode2DRenderTarget createInfo1(m_width, m_height, kanzi::GraphicsFormatR8G8B8A8_UNORM);
        if (m_texture) {
            delete m_texture.get();
            m_texture.reset();
            m_texture = NULL;
        }
        m_texture = kanzi::Texture::create(getDomain(), createInfo1, "color target 1");

        m_framebufferID = m_texture->getNativeFramebufferHandle();

        kzLogDebug(("m_framebufferID={}", m_framebufferID));
    }        
}

void Node2Dto3D::onTimeOutEventHandler()
{
    if (m_counter < 10) {
        setProperty(RenderTextureProperty, m_texture);
    }
}

Node2Dto3D::Node2Dto3D(kanzi::Domain* domain, kanzi::string_view name) :
    kanzi::Node2D(domain, name)
{
    kzLogDebug(("Node2Dto3D~"));    
    if (!m_renderer3d) {
        m_renderer3d = getDomain()->getRenderer3D();
        m_renderer = m_renderer3d->getCoreRenderer();
    }
}


void Node2Dto3D::renderOverride(Renderer3D& renderer, CompositionStack& compositionStack)
{
    createTexture();
    SRTValue2D _SRTValue2D = getProperty(Node2D::RenderTransformationProperty);
    ViewportRectangle v = m_renderer->getViewport();
    //v.setX(_SRTValue2D.getTranslationX());
    //v.setY(_SRTValue2D.getTranslationY() + m_height - 720);
    v.setX(0);
    v.setY(0);
    v.setWidth(m_width);
    v.setHeight(m_height);
    m_renderer->setViewport(v);

    if (m_framebufferID == -1) {
        return;
    }

    m_renderer->bindFramebuffer(m_framebufferID);
    m_renderer->setDefaultFramebuffer(m_framebufferID);
   
    m_renderer->clear();

    Matrix3x3 baseTransform = Matrix3x3::createIdentity();
    renderChildren(renderer, compositionStack, baseTransform);
    
    m_renderer->setDefaultFramebuffer(0);
    m_renderer->bindFramebuffer(0);

    setProperty(RenderTextureProperty, m_texture);
    m_counter++;

}

kanzi::PropertyType<ResourceSharedPtr> Node2Dto3D::RenderTextureProperty(kzMakeFixedString("Node2Dto3D.RenderTextureProperty"), ResourceSharedPtr(), 0, false,
    KZ_DECLARE_EDITOR_METADATA
    (
        metadata.category = "Node2Dto3D";
metadata.displayName = "RenderTextureProperty";
metadata.tooltip = "RenderTextureProperty.";
metadata.valueProvider = "ProjectObject:Texture";
metadata.host = "Node2Dto3D:auto";
));

主要实现

  • 通过定时器实现初次显示画面(因为画面没变化,没触发renderOverride,没输出RenderTextureProperty,导致图片为黑色)
  • createTexture在节点宽高变化时候重新创建纹理
  • renderOverride会实时把子节点内容绘制到纹理对应的帧缓存里

3. 使用插件

在插件节点下创建测试图片和测试按钮
在这里插入图片描述

  • 宽高为512
  • 屏幕高度720,减去节点高度,需要设置Y坐标208,因为viewport坐标是左下角原点

在这里插入图片描述在这里插入图片描述
Scene下创建PlaneBox,并把材质贴图绑到RenderTextureProperty

在这里插入图片描述
为了方便测试创建图片绑定RenderTextureProperty
在这里插入图片描述

4. 优化

贴图有锯齿,在模型材质里使用高斯模糊优化锯齿
VertexPhongTextured_blur.frag.glsl

precision mediump float;

uniform lowp float BlendIntensity;
uniform lowp vec4 Ambient;
varying lowp vec3 vNormal;
varying mediump vec3 vViewDirection;


uniform mediump vec2 kzTextureSize0;
uniform mediump vec2 BlurDirection;
uniform lowp float BlurRadius; 


varying mediump vec2 vTexCoord;
uniform sampler2D Texture;


vec4 gaussianBlur(mediump vec2 coord, lowp vec2 dir)
{
    // Defines the one-dimensional Gaussian Kernel with 9 samples.
    float GAUSSIAN_KERNEL[9];
    GAUSSIAN_KERNEL[0] = 0.028532;
    GAUSSIAN_KERNEL[1] = 0.067234;
    GAUSSIAN_KERNEL[2] = 0.124009;
    GAUSSIAN_KERNEL[3] = 0.179044;
    GAUSSIAN_KERNEL[4] = 0.20236;
    GAUSSIAN_KERNEL[5] = 0.179044;
    GAUSSIAN_KERNEL[6] = 0.124009;
    GAUSSIAN_KERNEL[7] = 0.067234;
    GAUSSIAN_KERNEL[8] = 0.028532;

    vec2 texel = 1.0/kzTextureSize0;
    vec4 sum = vec4(0.0);
    // Get the original texture coordinate for this fragment.
    vec2 tc = coord;
    // Get the amount to blur.
    float blur = BlurRadius;
    // Set the amount of blur in the horizontal direction.
    float hstep = dir.x*texel.x;
    // Set the amount of blur in the vertical direction.
    float vstep = dir.y*texel.y;

    // Sample the texture 9 times for every fragment.
    for(int i = 0; i < 9; i++)
    {
        float pixelOffset = (float(i) - floor(9.0 * 0.5));
        mediump vec2 coord = vec2(tc.x + pixelOffset * blur * hstep, tc.y + pixelOffset * blur * vstep);
        #if KANZI_SHADER_USE_BASECOLOR_TEXTURE
        sum += texture2D(Texture, coord) * GAUSSIAN_KERNEL[i];
        #endif
    }

    return sum;
}

void main()
{
    precision lowp float;       
    lowp vec3 color = vec3(0.0); 
    

    lowp vec4 baseColor = texture2D(Texture, vTexCoord).rgba;


    color += baseColor.rgb;
    

    //gl_FragColor = vec4(color, baseColor.a) * BlendIntensity;
    gl_FragColor = gaussianBlur(vTexCoord, BlurDirection) * BlendIntensity; 
}

在这里插入图片描述

5. demo

请添加图片描述

<think>我们讨论的是Three.js中的WebGLRenderer。在Three.js中,WebGLRenderer是一个核心类,它负责利用WebGL API将3D场景渲染到HTML5的Canvas元素上。下面我将详细解释这个过程。 ### 理解WebGLRenderer 1. **WebGL基础**: - WebGL是一种基于OpenGL ES 2.0的JavaScript API,允许在浏览器中渲染交互式3D2D图形,而无需使用插件。 - 它直接在HTML5 Canvas元素上绘制图形,使用GPU加速。 2. **Three.js中的WebGLRenderer**: - Three.js的WebGLRenderer类封装了WebGL的底层细节,提供了更简单的API来渲染3D场景。 - 它接收两个主要输入:场景(Scene)和相机(Camera)。场景包含所有要渲染的对象(如网格、灯光等),相机定义了从哪个视角和投影方式来渲染场景。 3. **渲染过程**: - 当调用`renderer.render(scene, camera)`时,渲染器会执行以下步骤: a. 根据相机的参数(如位置、朝向、投影类型等)计算视图矩阵和投影矩阵。 b. 遍历场景中的每个对象,确定哪些对象在相机的视锥体内(可见)。 c. 对于每个可见的对象,根据其几何体(Geometry)和材质(Material)创建着色器程序(Shader Program)。 d. 将对象的顶点数据、材质属性、光照信息等传递给GPU。 e. GPU执行顶点着色器和片元着色器,最终将像素绘制到Canvas上。 4. **Canvas元素**: - WebGLRenderer在初始化时会创建一个Canvas元素(除非你指定一个已存在的Canvas)。 - 这个Canvas被添加到DOM中(通常通过`document.body.appendChild(renderer.domElement)`),这样就能在网页上看到渲染结果。 ### 代码示例 下面是一个简单的示例,展示如何创建WebGLRenderer并将其Canvas添加到DOM: ```javascript // 创建渲染器实例,可以传递一些选项,比如抗锯齿 const renderer = new THREE.WebGLRenderer({ antialias: true }); // 设置渲染器的尺寸(这里设置为整个窗口大小) renderer.setSize(window.innerWidth, window.innerHeight); // 将渲染器的Canvas(domElement)添加到body中 document.body.appendChild(renderer.domElement); // 创建场景和相机(此处省略) // ... // 渲染场景 renderer.render(scene, camera); ``` ### 关键点 - `renderer.domElement`:这是渲染器创建的Canvas元素。所有的3D图形都将绘制在这个Canvas上。 - `setSize(width, height)`:设置Canvas的尺寸,同时设置WebGL的视口(viewport)大小。 - 渲染循环:通常我们会使用`requestAnimationFrame`或Three.js提供的`setAnimationLoop`(如示例代码中)来持续渲染,以创建动画效果。 ### 为什么使用WebGLRenderer? - **抽象底层复杂性**:WebGL本身非常底层,需要编写大量代码(如着色器程序、缓冲区管理)。Three.js通过WebGLRenderer类封装了这些细节。 - **跨浏览器兼容**:处理不同浏览器对WebGL的支持和差异。 - **性能优化**:自动进行批处理、状态排序等优化。 ### 总结 WebGLRenderer是Three.js的核心渲染器,它利用WebGL技术将3D场景(Scene)通过相机(Camera)的视角渲染到Canvas元素上。开发者无需直接操作WebGL API,而是通过Three.js提供的更高级API来创建和控制3D图形。 §§相关问题§§: 1. Three.js中除了WebGLRenderer,还有哪些渲染器?它们分别适用于什么场景? 2. 如何配置WebGLRenderer以启用阴影效果? 3. WebGLRenderer的初始化选项(如antialias, alpha)有哪些,它们的作用是什么? 4. 在Three.js中,如何实现离屏渲染(Offscreen Rendering)?</think>### 理解 WebGLRenderer 将 3D 场景绘制到 Canvas 上 在 Three.js 中,`WebGLRenderer` 是连接 3D 场景和浏览器显示的核心组件,它通过以下过程将三维场景渲染到二维 Canvas 上: #### 1. **渲染流程概述** ```mermaid graph LR A[3D场景 Scene] --> B[相机 Camera] B --> C[WebGLRenderer] C --> D[Canvas 元素] D --> E[浏览器显示] ``` #### 2. **详细工作流程** 1. **场景图遍历**: - 从场景节点开始,递归遍历所有对象(网格、灯光、辅助对象等) - 收集所有需要渲染的几何体和材质信息 2. **着色器编译**: - 根据材质类型自动生成顶点着色器和片元着色器 - 将着色器编译为 GPU 可执行的程序 3. **矩阵计算**: - 计算模型矩阵(物体位置/旋转/缩放) - 计算视图矩阵(相机位置和方向) - 计算投影矩阵(透视/正交投影) 4. **GPU 数据传输**: ```javascript // 伪代码示例:GPU 数据传递 gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); gl.vertexAttribPointer(positionAttribute, 3, gl.FLOAT, false, 0, 0); ``` 5. **光栅化与绘制**: - 将 3D 几何体转换为屏幕上的像素 - 执行深度测试、混合等操作 - 最终输出到 Canvas 的帧缓冲区 #### 3. **代码层面解释** 当执行 `renderer.render(scene, camera)` 时: ```javascript // 实际发生的底层操作(简化版) function render() { // 1. 清除画布 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 2. 更新相机矩阵 camera.updateMatrixWorld(); renderer.state.currentCamera = camera; // 3. 渲染场景图 renderScene(scene, camera); // 4. 实际绘制调用 for (const object of visibleObjects) { gl.drawElements(gl.TRIANGLES, object.count, gl.UNSIGNED_SHORT, 0); } } ``` #### 4. **关键概念解释** | 概念 | 作用 | 对应代码 | |------|------|----------| | **帧缓冲区** | 存储渲染结果的临时内存区域 | `gl.bindFramebuffer()` | | **顶点着色器** | 处理顶点位置变换 | `material.vertexShader` | | **片元着色器** | 计算像素最终颜色 | `material.fragmentShader` | | **深度缓冲区** | 管理像素深度信息(Z-buffer) | `gl.enable(gl.DEPTH_TEST)` | | **投影矩阵** | 将3D坐标映射到2D屏幕 | `camera.projectionMatrix` | #### 5. **可视化理解** 想象一个虚拟摄影棚: - **场景 (Scene)** = 摄影棚布景(物体、灯光、背景) - **相机 (Camera)** = 摄像机的位置和镜头参数 - **WebGLRenderer** = 摄影团队(导演、灯光师、特效师) - **Canvas** = 最终拍摄的电影画面 #### 6. **性能优化机制** WebGLRenderer 内部包含智能优化: 1. 自动批处理相同材质的物体 2. 视锥体裁剪(Frustum Culling) 3. 状态排序(减少GPU状态切换) 4. 自动LOD(Level of Detail)管理 ### 实际应用示例 ```javascript // 创建离屏渲染目标 const renderTarget = new THREE.WebGLRenderTarget(512, 512); renderer.setRenderTarget(renderTarget); renderer.render(scene, camera); renderer.setRenderTarget(null); // 现在renderTarget.texture包含渲染结果 const video = document.createElement('video'); video.srcObject = renderTarget.texture; ``` ### 总结 WebGLRenderer 本质上是 Three.js 的渲染引擎,它: 1. 将 JavaScript 对象描述的 3D 场景转化为 WebGL 底层指令 2. 管理 GPU 资源(缓冲区、纹理、着色器程序) 3. 处理投影变换和光栅化过程 4. 最终将计算结果输出到 Canvas 的像素缓冲区 这种设计使得开发者无需直接操作复杂的 WebGL API,就能高效创建 3D 图形应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值