Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wgsl does not understand "FloatType" in DataTextures #26576

Closed
Spiri0 opened this issue Aug 13, 2023 · 0 comments · Fixed by #26585
Closed

wgsl does not understand "FloatType" in DataTextures #26576

Spiri0 opened this issue Aug 13, 2023 · 0 comments · Fixed by #26585

Comments

@Spiri0
Copy link
Contributor

Spiri0 commented Aug 13, 2023

Description

During the WebGPU adaptation of one of my WebGL2 examples, I noticed that wgsl does not understand DataTextures that use FloatType if they are passed to a texture_2d parameter.

Reproduction steps

  1. Create a DataTexture with type "FloatType", in my case:
function GeneratePositions(g){
        //g = geometry 
	let vertAmount = g.attributes.position.count;
	let texWidth = Math.ceil(Math.sqrt(vertAmount));
	let texHeight = Math.ceil(vertAmount / texWidth);
  
	let data = new Float32Array(texWidth * texHeight * 4);
  
	for(let i = 0; i < vertAmount; i++){
		data[i * 4 + 0] = g.attributes.position.getX(i);
		data[i * 4 + 1] = g.attributes.position.getY(i);
		data[i * 4 + 2] = g.attributes.position.getZ(i);
		data[i * 4 + 3] = 0;
	}
  
	let dataTexture = new THREE.DataTexture(
		data, 
		texWidth, 
		texHeight, 
		THREE.RGBAFormat, 
		THREE.FloatType   
	);
  
	dataTexture.needsUpdate = true;
  
	return dataTexture;
}
  1. Assign the DataTexture to a wgslFn with a texture_2d parameter. I have created a detailed codePen example.

Check line 162.
With line 164 you can use a testTexture to see what it should look like if the wgslFn function understood the DataTexture and took the vertex positions from the DataTexture instead of the position attribute. To do this, line 103 must be included and line 105 deactivated.
The testTexture only serves the purpose that the shader has a texture. The testTexture itself is meaningless and unsuitable for the application.

Code

import * as THREE from "three";
import {texture, MeshBasicNodeMaterial, attribute, uniform, vec3, vec4, wgslFn } from 'three/nodes';
import {OrbitControls} from "three/addons/controls/OrbitControls.js";
import Stats from "three/addons/libs/stats.module.js";
import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';


const P = new THREE.Vector3();
const N1 = new THREE.Vector3();
const N2 = new THREE.Vector3();
const N3 = new THREE.Vector3();
const D1 = new THREE.Vector3();
const D2 = new THREE.Vector3();



let scene = new THREE.Scene();
scene.background = new THREE.Color(0x667799);
let renderer = new WebGPURenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth,window.innerHeight);
document.body.appendChild(renderer.domElement);
let camera = new THREE.PerspectiveCamera(50.0, window.innerWidth/window.innerHeight, 0.5, 10000);

camera.position.set(20, 20, -20);

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


init();
render();


function init() {
 
  
	let params = {
		resolution: 30,
		width: 20,
	}

	const positions = [];
	const vertexIndex = [];

	const resolution = params.resolution;
	const width = params.width;
	const half = width / 2;
  
 	let idx = 0;
        
	for (let x = 0; x <= resolution; x++) {
		const xp = width * x / resolution;
		for (let z = 0; z <= resolution; z++) {
			const zp = width * z / resolution;
			// Compute position
			P.set(xp - half, 0, zp - half);
			
			positions.push(P.x, P.y, P.z);
				
			vertexIndex.push(idx);
			idx += 1;
		}
	}

	// Generate indices and normals
	const indices = GenerateIndices(params);
	const normals = GenerateNormals(positions, indices);
  
	const bytesInFloat32 = 4;
	const bytesInInt32 = 4;
	const positionsArray = new Float32Array(new ArrayBuffer(bytesInFloat32 * positions.length));
	const normalsArray = new Float32Array(new ArrayBuffer(bytesInFloat32 * normals.length));
	const indicesArray = new Uint32Array(new ArrayBuffer(bytesInInt32 * indices.length));
	const vIndicesArray = new Uint32Array(new ArrayBuffer(bytesInInt32 * vertexIndex.length));

	positionsArray.set(positions, 0);
	normalsArray.set(normals, 0);
	indicesArray.set(indices, 0);
	vIndicesArray.set(vertexIndex, 0);  
  

	const textureLoader = new THREE.TextureLoader();
	const testTexture = textureLoader.load('https://picsum.photos/670/670');  //just a test texture

	const WGSLVertexPosition = wgslFn(`
		fn WGSLPosition(
			vertexTexture: texture_2d<f32>,   
			position: vec3<f32>,
			normal: vec3<f32>,
			vindex: i32,
		) -> vec4<f32> {

			var idx: f32 = f32(vindex);
			var texSize: vec2<u32> = textureDimensions(vertexTexture);
			var texWidth: f32 = f32(texSize.x);
 			var texHeight: f32 = f32(texSize.y);
 			var colIdx: u32 = u32(floor(idx/texWidth));
 			var rowIdx: u32 = u32(idx % texHeight);
			var texCoord: vec2<u32> = vec2<u32>(rowIdx, colIdx);
			var positionD: vec3<f32> = textureLoad(vertexTexture, texCoord, 0).xyz;
    
			//return vec4<f32>(positionD, 1.0);   //Get vertice positions from the DataTexture instead of the position attribute. But line 162 must work for this
    
			return vec4<f32>(position, 1.0);						
		}
	`);

	const WGSLFragmentColor = wgslFn(`
		fn WGSLPosition(
			position: vec3<f32>,
		) -> vec4<f32> {

			return vec4<f32>(position*0.1, 1.0);						
		}
	`);
 
	const wgslVertexParams = {
		vertexTexture: texture(null),
		position: attribute('position'),
		normal: attribute('normal'),
		vindex: attribute('vindex'),
	}
  
	const wgslFragmentParams = {
		position: attribute('position'),
	}


	const nodeMaterial = new MeshBasicNodeMaterial();
	nodeMaterial.colorNode = WGSLFragmentColor(wgslFragmentParams);
	nodeMaterial.positionNode = WGSLVertexPosition(wgslVertexParams); 
	nodeMaterial.wireframe = true;  
  
  
  
	const geometry = new THREE.BufferGeometry();	
	const mesh = new THREE.Mesh(geometry, nodeMaterial);
  
	mesh.castShadow = false;
	mesh.receiveShadow = true;
	mesh.frustumCulled = false;
	mesh.position.set(0, 0, 0);  
	mesh.rotation.x = Math.PI;
	scene.add(mesh);
  
  
	geometry.setAttribute('position', new THREE.Float32BufferAttribute(positionsArray, 3));
	geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normalsArray, 3));
	geometry.setAttribute('vindex', new THREE.Int32BufferAttribute(vIndicesArray, 1));
	geometry.setIndex(new THREE.BufferAttribute(indicesArray, 1));
		
	geometry.attributes.position.needsUpdate = true;
	geometry.attributes.normal.needsUpdate = true;
	geometry.attributes.vindex.needsUpdate = true;


	const VertexPositionsTexture = GeneratePositions(geometry);  
  
	//In my WebGL2 version it works with: mesh.material.uniforms.vertexTexture.value = VertexPositionsTexture; 
  
	mesh.material.positionNode.parameters.vertexTexture.value = VertexPositionsTexture;  //I am not sure if there is a bug here. It works with non FloatType DataTexture but it also have to work with
   
	//mesh.material.positionNode.parameters.vertexTexture.value = testTexture;   //Just to test: I can assign a non FloatType texture. 
}

function GenerateIndices(params) {
	const resolution = params.resolution;
	const indices = [];
	for (let i = 0; i < resolution; i++) {
		for (let j = 0; j < resolution; j++) {
			indices.push(
				i * (resolution + 1) + j,
				(i + 1) * (resolution + 1) + j + 1,
				i * (resolution + 1) + j + 1);
			indices.push(
				(i + 1) * (resolution + 1) + j,
				(i + 1) * (resolution + 1) + j + 1,
				i * (resolution + 1) + j);
			}
		}
	return indices;
}

function GeneratePositions(g){
  
	let vertAmount = g.attributes.position.count;
	let texWidth = Math.ceil(Math.sqrt(vertAmount));
	let texHeight = Math.ceil(vertAmount / texWidth);
  
	let data = new Float32Array(texWidth * texHeight * 4);
  
	for(let i = 0; i < vertAmount; i++){
		data[i * 4 + 0] = g.attributes.position.getX(i);
		data[i * 4 + 1] = g.attributes.position.getY(i);
		data[i * 4 + 2] = g.attributes.position.getZ(i);
		data[i * 4 + 3] = 0;
	}
  
	let dataTexture = new THREE.DataTexture(
		data, 
		texWidth, 
		texHeight, 
		THREE.RGBAFormat, 
		THREE.FloatType //without this line wgsl understand the texture. But the FloatType is essential and works with WebGL2
	);
  
	dataTexture.needsUpdate = true;
  
	return dataTexture;
}

function GenerateNormals(positions, indices) {
	const normals = new Array(positions.length).fill(0.0);
		for (let i = 0, n = indices.length; i < n; i+= 3) {
			const i1 = indices[i] * 3;
			const i2 = indices[i+1] * 3;
			const i3 = indices[i+2] * 3;

			N1.fromArray(positions, i1);
			N2.fromArray(positions, i2);
			N3.fromArray(positions, i3);

			D1.subVectors(N3, N2);
			D2.subVectors(N1, N2);
			D1.cross(D2);

			normals[i1] += D1.x;
			normals[i2] += D1.x;
			normals[i3] += D1.x;
			normals[i1+1] += D1.y;
			normals[i2+1] += D1.y;
			normals[i3+1] += D1.y;
			normals[i1+2] += D1.z;
			normals[i2+2] += D1.z;
			normals[i3+2] += D1.z;
		}
	return normals;
}

function render() {
	requestAnimationFrame(render);
	renderer.render(scene, camera);
}


function onWindowResize() {
	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();
	renderer.setSize(window.innerWidth, window.innerHeight);
}

Live example

https://codepen.io/Spiri0/pen/qBQzJNr

Screenshots

No response

Version

155

Device

Desktop

Browser

Chrome

OS

Windows

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants