文章目录
-
- 在当今数字化时代,增强现实(AR)技术正以前所未有的速度改变着消费者的购物体验。想象一下,当用户访问您的网站时,他们不仅能看到产品的平面图片,还能通过手机摄像头将虚拟产品"放置"在自己的真实环境中,从各个角度查看产品细节,甚至与产品进行互动。这种沉浸式体验不仅显著提升用户参与度,还能大幅降低退货率,提高转化率。 根据最新市场研究,采用AR技术的电商平台平均转化率提升了40%,客户互动时间增加了近一倍。对于WordPress网站所有者来说,集成AR功能不再是遥不可及的高科技梦想,而是可以通过代码二次开发实现的实用功能。 本教程将详细指导您如何为WordPress网站添加基于AR的虚拟产品摆放与场景体验功能,通过实用的代码示例和分步指南,帮助您打造前沿的交互式购物体验。
-
- 增强现实(AR)是一种将虚拟信息叠加到真实世界中的技术,通过设备摄像头捕捉现实场景,并在其上叠加计算机生成的图像、视频或3D模型。在电商领域,AR主要应用于: 虚拟试穿/试戴:用户可以看到产品穿戴在身上的效果 虚拟摆放:将家具、装饰品等放置在实际环境中查看效果 产品交互:允许用户旋转、缩放、自定义虚拟产品
- 在开始开发前,我们需要选择合适的AR技术方案: 方案一:基于Web的AR(WebAR) 优点:无需安装应用,跨平台兼容性好 技术栈:A-Frame、AR.js、Three.js 适合:轻量级AR体验,快速部署 方案二:原生AR SDK集成 优点:性能更好,功能更丰富 技术栈:ARKit(iOS)、ARCore(Android) 适合:高性能要求的复杂AR体验 方案三:混合方案 结合WebAR和原生SDK的优势 使用WebXR Device API 对于WordPress网站,我们推荐使用WebAR方案,因为它具有最好的兼容性和部署便利性。 开发环境准备: WordPress开发环境(本地或测试服务器) 代码编辑器(VS Code、Sublime Text等) 基础的前端开发知识(HTML、CSS、JavaScript) 3D模型处理工具(如Blender,用于准备产品模型)
- AR体验的核心是3D模型,我们需要为每个产品准备高质量的3D模型: 模型格式选择: GLTF/GLB:推荐格式,体积小,加载快 OBJ+MTL:通用格式,但文件较大 FBX:功能丰富,但需要转换 模型优化技巧: 减少多边形数量(在保持质量的前提下) 压缩纹理图像 使用合理的LOD(细节层次)系统 确保模型尺寸与实际产品一致 模型创建流程: 产品测量 → 3D建模 → 纹理贴图 → 模型优化 → 格式转换 → 测试验证
-
- 首先,我们需要创建一个WordPress插件来管理所有AR相关功能: <?php /** * Plugin Name: AR Product Viewer for WordPress * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress网站添加AR产品查看和虚拟摆放功能 * Version: 1.0.0 * Author: Your Name * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('ARPV_VERSION', '1.0.0'); define('ARPV_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('ARPV_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 class AR_Product_Viewer { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->init_hooks(); } private function init_hooks() { // 后台初始化 add_action('admin_init', array($this, 'admin_init')); // 添加管理菜单 add_action('admin_menu', array($this, 'add_admin_menu')); // 前端资源加载 add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets')); // 后台资源加载 add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); // 添加短代码 add_shortcode('ar_product_viewer', array($this, 'ar_product_viewer_shortcode')); // 为产品添加自定义字段 add_action('add_meta_boxes', array($this, 'add_product_ar_meta_box')); add_action('save_post', array($this, 'save_product_ar_meta')); } // 更多方法将在后续章节实现... } // 启动插件 AR_Product_Viewer::get_instance(); ?>
- 我们需要扩展WordPress数据库来存储AR相关数据: // 在插件激活时创建数据库表 register_activation_hook(__FILE__, 'arpv_create_database_tables'); function arpv_create_database_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'arpv_models'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, product_id bigint(20) NOT NULL, model_name varchar(255) NOT NULL, model_file_url varchar(500) NOT NULL, model_type varchar(50) DEFAULT 'glb', model_size int(11) DEFAULT 0, scale_x float DEFAULT 1.0, scale_y float DEFAULT 1.0, scale_z float DEFAULT 1.0, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY product_id (product_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 添加版本选项 add_option('arpv_db_version', '1.0'); }
- 创建用户友好的后台界面来管理AR模型: // 添加管理菜单 public function add_admin_menu() { add_menu_page( 'AR产品查看器', 'AR产品查看器', 'manage_options', 'ar-product-viewer', array($this, 'render_admin_page'), 'dashicons-visibility', 30 ); add_submenu_page( 'ar-product-viewer', '模型管理', '模型管理', 'manage_options', 'arpv-model-manager', array($this, 'render_model_manager_page') ); add_submenu_page( 'ar-product-viewer', 'AR设置', '设置', 'manage_options', 'arpv-settings', array($this, 'render_settings_page') ); } // 渲染模型管理页面 public function render_model_manager_page() { ?> <div class="wrap"> <h1>AR模型管理</h1> <div class="arpv-admin-container"> <div class="arpv-admin-header"> <button id="arpv-add-model" class="button button-primary">添加新模型</button> <div class="arpv-search-box"> <input type="text" id="arpv-model-search" placeholder="搜索模型..."> </div> </div> <div class="arpv-model-list"> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>ID</th> <th>产品名称</th> <th>模型名称</th> <th>文件类型</th> <th>文件大小</th> <th>操作</th> </tr> </thead> <tbody id="arpv-model-table-body"> <!-- 通过AJAX动态加载 --> </tbody> </table> </div> <!-- 模型上传模态框 --> <div id="arpv-upload-modal" class="arpv-modal" style="display:none;"> <div class="arpv-modal-content"> <span class="arpv-close-modal">×</span> <h2>上传3D模型</h2> <form id="arpv-upload-form"> <div class="arpv-form-group"> <label for="arpv-product-select">关联产品</label> <select id="arpv-product-select" required> <option value="">选择产品...</option> <?php $products = get_posts(array( 'post_type' => 'product', 'posts_per_page' => -1, 'post_status' => 'publish' )); foreach ($products as $product) { echo '<option value="' . $product->ID . '">' . $product->post_title . '</option>'; } ?> </select> </div> <div class="arpv-form-group"> <label for="arpv-model-name">模型名称</label> <input type="text" id="arpv-model-name" required> </div> <div class="arpv-form-group"> <label for="arpv-model-file">模型文件</label> <input type="file" id="arpv-model-file" accept=".glb,.gltf,.fbx,.obj" required> <p class="description">支持GLB, GLTF, FBX, OBJ格式,建议使用GLB格式以获得最佳性能</p> </div> <div class="arpv-form-group"> <label for="arpv-model-scale">模型缩放比例</label> <input type="number" id="arpv-model-scale" step="0.1" value="1.0" min="0.1" max="10"> </div> <button type="submit" class="button button-primary">上传模型</button> </form> </div> </div> </div> </div> <?php }
-
- 首先,我们需要在前端加载必要的AR和3D渲染库: // 前端资源加载 public function enqueue_frontend_assets() { // 只在需要AR功能的页面加载 if ($this->should_load_ar_assets()) { // Three.js - 3D渲染引擎 wp_enqueue_script('three-js', 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js', array(), 'r128', true); // AR.js - WebAR框架 wp_enqueue_script('ar-js', 'https://cdn.jsdelivr.net/npm/ar.js@latest/aframe/build/aframe-ar.min.js', array('three-js'), null, true); // GLTF加载器 wp_enqueue_script('gltf-loader', ARPV_PLUGIN_URL . 'assets/js/loaders/GLTFLoader.js', array('three-js'), '1.0', true); // 自定义AR控制器 wp_enqueue_script('arpv-ar-controller', ARPV_PLUGIN_URL . 'assets/js/ar-controller.js', array('three-js', 'ar-js', 'gltf-loader'), ARPV_VERSION, true); // AR样式 wp_enqueue_style('arpv-ar-style', ARPV_PLUGIN_URL . 'assets/css/ar-style.css', array(), ARPV_VERSION); // 传递数据到前端 wp_localize_script('arpv-ar-controller', 'arpv_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('arpv_nonce') )); } } // 判断是否需要加载AR资源 private function should_load_ar_assets() { // 检查是否在产品页面 if (is_product() || has_shortcode(get_post()->post_content, 'ar_product_viewer')) { return true; } // 检查是否在支持AR的页面模板 $template = get_page_template_slug(); if ($template === 'template-ar-product.php') { return true; } return false; }
- 实现核心的AR查看器组件: // assets/js/ar-controller.js class ARProductViewer { constructor(options) { this.options = { containerId: 'ar-viewer-container', productId: 0, modelUrl: '', modelScale: 1.0, ...options }; this.scene = null; this.camera = null; this.renderer = null; this.controls = null; this.model = null; this.arToolkitSource = null; this.arToolkitContext = null; this.init(); } init() { this.createARScene(); this.loadProductModel(); this.setupEventListeners(); this.animate(); } createARScene() { // 创建场景 this.scene = new THREE.Scene(); // 创建相机 this.camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 20); // 创建渲染器 this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); this.renderer.setPixelRatio(window.devicePixelRatio); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.outputEncoding = THREE.sRGBEncoding; // 添加到容器 const container = document.getElementById(this.options.containerId); if (container) { container.appendChild(this.renderer.domElement); } // 添加光源 const ambientLight = new THREE.AmbientLight(0xffffff, 0.8); this.scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); directionalLight.position.set(0, 10, 5); this.scene.add(directionalLight); // 初始化AR.js this.initARToolkit(); } initARToolkit() { // 创建AR.js源 this.arToolkitSource = new THREEx.ArToolkitSource({ sourceType: 'webcam', sourceWidth: 1280, sourceHeight: 720, displayWidth: window.innerWidth, displayHeight: window.innerHeight }); // 初始化源 this.arToolkitSource.init(() => { // 调整渲染器大小 this.onResize(); }); // 创建AR.js上下文 this.arToolkitContext = new THREEx.ArToolkitContext({ cameraParametersUrl: ARPV_PLUGIN_URL + 'assets/data/camera_para.dat', detectionMode: 'mono', maxDetectionRate: 30, canvasWidth: this.arToolkitSource.domElement.clientWidth, canvasHeight: this.arToolkitSource.domElement.clientHeight }); // 初始化上下文 this.arToolkitContext.init(() => { // 相机投影矩阵将根据AR上下文更新 this.camera.projectionMatrix.copy(this.arToolkitContext.getProjectionMatrix()); }); // 创建AR标记 const markerRoot = new THREE.Group(); this.scene.add(markerRoot); // 创建标记控制器 const markerControls = new THREEx.ArMarkerControls(this.arToolkitContext, markerRoot, { type: 'pattern', patternUrl: ARPV_PLUGIN_URL + 'assets/data/patt.hiro', changeMatrixMode: 'cameraTransformMatrix' }); // 将模型添加到标记根组 this.modelContainer = markerRoot; } async loadProductModel() { if (!this.options.modelUrl) { console.error('未提供模型URL'); return; } try { const loader = new THREE.GLTFLoader(); loader.load( this.options.modelUrl, (gltf) => { this.model = gltf.scene; // 调整模型大小和位置 const box = new THREE.Box3().setFromObject(this.model); const size = box.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); const scale = this.options.modelScale / maxDim; this.model.scale.set(scale, scale, scale); this.model.position.set(0, 0, 0); // 添加到场景 if (this.modelContainer) { this.modelContainer.add(this.model); } else { this.scene.add(this.model); } // 触发模型加载完成事件 this.dispatchEvent('modelLoaded', { model: this.model }); console.log('模型加载成功'); }, (progress) => { // 加载进度 const percent = (progress.loaded / progress.total * 100).toFixed(2); this.dispatchEvent('modelLoading', { percent }); }, (error) => { console.error('模型加载失败:', error); this.dispatchEvent('modelError', { error }); } ); } catch (error) { console.error('加载模型时出错:', error); }
- // 继续 assets/js/ar-controller.js setupEventListeners() { // 窗口大小调整 window.addEventListener('resize', () => this.onResize()); // 触摸/鼠标事件 this.setupInteractionEvents(); // 键盘控制 window.addEventListener('keydown', (e) => this.onKeyDown(e)); // 自定义事件监听 this.eventListeners = {}; } setupInteractionEvents() { const canvas = this.renderer.domElement; let isDragging = false; let previousMousePosition = { x: 0, y: 0 }; // 触摸事件 canvas.addEventListener('touchstart', (e) => { e.preventDefault(); if (e.touches.length === 1) { // 单指触摸 - 旋转 isDragging = true; previousMousePosition = { x: e.touches[0].clientX, y: e.touches[0].clientY }; } else if (e.touches.length === 2) { // 双指触摸 - 缩放 this.startPinchDistance = this.getPinchDistance(e); } }); canvas.addEventListener('touchmove', (e) => { e.preventDefault(); if (e.touches.length === 1 && isDragging && this.model) { // 单指拖动 - 旋转模型 const currentMousePosition = { x: e.touches[0].clientX, y: e.touches[0].clientY }; const delta = { x: currentMousePosition.x - previousMousePosition.x, y: currentMousePosition.y - previousMousePosition.y }; // 根据拖动距离旋转模型 this.rotateModel(delta.x * 0.01, delta.y * 0.01); previousMousePosition = currentMousePosition; } else if (e.touches.length === 2 && this.startPinchDistance && this.model) { // 双指捏合 - 缩放模型 const currentPinchDistance = this.getPinchDistance(e); const scaleFactor = currentPinchDistance / this.startPinchDistance; this.scaleModel(scaleFactor); this.startPinchDistance = currentPinchDistance; } }); canvas.addEventListener('touchend', (e) => { isDragging = false; this.startPinchDistance = null; }); // 鼠标事件(桌面设备) canvas.addEventListener('mousedown', (e) => { if (e.button === 0) { // 左键 isDragging = true; previousMousePosition = { x: e.clientX, y: e.clientY }; } }); canvas.addEventListener('mousemove', (e) => { if (isDragging && this.model) { const currentMousePosition = { x: e.clientX, y: e.clientY }; const delta = { x: currentMousePosition.x - previousMousePosition.x, y: currentMousePosition.y - previousMousePosition.y }; this.rotateModel(delta.x * 0.01, delta.y * 0.01); previousMousePosition = currentMousePosition; } }); canvas.addEventListener('mouseup', () => { isDragging = false; }); // 鼠标滚轮缩放 canvas.addEventListener('wheel', (e) => { e.preventDefault(); if (this.model) { const scaleFactor = e.deltaY > 0 ? 0.9 : 1.1; this.scaleModel(scaleFactor); } }); } getPinchDistance(e) { const dx = e.touches[0].clientX - e.touches[1].clientX; const dy = e.touches[0].clientY - e.touches[1].clientY; return Math.sqrt(dx * dx + dy * dy); } rotateModel(deltaX, deltaY) { if (!this.model) return; // 限制旋转角度 this.model.rotation.y += deltaX; this.model.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, this.model.rotation.x + deltaY)); } scaleModel(factor) { if (!this.model) return; // 限制缩放范围 const currentScale = this.model.scale.x; const newScale = Math.max(0.1, Math.min(5, currentScale * factor)); this.model.scale.set(newScale, newScale, newScale); } onKeyDown(e) { if (!this.model) return; switch(e.key) { case 'r': // 重置模型位置和旋转 this.model.rotation.set(0, 0, 0); this.model.scale.set(1, 1, 1); break; case '+': // 放大 this.scaleModel(1.1); break; case '-': // 缩小 this.scaleModel(0.9); break; case ' ': // 空格键 - 切换AR模式 this.toggleARMode(); break; } } toggleARMode() { // 切换AR模式和普通3D查看模式 this.isARMode = !this.isARMode; if (this.isARMode) { this.enterARMode(); } else { this.exitARMode(); } } enterARMode() { // 启动摄像头 this.arToolkitSource.init(() => { this.arToolkitContext.arController.play(); }); this.dispatchEvent('arModeEntered'); } exitARMode() { // 停止摄像头 this.arToolkitContext.arController.stop(); this.dispatchEvent('arModeExited'); } onResize() { if (this.arToolkitSource) { this.arToolkitSource.onResizeElement(); this.arToolkitSource.copyElementSizeTo(this.renderer.domElement); } if (this.camera) { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); } this.renderer.setSize(window.innerWidth, window.innerHeight); } animate() { requestAnimationFrame(() => this.animate()); // 更新AR上下文 if (this.arToolkitSource && this.arToolkitSource.ready) { this.arToolkitContext.update(this.arToolkitSource.domElement); } // 渲染场景 this.renderer.render(this.scene, this.camera); } // 事件系统 addEventListener(event, callback) { if (!this.eventListeners[event]) { this.eventListeners[event] = []; } this.eventListeners[event].push(callback); } removeEventListener(event, callback) { if (this.eventListeners[event]) { const index = this.eventListeners[event].indexOf(callback); if (index > -1) { this.eventListeners[event].splice(index, 1); } } } dispatchEvent(event, data) { if (this.eventListeners[event]) { this.eventListeners[event].forEach(callback => { callback(data); }); } } // 销毁清理 destroy() { window.removeEventListener('resize', this.onResize); if (this.model && this.scene) { this.scene.remove(this.model); } if (this.renderer) { this.renderer.dispose(); this.renderer.forceContextLoss(); } if (this.arToolkitContext) { this.arToolkitContext.arController.stop(); } } } // 全局访问 window.ARProductViewer = ARProductViewer;
- // 在插件主类中添加短代码处理 public function ar_product_viewer_shortcode($atts) { $atts = shortcode_atts(array( 'product_id' => 0, 'width' => '100%', 'height' => '500px', 'mode' => 'both' // '3d', 'ar', 'both' ), $atts, 'ar_product_viewer'); // 获取产品信息 $product_id = $atts['product_id'] ?: get_the_ID(); $model_data = $this->get_product_model_data($product_id); if (!$model_data) { return '<p class="arpv-error">该产品暂无AR模型</p>'; } // 生成唯一ID $viewer_id = 'arpv-' . uniqid(); ob_start(); ?> <div class="arpv-product-viewer-container"> <div id="<?php echo esc_attr($viewer_id); ?>" class="arpv-viewer" style="width: <?php echo esc_attr($atts['width']); ?>; height: <?php echo esc_attr($atts['height']); ?>;"> </div> <div class="arpv-controls"> <div class="arpv-control-group"> <button class="arpv-btn arpv-btn-rotate" title="旋转"> <span class="dashicons dashicons-image-rotate"></span> </button> <button class="arpv-btn arpv-btn-zoom-in" title="放大"> <span class="dashicons dashicons-plus"></span> </button> <button class="arpv-btn arpv-btn-zoom-out" title="缩小"> <span class="dashicons dashicons-minus"></span> </button> <button class="arpv-btn arpv-btn-reset" title="重置"> <span class="dashicons dashicons-image-rotate"></span> </button> </div> <?php if ($atts['mode'] !== '3d') : ?> <div class="arpv-control-group"> <button class="arpv-btn arpv-btn-ar arpv-btn-primary" title="AR模式"> <span class="dashicons dashicons-camera"></span> <span>在您的空间中查看</span> </button> </div> <?php endif; ?> <div class="arpv-control-group arpv-instructions"> <p class="arpv-hint"> <span class="dashicons dashicons-info"></span> 提示:使用鼠标拖动旋转,滚轮缩放,或点击AR按钮在真实环境中查看 </p> </div> </div> <div class="arpv-ar-overlay" style="display: none;"> <div class="arpv-ar-header"> <button class="arpv-btn arpv-btn-close-ar">返回</button> <h3>AR模式</h3> <p>将相机对准平面表面,等待模型出现</p> </div> <div class="arpv-ar-hint"> <p>移动设备:单指旋转模型,双指缩放</p> </div> </div> </div> <script type="text/javascript"> jQuery(document).ready(function($) { // 初始化AR查看器 const viewer = new ARProductViewer({ containerId: '<?php echo esc_js($viewer_id); ?>', productId: <?php echo intval($product_id); ?>, modelUrl: '<?php echo esc_js($model_data['url']); ?>', modelScale: <?php echo floatval($model_data['scale']); ?> }); // 绑定控制按钮 $('.arpv-btn-rotate').on('click', function() { // 自动旋转动画 viewer.startAutoRotate(); }); $('.arpv-btn-zoom-in').on('click', function() { viewer.scaleModel(1.2); }); $('.arpv-btn-zoom-out').on('click', function() { viewer.scaleModel(0.8); }); $('.arpv-btn-reset').on('click', function() { viewer.resetModel(); }); $('.arpv-btn-ar').on('click', function() { $('.arpv-ar-overlay').show(); viewer.enterARMode(); }); $('.arpv-btn-close-ar').on('click', function() { $('.arpv-ar-overlay').hide(); viewer.exitARMode(); }); // 处理模型加载事件 viewer.addEventListener('modelLoaded', function(data) { console.log('模型加载完成', data); $('.arpv-hint').fadeOut(); }); viewer.addEventListener('modelLoading', function(data) { console.log('模型加载进度:', data.percent + '%'); }); }); </script> <?php return ob_get_clean(); } private function get_product_model_data($product_id) { global $wpdb; $table_name = $wpdb->prefix . 'arpv_models'; $model = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE product_id = %d ORDER BY id DESC LIMIT 1", $product_id )); if ($model) { return array( 'url' => $model->model_file_url, 'scale' => $model->scale_x, 'name' => $model->model_name ); } return false; }
-
- // 添加多模型支持 public function add_scene_builder_meta_box() { add_meta_box( 'arpv_scene_builder', 'AR场景构建器', array($this, 'render_scene_builder_meta_box'), 'product', 'normal', 'high' ); } public function render_scene_builder_meta_box($post) { wp_nonce_field('arpv_scene_builder', 'arpv_scene_builder_nonce'); $scene_data = get_post_meta($post->ID, '_arpv_scene_data', true); $scene_data = $scene_data ? json_decode($scene_data, true) : array(); ?> <div class="arpv-scene-builder"> <div class="arpv-scene-controls"> <button type="button" class="button arpv-add-to-scene">添加产品到场景</button> <button type="button" class="button arpv-save-scene">保存场景</button> <button type="button" class="button arpv-reset-scene">重置场景</button> </div> <div class="arpv-scene-preview"> <div id="arpv-scene-viewer" style="width: 100%; height: 400px; background: #f5f5f5;"> <!-- 3D场景预览 --> </div> </div> <div class="arpv-scene-items"> <h3>场景中的产品</h3> <ul id="arpv-scene-list"> <?php if (!empty($scene_data['items'])) : ?> <?php foreach ($scene_data['items'] as $item) : ?> <li data-product-id="<?php echo esc_attr($item['product_id']); ?>"> <span class="arpv-item-name"><?php echo esc_html($item['name']); ?></span> <button type="button" class="button arpv-remove-item">移除</button> </li> <?php endforeach; ?> <?php endif; ?> </ul> </div> <input type="hidden" id="arpv-scene-data" name="arpv_scene_data" value="<?php echo esc_attr(json_encode($scene_data)); ?>"> </div> <script type="text/javascript"> jQuery(document).ready(function($) { let sceneViewer = null; let sceneItems = <?php echo $scene_data ? json_encode($scene_data['items']) : '[]'; ?>; // 初始化场景查看器 function initSceneViewer() { sceneViewer = new ARSceneViewer({ containerId: 'arpv-scene-viewer', items: sceneItems }); } // 添加产品到场景 $('.arpv-add-to-scene').on('click', function() { // 打开产品选择模态框 $('#arpv-product-selector').show(); }); // 保存场景 $('.arpv-save-scene').on('click', function() { const sceneData = { items: sceneItems, camera_position: sceneViewer ? sceneViewer.getCameraPosition() : null, lighting: sceneViewer ? sceneViewer.getLightingSettings() : null }; $('#arpv-scene-data').val(JSON.stringify(sceneData)); alert('场景已保存'); }); // 初始化 initSceneViewer(); }); </script> <?php }
- // 实现模型缓存和CDN集成 class ARPV_Cache_Manager { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } public function get_model_url($model_id, $use_cache = true) { $cache_key = 'arpv_model_' . $model_id; if ($use_cache) { $cached_url = get_transient($cache_key); if ($cached_url !== false) { return $cached_url; } } // 从数据库获取模型信息 global $wpdb; $table_name = $wpdb->prefix . 'arpv_models'; $model = $wpdb->get_row($wpdb->prepare(
在当今数字化时代,增强现实(AR)技术正以前所未有的速度改变着消费者的购物体验。想象一下,当用户访问您的网站时,他们不仅能看到产品的平面图片,还能通过手机摄像头将虚拟产品"放置"在自己的真实环境中,从各个角度查看产品细节,甚至与产品进行互动。这种沉浸式体验不仅显著提升用户参与度,还能大幅降低退货率,提高转化率。
根据最新市场研究,采用AR技术的电商平台平均转化率提升了40%,客户互动时间增加了近一倍。对于WordPress网站所有者来说,集成AR功能不再是遥不可及的高科技梦想,而是可以通过代码二次开发实现的实用功能。
本教程将详细指导您如何为WordPress网站添加基于AR的虚拟产品摆放与场景体验功能,通过实用的代码示例和分步指南,帮助您打造前沿的交互式购物体验。
增强现实(AR)是一种将虚拟信息叠加到真实世界中的技术,通过设备摄像头捕捉现实场景,并在其上叠加计算机生成的图像、视频或3D模型。在电商领域,AR主要应用于:
- 虚拟试穿/试戴:用户可以看到产品穿戴在身上的效果
- 虚拟摆放:将家具、装饰品等放置在实际环境中查看效果
- 产品交互:允许用户旋转、缩放、自定义虚拟产品
在开始开发前,我们需要选择合适的AR技术方案:
方案一:基于Web的AR(WebAR)
- 优点:无需安装应用,跨平台兼容性好
- 技术栈:A-Frame、AR.js、Three.js
- 适合:轻量级AR体验,快速部署
方案二:原生AR SDK集成
- 优点:性能更好,功能更丰富
- 技术栈:ARKit(iOS)、ARCore(Android)
- 适合:高性能要求的复杂AR体验
方案三:混合方案
- 结合WebAR和原生SDK的优势
- 使用WebXR Device API
对于WordPress网站,我们推荐使用WebAR方案,因为它具有最好的兼容性和部署便利性。
开发环境准备:
- WordPress开发环境(本地或测试服务器)
- 代码编辑器(VS Code、Sublime Text等)
- 基础的前端开发知识(HTML、CSS、JavaScript)
- 3D模型处理工具(如Blender,用于准备产品模型)
AR体验的核心是3D模型,我们需要为每个产品准备高质量的3D模型:
-
模型格式选择:
- GLTF/GLB:推荐格式,体积小,加载快
- OBJ+MTL:通用格式,但文件较大
- FBX:功能丰富,但需要转换
-
模型优化技巧:
- 减少多边形数量(在保持质量的前提下)
- 压缩纹理图像
- 使用合理的LOD(细节层次)系统
- 确保模型尺寸与实际产品一致
-
模型创建流程:
产品测量 → 3D建模 → 纹理贴图 → 模型优化 → 格式转换 → 测试验证
首先,我们需要创建一个WordPress插件来管理所有AR相关功能:
<?php
/**
* Plugin Name: AR Product Viewer for WordPress
* Plugin URI: https://yourwebsite.com/
* Description: 为WordPress网站添加AR产品查看和虚拟摆放功能
* Version: 1.0.0
* Author: Your Name
* License: GPL v2 or later
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('ARPV_VERSION', '1.0.0');
define('ARPV_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('ARPV_PLUGIN_URL', plugin_dir_url(__FILE__));
// 初始化插件
class AR_Product_Viewer {
private static $instance = null;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init_hooks();
}
private function init_hooks() {
// 后台初始化
add_action('admin_init', array($this, 'admin_init'));
// 添加管理菜单
add_action('admin_menu', array($this, 'add_admin_menu'));
// 前端资源加载
add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets'));
// 后台资源加载
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
// 添加短代码
add_shortcode('ar_product_viewer', array($this, 'ar_product_viewer_shortcode'));
// 为产品添加自定义字段
add_action('add_meta_boxes', array($this, 'add_product_ar_meta_box'));
add_action('save_post', array($this, 'save_product_ar_meta'));
}
// 更多方法将在后续章节实现...
}
// 启动插件
AR_Product_Viewer::get_instance();
?>
我们需要扩展WordPress数据库来存储AR相关数据:
// 在插件激活时创建数据库表
register_activation_hook(__FILE__, 'arpv_create_database_tables');
function arpv_create_database_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$table_name = $wpdb->prefix . 'arpv_models';
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
product_id bigint(20) NOT NULL,
model_name varchar(255) NOT NULL,
model_file_url varchar(500) NOT NULL,
model_type varchar(50) DEFAULT 'glb',
model_size int(11) DEFAULT 0,
scale_x float DEFAULT 1.0,
scale_y float DEFAULT 1.0,
scale_z float DEFAULT 1.0,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY product_id (product_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
// 添加版本选项
add_option('arpv_db_version', '1.0');
}
创建用户友好的后台界面来管理AR模型:
// 添加管理菜单
public function add_admin_menu() {
add_menu_page(
'AR产品查看器',
'AR产品查看器',
'manage_options',
'ar-product-viewer',
array($this, 'render_admin_page'),
'dashicons-visibility',
30
);
add_submenu_page(
'ar-product-viewer',
'模型管理',
'模型管理',
'manage_options',
'arpv-model-manager',
array($this, 'render_model_manager_page')
);
add_submenu_page(
'ar-product-viewer',
'AR设置',
'设置',
'manage_options',
'arpv-settings',
array($this, 'render_settings_page')
);
}
// 渲染模型管理页面
public function render_model_manager_page() {
?>
<div class="wrap">
<h1>AR模型管理</h1>
<div class="arpv-admin-container">
<div class="arpv-admin-header">
<button id="arpv-add-model" class="button button-primary">添加新模型</button>
<div class="arpv-search-box">
<input type="text" id="arpv-model-search" placeholder="搜索模型...">
</div>
</div>
<div class="arpv-model-list">
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>ID</th>
<th>产品名称</th>
<th>模型名称</th>
<th>文件类型</th>
<th>文件大小</th>
<th>操作</th>
</tr>
</thead>
<tbody id="arpv-model-table-body">
<!-- 通过AJAX动态加载 -->
</tbody>
</table>
</div>
<!-- 模型上传模态框 -->
<div id="arpv-upload-modal" class="arpv-modal" style="display:none;">
<div class="arpv-modal-content">
<span class="arpv-close-modal">×</span>
<h2>上传3D模型</h2>
<form id="arpv-upload-form">
<div class="arpv-form-group">
<label for="arpv-product-select">关联产品</label>
<select id="arpv-product-select" required>
<option value="">选择产品...</option>
<?php
$products = get_posts(array(
'post_type' => 'product',
'posts_per_page' => -1,
'post_status' => 'publish'
));
foreach ($products as $product) {
echo '<option value="' . $product->ID . '">' . $product->post_title . '</option>';
}
?>
</select>
</div>
<div class="arpv-form-group">
<label for="arpv-model-name">模型名称</label>
<input type="text" id="arpv-model-name" required>
</div>
<div class="arpv-form-group">
<label for="arpv-model-file">模型文件</label>
<input type="file" id="arpv-model-file" accept=".glb,.gltf,.fbx,.obj" required>
<p class="description">支持GLB, GLTF, FBX, OBJ格式,建议使用GLB格式以获得最佳性能</p>
</div>
<div class="arpv-form-group">
<label for="arpv-model-scale">模型缩放比例</label>
<input type="number" id="arpv-model-scale" step="0.1" value="1.0" min="0.1" max="10">
</div>
<button type="submit" class="button button-primary">上传模型</button>
</form>
</div>
</div>
</div>
</div>
<?php
}
首先,我们需要在前端加载必要的AR和3D渲染库:
// 前端资源加载
public function enqueue_frontend_assets() {
// 只在需要AR功能的页面加载
if ($this->should_load_ar_assets()) {
// Three.js - 3D渲染引擎
wp_enqueue_script('three-js', 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js', array(), 'r128', true);
// AR.js - WebAR框架
wp_enqueue_script('ar-js', 'https://cdn.jsdelivr.net/npm/ar.js@latest/aframe/build/aframe-ar.min.js', array('three-js'), null, true);
// GLTF加载器
wp_enqueue_script('gltf-loader', ARPV_PLUGIN_URL . 'assets/js/loaders/GLTFLoader.js', array('three-js'), '1.0', true);
// 自定义AR控制器
wp_enqueue_script('arpv-ar-controller', ARPV_PLUGIN_URL . 'assets/js/ar-controller.js', array('three-js', 'ar-js', 'gltf-loader'), ARPV_VERSION, true);
// AR样式
wp_enqueue_style('arpv-ar-style', ARPV_PLUGIN_URL . 'assets/css/ar-style.css', array(), ARPV_VERSION);
// 传递数据到前端
wp_localize_script('arpv-ar-controller', 'arpv_ajax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('arpv_nonce')
));
}
}
// 判断是否需要加载AR资源
private function should_load_ar_assets() {
// 检查是否在产品页面
if (is_product() || has_shortcode(get_post()->post_content, 'ar_product_viewer')) {
return true;
}
// 检查是否在支持AR的页面模板
$template = get_page_template_slug();
if ($template === 'template-ar-product.php') {
return true;
}
return false;
}
实现核心的AR查看器组件:
// assets/js/ar-controller.js
class ARProductViewer {
constructor(options) {
this.options = {
containerId: 'ar-viewer-container',
productId: 0,
modelUrl: '',
modelScale: 1.0,
...options
};
this.scene = null;
this.camera = null;
this.renderer = null;
this.controls = null;
this.model = null;
this.arToolkitSource = null;
this.arToolkitContext = null;
this.init();
}
init() {
this.createARScene();
this.loadProductModel();
this.setupEventListeners();
this.animate();
}
createARScene() {
// 创建场景
this.scene = new THREE.Scene();
// 创建相机
this.camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 20);
// 创建渲染器
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.outputEncoding = THREE.sRGBEncoding;
// 添加到容器
const container = document.getElementById(this.options.containerId);
if (container) {
container.appendChild(this.renderer.domElement);
}
// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(0, 10, 5);
this.scene.add(directionalLight);
// 初始化AR.js
this.initARToolkit();
}
initARToolkit() {
// 创建AR.js源
this.arToolkitSource = new THREEx.ArToolkitSource({
sourceType: 'webcam',
sourceWidth: 1280,
sourceHeight: 720,
displayWidth: window.innerWidth,
displayHeight: window.innerHeight
});
// 初始化源
this.arToolkitSource.init(() => {
// 调整渲染器大小
this.onResize();
});
// 创建AR.js上下文
this.arToolkitContext = new THREEx.ArToolkitContext({
cameraParametersUrl: ARPV_PLUGIN_URL + 'assets/data/camera_para.dat',
detectionMode: 'mono',
maxDetectionRate: 30,
canvasWidth: this.arToolkitSource.domElement.clientWidth,
canvasHeight: this.arToolkitSource.domElement.clientHeight
});
// 初始化上下文
this.arToolkitContext.init(() => {
// 相机投影矩阵将根据AR上下文更新
this.camera.projectionMatrix.copy(this.arToolkitContext.getProjectionMatrix());
});
// 创建AR标记
const markerRoot = new THREE.Group();
this.scene.add(markerRoot);
// 创建标记控制器
const markerControls = new THREEx.ArMarkerControls(this.arToolkitContext, markerRoot, {
type: 'pattern',
patternUrl: ARPV_PLUGIN_URL + 'assets/data/patt.hiro',
changeMatrixMode: 'cameraTransformMatrix'
});
// 将模型添加到标记根组
this.modelContainer = markerRoot;
}
async loadProductModel() {
if (!this.options.modelUrl) {
console.error('未提供模型URL');
return;
}
try {
const loader = new THREE.GLTFLoader();
loader.load(
this.options.modelUrl,
(gltf) => {
this.model = gltf.scene;
// 调整模型大小和位置
const box = new THREE.Box3().setFromObject(this.model);
const size = box.getSize(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
const scale = this.options.modelScale / maxDim;
this.model.scale.set(scale, scale, scale);
this.model.position.set(0, 0, 0);
// 添加到场景
if (this.modelContainer) {
this.modelContainer.add(this.model);
} else {
this.scene.add(this.model);
}
// 触发模型加载完成事件
this.dispatchEvent('modelLoaded', { model: this.model });
console.log('模型加载成功');
},
(progress) => {
// 加载进度
const percent = (progress.loaded / progress.total * 100).toFixed(2);
this.dispatchEvent('modelLoading', { percent });
},
(error) => {
console.error('模型加载失败:', error);
this.dispatchEvent('modelError', { error });
}
);
} catch (error) {
console.error('加载模型时出错:', error);
}
// 继续 assets/js/ar-controller.js
setupEventListeners() {
// 窗口大小调整
window.addEventListener('resize', () => this.onResize());
// 触摸/鼠标事件
this.setupInteractionEvents();
// 键盘控制
window.addEventListener('keydown', (e) => this.onKeyDown(e));
// 自定义事件监听
this.eventListeners = {};
}
setupInteractionEvents() {
const canvas = this.renderer.domElement;
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
// 触摸事件
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
if (e.touches.length === 1) {
// 单指触摸 - 旋转
isDragging = true;
previousMousePosition = {
x: e.touches[0].clientX,
y: e.touches[0].clientY
};
} else if (e.touches.length === 2) {
// 双指触摸 - 缩放
this.startPinchDistance = this.getPinchDistance(e);
}
});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (e.touches.length === 1 && isDragging && this.model) {
// 单指拖动 - 旋转模型
const currentMousePosition = {
x: e.touches[0].clientX,
y: e.touches[0].clientY
};
const delta = {
x: currentMousePosition.x - previousMousePosition.x,
y: currentMousePosition.y - previousMousePosition.y
};
// 根据拖动距离旋转模型
this.rotateModel(delta.x * 0.01, delta.y * 0.01);
previousMousePosition = currentMousePosition;
} else if (e.touches.length === 2 && this.startPinchDistance && this.model) {
// 双指捏合 - 缩放模型
const currentPinchDistance = this.getPinchDistance(e);
const scaleFactor = currentPinchDistance / this.startPinchDistance;
this.scaleModel(scaleFactor);
this.startPinchDistance = currentPinchDistance;
}
});
canvas.addEventListener('touchend', (e) => {
isDragging = false;
this.startPinchDistance = null;
});
// 鼠标事件(桌面设备)
canvas.addEventListener('mousedown', (e) => {
if (e.button === 0) { // 左键
isDragging = true;
previousMousePosition = { x: e.clientX, y: e.clientY };
}
});
canvas.addEventListener('mousemove', (e) => {
if (isDragging && this.model) {
const currentMousePosition = { x: e.clientX, y: e.clientY };
const delta = {
x: currentMousePosition.x - previousMousePosition.x,
y: currentMousePosition.y - previousMousePosition.y
};
this.rotateModel(delta.x * 0.01, delta.y * 0.01);
previousMousePosition = currentMousePosition;
}
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
});
// 鼠标滚轮缩放
canvas.addEventListener('wheel', (e) => {
e.preventDefault();
if (this.model) {
const scaleFactor = e.deltaY > 0 ? 0.9 : 1.1;
this.scaleModel(scaleFactor);
}
});
}
getPinchDistance(e) {
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
return Math.sqrt(dx * dx + dy * dy);
}
rotateModel(deltaX, deltaY) {
if (!this.model) return;
// 限制旋转角度
this.model.rotation.y += deltaX;
this.model.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, this.model.rotation.x + deltaY));
}
scaleModel(factor) {
if (!this.model) return;
// 限制缩放范围
const currentScale = this.model.scale.x;
const newScale = Math.max(0.1, Math.min(5, currentScale * factor));
this.model.scale.set(newScale, newScale, newScale);
}
onKeyDown(e) {
if (!this.model) return;
switch(e.key) {
case 'r': // 重置模型位置和旋转
this.model.rotation.set(0, 0, 0);
this.model.scale.set(1, 1, 1);
break;
case '+': // 放大
this.scaleModel(1.1);
break;
case '-': // 缩小
this.scaleModel(0.9);
break;
case ' ': // 空格键 - 切换AR模式
this.toggleARMode();
break;
}
}
toggleARMode() {
// 切换AR模式和普通3D查看模式
this.isARMode = !this.isARMode;
if (this.isARMode) {
this.enterARMode();
} else {
this.exitARMode();
}
}
enterARMode() {
// 启动摄像头
this.arToolkitSource.init(() => {
this.arToolkitContext.arController.play();
});
this.dispatchEvent('arModeEntered');
}
exitARMode() {
// 停止摄像头
this.arToolkitContext.arController.stop();
this.dispatchEvent('arModeExited');
}
onResize() {
if (this.arToolkitSource) {
this.arToolkitSource.onResizeElement();
this.arToolkitSource.copyElementSizeTo(this.renderer.domElement);
}
if (this.camera) {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
}
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
animate() {
requestAnimationFrame(() => this.animate());
// 更新AR上下文
if (this.arToolkitSource && this.arToolkitSource.ready) {
this.arToolkitContext.update(this.arToolkitSource.domElement);
}
// 渲染场景
this.renderer.render(this.scene, this.camera);
}
// 事件系统
addEventListener(event, callback) {
if (!this.eventListeners[event]) {
this.eventListeners[event] = [];
}
this.eventListeners[event].push(callback);
}
removeEventListener(event, callback) {
if (this.eventListeners[event]) {
const index = this.eventListeners[event].indexOf(callback);
if (index > -1) {
this.eventListeners[event].splice(index, 1);
}
}
}
dispatchEvent(event, data) {
if (this.eventListeners[event]) {
this.eventListeners[event].forEach(callback => {
callback(data);
});
}
}
// 销毁清理
destroy() {
window.removeEventListener('resize', this.onResize);
if (this.model && this.scene) {
this.scene.remove(this.model);
}
if (this.renderer) {
this.renderer.dispose();
this.renderer.forceContextLoss();
}
if (this.arToolkitContext) {
this.arToolkitContext.arController.stop();
}
}
}
// 全局访问
window.ARProductViewer = ARProductViewer;
// 继续 assets/js/ar-controller.js
setupEventListeners() {
// 窗口大小调整
window.addEventListener('resize', () => this.onResize());
// 触摸/鼠标事件
this.setupInteractionEvents();
// 键盘控制
window.addEventListener('keydown', (e) => this.onKeyDown(e));
// 自定义事件监听
this.eventListeners = {};
}
setupInteractionEvents() {
const canvas = this.renderer.domElement;
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
// 触摸事件
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
if (e.touches.length === 1) {
// 单指触摸 - 旋转
isDragging = true;
previousMousePosition = {
x: e.touches[0].clientX,
y: e.touches[0].clientY
};
} else if (e.touches.length === 2) {
// 双指触摸 - 缩放
this.startPinchDistance = this.getPinchDistance(e);
}
});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (e.touches.length === 1 && isDragging && this.model) {
// 单指拖动 - 旋转模型
const currentMousePosition = {
x: e.touches[0].clientX,
y: e.touches[0].clientY
};
const delta = {
x: currentMousePosition.x - previousMousePosition.x,
y: currentMousePosition.y - previousMousePosition.y
};
// 根据拖动距离旋转模型
this.rotateModel(delta.x * 0.01, delta.y * 0.01);
previousMousePosition = currentMousePosition;
} else if (e.touches.length === 2 && this.startPinchDistance && this.model) {
// 双指捏合 - 缩放模型
const currentPinchDistance = this.getPinchDistance(e);
const scaleFactor = currentPinchDistance / this.startPinchDistance;
this.scaleModel(scaleFactor);
this.startPinchDistance = currentPinchDistance;
}
});
canvas.addEventListener('touchend', (e) => {
isDragging = false;
this.startPinchDistance = null;
});
// 鼠标事件(桌面设备)
canvas.addEventListener('mousedown', (e) => {
if (e.button === 0) { // 左键
isDragging = true;
previousMousePosition = { x: e.clientX, y: e.clientY };
}
});
canvas.addEventListener('mousemove', (e) => {
if (isDragging && this.model) {
const currentMousePosition = { x: e.clientX, y: e.clientY };
const delta = {
x: currentMousePosition.x - previousMousePosition.x,
y: currentMousePosition.y - previousMousePosition.y
};
this.rotateModel(delta.x * 0.01, delta.y * 0.01);
previousMousePosition = currentMousePosition;
}
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
});
// 鼠标滚轮缩放
canvas.addEventListener('wheel', (e) => {
e.preventDefault();
if (this.model) {
const scaleFactor = e.deltaY > 0 ? 0.9 : 1.1;
this.scaleModel(scaleFactor);
}
});
}
getPinchDistance(e) {
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
return Math.sqrt(dx * dx + dy * dy);
}
rotateModel(deltaX, deltaY) {
if (!this.model) return;
// 限制旋转角度
this.model.rotation.y += deltaX;
this.model.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, this.model.rotation.x + deltaY));
}
scaleModel(factor) {
if (!this.model) return;
// 限制缩放范围
const currentScale = this.model.scale.x;
const newScale = Math.max(0.1, Math.min(5, currentScale * factor));
this.model.scale.set(newScale, newScale, newScale);
}
onKeyDown(e) {
if (!this.model) return;
switch(e.key) {
case 'r': // 重置模型位置和旋转
this.model.rotation.set(0, 0, 0);
this.model.scale.set(1, 1, 1);
break;
case '+': // 放大
this.scaleModel(1.1);
break;
case '-': // 缩小
this.scaleModel(0.9);
break;
case ' ': // 空格键 - 切换AR模式
this.toggleARMode();
break;
}
}
toggleARMode() {
// 切换AR模式和普通3D查看模式
this.isARMode = !this.isARMode;
if (this.isARMode) {
this.enterARMode();
} else {
this.exitARMode();
}
}
enterARMode() {
// 启动摄像头
this.arToolkitSource.init(() => {
this.arToolkitContext.arController.play();
});
this.dispatchEvent('arModeEntered');
}
exitARMode() {
// 停止摄像头
this.arToolkitContext.arController.stop();
this.dispatchEvent('arModeExited');
}
onResize() {
if (this.arToolkitSource) {
this.arToolkitSource.onResizeElement();
this.arToolkitSource.copyElementSizeTo(this.renderer.domElement);
}
if (this.camera) {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
}
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
animate() {
requestAnimationFrame(() => this.animate());
// 更新AR上下文
if (this.arToolkitSource && this.arToolkitSource.ready) {
this.arToolkitContext.update(this.arToolkitSource.domElement);
}
// 渲染场景
this.renderer.render(this.scene, this.camera);
}
// 事件系统
addEventListener(event, callback) {
if (!this.eventListeners[event]) {
this.eventListeners[event] = [];
}
this.eventListeners[event].push(callback);
}
removeEventListener(event, callback) {
if (this.eventListeners[event]) {
const index = this.eventListeners[event].indexOf(callback);
if (index > -1) {
this.eventListeners[event].splice(index, 1);
}
}
}
dispatchEvent(event, data) {
if (this.eventListeners[event]) {
this.eventListeners[event].forEach(callback => {
callback(data);
});
}
}
// 销毁清理
destroy() {
window.removeEventListener('resize', this.onResize);
if (this.model && this.scene) {
this.scene.remove(this.model);
}
if (this.renderer) {
this.renderer.dispose();
this.renderer.forceContextLoss();
}
if (this.arToolkitContext) {
this.arToolkitContext.arController.stop();
}
}
}
// 全局访问
window.ARProductViewer = ARProductViewer;
// 在插件主类中添加短代码处理
public function ar_product_viewer_shortcode($atts) {
$atts = shortcode_atts(array(
'product_id' => 0,
'width' => '100%',
'height' => '500px',
'mode' => 'both' // '3d', 'ar', 'both'
), $atts, 'ar_product_viewer');
// 获取产品信息
$product_id = $atts['product_id'] ?: get_the_ID();
$model_data = $this->get_product_model_data($product_id);
if (!$model_data) {
return '<p class="arpv-error">该产品暂无AR模型</p>';
}
// 生成唯一ID
$viewer_id = 'arpv-' . uniqid();
ob_start();
?>
<div class="arpv-product-viewer-container">
<div id="<?php echo esc_attr($viewer_id); ?>"
class="arpv-viewer"
style="width: <?php echo esc_attr($atts['width']); ?>;
height: <?php echo esc_attr($atts['height']); ?>;">
</div>
<div class="arpv-controls">
<div class="arpv-control-group">
<button class="arpv-btn arpv-btn-rotate" title="旋转">
<span class="dashicons dashicons-image-rotate"></span>
</button>
<button class="arpv-btn arpv-btn-zoom-in" title="放大">
<span class="dashicons dashicons-plus"></span>
</button>
<button class="arpv-btn arpv-btn-zoom-out" title="缩小">
<span class="dashicons dashicons-minus"></span>
</button>
<button class="arpv-btn arpv-btn-reset" title="重置">
<span class="dashicons dashicons-image-rotate"></span>
</button>
</div>
<?php if ($atts['mode'] !== '3d') : ?>
<div class="arpv-control-group">
<button class="arpv-btn arpv-btn-ar arpv-btn-primary" title="AR模式">
<span class="dashicons dashicons-camera"></span>
<span>在您的空间中查看</span>
</button>
</div>
<?php endif; ?>
<div class="arpv-control-group arpv-instructions">
<p class="arpv-hint">
<span class="dashicons dashicons-info"></span>
提示:使用鼠标拖动旋转,滚轮缩放,或点击AR按钮在真实环境中查看
</p>
</div>
</div>
<div class="arpv-ar-overlay" style="display: none;">
<div class="arpv-ar-header">
<button class="arpv-btn arpv-btn-close-ar">返回</button>
<h3>AR模式</h3>
<p>将相机对准平面表面,等待模型出现</p>
</div>
<div class="arpv-ar-hint">
<p>移动设备:单指旋转模型,双指缩放</p>
</div>
</div>
</div>
<script type="text/javascript">
jQuery(document).ready(function($) {
// 初始化AR查看器
const viewer = new ARProductViewer({
containerId: '<?php echo esc_js($viewer_id); ?>',
productId: <?php echo intval($product_id); ?>,
modelUrl: '<?php echo esc_js($model_data['url']); ?>',
modelScale: <?php echo floatval($model_data['scale']); ?>
});
// 绑定控制按钮
$('.arpv-btn-rotate').on('click', function() {
// 自动旋转动画
viewer.startAutoRotate();
});
$('.arpv-btn-zoom-in').on('click', function() {
viewer.scaleModel(1.2);
});
$('.arpv-btn-zoom-out').on('click', function() {
viewer.scaleModel(0.8);
});
$('.arpv-btn-reset').on('click', function() {
viewer.resetModel();
});
$('.arpv-btn-ar').on('click', function() {
$('.arpv-ar-overlay').show();
viewer.enterARMode();
});
$('.arpv-btn-close-ar').on('click', function() {
$('.arpv-ar-overlay').hide();
viewer.exitARMode();
});
// 处理模型加载事件
viewer.addEventListener('modelLoaded', function(data) {
console.log('模型加载完成', data);
$('.arpv-hint').fadeOut();
});
viewer.addEventListener('modelLoading', function(data) {
console.log('模型加载进度:', data.percent + '%');
});
});
</script>
<?php
return ob_get_clean();
}
private function get_product_model_data($product_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'arpv_models';
$model = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE product_id = %d ORDER BY id DESC LIMIT 1",
$product_id
));
if ($model) {
return array(
'url' => $model->model_file_url,
'scale' => $model->scale_x,
'name' => $model->model_name
);
}
return false;
}
// 在插件主类中添加短代码处理
public function ar_product_viewer_shortcode($atts) {
$atts = shortcode_atts(array(
'product_id' => 0,
'width' => '100%',
'height' => '500px',
'mode' => 'both' // '3d', 'ar', 'both'
), $atts, 'ar_product_viewer');
// 获取产品信息
$product_id = $atts['product_id'] ?: get_the_ID();
$model_data = $this->get_product_model_data($product_id);
if (!$model_data) {
return '<p class="arpv-error">该产品暂无AR模型</p>';
}
// 生成唯一ID
$viewer_id = 'arpv-' . uniqid();
ob_start();
?>
<div class="arpv-product-viewer-container">
<div id="<?php echo esc_attr($viewer_id); ?>"
class="arpv-viewer"
style="width: <?php echo esc_attr($atts['width']); ?>;
height: <?php echo esc_attr($atts['height']); ?>;">
</div>
<div class="arpv-controls">
<div class="arpv-control-group">
<button class="arpv-btn arpv-btn-rotate" title="旋转">
<span class="dashicons dashicons-image-rotate"></span>
</button>
<button class="arpv-btn arpv-btn-zoom-in" title="放大">
<span class="dashicons dashicons-plus"></span>
</button>
<button class="arpv-btn arpv-btn-zoom-out" title="缩小">
<span class="dashicons dashicons-minus"></span>
</button>
<button class="arpv-btn arpv-btn-reset" title="重置">
<span class="dashicons dashicons-image-rotate"></span>
</button>
</div>
<?php if ($atts['mode'] !== '3d') : ?>
<div class="arpv-control-group">
<button class="arpv-btn arpv-btn-ar arpv-btn-primary" title="AR模式">
<span class="dashicons dashicons-camera"></span>
<span>在您的空间中查看</span>
</button>
</div>
<?php endif; ?>
<div class="arpv-control-group arpv-instructions">
<p class="arpv-hint">
<span class="dashicons dashicons-info"></span>
提示:使用鼠标拖动旋转,滚轮缩放,或点击AR按钮在真实环境中查看
</p>
</div>
</div>
<div class="arpv-ar-overlay" style="display: none;">
<div class="arpv-ar-header">
<button class="arpv-btn arpv-btn-close-ar">返回</button>
<h3>AR模式</h3>
<p>将相机对准平面表面,等待模型出现</p>
</div>
<div class="arpv-ar-hint">
<p>移动设备:单指旋转模型,双指缩放</p>
</div>
</div>
</div>
<script type="text/javascript">
jQuery(document).ready(function($) {
// 初始化AR查看器
const viewer = new ARProductViewer({
containerId: '<?php echo esc_js($viewer_id); ?>',
productId: <?php echo intval($product_id); ?>,
modelUrl: '<?php echo esc_js($model_data['url']); ?>',
modelScale: <?php echo floatval($model_data['scale']); ?>
});
// 绑定控制按钮
$('.arpv-btn-rotate').on('click', function() {
// 自动旋转动画
viewer.startAutoRotate();
});
$('.arpv-btn-zoom-in').on('click', function() {
viewer.scaleModel(1.2);
});
$('.arpv-btn-zoom-out').on('click', function() {
viewer.scaleModel(0.8);
});
$('.arpv-btn-reset').on('click', function() {
viewer.resetModel();
});
$('.arpv-btn-ar').on('click', function() {
$('.arpv-ar-overlay').show();
viewer.enterARMode();
});
$('.arpv-btn-close-ar').on('click', function() {
$('.arpv-ar-overlay').hide();
viewer.exitARMode();
});
// 处理模型加载事件
viewer.addEventListener('modelLoaded', function(data) {
console.log('模型加载完成', data);
$('.arpv-hint').fadeOut();
});
viewer.addEventListener('modelLoading', function(data) {
console.log('模型加载进度:', data.percent + '%');
});
});
</script>
<?php
return ob_get_clean();
}
private function get_product_model_data($product_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'arpv_models';
$model = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE product_id = %d ORDER BY id DESC LIMIT 1",
$product_id
));
if ($model) {
return array(
'url' => $model->model_file_url,
'scale' => $model->scale_x,
'name' => $model->model_name
);
}
return false;
}
// 添加多模型支持
public function add_scene_builder_meta_box() {
add_meta_box(
'arpv_scene_builder',
'AR场景构建器',
array($this, 'render_scene_builder_meta_box'),
'product',
'normal',
'high'
);
}
public function render_scene_builder_meta_box($post) {
wp_nonce_field('arpv_scene_builder', 'arpv_scene_builder_nonce');
$scene_data = get_post_meta($post->ID, '_arpv_scene_data', true);
$scene_data = $scene_data ? json_decode($scene_data, true) : array();
?>
<div class="arpv-scene-builder">
<div class="arpv-scene-controls">
<button type="button" class="button arpv-add-to-scene">添加产品到场景</button>
<button type="button" class="button arpv-save-scene">保存场景</button>
<button type="button" class="button arpv-reset-scene">重置场景</button>
</div>
<div class="arpv-scene-preview">
<div id="arpv-scene-viewer" style="width: 100%; height: 400px; background: #f5f5f5;">
<!-- 3D场景预览 -->
</div>
</div>
<div class="arpv-scene-items">
<h3>场景中的产品</h3>
<ul id="arpv-scene-list">
<?php if (!empty($scene_data['items'])) : ?>
<?php foreach ($scene_data['items'] as $item) : ?>
<li data-product-id="<?php echo esc_attr($item['product_id']); ?>">
<span class="arpv-item-name"><?php echo esc_html($item['name']); ?></span>
<button type="button" class="button arpv-remove-item">移除</button>
</li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</div>
<input type="hidden" id="arpv-scene-data" name="arpv_scene_data"
value="<?php echo esc_attr(json_encode($scene_data)); ?>">
</div>
<script type="text/javascript">
jQuery(document).ready(function($) {
let sceneViewer = null;
let sceneItems = <?php echo $scene_data ? json_encode($scene_data['items']) : '[]'; ?>;
// 初始化场景查看器
function initSceneViewer() {
sceneViewer = new ARSceneViewer({
containerId: 'arpv-scene-viewer',
items: sceneItems
});
}
// 添加产品到场景
$('.arpv-add-to-scene').on('click', function() {
// 打开产品选择模态框
$('#arpv-product-selector').show();
});
// 保存场景
$('.arpv-save-scene').on('click', function() {
const sceneData = {
items: sceneItems,
camera_position: sceneViewer ? sceneViewer.getCameraPosition() : null,
lighting: sceneViewer ? sceneViewer.getLightingSettings() : null
};
$('#arpv-scene-data').val(JSON.stringify(sceneData));
alert('场景已保存');
});
// 初始化
initSceneViewer();
});
</script>
<?php
}
// 添加多模型支持
public function add_scene_builder_meta_box() {
add_meta_box(
'arpv_scene_builder',
'AR场景构建器',
array($this, 'render_scene_builder_meta_box'),
'product',
'normal',
'high'
);
}
public function render_scene_builder_meta_box($post) {
wp_nonce_field('arpv_scene_builder', 'arpv_scene_builder_nonce');
$scene_data = get_post_meta($post->ID, '_arpv_scene_data', true);
$scene_data = $scene_data ? json_decode($scene_data, true) : array();
?>
<div class="arpv-scene-builder">
<div class="arpv-scene-controls">
<button type="button" class="button arpv-add-to-scene">添加产品到场景</button>
<button type="button" class="button arpv-save-scene">保存场景</button>
<button type="button" class="button arpv-reset-scene">重置场景</button>
</div>
<div class="arpv-scene-preview">
<div id="arpv-scene-viewer" style="width: 100%; height: 400px; background: #f5f5f5;">
<!-- 3D场景预览 -->
</div>
</div>
<div class="arpv-scene-items">
<h3>场景中的产品</h3>
<ul id="arpv-scene-list">
<?php if (!empty($scene_data['items'])) : ?>
<?php foreach ($scene_data['items'] as $item) : ?>
<li data-product-id="<?php echo esc_attr($item['product_id']); ?>">
<span class="arpv-item-name"><?php echo esc_html($item['name']); ?></span>
<button type="button" class="button arpv-remove-item">移除</button>
</li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</div>
<input type="hidden" id="arpv-scene-data" name="arpv_scene_data"
value="<?php echo esc_attr(json_encode($scene_data)); ?>">
</div>
<script type="text/javascript">
jQuery(document).ready(function($) {
let sceneViewer = null;
let sceneItems = <?php echo $scene_data ? json_encode($scene_data['items']) : '[]'; ?>;
// 初始化场景查看器
function initSceneViewer() {
sceneViewer = new ARSceneViewer({
containerId: 'arpv-scene-viewer',
items: sceneItems
});
}
// 添加产品到场景
$('.arpv-add-to-scene').on('click', function() {
// 打开产品选择模态框
$('#arpv-product-selector').show();
});
// 保存场景
$('.arpv-save-scene').on('click', function() {
const sceneData = {
items: sceneItems,
camera_position: sceneViewer ? sceneViewer.getCameraPosition() : null,
lighting: sceneViewer ? sceneViewer.getLightingSettings() : null
};
$('#arpv-scene-data').val(JSON.stringify(sceneData));
alert('场景已保存');
});
// 初始化
initSceneViewer();
});
</script>
<?php
}
// 实现模型缓存和CDN集成
class ARPV_Cache_Manager {
private static $instance = null;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function get_model_url($model_id, $use_cache = true) {
$cache_key = 'arpv_model_' . $model_id;
if ($use_cache) {
$cached_url = get_transient($cache_key);
if ($cached_url !== false) {
return $cached_url;
}
}
// 从数据库获取模型信息
global $wpdb;
$table_name = $wpdb->prefix . 'arpv_models';
$model = $wpdb->get_row($wpdb->prepare(
// 实现模型缓存和CDN集成
class ARPV_Cache_Manager {
private static $instance = null;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function get_model_url($model_id, $use_cache = true) {
$cache_key = 'arpv_model_' . $model_id;
if ($use_cache) {
$cached_url = get_transient($cache_key);
if ($cached_url !== false) {
return $cached_url;
}
}
// 从数据库获取模型信息
global $wpdb;
$table_name = $wpdb->prefix . 'arpv_models';
$model = $wpdb->get_row($wpdb->prepare(


