SphereBufferGeometry 上的 THREE.js ShaderMaterial UV 包裹问题

Posted

技术标签:

【中文标题】SphereBufferGeometry 上的 THREE.js ShaderMaterial UV 包裹问题【英文标题】:THREE.js ShaderMaterial UV wrapping issues on SphereBufferGeometry 【发布时间】:2021-12-13 01:46:21 【问题描述】:

我正在尝试用 ShaderMaterial 包裹 SphereBufferGeometry,我在其中使用噪声来模拟木星的表面,但它非常奇怪地包裹到球体几何体。所有动画纹理都出现在一条纬线周围的细带中,而不是像普通纹理那样缠绕在“行星”周围。我在下面附上了图片。

它在平面上运行良好,但我可能天真地认为它会像纹理一样简单地包裹,而且我对着色器编程很陌生,所以我有点卡住了。

this is the plane which is wrapping fine

this is not wrapping correctly

我有一种感觉,也许我可以将噪声方程移动到 fragmentShader - 但我的知识还不存在,当我尝试时它坏了。我什至尝试将平面的目标变形为球体,但 ShaderMaterial 本身并不支持 morphTargets,而且我在尝试使用 onBeforeCompile 注入 #include <morphtarget_pars_vertex> 之后仍然无法正常工作。我还尝试了 THREE 在统一纹理上的包裹方程,但它产生了类似的结果。

这是我所有的代码,shaderMaterial 实现在addPlanet()

import * as THREE from '../../build/three.module';
import  OrbitControls  from '../../examples/jsm/controls/OrbitControls';

const displacementVert = `
precision mediump float;

varying vec2 vUv;
varying float vWave;
uniform float uTime;

//
// Description : Array and textureless GLSL 2D/3D/4D simplex
//               noise functions.
//      Author : Ian McEwan, Ashima Arts.
//  Maintainer : ijm
//     Lastmod : 20110822 (ijm)
//     License : Copyright (C) 2011 Ashima Arts. All rights reserved.
//               Distributed under the MIT License. See LICENSE file.
//               https://github.com/ashima/webgl-noise
//

vec3 mod289(vec3 x) 
  return x - floor(x * (1.0 / 289.0)) * 289.0;


vec4 mod289(vec4 x) 
  return x - floor(x * (1.0 / 289.0)) * 289.0;


vec4 permute(vec4 x) 
     return mod289(((x*34.0)+1.0)*x);


vec4 taylorInvSqrt(vec4 r)

  return 1.79284291400159 - 0.85373472095314 * r;


float snoise(vec3 v) 
  const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;
  const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);
  
  // First corner
  vec3 i  = floor(v + dot(v, C.yyy) );
  vec3 x0 =   v - i + dot(i, C.xxx) ;
  
  // Other corners
  vec3 g = step(x0.yzx, x0.xyz);
  vec3 l = 1.0 - g;
  vec3 i1 = min( g.xyz, l.zxy );
  vec3 i2 = max( g.xyz, l.zxy );

  //   x0 = x0 - 0.0 + 0.0 * C.xxx;
  //   x1 = x0 - i1  + 1.0 * C.xxx;
  //   x2 = x0 - i2  + 2.0 * C.xxx;
  //   x3 = x0 - 1.0 + 3.0 * C.xxx;
  vec3 x1 = x0 - i1 + C.xxx;
  vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
  vec3 x3 = x0 - D.yyy;      // -1.0+3.0*C.x = -0.5 = -D.y
  
  // Permutations
  i = mod289(i);
  vec4 p = permute( permute( permute(
             i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
           + i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
           + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
           
  // Gradients: 7x7 points over a square, mapped onto an octahedron.
  // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
  float n_ = 0.142857142857; // 1.0/7.0
  vec3  ns = n_ * D.wyz - D.xzx;

  vec4 j = p - 49.0 * floor(p * ns.z * ns.z);  //  mod(p,7*7)

  vec4 x_ = floor(j * ns.z);
  vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)

  vec4 x = x_ *ns.x + ns.yyyy;
  vec4 y = y_ *ns.x + ns.yyyy;
  vec4 h = 1.0 - abs(x) - abs(y);

  vec4 b0 = vec4( x.xy, y.xy );
  vec4 b1 = vec4( x.zw, y.zw );

  //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
  //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
  vec4 s0 = floor(b0)*2.0 + 1.0;
  vec4 s1 = floor(b1)*2.0 + 1.0;
  vec4 sh = -step(h, vec4(0.0));

  vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
  vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;

  vec3 p0 = vec3(a0.xy,h.x);
  vec3 p1 = vec3(a0.zw,h.y);
  vec3 p2 = vec3(a1.xy,h.z);
  vec3 p3 = vec3(a1.zw,h.w);
  
  // Normalise gradients
  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
  p0 *= norm.x;
  p1 *= norm.y;
  p2 *= norm.z;
  p3 *= norm.w;
  
  // Mix final noise value
  vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
  m = m * m;
  return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
                                dot(p2,x2), dot(p3,x3) ) );


void main() 
  vUv = uv;

  vec3 pos = position;
  float noiseFreq = 3.5;
  float noiseAmp = 0.15; 
  vec3 noisePos = vec3(pos.x * noiseFreq + uTime, pos.y, pos.z);
  pos.z += snoise(noisePos) * noiseAmp;
  vWave = pos.z;

  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.);

`;

const displacementFrag = `
precision mediump float;

varying vec2 vUv;
varying float vWave;
uniform sampler2D uTexture;

void main() 
  float wave = vWave * 0.25;
  vec3 texture = texture2D(uTexture, vUv + wave).rgb;
  gl_FragColor = vec4(texture, 1.);
`;


let width, height;
let scene, camera, renderer;
let controls;
let wireframe;
let clock;
let planetShaderMaterial;
let jupiterSphere;

const init = ( params ) => 
    
    colors = params.colors;
    model = params.model;
    width = params.width;
    height = params.height;
    scene = new THREE.Scene();

    clock = new THREE.Clock();
    camera = new THREE.PerspectiveCamera( params.fov, width / height, params.near, params.far );
    camera.position.set( params.cameraPos.x, params.cameraPos.y, params.cameraPos.z );
    renderer = new THREE.WebGLRenderer( antialias: true, logarithmicDepthBuffer: true );
    renderer.setSize( width, height );
    renderer.outputEncoding = THREE.sRGBEncoding;
    
    wireframe = params.wireframe;

    renderer.render( scene, camera );

    controls = new OrbitControls( camera, renderer.domElement );

    addLights();
    addPlanet();


const addLights = () => 
    
    const ambientLight = new THREE.AmbientLight( 0xffffff, 10 );
    scene.add( ambientLight );

    const dir = 1024;
    const light = new THREE.DirectionalLight( 0xffffff, 1 );
    light.position.set( 100, 100, 50 );
    light.castShadow = true;
    light.shadow.camera.left = -dir;
    light.shadow.camera.right = dir;
    light.shadow.camera.top = dir;
    light.shadow.camera.bottom = -dir;
    light.shadow.camera.near = 0.1;
    light.shadow.camera.far = 1000;
    light.shadow.mapSize.x = 1024;
    light.shadow.mapSize.y = 1024;

    scene.add( light ); 


// ******** HERE'S THE ShaderMaterial implementation
const addPlanet = () => 
    
    const texture = new THREE.TextureLoader().load( './assets/textures/disp/jupiter.jpg' );

    planetShaderMaterial = new THREE.ShaderMaterial( 
        uniforms: 
            uTime:  value: 0.0 ,
            uTexture:  value: texture 
        ,
        wireframe: false,
        side: THREE.FrontSide,
        vertexShader: displacementVert,
        fragmentShader: displacementFrag,

    );
    
    // these have no effect. Repeat Wrapping just repeats the current effect
    planetShaderMaterial.uniforms.uTexture.value.wrapS = THREE.ClampToEdgeWrapping;
    planetShaderMaterial.uniforms.uTexture.value.wrapT = THREE.ClampToEdgeWrapping;
    
    jupiterSphere = new THREE.Mesh( new THREE.SphereBufferGeometry( 25, 32, 32), planetShaderMaterial );

    scene.add( jupiterSphere );


const render = () => 

    planetShaderMaterial.uniforms.uTime.value = clock.getElapsedTime();

    renderer.render( scene, camera );


const resize = ( width, height ) => 
    windowWidth = width; 
    windowHeight = height;

    camera.aspect = width / height;
    camera.updateProjectionMatrix();

    renderer.setSize( width, height );


const getRenderer = () => 
    return renderer;



const TestWorld = 
    init,
    render,
    resize,
    getRenderer
;

export default TestWorld;

【问题讨论】:

请修剪您的代码,以便更容易找到您的问题。请按照以下指南创建minimal reproducible example。 【参考方案1】:

问题可能在于您的uv 位移量级。这基本上就是你的着色器正在做的事情:

vWave = pos.z;
float wave = vWave * 0.25;
vec3 texture = texture2D(uTexture, vUv + wave);

您的 SphereGeometry 的半径为 25,因此您将根据其沿 z 轴的深度将 UV 置换 25 * 0.25。这意味着您将获得范围约为 [-6.25, 6.25] 的 UV。

您可以重新计算该值以使其更小(请记住,UV 通常在 [0, 1] 范围内,因此 6 的位移将远远超出此范围。或者,您可以保持 UV 位移非常大,并允许纹理重复:

texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;

您可以在Texture docs page 中阅读有关包装的信息

【讨论】:

做到了!谢谢@Marquizzo。也很有意义,宝贝步骤!非常感谢人。 * 我试图将其标记为答案,但它说我不能,因为我是新手,但我会的:)

以上是关于SphereBufferGeometry 上的 THREE.js ShaderMaterial UV 包裹问题的主要内容,如果未能解决你的问题,请参考以下文章

Three.js地球开发—5.国家边界线和国家轮廓Mesh进行组合

threejs14精灵模型Sprite模拟树林效果

threejs 13 纹理texture-canvasTexture

threejs7 实现背景图和360全景图的加载

生成组合/排列(v 符号上的 t 集)

如何在 ARM 上的 uint64_t 和 poly64_t 之间进行转换?