Shader - Effect file control

本文详细介绍了在DirectX9.0游戏开发中,如何通过EffectsFramework实现对渲染状态的精细化控制,包括VertexShader、PixelShader、固定渲染管线的配置,以及材质、灯光的统一管理。通过编写Effects文件,程序员能够更深入地定制渲染效果,提高效率。以《LightAndTexture》为例,展示Effects文件的编写与应用过程。

按照书上的顺序(一般来说,写教程式的书籍,最重要的莫过于顺序,一本好书他章节排序能让你很好的理解内容)在经过 Vertex Shader 与

Pixel Shader 之后,就是书上的最后一章 Effects Framework (我参考的是《DirectX 9.0 游戏开发编程基础》这本书,第二次看了),这个后缀名

通常被命名为 "xxx.fx" 的文件,将之前所学习的渲染状态控制的灵活性完全释放出来,在这个文件中不仅能包含 Vertex Shader,也能够包含Pixel -

Shader,而且还能对 Fixed Pipeline 进行控制,DX SDK 的帮助文档中有一副对 Effects Frramework 进行描述的图示,如下:



PS:这就是Effects能操控的阶段,这释放了所有封闭的操作,程序员可以操作到更微小的 “元素” 以至于更好的控制渲染效果、效率。


利用书上的《LightAndTexture》来作为实例程序:


// File name : EffectsControl.fx
// Author : Dormy.X.Cui
// Description :
//

matrix worldMat;
matrix viewMat;
matrix projMat;

texture mountainTex;

// Texture sampler
sampler S0 = sampler_state
{
	Texture		= (mountainTex);
	MinFilter	= ANISOTROPIC;
	MagFilter	= ANISOTROPIC;
	MipFilter	= ANISOTROPIC;
};

technique LightAndTexture
{
	pass P0
	{
		vertexshader			= null;
		pixelshader			= null;
		fvf				= XYZ | Normal | Tex1;
		Lighting			= true;
		NormalizeNormals		= true;
		SpecularEnable			= false;

		WorldTransform[0]		= (worldMat);
		ViewTransform			= (viewMat);
		ProjectionTransform		= (projMat);

		LightType[0]			= Directional;
		LightAmbient[0]			= {0.2f,  0.2f, 0.2f, 1.0f};
		LightDiffuse[0]			= {1.0f,  1.0f, 1.0f, 1.0f};
		LightSpecular[0]		= {0.0f,  0.0f, 0.0f, 1.0f};
		LightDirection[0]		= {1.0f, -1.0f, 1.0f, 0.0f};
		LightPosition[0]		= {0.0f,  0.0f, 0.0f, 0.0f};
		LightFalloff[0]			= 0.0f;
		LightRange[0]			= 0.0f;
		LightTheta[0]			= 0.0f;
		LightPhi[0]			= 0.0f;
		LightAttenuation0[0]		= 1.0f;
		LightAttenuation1[0]		= 0.0f;
		LightAttenuation2[0]		= 0.0f;

		LightEnable[0]			= true;

		MaterialAmbient			= {1.0f, 1.0f, 1.0f, 1.0f};
		MaterialDiffuse			= {1.0f, 1.0f, 1.0f, 1.0f};
		MaterialSpecular		= {1.0f, 1.0f, 1.0f, 1.0f};
		MaterialEmissive		= {0.0f, 0.0f, 0.0f, 0.0f};
		MaterialPower			= 1.0f;

		Sampler[0] = (S0);

	}
}

这段程序里包含了一个  Technique 以及在 这个  Technique 中包含一个(也可以包含多个)Pass(通常被称作渲染通道),如你所见,在渲染通道的一开始我们

就声明了不使用 Vertex Shader  & Pixel Shader ,而使用固定渲染管线中已有的材质与灯光处理。


效果文件编写完成后,我们转回 Application 中来调用对效果文件进行编译的API,以及从常量表中取出需要初始化的常量。


代码如下:

ID3DXBuffer *pError = NULL;

	hr = ::D3DXCreateEffectFromFile(::g_pd3dDevice, L"..\\Shader\\EffectsControl.fx",
					  NULL, NULL,
					  D3DXSHADER_DEBUG | D3DXSHADER_ENABLE_BACKWARDS_COMPATIBILITY,
					  NULL, &::g_pEffect, &pError);

	if( FAILED(hr) )
	{
		::MessageBox(NULL, L"Call D3DXCreateEffectFromFromFile() - failed !", NULL, 0);

		if( pError )
		{
			::MessageBoxA(NULL, (char*)pError->GetBufferPointer(), NULL, 0);
			Demo::Release<ID3DXBuffer*>(pError);
			return E_FAIL;
		}
	}

	
	g_hWorldMatrixHandle	= ::g_pEffect->GetParameterByName(NULL, "worldMat");
	g_hViewMatrixHandle	= ::g_pEffect->GetParameterByName(NULL, "viewMat");
	g_hProjMatrixHandle	= ::g_pEffect->GetParameterByName(NULL, "projMat");
	g_hTexHandle		= ::g_pEffect->GetParameterByName(NULL, "mountainTex");
	g_hLightTexTechHandle	= ::g_pEffect->GetTechniqueByName("LightAndTexture");

	D3DXMATRIXA16 tmp_world;
	D3DXMATRIXA16 tmp_proj;

	::D3DXMatrixIdentity(&tmp_world);
	::D3DXMatrixPerspectiveFovLH(&tmp_proj, D3DX_PI * 0.25f, (float)(WND_WIDTH)/(float)(WND_HEIGHT), 1.0f, 1000.0f);

	g_pEffect->SetMatrix(::g_hWorldMatrixHandle, &tmp_world);
	g_pEffect->SetMatrix(::g_hProjMatrixHandle, &tmp_proj);

	LPDIRECT3DTEXTURE9 pTex = NULL;

	hr = ::D3DXCreateTextureFromFile(::g_pd3dDevice, L"..\\Res\\Terrain_3x_diffcol.jpg", &pTex);

	if( FAILED(hr) )
	{
		::MessageBox(NULL, L"Call D3DXCreateTextureFromFile() - failed !", NULL, 0);
		return E_FAIL;
	}

	Demo::Release<LPDIRECT3DTEXTURE9>(pTex);

	return S_OK;

这些完成后,效果文件的操作就完成了,这样我们就能够插入一些测试性的代码,比如添加一些.X文件模型看看效果,或者自己手工填充顶点缓存构造一些简单的几何体

来测试效果,效果文件很好的管理的渲染状态,从应用程序中手工调用 SetRenderState 来进行状态的设置搬家到 "xxx.fx" 文件中统一管理材质、灯光等,并且还有Shader

的支持,这样的灵活性。






修复以下 pipe 无法实现纹路流动的问题 : <template> <div ref="container" class="renderer-container"></div> </template> <script setup> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; import TWEEN from '@tweenjs/tween.js'; // Vue ref for mounting the renderer const container = ref(null); // Three.js variables let scene, camera, renderer, controls, clock, animationId; const animatedMaterials = []; // --- Shader Definition --- // This shader creates a flowing line effect using UV coordinates, // which is more reliable than using model position. const flowShader = { uniforms: { time: { value: 0.0 }, // A 'progress' uniform to control the visibility/intensity of the effect progress: { value: 0.0 }, // The color of the flowing lines color: { value: new THREE.Color(0x00ffff) }, // A cyan color }, vertexShader: ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform float time; uniform float progress; uniform vec3 color; varying vec2 vUv; void main() { float pattern = fract(vUv.y * 5.0 - time); // Create a glowing line effect using pow() for a sharp peak float intensity = pow(1.0 - abs(pattern - 0.5) * 2.0, 2.0); // The 'progress' uniform controls the overall intensity, allowing for fade-in/out effects. intensity *= progress; // Mix a dark base color with the glowing line color vec3 baseColor = vec3(0.05, 0.1, 0.15); // Dark blue base vec3 finalColor = mix(baseColor, color, intensity); gl_FragColor = vec4(finalColor, 1.0); } ` }; // --- Lifecycle Hooks --- onMounted(() => { initThreeJS(); createPipe(); // Using a generated pipe instead of loading a file animate(); window.addEventListener('resize', onWindowResize); }); onBeforeUnmount(() => { // Clean up resources to prevent memory leaks cancelAnimationFrame(animationId); window.removeEventListener('resize', onWindowResize); if (renderer) { renderer.dispose(); } animatedMaterials.forEach(mat => mat.dispose()); }); // --- Core Functions --- function initThreeJS() { // Scene scene = new THREE.Scene(); scene.background = new THREE.Color(0x101520); // Darker background clock = new THREE.Clock(); // Camera camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 5, 15); // Renderer renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); container.value.appendChild(renderer.domElement); // Controls controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.05; // Lights const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(5, 10, 7); scene.add(directionalLight); } /** * Creates a procedural pipe geometry and applies the animated shader material. * This function replaces the original GLTF loader for a self-contained example. */ function createPipe() { // Define a path for the pipe const path = new THREE.CatmullRomCurve3([ new THREE.Vector3(-10, -3, 0), new THREE.Vector3(-6, 0, 0), new THREE.Vector3(0, 3, 0), new THREE.Vector3(6, 0, 0), new THREE.Vector3(10, -3, 0) ]); // Create the tube geometry from the path const tubeGeometry = new THREE.TubeGeometry(path, 64, 0.5, 12, false); // Create the custom shader material. // It's crucial to clone the uniforms to ensure this material instance // has its own unique set of values to animate. const customMaterial = new THREE.ShaderMaterial({ uniforms: THREE.UniformsUtils.clone(flowShader.uniforms), vertexShader: flowShader.vertexShader, fragmentShader: flowShader.fragmentShader, side: THREE.DoubleSide, }); // Add the material to our array for updating in the animate loop animatedMaterials.push(customMaterial); // Create the mesh and add it to the scene const pipeMesh = new THREE.Mesh(tubeGeometry, customMaterial); scene.add(pipeMesh); // Use TWEEN to animate the 'progress' uniform. // This will create a pulsing effect, fading the flow in and out. new TWEEN.Tween(customMaterial.uniforms.progress) .to({ value: 1.0 }, 2500) // Animate to full intensity .easing(TWEEN.Easing.Quadratic.InOut) .repeat(Infinity) // Loop forever .yoyo(true) // Animate back and forth .start(); } /** * The main animation loop. */ function animate() { animationId = requestAnimationFrame(animate); const delta = clock.getDelta(); // Update shader time uniform for each animated material animatedMaterials.forEach(material => { material.uniforms.time.value += delta; }); // Update all active tweens TWEEN.update(); // Update orbit controls controls.update(); // Render the scene renderer.render(scene, camera); } /** * Handles window resize events to keep the viewport correct. */ function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } </script> <style scoped> .renderer-container { width: 100%; height: 100vh; overflow: hidden; margin: 0; padding: 0; } </style> <!-- <template> <div ref="container"></div> </template> <script setup name="Sence" > import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as THREE from 'three'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import TWEEN from '@tweenjs/tween.js'; const container = ref(null); let scene, camera, renderer, mixer, clock; // 自定义着色器代码 const flowShader = { uniforms: { time: { value: 0 }, speed: { value: 0.5 }, color: { value: new THREE.Color(0x00aaff) }, progress: { value: 0 } }, vertexShader: ` varying vec2 vUv; varying vec3 vPosition; void main() { vUv = uv; vPosition = position; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform float time; uniform float speed; uniform vec3 color; uniform float progress; varying vec2 vUv; varying vec3 vPosition; void main() { // 创建流动条纹 float stripe = sin(vPosition.x * 2.0 + vPosition.z * 2.0 - time * speed) * 0.5 + 0.5; stripe = smoothstep(0.3, 0.7, stripe); // 混合颜色 vec3 finalColor = mix(vec3(0.2), color, stripe * progress); gl_FragColor = vec4(finalColor, 1.0); } ` }; onMounted(() => { initThreeJS(); loadModel(); animate(); }); onBeforeUnmount(() => { cancelAnimationFrame(animationId); renderer.dispose(); }); let animationId; const animate = () => { animationId = requestAnimationFrame(animate); const delta = clock.getDelta(); if (mixer) mixer.update(delta); flowShader.uniforms.time.value += delta; TWEEN.update(); renderer.render(scene, camera); }; const initThreeJS = () => { // 初始化场景 scene = new THREE.Scene(); scene.background = new THREE.Color(0x111111); // 初始化相机 camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(5, 5, 5); // 初始化渲染器 renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); container.value.appendChild(renderer.domElement); // 添加轨道控制器 new OrbitControls(camera, renderer.domElement); // 添加灯光 const light1 = new THREE.DirectionalLight(0xffffff, 1); light1.position.set(1, 1, 1); scene.add(light1); const light2 = new THREE.AmbientLight(0x404040); scene.add(light2); clock = new THREE.Clock(); }; const loadModel = () => { const loader = new GLTFLoader(); loader.load( '/models/pipe.glb', (gltf) => { gltf.scene.traverse((child) => { if (child.isMesh && child.material.name.includes('管道')) { // 创建自定义材质 const customMaterial = new THREE.ShaderMaterial({ ...flowShader, uniforms: THREE.UniformsUtils.clone(flowShader.uniforms), lights: true, side: THREE.DoubleSide }); // 保留原始材质属性 customMaterial.color = child.material.color; customMaterial.roughness = child.material.roughness; customMaterial.metalness = child.material.metalness; child.material = customMaterial; // 添加动画效果 new TWEEN.Tween(flowShader.uniforms.progress) .to({ value: 1 }, 2000) .easing(TWEEN.Easing.Quadratic.InOut) .start(); } }); scene.add(gltf.scene); }, undefined, (error) => { console.error('Error loading GLB model:', error); } ); }; </script> <style scoped> div { width: 100%; height: 100vh; overflow: hidden; } </style> -->
06-12
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值