分享WebGL物体三维建模

news2024/12/26 14:20:14

界面效果

代码结构

模型素材类似CT (Computed Tomography),即电子计算机断层扫描,它是利用精确准直的X线束、γ射线、超声波等,与灵敏度极高的探测器一同围绕物体的某一部位作一个接一个的断面扫描。

坐标系统

渲染流程

渲染流程是个将之前准备好的模型输出到屏幕的过程。3D 渲染流程会接受使用顶点描述 3D 物体的原始数据作为输入用于处理,并计算其片段 (fragment), 然后渲染为像素 (pixels) 输出到屏幕。

着色器

使用 GLSL 的着色器(shader),GLSL 是一门特殊的有着类似于 C 语言的语法,在图形管道 (graphic pipeline) 中直接可执行的 OpenGL 着色语言。着色器有两种类型——顶点着色器 (Vertex Shader) 和片段着色器(Fragment Shader)。前者是将形状转换到真实的 3D 绘制坐标中,后者是计算最终渲染的颜色和其他属性用的。

GLSL 不同于 JavaScript, 它是强类型语言,并且内置很多数学公式用于计算向量和矩阵。快速编写着色器非常复杂,但创建一个简单的着色器并不难。

一个着色器实际上就是一个绘制东西到屏幕上的函数。着色器运行在 GPU 中,它对这些操作进行了很多的优化,这样你就可以卸载很多不必要的 CPU, 然后集中处理能力去执行你自己的代码。

顶点着色器操作 3D 空间的坐标并且每个顶点都会调用一次这个函数。其目的是设置 gl_Position 变量——这是一个特殊的全局内置变量,它是用来存储当前顶点的位置。

片段 (或者纹理) 着色器 在计算时定义了每像素的 RGBA 颜色 — 每个像素只调用一次片段着色器。这个着色器的作用是设置 gl_FragColor 变量,也是一个 GLSL 内置变量。

        <script id="fragmentShaderFirstPass" type="x-shader/x-fragment">
			varying vec3 worldSpaceCoords;

			void main()
			{
				//The fragment's world space coordinates as fragment output.
				gl_FragColor = vec4( worldSpaceCoords.x , worldSpaceCoords.y, worldSpaceCoords.z, 1 );
			}
		</script>

		<script id="vertexShaderFirstPass" type="x-shader/x-vertex">
			varying vec3 worldSpaceCoords;

			void main()
			{
				//Set the world space coordinates of the back faces vertices as output.
				worldSpaceCoords = position + vec3(0.5, 0.5, 0.5); //move it from [-0.5;0.5] to [0,1]
				gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
			}
		</script>

		<script id="fragmentShaderSecondPass" type="x-shader/x-fragment">
			varying vec3 worldSpaceCoords;
			varying vec4 projectedCoords;
			uniform sampler2D tex, cubeTex, transferTex;
			uniform float steps;
			uniform float alphaCorrection;
			// The maximum distance through our rendering volume is sqrt(3).
			// The maximum number of steps we take to travel a distance of 1 is 512.
			// ceil( sqrt(3) * 512 ) = 887
			// This prevents the back of the image from getting cut off when steps=512 & viewing diagonally.
			const int MAX_STEPS = 887;

			//Acts like a texture3D using Z slices and trilinear filtering.
			vec4 sampleAs3DTexture( vec3 texCoord )
			{
				vec4 colorSlice1, colorSlice2;
				vec2 texCoordSlice1, texCoordSlice2;

				//The z coordinate determines which Z slice we have to look for.
				//Z slice number goes from 0 to 255.
				float zSliceNumber1 = floor(texCoord.z  * 255.0);

				//As we use trilinear we go the next Z slice.
				float zSliceNumber2 = min( zSliceNumber1 + 1.0, 255.0); //Clamp to 255

				//The Z slices are stored in a matrix of 16x16 of Z slices.
				//The original UV coordinates have to be rescaled by the tile numbers in each row and column.
				texCoord.xy /= 16.0;

				texCoordSlice1 = texCoordSlice2 = texCoord.xy;

				//Add an offset to the original UV coordinates depending on the row and column number.
				texCoordSlice1.x += (mod(zSliceNumber1, 16.0 ) / 16.0);
				texCoordSlice1.y += floor((255.0 - zSliceNumber1) / 16.0) / 16.0;

				texCoordSlice2.x += (mod(zSliceNumber2, 16.0 ) / 16.0);
				texCoordSlice2.y += floor((255.0 - zSliceNumber2) / 16.0) / 16.0;

				//Get the opacity value from the 2D texture.
				//Bilinear filtering is done at each texture2D by default.
				colorSlice1 = texture2D( cubeTex, texCoordSlice1 );
				colorSlice2 = texture2D( cubeTex, texCoordSlice2 );

				//Based on the opacity obtained earlier, get the RGB color in the transfer function texture.
				colorSlice1.rgb = texture2D( transferTex, vec2( colorSlice1.a, 1.0) ).rgb;
				colorSlice2.rgb = texture2D( transferTex, vec2( colorSlice2.a, 1.0) ).rgb;

				//How distant is zSlice1 to ZSlice2. Used to interpolate between one Z slice and the other.
				float zDifference = mod(texCoord.z * 255.0, 1.0);

				//Finally interpolate between the two intermediate colors of each Z slice.
				return mix(colorSlice1, colorSlice2, zDifference) ;
			}


			void main( void ) {

				//Transform the coordinates it from [-1;1] to [0;1]
				vec2 texc = vec2(((projectedCoords.x / projectedCoords.w) + 1.0 ) / 2.0,
								((projectedCoords.y / projectedCoords.w) + 1.0 ) / 2.0 );

				//The back position is the world space position stored in the texture.
				vec3 backPos = texture2D(tex, texc).xyz;

				//The front position is the world space position of the second render pass.
				vec3 frontPos = worldSpaceCoords;

				//The direction from the front position to back position.
				vec3 dir = backPos - frontPos;

				float rayLength = length(dir);

				//Calculate how long to increment in each step.
				float delta = 1.0 / steps;

				//The increment in each direction for each step.
				vec3 deltaDirection = normalize(dir) * delta;
				float deltaDirectionLength = length(deltaDirection);

				//Start the ray casting from the front position.
				vec3 currentPosition = frontPos;

				//The color accumulator.
				vec4 accumulatedColor = vec4(0.0);

				//The alpha value accumulated so far.
				float accumulatedAlpha = 0.0;

				//How long has the ray travelled so far.
				float accumulatedLength = 0.0;

				//If we have twice as many samples, we only need ~1/2 the alpha per sample.
				//Scaling by 256/10 just happens to give a good value for the alphaCorrection slider.
				float alphaScaleFactor = 25.6 * delta;

				vec4 colorSample;
				float alphaSample;

				//Perform the ray marching iterations
				for(int i = 0; i < MAX_STEPS; i++)
				{
					//Get the voxel intensity value from the 3D texture.
					colorSample = sampleAs3DTexture( currentPosition );

					//Allow the alpha correction customization.
					alphaSample = colorSample.a * alphaCorrection;

					//Applying this effect to both the color and alpha accumulation results in more realistic transparency.
					alphaSample *= (1.0 - accumulatedAlpha);

					//Scaling alpha by the number of steps makes the final color invariant to the step size.
					alphaSample *= alphaScaleFactor;

					//Perform the composition.
					accumulatedColor += colorSample * alphaSample;

					//Store the alpha accumulated so far.
					accumulatedAlpha += alphaSample;

					//Advance the ray.
					currentPosition += deltaDirection;
					accumulatedLength += deltaDirectionLength;

					//If the length traversed is more than the ray length, or if the alpha accumulated reaches 1.0 then exit.
					if(accumulatedLength >= rayLength || accumulatedAlpha >= 1.0 )
						break;
				}

				gl_FragColor  = accumulatedColor;

			}
		</script>

		<script id="vertexShaderSecondPass" type="x-shader/x-vertex">
			varying vec3 worldSpaceCoords;
			varying vec4 projectedCoords;

			void main()
			{
				worldSpaceCoords = (modelMatrix * vec4(position + vec3(0.5, 0.5,0.5), 1.0 )).xyz;
				gl_Position = projectionMatrix *  modelViewMatrix * vec4( position, 1.0 );
				projectedCoords =  projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
			}
		</script>

三维场景构建

         <script>
			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

			var container, stats;
			var camera, sceneFirstPass, sceneSecondPass, renderer;

			var clock = new THREE.Clock();
			var rtTexture, transferTexture;
			var cubeTextures = ['bonsai', 'foot', 'teapot'];
			var histogram = [];
			var guiControls;

			var materialSecondPass;
			
			init();
			//animate();

			function init() {

				//Parameters that can be modified.
				guiControls = new function() {
					this.model = 'bonsai';
					this.steps = 256.0;
					this.alphaCorrection = 1.0;
					this.color1 = "#00FA58";
					this.stepPos1 = 0.1;
					this.color2 = "#CC6600";
					this.stepPos2 = 0.7;
					this.color3 = "#F2F200";
					this.stepPos3 = 1.0;
				};

				container = document.getElementById( 'container' );

				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 0.01, 3000.0 );
				camera.position.z = 2.0;

				controls = new THREE.OrbitControls( camera, container );
				controls.center.set( 0.0, 0.0, 0.0 );


				//Load the 2D texture containing the Z slices.
				
				THREE.ImageUtils.crossOrigin = 'anonymous'; //处理纹理图加载跨域问题
				
				cubeTextures['bonsai'] = THREE.ImageUtils.loadTexture('./images/bonsai.raw.png');
				cubeTextures['teapot'] = THREE.ImageUtils.loadTexture('./images/teapot.raw.png');
				cubeTextures['foot'] = THREE.ImageUtils.loadTexture('./images/foot.raw.png');

				//Don't let it generate mipmaps to save memory and apply linear filtering to prevent use of LOD.
				cubeTextures['bonsai'].generateMipmaps = false;
				cubeTextures['bonsai'].minFilter = THREE.LinearFilter;
				cubeTextures['bonsai'].magFilter = THREE.LinearFilter;

				cubeTextures['teapot'].generateMipmaps = false;
				cubeTextures['teapot'].minFilter = THREE.LinearFilter;
				cubeTextures['teapot'].magFilter = THREE.LinearFilter;

				cubeTextures['foot'].generateMipmaps = false;
				cubeTextures['foot'].minFilter = THREE.LinearFilter;
				cubeTextures['foot'].magFilter = THREE.LinearFilter;


				var transferTexture = updateTransferFunction();

				var screenSize = new THREE.Vector2( window.innerWidth, window.innerHeight );
				rtTexture = new THREE.WebGLRenderTarget( screenSize.x, screenSize.y,
														{ 	minFilter: THREE.LinearFilter,
															magFilter: THREE.LinearFilter,
															wrapS:  THREE.ClampToEdgeWrapping,
															wrapT:  THREE.ClampToEdgeWrapping,
															format: THREE.RGBFormat,
															type: THREE.FloatType,
															generateMipmaps: false} );


				var materialFirstPass = new THREE.ShaderMaterial( {
					vertexShader: document.getElementById( 'vertexShaderFirstPass' ).textContent,
					fragmentShader: document.getElementById( 'fragmentShaderFirstPass' ).textContent,
					side: THREE.BackSide
				} );

				materialSecondPass = new THREE.ShaderMaterial( {
					vertexShader: document.getElementById( 'vertexShaderSecondPass' ).textContent,
					fragmentShader: document.getElementById( 'fragmentShaderSecondPass' ).textContent,
					side: THREE.FrontSide,
					uniforms: {	tex:  { type: "t", value: rtTexture },
								cubeTex:  { type: "t", value: cubeTextures['bonsai'] },
								transferTex:  { type: "t", value: transferTexture },
								steps : {type: "1f" , value: guiControls.steps },
								alphaCorrection : {type: "1f" , value: guiControls.alphaCorrection }}
				 });

				sceneFirstPass = new THREE.Scene();
				sceneSecondPass = new THREE.Scene();

				var boxGeometry = new THREE.BoxGeometry(1.0, 1.0, 1.0);
				boxGeometry.doubleSided = true;

				var meshFirstPass = new THREE.Mesh( boxGeometry, materialFirstPass );
				var meshSecondPass = new THREE.Mesh( boxGeometry, materialSecondPass );

				sceneFirstPass.add( meshFirstPass );
				sceneSecondPass.add( meshSecondPass );

				renderer = new THREE.WebGLRenderer();
				container.appendChild( renderer.domElement );

				stats = new Stats();
				stats.domElement.style.position = 'absolute';
				stats.domElement.style.top = '0px';
				container.appendChild( stats.domElement );


				var gui = new dat.GUI();
				var modelSelected = gui.add(guiControls, 'model', [ 'bonsai', 'foot', 'teapot' ] );
				gui.add(guiControls, 'steps', 0.0, 512.0);
				gui.add(guiControls, 'alphaCorrection', 0.01, 5.0).step(0.01);

				modelSelected.onChange(function(value) { materialSecondPass.uniforms.cubeTex.value =  cubeTextures[value]; } );


				//Setup transfer function steps.
				var step1Folder = gui.addFolder('Step 1');
				var controllerColor1 = step1Folder.addColor(guiControls, 'color1');
				var controllerStepPos1 = step1Folder.add(guiControls, 'stepPos1', 0.0, 1.0);
				controllerColor1.onChange(updateTextures);
				controllerStepPos1.onChange(updateTextures);

				var step2Folder = gui.addFolder('Step 2');
				var controllerColor2 = step2Folder.addColor(guiControls, 'color2');
				var controllerStepPos2 = step2Folder.add(guiControls, 'stepPos2', 0.0, 1.0);
				controllerColor2.onChange(updateTextures);
				controllerStepPos2.onChange(updateTextures);

				var step3Folder = gui.addFolder('Step 3');
				var controllerColor3 = step3Folder.addColor(guiControls, 'color3');
				var controllerStepPos3 = step3Folder.add(guiControls, 'stepPos3', 0.0, 1.0);
				controllerColor3.onChange(updateTextures);
				controllerStepPos3.onChange(updateTextures);

				step1Folder.open();
				step2Folder.open();
				step3Folder.open();


				onWindowResize();

				window.addEventListener( 'resize', onWindowResize, false );

			}
			
			function imageLoad(url){   
				var image = document.createElement('img');
				image.crossOrigin = '';
				image.src = url;
				var loader = new THREE.TextureLoader();
				
				// load a resource
				const textureAssets = null
				loader.load(
					// resource URL
					image,
					// Function when resource is loaded
					function ( texture ) {
						// do something with the texture
						texture.wrapS = THREE.RepeatWrapping;
						texture.wrapT = THREE.RepeatWrapping;
						texture.offset.x = 90/(2*Math.PI);
						textureAssets = texture
					},
					// Function called when download progresses
					function ( xhr ) {
						console.log( (xhr.loaded / xhr.total * 100) + '% loaded' );
					},
					// Function called when download errors
					function ( xhr ) {
						console.log( 'An error happened' );
					}
				);
				
				return textureAssets; // return the texture
			}
			

			function updateTextures(value)
			{
				materialSecondPass.uniforms.transferTex.value = updateTransferFunction();
			}
			function updateTransferFunction()
			{
				var canvas = document.createElement('canvas');
				canvas.height = 20;
				canvas.width = 256;

				var ctx = canvas.getContext('2d');

				var grd = ctx.createLinearGradient(0, 0, canvas.width -1 , canvas.height - 1);
				grd.addColorStop(guiControls.stepPos1, guiControls.color1);
				grd.addColorStop(guiControls.stepPos2, guiControls.color2);
				grd.addColorStop(guiControls.stepPos3, guiControls.color3);

				ctx.fillStyle = grd;
				ctx.fillRect(0,0,canvas.width -1 ,canvas.height -1 );

				var img = document.getElementById("transferFunctionImg");
				img.src = canvas.toDataURL();
				img.style.width = "256 px";
				img.style.height = "128 px";

				transferTexture =  new THREE.Texture(canvas);
				transferTexture.wrapS = transferTexture.wrapT =  THREE.ClampToEdgeWrapping;
				transferTexture.needsUpdate = true;

				return transferTexture;
			}

			function onWindowResize( event ) {

				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				renderer.setSize( window.innerWidth, window.innerHeight );
			}

			function animate() {

				requestAnimationFrame( animate );

				render();
				stats.update();
			}

			function render() {

				var delta = clock.getDelta();

				//Render first pass and store the world space coords of the back face fragments into the texture.
				renderer.render( sceneFirstPass, camera, rtTexture, true );

				//Render the second pass and perform the volume rendering.
				renderer.render( sceneSecondPass, camera );

				materialSecondPass.uniforms.steps.value = guiControls.steps;
				materialSecondPass.uniforms.alphaCorrection.value = guiControls.alphaCorrection;
			}

			//Leandro R Barbagallo - 2015 - lebarba at gmail.com
			
			
			const VSHADER_SOURCE = `
			attribute vec4 a_Position;
			attribute vec2 a_uv;
			varying vec2 v_uv;
			void main() {
			  gl_Position = a_Position;
			  v_uv = a_uv;
			}
			`
			const FSHADER_SOURCE = `
			precision mediump float;
			// 定义一个取样器。sampler2D 是一种数据类型,就像 vec2
			uniform sampler2D u_Sampler;
			uniform sampler2D u_Sampler2;
			varying vec2 v_uv;
			void main() {
			  // texture2D(sampler2D sampler, vec2 coord) - 着色器语言内置函数,从 sampler 指定的纹理上获取 coord 指定的纹理坐标处的像素
			  vec4 color = texture2D(u_Sampler, v_uv);
			  vec4 color2 = texture2D(u_Sampler2, v_uv);
			  gl_FragColor = color * color2;
			}
			`
			 
			function main() {
				const canvas = document.getElementById('webgl');
				const gl = canvas.getContext("webgl");
				if (!gl) {
					console.log('Failed to get the rendering context for WebGL');
					return;
				}
			 
				if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
					console.log('Failed to intialize shaders.');
					return;
				}
			 
				gl.clearColor(0.0, 0.5, 0.5, 1.0);
			 
				// 几何图形的4个顶点的坐标
				const verticesOfPosition = new Float32Array([
					// 左下角是第一个点,逆时针
					-0.5, -0.5,
					0.5, -0.5,
					0.5, 0.5,
					-0.5, 0.5,
				])
			 
				// 纹理的4个点的坐标
				const uvs = new Float32Array([
					// 左下角是第一个点,逆时针,与顶点坐标保持对应
					0.0, 0.0,
					1.0, 0.0,
					1.0, 1.0,
					0.0, 1.0
				])
			 
				initVertexBuffers(gl, verticesOfPosition)
			 
				initUvBuffers(gl, uvs)
			 
				initTextures(gl)
				initMaskTextures(gl)
			 
			}
			 
			// 初始化纹理。之所以为复数 s 是因为可以贴多张图片。
			function initTextures(gl) {
				// 定义图片
				const img = new Image();
				// 请求 CORS 许可。解决图片跨域问题
				img.crossOrigin = "";
				// The image element contains cross-origin data, and may not be loaded.
				img.src = "./images/bonsai.raw.png";
			 
				img.onload = () => {
					// 创建纹理
					const texture = gl.createTexture();
			 
					// 取得取样器
					const u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
					if (!u_Sampler) {
						console.log('Failed to get the storage location of u_Sampler');
						return false;
					}
					// pixelStorei - 图像预处理:图片上下对称翻转坐标轴 (图片本身不变)
					gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
					// 激活纹理单元
					gl.activeTexture(gl.TEXTURE0);
					// 绑定纹理对象
					gl.bindTexture(gl.TEXTURE_2D, texture);
					// 配置纹理参数
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT);
					// 纹理图片分配给纹理对象
					gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
					// 将纹理单元传给片元着色器
					gl.uniform1i(u_Sampler, 0);
			 
					gl.clear(gl.COLOR_BUFFER_BIT);
					gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
				}
			}
			 
			// 初始化纹理。之所以为复数 s 是因为可以贴多张图片。
			function initMaskTextures(gl) {
				const img = new Image();
				img.src = "./images/teapot.raw.png";
			 
				img.onload = () => {
					// 创建纹理
					const texture = gl.createTexture();
			 
					// 取得取样器
					const u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler2');
					if (!u_Sampler) {
						console.log('Failed to get the storage location of u_Sampler');
						return false;
					}
					// pixelStorei - 图像预处理:图片上下对称翻转坐标轴 (图片本身不变)
					gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
					// 激活纹理单元
					gl.activeTexture(gl.TEXTURE1);
					// 绑定纹理对象
					gl.bindTexture(gl.TEXTURE_2D, texture);
					// 配置纹理参数
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
					// 纹理图片分配给纹理对象
					gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
					// 将纹理单元传给片元着色器
					gl.uniform1i(u_Sampler, 1);
			 
					gl.clear(gl.COLOR_BUFFER_BIT);
					gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
				}
			}
			 
			function initVertexBuffers(gl, positions) {
				const vertexBuffer = gl.createBuffer();
				if (!vertexBuffer) {
					console.log('创建缓冲区对象失败');
					return -1;
				}
			 
				gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
			 
				gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
			 
				const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
				if (a_Position < 0) {
					console.log('Failed to get the storage location of a_Position');
					return -1;
				}
			 
				gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
			 
				gl.enableVertexAttribArray(a_Position);
			}
			 
			function initUvBuffers(gl, uvs) {
				const uvsBuffer = gl.createBuffer();
				if (!uvsBuffer) {
					console.log('创建 uvs 缓冲区对象失败');
					return -1;
				}
				gl.bindBuffer(gl.ARRAY_BUFFER, uvsBuffer);
				gl.bufferData(gl.ARRAY_BUFFER, uvs, gl.STATIC_DRAW);
				const a_uv = gl.getAttribLocation(gl.program, 'a_uv');
				if (a_uv < 0) {
					console.log('Failed to get the storage location of a_uv');
					return -1;
				}
			 
				gl.vertexAttribPointer(a_uv, 2, gl.FLOAT, false, 0, 0);
			 
				gl.enableVertexAttribArray(a_uv);
			}

		</script>

环境设置


你只需关注着色器代码。Three.js 和其他 3D 库给你抽象了很多东西出来——如果你想要用纯 WebGL 创建这个例子,你得写很多其他的代码才能运行。要开始编写 WebGL 着色器你不需要做太多,只需如下三步:

1、确保你在使用对 WebGL 有良好支持的现代浏览器,比如最新版的 Firefox 或 Chrome.
2、创建一个目录保存你的实验。
3、拷贝一份的 压缩版的 Three.js 库 到你的目录。

参见:

LearnOpenGL - Coordinate Systems

WebGL model view projection - Web API 接口参考 | MDN

GLSL 着色器 - 游戏开发 | MDN

解释基本的 3D 原理 - 游戏开发 | MDN

Lebarba - WebGL Volume Rendering made easy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1463543.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

报表控件Stimulsoft 新版本2024.1中,功能区工具栏新功能

今天&#xff0c;我们将讨论Stimulsoft Reports、Dashboards 和 Forms 2024.1版本中的一项重要创新 - 在一行中使用功能区工具栏的能力。 Stimulsoft Ultimate &#xff08;原Stimulsoft Reports.Ultimate&#xff09;是用于创建报表和仪表板的通用工具集。该产品包括用于WinF…

game项目(梦开始的地方)

梦开始的地方 由于easyx只支持vis&#xff0c;所以这个项目的书写以后都是在vis上进行&#xff0c;希望自己能够把这个项目好好完成&#xff0c;相信自己&#xff0c;加油&#xff01; 我们需要一个头文件来包括作图工具 (这个头文件在easyx上面下载) #include<graphics.…

PostgreSQL与MySQL,谁更胜一筹

前言 PostgreSQL与MySQL都是优秀的开源数据库。在日常学习中&#xff0c;新手可能接触最多的是MySql,但是实际工作中&#xff0c;两者的应用场景其实都很广。我之前的做过上网流量销售业务&#xff0c;用的是MySQL,现在接触广告业务&#xff0c;用的是pg数据库&#xff0c;每天…

汉诺塔问题—java详解(附源码)

来源及应用 相传在古印度圣庙中&#xff0c;有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上&#xff0c;有三根杆(编号A、B、C)&#xff0c;在A杆自下而上、由大到小按顺序放置64个金盘(如图1)。游戏的目标&#xff1a;把A杆上的金盘全部移到C杆上&#xff0c;并仍…

Spring学习上下文【ConfigurableApplicationContext】

话不多说&#xff0c;先上图&#xff1a; ConfigurableApplicationContext是Spring框架中的一个接口&#xff0c;它继承了ApplicationContext接口&#xff0c;并扩展了一些额外的方法&#xff0c;用于允许应用程序在运行时动态地修改和管理应用上下文。ConfigurableApplicati…

matlab代码--基于matlabLDPC-和积译码系统

LDPC编码 一个码长为n、信息位个数为k的线性分组码&#xff08;n,k&#xff09;可以由一个生成矩阵 来定义&#xff0c;信息序列 通过G被映射到码字XS.G。线性分组码也可以由一个校验矩阵 来描述。所以码字均满足 。校验矩阵的每一行表示一个校验约束 &#xff0c;其中所有的非…

【大数据】Flink 之部署篇

Flink 之部署篇 1.概述和参考架构2.可重复的资源清理3.部署模式3.1 Application 模式3.2 Per-Job 模式&#xff08;已废弃&#xff09;3.3 Session 模式 Flink 是一个多用途框架&#xff0c;支持多种不同的混合部署方案。下面&#xff0c;我们将简要介绍 Flink 集群的构建模块、…

免费搭建个人网盘

免费搭建一个属于个人的网盘。 服务端 详情请参考原网站的服务端下载和安装虚拟磁盘Fuse4Ui可以支持把网盘内容挂载成系统的分区&#xff1b; 挂载工具效果图&#xff1a;应用端应用端的下载 效果图

教你零基础制作产品画册,打开线上市场

​ 随着市场竞争的日益激烈&#xff0c;越来越多的企业开始注重产品的宣传和推广。而产品画册作为产品宣传的重要手段之一&#xff0c;也越来越受到企业的关注。今天&#xff0c;分享一个零基础制作产品画册的方法&#xff0c;帮助你打开线上市场的大门 1.选择合适的企业宣传…

代码随想录算法训练营第58天 | 392.判断子序列 115.不同的子序列

判断子序列 这道题可以双指针方法解决。 class Solution { public:bool isSubsequence(string s, string t) {int s_index 0;for(int t_index 0; t_index < t.size(); t_index) {if(s[s_index] t[t_index]) {s_index;}}return s_index s.size();} };用动态规划也是可解…

cilium-agent远程debug

文章目录 概述编译cilium-agent运行cilium-agent开启远程debug参考资料 概述 通过远程 debug&#xff0c;来分析 cilium-agent 是如何在容器创建的时候定义容器网络接口。 编译cilium-agent 首先是在本地编译 cilium-agent&#xff0c;下面是以 v1.14.4 的代码作为例子来阐述…

AJAX.

概念:AJAX&#xff1a;异步的 JavaScript 和 XML AJAX作用: 1.与服务器进行数据交换: 通过AJAX可以给服务器发送请求&#xff0c;并获取服务器响应的是数据 使用了AJAX和服务器进行通讯&#xff0c;就可以使用HTML和AJAX来替换JSP页面了 2.异步交互:可以在不重新加载整个页面的…

VSCode The preLaunchTask ‘C/C++: clang++ 生成活动文件‘ terminated with exit code -1

更改tasks.json文件里面的type为shell 选择g 选择g&#xff0c;然后点回到text.c&#xff0c;按下F5. 得到结果。 文中内容参考: 从零开始手把手教你配置属于你的VS Code_哔哩哔哩_bilibili https://blog.csdn.net/qq_63872647/article/details/128006861

Windows7安装指南

概要&#xff1a; 本篇演示Windows7的安装过程 一、说明 1、电脑 笔者的电脑品牌是acer(宏碁/宏基) 电脑开机按F2可进入BIOS 2、Windows7启动U盘 Windows7启动U盘作为Windows7的安装来源 该U盘的制作可参考笔者的文章 Windows制作Windows的U盘启动盘 Windows7没有USB…

《图解HTTP》笔记1:http的诞生

1&#xff0c;http的诞生&#xff1a; 1.1 为共享知识而生 我们现在使用web&#xff08;World Wide Web的简称&#xff0c;即万维网&#xff09;浏览器&#xff0c;目前可以输入一个网址&#xff08;http://www.baidu.com)&#xff0c;就会有一个网页显示出来。 最开始设想出…

百面嵌入式专栏(经验篇)如何在面试中介绍自己的项目经验

文章目录 1. 在面试前准备项目描述,别害怕,因为面试官什么都不知道2. 准备项目的各种细节,一旦被问倒了,就说明你没做过3.不露痕迹地说出面试官爱听的话4.一定要主动,面试官没有义务挖掘你的亮点5.一旦有低级错误,可能会直接出局6.引导篇:准备些加分点,在介绍时有意提到…

36、IO进程线程/进程和线程之间的通信练习

一、使用有名管道完成两个进程的相互通信(提示&#xff1a;可以使用多进程或多线程完成)。 代码1&#xff1a;创建两个有名管道文件 #include<myhead.h>int main(int argc, const char *argv[]) {if(mkfifo("./mingtohua",0664)-1)//创建小明向小华发信息的管…

【CSS】设置文字(文本)的渐变色

# 渐变色 文字 第一步 设置渐变颜色 background: linear-gradient(278.83deg, #5022bd 31.42%, #8636d1 75.55%); // 先设置渐变色背景&#xff1b; 第二步 设置颜色的使用范围 background-clip: text; // 背景被裁剪成文字的前景色。 -webkit-background-clip: text; 第三步…

C#使用一个泛型方法操作不同数据类型的数组

目录 一、泛型方法及其存在的意义 二 、实例 1.源码 2.生成效果 再发一个泛型方法的示例。 一、泛型方法及其存在的意义 实际应用中&#xff0c;查找或遍历数组中的值时&#xff0c;有时因为数组类型的不同&#xff0c;需要对不同的数组进行操作&#xff0c;那么,可以使用…

大工程 从0到1 数据治理 数仓篇(sample database classicmodels _No.7)

大工程 从0到1 数据治理 之数仓篇 我这里还是sample database classicmodels为案列&#xff0c;可以下载&#xff0c;我看 网上还没有类似的 案列&#xff0c;那就 从 0-1开始吧&#xff01; 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参…