最近为了实现油藏模型的解析与三维可视化操作,对three.js和eclipse绘制的三维油藏模型格式解析进行了研究,实现了三维油藏模型可视化及简单交互操作。主要实现了三维油藏模型构建、解析加载和三维可视化交互,并根据油藏属性(如孔隙度PORO)等进行层位显示,同时支持按照X/Y/Z轴切割显示,以及任意多边形、任意点的切割显示,具体如下,供大家参考。
一、基于Theejs实现油藏模型的三维可视化效果图


二、主要核心代码
主要基于Vue或HTML+Three.js,实现了三维油藏模型解析加载、三维显示、油藏属性填充显示、可视化交互操作,以及三维空间的多种切割交互及显示等。以下主要是第一幅效果图的三维可视化交互代码,代码如下。
<script>
// 模拟的油藏模型(来自grid-4layer.grdecl)
const reservoirData = {
nx: 50,
ny: 50,
nz: 4,
layers: [
{
name: '底层油藏',
depth: [0, 8],
porosity: 0.15,
color: '#8B4513'
},
{
name: '主力油层',
depth: [8, 14],
porosity: 0.22,
color: '#D2691E'
},
{
name: '顶层油藏',
depth: [14, 23],
porosity: 0.40,
color: '#397235'
},
{
name: '盖层',
depth: [24, 29],
porosity: 0.12,
color: '#DEB887'
}
],
porosityData: {
// 简化的油藏孔隙度数据,实际来自文件
layer0: Array(2500).fill(0).map(() => 0.15 + Math.random() * 0.03),
layer1: Array(2500).fill(0).map(() => 0.19 + Math.random() * 0.06),
layer2: Array(2500).fill(0).map(() => 0.30 + Math.random() * 0.06),
layer3: Array(2500).fill(0).map(() => 0.10 + Math.random() * 0.05)
}
};
// 场景设置
let scene, camera, renderer, controls;
let reservoirLayers = [];
let porosityMode = false;
function init() {
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color('#b7c0ce');
// 创建相机
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(50, 30, 50);
camera.lookAt(0, 0, 0);
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('container').appendChild(renderer.domElement);
// 添加轨道控制器
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// 添加环境光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
// 添加平行光源
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.4);
directionalLight.position.set(50, 100, 50);
scene.add(directionalLight);
// 添加参考网格
const gridHelper = new THREE.GridHelper(200, 50, '#444444', '#6e6c6c');
scene.add(gridHelper);
// 添加坐标轴
const axesHelper = new THREE.AxesHelper(50);
scene.add(axesHelper);
// 创建油藏模型
createReservoirModel();
// 处理窗口大小变化
window.addEventListener('resize', onWindowResize, false);
// 开始渲染循环
animate();
}
// 创建油藏模型
function createReservoirModel() {
reservoirData.layers.forEach((layer, index) => {
const height = layer.depth[1] - layer.depth[0];
const geometry = createLayerGeometry(height, index);
// 创建材质
let material;
if (porosityMode) {
// 利用孔隙度值创建纹理
material = createPorosityMaterial(layer, index);
} else {
// 利用默认颜色创建纹理
material = new THREE.MeshPhongMaterial({
color: layer.color,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.7
});
}
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = layer.depth[0] + height / 2;
mesh.userData = { layerName: layer.name, layerIndex: index };
scene.add(mesh);
reservoirLayers.push(mesh);
});
}
// 创建层几何体
function createLayerGeometry(height, seed) {
const geometry = new THREE.BoxGeometry(reservoirData.nx, height, reservoirData.ny, reservoirData.nx, 4, reservoirData.ny);
const positions = geometry.attributes.position;
const vector = new THREE.Vector3();
for (let i = 0; i < positions.count; i++) {
vector.fromBufferAttribute(positions, i);
const freq = 0.08 + seed * 0.01;
const amp = 0.5 + seed * 0.2;
const noise = Math.sin(vector.x * freq) * Math.cos(vector.z * freq) * amp +
Math.sin(vector.x * freq * 2) * 0.2;
if (vector.y > 0) {
vector.y += noise;
} else {
vector.y += noise * 0.3;
}
positions.setXYZ(i, vector.x, vector.y, vector.z);
}
geometry.computeVertexNormals();
return geometry;
}
// 处理窗口大小变化
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 渲染循环
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
// 初始化
init();
</script>